#!/bin/sh
set -eu

umask 077
export LC_ALL=C

usage() {
    printf '%s\n' "Usage: install.sh --archive PACKAGE.tar.gz --signature PACKAGE.sig --public-key PUBLIC.pem [--manifest-url URL]" >&2
    exit 2
}

archive= signature= public_key= manifest_url=https://icq2.net/releases/unix/linux-x86_64/friends.manifest
while [ "$#" -gt 0 ]; do
    case "$1" in
        --archive) [ "$#" -ge 2 ] || usage; archive=$2; shift 2 ;;
        --signature) [ "$#" -ge 2 ] || usage; signature=$2; shift 2 ;;
        --public-key) [ "$#" -ge 2 ] || usage; public_key=$2; shift 2 ;;
        --manifest-url) [ "$#" -ge 2 ] || usage; manifest_url=$2; shift 2 ;;
        *) usage ;;
    esac
done
[ -f "$archive" ] && [ -f "$signature" ] && [ -f "$public_key" ] || usage
archive_size=$(wc -c <"$archive")
signature_size=$(wc -c <"$signature")
public_key_size=$(wc -c <"$public_key")
[ "$archive_size" -gt 0 ] && [ "$archive_size" -le 536870912 ] &&
    [ "$signature_size" -gt 0 ] && [ "$signature_size" -le 4096 ] &&
    [ "$public_key_size" -gt 0 ] && [ "$public_key_size" -le 16384 ] || {
        printf '%s\n' "The ICQ2 package or signature input exceeds safety limits." >&2
        exit 1
    }

case "$manifest_url" in
    https://icq2.net/*|https://www.icq2.net/*|https://download.icq2.net/*) ;;
    *) printf '%s\n' "The update discovery URL is not an approved ICQ2 HTTPS URL." >&2; exit 1 ;;
esac
case "$manifest_url" in *'?'*|*'#'*|*'@'*|*'\\'*) printf '%s\n' "The update discovery URL is invalid." >&2; exit 1 ;; esac

openssl pkeyutl -verify -pubin -inkey "$public_key" -rawin \
    -in "$archive" -sigfile "$signature" >/dev/null 2>&1 || {
        printf '%s\n' "The ICQ2 package signature is invalid." >&2
        exit 1
    }

[ "$(uname -s 2>/dev/null || true)" = Linux ] || {
    printf '%s\n' "This direct package supports Linux only." >&2
    exit 1
}
case "$(uname -m 2>/dev/null || true)" in
    x86_64|amd64) ;;
    *) printf '%s\n' "This package supports Linux x86-64 only." >&2; exit 1 ;;
esac

data_home=${XDG_DATA_HOME:-"$HOME/.local/share"}
cache_home=${XDG_CACHE_HOME:-"$HOME/.cache"}
bin_dir=${ICQ2_BIN_DIR:-"$HOME/.local/bin"}
install_cache="$cache_home/icq2/install"
mkdir -p "$install_cache"
temporary_dir=$(mktemp -d "$install_cache/install.XXXXXX")
cleanup() {
    case "$temporary_dir" in "$install_cache"/install.*) rm -rf -- "$temporary_dir" ;; esac
}
trap cleanup EXIT HUP INT TERM

archive_root=$(tar -tzf "$archive" | sed -n '1{s:/*$::;s:/.*::;p;}')
printf '%s\n' "$archive_root" | grep -Eq '^icq2(-gui)?-[0-9]+\.[0-9]+\.[0-9][0-9]([ab])?-linux-x86_64$' || {
    printf '%s\n' "The ICQ2 package root is invalid." >&2
    exit 1
}
tar -tzf "$archive" | awk -v root="$archive_root" '
    BEGIN { ok=1; count=0 }
    { count++; entry=$0; sub(/\/$/,"",entry);
      if (entry=="" || substr(entry,1,1)=="/" || index(entry,"\\")!=0) ok=0;
      n=split(entry,p,"/"); if (p[1]!=root) ok=0;
      for(i=1;i<=n;i++) if(p[i]==".." || p[i]=="") ok=0 }
    END { exit !(ok && count>0) }
' || { printf '%s\n' "The ICQ2 package contains unsafe paths." >&2; exit 1; }
tar -tvzf "$archive" | awk '
    BEGIN { ok=1; total=0 }
    { if (NR>2048 || $3 !~ /^[0-9]+$/) ok=0; total+=$3; if(total>1073741824) ok=0 }
    END { exit !(ok && NR>0) }
' || { printf '%s\n' "The ICQ2 package expands beyond safety limits." >&2; exit 1; }
tar --no-same-owner --no-same-permissions -xzf "$archive" -C "$temporary_dir"
payload="$temporary_dir/$archive_root"
if find "$payload" -type l -print -quit | grep -q .; then
    printf '%s\n' "The ICQ2 package contains symbolic links." >&2
    exit 1
fi

metadata="$payload/package.env"
version=$(sed -n 's/^version=//p' "$metadata" | sed -n '1p')
product=$(sed -n 's/^product=//p' "$metadata" | sed -n '1p')
case "$product" in
    icq2-unix-tui)
        expected_root="icq2-$version-linux-x86_64"
        app_default="$data_home/icq2/client"
        client_binary=icq-tui
        client_command=icq2-tui
        update_command=icq2-update
        ;;
    icq2-linux-gui)
        expected_root="icq2-gui-$version-linux-x86_64"
        app_default="$data_home/icq2/gui-client"
        client_binary=icq-desktop
        client_command=icq2
        update_command=icq2-gui-update
        [ "$(sed -n '1p' "$payload/runtime/update-variant" 2>/dev/null || true)" = linux-gui ] || {
            printf '%s\n' "The ICQ2 GUI update variant is missing." >&2
            exit 1
        }
        ;;
    *)
        printf '%s\n' "The ICQ2 package product is invalid." >&2
        exit 1
        ;;
esac
[ "$archive_root" = "$expected_root" ] || {
    printf '%s\n' "The ICQ2 package metadata does not match its name." >&2
    exit 1
}
[ "$(sed -n 's/^platform=//p' "$metadata" | sed -n '1p')" = linux ] || exit 1
[ "$(sed -n 's/^architecture=//p' "$metadata" | sed -n '1p')" = x86_64 ] || exit 1
[ -x "$payload/bin/$client_binary" ] && [ -x "$payload/runtime/icq2-update" ] &&
    [ -x "$payload/runtime/icq2-launcher" ] || {
        printf '%s\n' "The ICQ2 package is incomplete." >&2
        exit 1
    }
[ "$($payload/bin/$client_binary --version 2>/dev/null || true)" = "$version" ] || {
    printf '%s\n' "The ICQ2 executable version does not match the package." >&2
    exit 1
}

app_root=${ICQ2_APP_ROOT:-"$app_default"}
mkdir -p "$app_root/versions" "$app_root/runtime" "$bin_dir"
for command_path in "$bin_dir/$client_command" "$bin_dir/$update_command"; do
    if [ -e "$command_path" ] && [ ! -L "$command_path" ]; then
        printf '%s\n' "Refusing to replace a non-link command path: $command_path" >&2
        exit 1
    fi
done

destination="$app_root/versions/$version"
[ ! -e "$destination" ] || {
    printf '%s\n' "ICQ2 $version is already installed." >&2
    exit 1
}
mv "$payload" "$destination"
cp "$public_key" "$destination/runtime/update-public-key.pem"
chmod 0644 "$destination/runtime/update-public-key.pem"
for file in icq2-launcher icq2-update; do
    cp "$destination/runtime/$file" "$app_root/runtime/$file.new.$$"
    chmod 0755 "$app_root/runtime/$file.new.$$"
    mv -f "$app_root/runtime/$file.new.$$" "$app_root/runtime/$file"
done
if [ "$product" = icq2-linux-gui ]; then
    cp "$destination/runtime/update-variant" "$app_root/runtime/update-variant.new.$$"
    chmod 0644 "$app_root/runtime/update-variant.new.$$"
    mv -f "$app_root/runtime/update-variant.new.$$" "$app_root/runtime/update-variant"
else
    rm -f -- "$app_root/runtime/update-variant"
fi
cp "$public_key" "$app_root/runtime/update-public-key.pem.new.$$"
chmod 0644 "$app_root/runtime/update-public-key.pem.new.$$"
mv -f "$app_root/runtime/update-public-key.pem.new.$$" "$app_root/runtime/update-public-key.pem"
printf 'manifest_url=%s\n' "$manifest_url" >"$app_root/update.conf.tmp.$$"
mv -f "$app_root/update.conf.tmp.$$" "$app_root/update.conf"
printf '%s\n' "$version" >"$app_root/staged-version"
"$app_root/runtime/icq2-update" --activate-staged

ln -s "$app_root/runtime/icq2-launcher" "$bin_dir/$client_command.new.$$"
mv -Tf "$bin_dir/$client_command.new.$$" "$bin_dir/$client_command"
ln -s "$app_root/runtime/icq2-update" "$bin_dir/$update_command.new.$$"
mv -Tf "$bin_dir/$update_command.new.$$" "$bin_dir/$update_command"

printf '%s\n' "ICQ2 $version was installed for the current user."
printf '%s\n' "Run: $bin_dir/$client_command"
