#!/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 } set_browser() { if [ "x$1" = "xmozilla" -o "x$1" = "xchromium" ]; then BROWSER="$1" else errcho "usage: $0 mozilla|chromium" exit 1 fi } main() { set_browser "$1" # 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)" BUILDDIR=build_$BROWSER rm -rf $BUILDDIR mkdir $BUILDDIR for DIR in $(find $SCRIPTDIRS -type d); do mkdir -p $BUILDDIR/$DIR done CHROMIUM_KEY='' GECKO_APPLICATIONS='' 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_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\"\n\ }\n\ }," fi sed "\ s^_GECKO_APPLICATIONS_^$GECKO_APPLICATIONS^ s^_CHROMIUM_KEY_^$CHROMIUM_KEY^ 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 } main "$@"