#!/bin/sh set -eu export LC_ALL='C' # Metadata. if [ -z "${HBLOCK_VERSION+x}" ]; then HBLOCK_VERSION='3.4.2'; fi if [ -z "${HBLOCK_AUTHOR+x}" ]; then HBLOCK_AUTHOR='Héctor Molinero Fernández '; fi if [ -z "${HBLOCK_LICENSE+x}" ]; then HBLOCK_LICENSE='MIT, https://opensource.org/licenses/MIT'; fi if [ -z "${HBLOCK_REPOSITORY+x}" ]; then HBLOCK_REPOSITORY='https://github.com/hectorm/hblock'; fi # Emulate ksh if the shell is zsh. if [ -n "${ZSH_VERSION-}" ]; then emulate -L ksh; fi # Define system and user configuration directories. if [ -z "${ETCDIR+x}" ]; then ETCDIR='/etc'; fi if [ -z "${XDG_CONFIG_HOME+x}" ]; then XDG_CONFIG_HOME="${HOME-}/.config"; fi # Remove temporary files on exit. cleanup() { ret="$?"; rm -rf -- "${TMPDIR:-${TMP:-/tmp}}/hblock.${$}."*; trap - EXIT; exit "${ret:?}"; } { trap cleanup EXIT ||:; trap cleanup TERM ||:; trap cleanup INT ||:; trap cleanup HUP ||:; } 2>/dev/null # Built-in header. HOSTNAME="${HOSTNAME-"$(uname -n)"}" HBLOCK_HEADER_BUILTIN="$(cat <<-EOF 127.0.0.1 localhost ${HOSTNAME?} 255.255.255.255 broadcasthost ::1 localhost ${HOSTNAME?} ::1 ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ff02::3 ip6-allhosts EOF )" # Built-in footer. HBLOCK_FOOTER_BUILTIN='' # Built-in sources. HBLOCK_SOURCES_BUILTIN="$(cat <<-'EOF' https://raw.githubusercontent.com/hectorm/hmirror/master/data/adaway.org/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/adblock-nocoin-list/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/adguard-cname-trackers/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/adguard-simplified/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/dandelionsprout-nordic/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easylist/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easylist-ara/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easylist-bul/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easylist-ces-slk/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easylist-deu/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easylist-fra/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easylist-heb/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easylist-ind/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easylist-ita/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easylist-kor/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easylist-lav/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easylist-lit/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easylist-nld/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easylist-por/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easylist-rus/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easylist-spa/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easylist-zho/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/easyprivacy/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/eth-phishing-detect/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/gfrogeye-firstparty-trackers/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/hostsvn/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/kadhosts/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/matomo.org-spammers/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/mitchellkrogza-badd-boyz-hosts/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/pgl.yoyo.org/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/phishing.army/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/someonewhocares.org/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/spam404.com/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/stevenblack/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/turkish-ad-hosts/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/ublock/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/ublock-2020/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/ublock-2021/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/ublock-2022/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/ublock-2023/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/ublock-abuse/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/ublock-badware/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/ublock-privacy/list.txt https://raw.githubusercontent.com/hectorm/hmirror/master/data/urlhaus/list.txt EOF )" # Built-in allowlist. HBLOCK_ALLOWLIST_BUILTIN='' # Built-in denylist. HBLOCK_DENYLIST_BUILTIN="$(cat <<-'EOF' # Special domain that is used to check if hBlock is enabled. hblock-check.molinero.dev EOF )" # Parse command line options. optParse() { SEP="$(printf '\037')" while [ "${#}" -gt '0' ]; do case "${1?}" in # Short options that accept a value need a "*" in their pattern because they can be found in the "-A" form. '-O'*|'--output') optArgStr "${@-}"; outputFile="${optArg?}"; shift "${optShift:?}" ;; '-H'*|'--header') optArgStr "${@-}"; headerFile="${optArg?}"; shift "${optShift:?}" ;; '-F'*|'--footer') optArgStr "${@-}"; footerFile="${optArg?}"; shift "${optShift:?}" ;; '-S'*|'--sources') optArgStr "${@-}"; sourcesFile="${optArg?}"; shift "${optShift:?}" ;; '-A'*|'--allowlist') optArgStr "${@-}"; allowlistFile="${optArg?}"; shift "${optShift:?}" ;; '-D'*|'--denylist') optArgStr "${@-}"; denylistFile="${optArg?}"; shift "${optShift:?}" ;; '-R'*|'--redirection') optArgStr "${@-}"; redirection="${optArg?}"; shift "${optShift:?}" ;; '-W'*|'--wrap') optArgStr "${@-}"; wrap="${optArg?}"; shift "${optShift:?}" ;; '-T'*|'--template') optArgStr "${@-}"; template="${optArg?}"; shift "${optShift:?}" ;; '-C'*|'--comment') optArgStr "${@-}"; comment="${optArg?}"; shift "${optShift:?}" ;; '-l' |'--lenient'|'--no-lenient') optArgBool "${@-}"; lenient="${optArg:?}" ;; '-r' |'--regex'|'--no-regex') optArgBool "${@-}"; regex="${optArg:?}" ;; '-f' |'--filter-subdomains'|'--no-filter-subdomains') optArgBool "${@-}"; filterSubdomains="${optArg:?}" ;; '-c' |'--continue'|'--no-continue') optArgBool "${@-}"; continue="${optArg:?}" ;; '-p'*|'--parallel') optArgStr "${@-}"; parallel="${optArg?}"; shift "${optShift:?}" ;; '-q' |'--quiet'|'--no-quiet') optArgBool "${@-}"; quiet="${optArg:?}" ;; '-x'*|'--color') optArgStr "${@-}"; color="${optArg?}"; shift "${optShift:?}" ;; '-v' |'--version') showVersion ;; '-h' |'--help') showHelp ;; # If "--" is found, the remaining positional parameters are saved and the parsing ends. --) shift; _IFS="${IFS?}"; IFS="${SEP:?}"; POS="${POS-}${POS+${SEP:?}}${*-}"; IFS="${_IFS?}"; break ;; # If a long option in the form "--opt=value" is found, it is split into "--opt" and "value". --*=*) optSplitEquals "${@-}"; shift; set -- "${optName:?}" "${optArg?}" "${@-}"; continue ;; # If an option did not match any pattern, an error is thrown. -?|--*) optDie "Illegal option ${1:?}" ;; # If multiple short options in the form "-AB" are found, they are split into "-A" and "-B". -?*) optSplitShort "${@-}"; shift; set -- "${optAName:?}" "${optBName:?}" "${@-}"; continue ;; # If a positional parameter is found, it is saved. *) POS="${POS-}${POS+${SEP:?}}${1?}" ;; esac shift done } optSplitShort() { optAName="${1%"${1#??}"}"; optBName="-${1#??}" } optSplitEquals() { optName="${1%="${1#--*=}"}"; optArg="${1#--*=}" } optArgStr() { if [ -n "${1#??}" ] && [ "${1#--}" = "${1:?}" ]; then optArg="${1#??}"; optShift='0'; elif [ -n "${2+x}" ]; then optArg="${2-}"; optShift='1'; else optDie "No argument for ${1:?} option"; fi } optArgBool() { if [ "${1#--no-}" = "${1:?}" ]; then optArg='true'; else optArg='false'; fi } optDie() { printf '%s\n' "${@-}" "Try 'hblock --help' for more information" >&2 exit 2 } # Show help and quit. showHelp() { printf '%s\n' "$(sed -e 's/%NL/\n/g' <<-EOF Usage: hblock [OPTION]... hBlock is a POSIX-compliant shell script that gets a list of domains that serve ads, tracking scripts and malware from multiple sources and creates a hosts file, among other formats, that prevents your system from connecting to them. Options: -O, --output , \${HBLOCK_OUTPUT_FILE}%NL Output file location.%NL If equals "-", it is printed to stdout.%NL (default: ${outputFile?})%NL -H, --header , \${HBLOCK_HEADER_FILE}%NL File to be included at the beginning of the output file.%NL If equals "builtin", the built-in value is used.%NL If equals "none", an empty value is used.%NL If equals "-", the stdin content is used.%NL If unspecified and any of the following files exists, its content is used.%NL \${XDG_CONFIG_HOME}/hblock/header%NL ${ETCDIR?}/hblock/header%NL (default: ${headerFile?})%NL -F, --footer , \${HBLOCK_FOOTER_FILE}%NL File to be included at the end of the output file.%NL If equals "builtin", the built-in value is used.%NL If equals "none", an empty value is used.%NL If equals "-", the stdin content is used.%NL If unspecified and any of the following files exists, its content is used.%NL \${XDG_CONFIG_HOME}/hblock/footer%NL ${ETCDIR?}/hblock/footer%NL (default: ${footerFile?})%NL -S, --sources , \${HBLOCK_SOURCES_FILE}%NL File with line separated URLs used to generate the blocklist.%NL If equals "builtin", the built-in value is used.%NL If equals "none", an empty value is used.%NL If equals "-", the stdin content is used.%NL If unspecified and any of the following files exists, its content is used.%NL \${XDG_CONFIG_HOME}/hblock/sources.list%NL ${ETCDIR?}/hblock/sources.list%NL (default: ${sourcesFile?})%NL -A, --allowlist , \${HBLOCK_ALLOWLIST_FILE}%NL File with line separated entries to be removed from the blocklist.%NL If equals "builtin", the built-in value is used.%NL If equals "none", an empty value is used.%NL If equals "-", the stdin content is used.%NL If unspecified and any of the following files exists, its content is used.%NL \${XDG_CONFIG_HOME}/hblock/allow.list%NL ${ETCDIR?}/hblock/allow.list%NL (default: ${allowlistFile?})%NL -D, --denylist , \${HBLOCK_DENYLIST_FILE}%NL File with line separated entries to be added to the blocklist.%NL If equals "builtin", the built-in value is used.%NL If equals "none", an empty value is used.%NL If equals "-", the stdin content is used.%NL If unspecified and any of the following files exists, its content is used.%NL \${XDG_CONFIG_HOME}/hblock/deny.list%NL ${ETCDIR?}/hblock/deny.list%NL (default: ${denylistFile?})%NL -R, --redirection , \${HBLOCK_REDIRECTION}%NL Redirection for all entries in the blocklist.%NL (default: ${redirection?})%NL -W, --wrap , \${HBLOCK_WRAP}%NL Break blocklist lines after this number of entries.%NL (default: ${wrap?})%NL -T, --template