aboutsummaryrefslogtreecommitdiff
#!/bin/sh

# Copyright (C) 2021 Wojtek Kosior
# Redistribution terms are gathered in the `copyright' file.

. ./shell_utils.sh

handle_export_line() {
    if [ "x$1" = "xEXPORTS_START" ]; then
	if [ "$STATE" = "before_block" ]; then
	    STATE="in_block"
	fi
    elif [ "x$1" = "xEXPORT" ]; then
	if [ "$STATE" != "in_block" ]; then
	    return
	fi

	EXPORTCODE="${EXPORTCODE}window.killtheweb.$2 = $2;$ENDL"

	PREVIOUS_FILE="$(map_get EXPORTS $2)"
	if [ "x$PREVIOUS_FILE" != "x" ]; then
	    errcho "export $2 present in both $PREVIOUS_FILE and $FILE"
	    return 1
	fi

	map_set_instr EXPORTS $2 "$FILE"

    elif [ "x$1" = "xEXPORTS_END" ]; then
	if [ "$STATE" = "in_block" ]; then
	    STATE="after_block"
	fi
    fi
}

translate_exports() {
    STATE="before_block"
    EXPORTCODE=''

    while read EXPORT_LINE; do
	handle_export_line $EXPORT_LINE || return 1
    done

    map_set_instr EXPORTCODES $FILEKEY "$EXPORTCODE"
}

add_exports() {
    FILE="$1"
    FILEKEY="$(sanitize "$FILE")"

    eval "$(grep -o 'EXPORT.\+' "$1" | translate_exports || exit 1)"
}

handle_import_line() {
    if [ "x$1" = "xIMPORTS_START" ]; then
	if [ "$STATE" = "before_block" ]; then
	    STATE="in_block"
	fi
    elif [ "x$1" = "xIMPORT" ]; then
	if [ "$STATE" != "in_block" ]; then
	    return
	fi

	IMPORTCODE="${IMPORTCODE}const $2 = window.killtheweb.$2;$ENDL"

	IMPORTS="$IMPORTS $2"

    elif [ "x$1" = "xIMPORTS_END" ]; then
	if [ "$STATE" = "in_block" ]; then
	    STATE="after_block"
	fi
    fi
}

translate_imports() {
    STATE="before_block"
    IMPORTCODE=''
    IMPORTS=''

    while read IMPORT_LINE; do
	handle_import_line $IMPORT_LINE || return 1
    done

    map_set_instr IMPORTCODES $FILEKEY "$IMPORTCODE"
    map_set_instr IMPORTS $FILEKEY "$IMPORTS"
}

add_imports() {
    FILE="$1"
    FILEKEY="$(sanitize "$FILE")"

    eval "$(grep -o 'IMPORT.\+' "$1" | translate_imports || exit 1)"
}

compute_scripts_list_rec() {
    local FILE="$1"
    local FILEKEY=$(sanitize "$1")

    local FILESTATE="$(map_get FILESTATES $FILEKEY)"
    if [ "xprocessed" = "x$FILESTATE" ]; then
	return
    fi
    if [ "xprocessing" = "x$FILESTATE" ]; then
	errcho "import loop on $FILE"
	return 1
    fi

    USED="$USED $FILEKEY"

    map_set FILESTATES $FILEKEY "processing"

    local IMPORT
    for IMPORT in $(map_get IMPORTS $FILEKEY); do
	NEXT_FILE="$(map_get EXPORTS $IMPORT)"
	if [ "x" = "x$NEXT_FILE" ]; then
	    errcho "nothing exports $IMPORT, required by $FILE"
	    return 1
	fi
	if ! compute_scripts_list_rec "$NEXT_FILE"; then
	    errcho "when satisfying $IMPORT for $FILE"
	    return 1
	fi
    done

    [ "x$FILE" = "xexports_init.js" ] || echo $FILE # exports_init.js is hardcoded to load first; the entire export system depends on it
    map_set FILESTATES $FILEKEY "processed"
}

compute_scripts_list() {
    USED=''
    echo COMPUTED_SCRIPTS=\"exports_init.js
    compute_scripts_list_rec "$1"
    echo \"

    for FILEKEY in $USED; do
	map_set_instr USED $FILEKEY yes
    done
}

as_json_list() {
    while true; do
	if [ "x" = "x$2" ]; then
	    echo -n '\\n'"\t\t\"$1\""'\\n\t'
	    return
	fi
	echo -n '\\n'"\t\t\"$1\","
	shift
    done
}

as_html_list() {
    while [ "x" != "x$1" ]; do
	echo -n '\\n'"    <script src=\"/$1\"></script>"
	shift
    done
}

main() {
    # placate importers of these, as they are exported by the yet-to-be-created exports_init.js
    EXPORTS__browser=exports_init.js
    EXPORTS__is_chrome=exports_init.js
    EXPORTS__is_mozilla=exports_init.js

    SCRIPTDIRS='background html common content'

    SCRIPTS=$(find $SCRIPTDIRS -name '[^.#]*.js')

    for SCRIPT in $SCRIPTS; do
	add_exports $SCRIPT
	add_imports $SCRIPT
    done

    eval "$(compute_scripts_list background/main.js || exit 1)"
    BGSCRIPTS="$(as_json_list $COMPUTED_SCRIPTS)"
    eval "$(compute_scripts_list content/main.js || exit 1)"
    CONTENTSCRIPTS="$(as_json_list $COMPUTED_SCRIPTS)"
    eval "$(compute_scripts_list html/display-panel.js || exit 1)"
    POPUPSCRIPTS="$(as_html_list $COMPUTED_SCRIPTS)"
    eval "$(compute_scripts_list html/options_main.js || exit 1)"
    OPTIONSSCRIPTS="$(as_html_list $COMPUTED_SCRIPTS)"

    rm -rf $BUILDDIR
    mkdir $BUILDDIR
    for DIR in $(find $SCRIPTDIRS -type d); do
	mkdir -p $BUILDDIR/$DIR
    done

    CHROMIUM_KEY=''
    CHROMIUM_UPDATE_URL=''
    GECKO_APPLICATIONS=''
    
    if [ "x$UPDATE_URL" != x ]; then
	UPDATE_URL=",\n    \"update_url\": \"$UPDATE_URL\""
    fi

    if [ "$BROWSER" = "chromium" ]; then
	CHROMIUM_KEY="$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64)"
	CHROMIUM_KEY=$(echo chromium-key-dummy-file-$CHROMIUM_KEY | tr / -)
	touch $BUILDDIR/$CHROMIUM_KEY

	CHROMIUM_UPDATE_URL="$UPDATE_URL"

	CHROMIUM_KEY="\n\
	// WARNING!!!\n\
	// EACH USER SHOULD REPLACE DUMMY FILE's VALUE WITH A UNIQUE ONE!!!\n\
	// OTHERWISE, SECURITY CAN BE TRIVIALLY COMPROMISED!\n\
	// Only relevant to users of chrome-based browsers.\n\
	// Users of Firefox forks are safe.\n\
	\"$CHROMIUM_KEY\"\
"
    else
	GECKO_APPLICATIONS="\n\
    \"applications\": {\n\
	\"gecko\": {\n\
	    \"id\": \"{6fe13369-88e9-440f-b837-5012fb3bedec}\",\n\
	    \"strict_min_version\": \"60.0\"$UPDATE_URL\n\
	}\n\
    },"
    fi

    sed "\
s^_GECKO_APPLICATIONS_^$GECKO_APPLICATIONS^
s^_CHROMIUM_KEY_^$CHROMIUM_KEY^
s^_CHROMIUM_UPDATE_URL_^$CHROMIUM_UPDATE_URL^
s^_BGSCRIPTS_^$BGSCRIPTS^
s^_CONTENTSCRIPTS_^$CONTENTSCRIPTS^" \
	< manifest.json > $BUILDDIR/manifest.json

    ./process_html_file.sh html/display-panel.html |
	sed "s^_POPUPSCRIPTS_^$POPUPSCRIPTS^" \
	    > $BUILDDIR/html/display-panel.html

    ./process_html_file.sh html/options.html |
	sed "s^_OPTIONSSCRIPTS_^$OPTIONSSCRIPTS^" \
	    > $BUILDDIR/html/options.html

    for FILE in $SCRIPTS; do
	FILEKEY=$(sanitize "$FILE")
	if [ "xyes" != "x$(map_get USED $FILEKEY)" ]; then
	    errcho "WARNING! $FILE not used"
	else
	    (echo "\
\"use strict\";

({fun: (function() {
$(map_get IMPORTCODES $FILEKEY)

";

# A hack to insert the contents of default_settings.json at the appropriate location in background/main.js
if [ "$FILE" = "background/main.js" ]; then
    # Uses an internal sed expression to escape and indent the JSON file for use in the external sed expression
    sed 's/^        `DEFAULT SETTINGS`$/'"$(sed -E 's/([\\\&\/])/\\\1/g; s/^/        /; s/$/\\/' < default_settings.json) "/g < "$FILE"
else
    cat $FILE
fi

echo "

$(map_get EXPORTCODES $FILEKEY)
})}).fun();") > $BUILDDIR/$FILE
	fi
    done

    if [ "$BROWSER" = "chromium" ]; then
	cat > $BUILDDIR/exports_init.js <<EOF
window.killtheweb={is_chrome: true, browser: window.chrome};
EOF
    else
	cat > $BUILDDIR/exports_init.js <<EOF
/* Polyfill for IceCat 60. */
String.prototype.matchAll = String.prototype.matchAll || function(regex) {
    if (regex.flags.search("g") === -1)
        throw new TypeError("String.prototype.matchAll called with a non-global RegExp argument");

    for (const matches = [];;) {
        if (matches[matches.push(regex.exec(this)) - 1] === null)
	    return matches.splice(0, matches.length - 1);
    }
}

window.killtheweb={is_mozilla: true, browser: this.browser};
EOF
    fi

    cp -r copyright licenses/ $BUILDDIR
    cp html/*.css $BUILDDIR/html
    mkdir $BUILDDIR/icons
    cp icons/*.png $BUILDDIR/icons

    if [ "$BROWSER" = "chromium" ]; then
	for MOZILLA_FILE in $(find $BUILDDIR -name "MOZILLA_*"); do
	    echo > "$MOZILLA_FILE"
	done
    fi
    if [ "$BROWSER" = "mozilla" ]; then
	for CHROMIUM_FILE in $(find $BUILDDIR -name "CHROMIUM_*"); do
	    echo > "$CHROMIUM_FILE"
	done
    fi
}

make_zip() (
    cd "$PACKDIR"
    if [ "x$KEY" = x ]; then
	${HAKETILO_ZIP:-zip -r} build.zip *;
    else
	${HAKETILO_CHROMIUM:-chromium} --pack-extension="$(realpath inner)" --pack-extension-key="$KEY"
	mv *.crx build.zip
    fi
)