updater 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870
  1. #!/bin/sh
  2. ## Updater für die user.js von Privacy-Handbuch.de
  3. ## Version: 1.6.2
  4. ## by: TotallyLeGIT
  5. ## based on the ghacks user.js updater by:
  6. ## Author: Pat Johnson (@overdodactyl)
  7. ## Additional contributors: @earthlng, @ema-pe, @claustromaniac
  8. ## Dependencies: ·curl or wget
  9. #########################
  10. # Misc/Helper Functions #
  11. #########################
  12. # Set variables based on the operating system reported by uname.
  13. abstract_os() {
  14. if [ "$(uname)" = 'Darwin' ]; then
  15. readonly open_command='open'
  16. readonly ff_dir="${HOME}/Library/Application Support/Firefox"
  17. readonly ff_file_path="${ff_dir}/profiles.ini"
  18. readonly global_override_path="${global_override_path:-${ff_dir}/global-user-overrides.js}"
  19. readonly tb_file_path="${HOME}/Library/Thunderbird/profiles.ini"
  20. readonly ff_exec_dir='/Applications/Firefox.app/Contents/MacOS/'
  21. readonly sed_compat_os_script='s/Resources$/MacOS/'
  22. else
  23. readonly open_command='xdg-open'
  24. readonly ff_dir="${HOME}/.mozilla/firefox"
  25. readonly ff_file_path="${ff_dir}/profiles.ini"
  26. readonly global_override_path="${global_override_path:-${ff_dir}/global-user-overrides.js}"
  27. readonly tb_file_path="${HOME}/.thunderbird/profiles.ini"
  28. readonly ff_exec_dir=''
  29. readonly sed_compat_os_script=''
  30. fi
  31. # set effective path for each profile.ini
  32. [ -f "${ff_file_path}" ] && ff_file="${ff_file_path}" || ff_file=''
  33. [ -f "${tb_file_path}" ] && tb_file="${tb_file_path}" || tb_file=''
  34. readonly ff_file tb_file
  35. }
  36. err_exit() {
  37. printv '3' '%s\n' "${color_err}[Error] ${*}${color_neutral_stderr}" 1>&2
  38. exit 1
  39. }
  40. err_help_exit() {
  41. printf '%s\n' "${color_err}[Error] ${*}${color_neutral_stderr}" 1>&2
  42. printf "Use '-h' option for more information.\n" 1>&2
  43. exit 1
  44. }
  45. print_global_overrides() {
  46. [ ! -e "${global_override_path}" ] && return 0
  47. printv_info '0' 'global overrides' "${global_override_path}"
  48. }
  49. print_warn() {
  50. printv '2' '%s\n' "${color_warn}[Warning] ${*}${color_neutral_stderr}" 1>&2
  51. }
  52. # Print if message is more important than the quiescence specified.
  53. # $1: importance (should be a non-negative integer)
  54. # $2: printf format string
  55. # $3: string to print (only one)
  56. printv() {
  57. # shellcheck disable=SC2059
  58. [ "${1}" -ge "${quiescence}" ] && printf "${2}" "${3}" || return 0
  59. }
  60. # Format and print informational text to terminal.
  61. # $1 : importance (should be a non-negative integer)
  62. # $2 : information label
  63. # $3…: information (all arguments >= 3)
  64. printv_info() {
  65. importance="${1}"
  66. shift
  67. printv "${importance}" '%-19s' "${1}:"
  68. shift
  69. printv "${importance}" '%s\n' "${color_info}${*}${no_color_stdout}"
  70. }
  71. # See 'extract_version', but without Thunderbird.
  72. extract_ff_version() {
  73. if ! ff_version_output="$("${1}"firefox --version 2>/dev/null)"; then
  74. return 1
  75. elif printf '%s' "${ff_version_output}" | grep -q 'esr'; then
  76. ff_version_extracted="Firefox ESR $(printf '%s' "${ff_version_output}" \
  77. | cut -d ' ' -f 3 | cut -d '.' -f 1)"
  78. else
  79. ff_version_extracted='Firefox release'
  80. fi
  81. printf '%s' "${ff_version_extracted}"
  82. }
  83. # Determine if a given directory contains Thunderbird or Firefox.
  84. # Print 'Firefox ESR XY' (XY being the major version number), 'Firefox release'
  85. # or 'Thunderbird'. Return 1 if none found.
  86. # $1: directory of executable (CAUTION: append a trailing / )
  87. # If no argument is given, executables are searched for in $PATH.
  88. extract_version() {
  89. if [ -f "${1}thunderbird" ] && [ -x "${1}thunderbird" ]; then
  90. printf 'Thunderbird'
  91. return 0
  92. else
  93. extract_ff_version "${1}"
  94. fi
  95. }
  96. # Parse command line options and set variables accordingly.
  97. parse_options() {
  98. if [ "${#}" -ne 0 ]; then
  99. # display usage if first argument contains -help
  100. if printf '%s' "${1}" | grep -qi '\-help'; then
  101. usage
  102. exit 0
  103. else
  104. while getopts ':abc:de:g:hilno:p:qrstuvy' opt; do
  105. case "${opt}" in
  106. a)
  107. profile_path_option='all'
  108. ;;
  109. b)
  110. backup_multiple='false'
  111. ;;
  112. c)
  113. if ! config_wanted_global="$(printf '%s' "${OPTARG}" \
  114. | tr '[:upper:]' '[:lower:]' \
  115. | awk '{print $1; exit}' \
  116. | grep '^arkenfox$\|^minimal$\|^moderat$\|^medium$\|^streng$\|^hotspot$\|^empty$\|^horlogeskynet$\|^tb$\|^tb-streng$')"
  117. then
  118. err_help_exit "Invalid CONFIG: '${OPTARG}'"
  119. fi
  120. ;;
  121. d)
  122. compare='true'
  123. ;;
  124. e)
  125. if ff_version_wanted_global="$(printf '%s' "${OPTARG}" \
  126. | awk '{print $1; exit}' \
  127. | grep '^[[:digit:]]*$')"
  128. then
  129. ff_version_specified='true'
  130. else
  131. err_help_exit 'VERSION must be an integer'
  132. fi
  133. ;;
  134. g)
  135. [ ! -e "${OPTARG}" ] && err_exit "'${OPTARG}': not found"
  136. global_override_path="${OPTARG}"
  137. ;;
  138. h)
  139. usage
  140. exit 0
  141. ;;
  142. i)
  143. view='true'
  144. ;;
  145. l)
  146. profile_path_option='list'
  147. ;;
  148. n)
  149. override='false'
  150. ;;
  151. o)
  152. override='true'
  153. override_path="${OPTARG}"
  154. ;;
  155. p)
  156. update_profile_path "${OPTARG}"
  157. [ "${profile_path_option}" = 'false' ] && profile_path_option='true'
  158. ;;
  159. q)
  160. quiescence="$((quiescence+1))"
  161. ;;
  162. r)
  163. just_read='true'
  164. ;;
  165. # TODO: remove '-s' in future version
  166. s|u)
  167. update='true'
  168. ;;
  169. t)
  170. confirm='no'
  171. dry_run='true'
  172. ;;
  173. v)
  174. printf '%s\n' "PH-userjs-updater ${version}"
  175. exit 0
  176. ;;
  177. y)
  178. confirm='no'
  179. ;;
  180. \?)
  181. err_help_exit "Invalid option: '${OPTARG}'"
  182. ;;
  183. :)
  184. err_help_exit "Option requires an argument: '${OPTARG}'"
  185. ;;
  186. esac
  187. done
  188. fi
  189. fi
  190. }
  191. # Initialize misc stuff.
  192. # $1: this script's name (usually $0)
  193. prepare() {
  194. tmp_dir="$(mktemp -dt userjs.XXXXXX)"
  195. readonly tmp_dir
  196. trap 'exit 130' INT HUP QUIT TERM ALRM
  197. # shellcheck disable=SC2154
  198. trap 'rc="${?}"; rm -rf "${tmp_dir}"; exit "${rc}"' EXIT
  199. script_path="$(readlink -f "${1}" 2>/dev/null \
  200. || greadlink -f "${1}" 2>/dev/null || printf '%s' "${1}")"
  201. readonly script_path
  202. # Download method priority: curl > wget
  203. if command -v curl >/dev/null; then
  204. readonly download_command='curl --location --max-redirs 2 --max-time 30 --fail --silent --output'
  205. elif command -v wget >/dev/null; then
  206. # -O file = --output-document=file
  207. readonly download_command='wget --max-redirect=2 --timeout=30 --quiet -O'
  208. else
  209. err_exit 'This script requires curl or wget'
  210. fi
  211. }
  212. # Determine Firefox (FF) update channel and set $ff_version_wanted_global
  213. # accordingly: major version number for FF esr (e. g. 91) or 0 for FF release
  214. set_ff_version_wanted_global() {
  215. if [ "${ff_version_specified}" = 'false' ]; then
  216. if ! ff_version_wanted_global="$(extract_ff_version "${ff_exec_dir}")"; then
  217. ff_version_wanted_global='none'
  218. fi
  219. else
  220. if [ "${ff_version_wanted_global}" -eq 0 ]; then
  221. ff_version_wanted_global='Firefox release'
  222. else
  223. esr_to_dl="ff${ff_version_wanted_global}"
  224. ff_version_wanted_global="Firefox ESR ${ff_version_wanted_global}"
  225. fi
  226. fi
  227. readonly ff_version_wanted_global
  228. printv_info '0' 'default browser' "${ff_version_wanted_global}"
  229. }
  230. # Initialize variables needed for parse_options().
  231. set_variables() {
  232. # Colors used for printing only when stdout is a terminal
  233. if [ -t 1 ]; then
  234. # green
  235. color_info="$(tput setaf 2)"
  236. # blue
  237. color_usage="$(tput setaf 4)"
  238. # blue, bold
  239. color_user_input="$(tput setaf 4)$(tput bold)"
  240. no_color_stdout="$(tput sgr0)"
  241. else
  242. color_info=''
  243. color_usage=''
  244. color_user_input=''
  245. no_color_stdout=''
  246. fi
  247. if [ -t 2 ]; then
  248. # red
  249. color_err="$(tput setaf 1)"
  250. # yellow (orange-ish)
  251. color_warn="$(tput setaf 3)"
  252. color_neutral_stderr="$(tput sgr0)"
  253. else
  254. color_err=''
  255. color_warn=''
  256. color_neutral_stderr=''
  257. fi
  258. readonly color_info color_usage color_user_input no_color_stdout
  259. readonly color_err color_warn color_neutral_stderr
  260. # Argument defaults
  261. backup_multiple='true'
  262. compare='false'
  263. compat_file='compatibility.ini'
  264. config_wanted_global='keep'
  265. confirm='yes'
  266. dry_run='false'
  267. ff_version_specified='false'
  268. just_read='false'
  269. # soft: look for user-overrides.js but only give info when none found
  270. override='soft'
  271. override_path='user-overrides.js'
  272. profile_path=''
  273. profile_path_option='false'
  274. pwd_start="$(pwd)"
  275. quiescence=0
  276. update='false'
  277. view='false'
  278. # constants
  279. readonly backup_dir='userjs_backups'
  280. readonly diff_dir='userjs_diffs'
  281. # TODO: remove version comment in line 4 in v2.0
  282. readonly version='1.6.2'
  283. }
  284. usage() {
  285. printf '%s' "${color_usage}userjs-updater${no_color_stdout}
  286. Updater: ${color_usage}https://notabug.org/TotallyLeGIT/PH-userjs-updater${no_color_stdout}
  287. Firefox user.js: ${color_usage}https://www.privacy-handbuch.de/handbuch_21u.htm
  288. https://github.com/arkenfox/user.js${no_color_stdout}
  289. Thunderbird user.js: ${color_usage}https://www.privacy-handbuch.de/handbuch_31p.htm
  290. https://github.com/HorlogeSkynet/thunderbird-user.js${no_color_stdout}
  291. ${color_usage}Usage: ${0} [-abdghilnruvy] [-c CONFIG] [-e VERSION] [-o OVERRIDE] [-p PROFILE]${no_color_stdout}
  292. -a Update all Firefox and Thunderbird profiles at once while keeping
  293. CONFIGs the same. Profiles without a user.js will be skipped.
  294. -b Only keep one user.js backup and one diff file.
  295. -c CONFIG Specify the Firefox user.js config you want: arkenfox, minimal, moderat, medium
  296. (= medium streng), streng (= sehr streng), hotspot or empty
  297. For Thunderbird: HorlogeSkynet, tb or tb-streng
  298. Empty creates a user.js containing just one commented line so that (global)
  299. overrides will be apllied to this profile.
  300. This option is only needed if you want to change to another config.
  301. Note that this option will always have effect on all selected profiles.
  302. -d Create a diff file comparing old and new user.js within ${diff_dir} subdirectory.
  303. -e VERSION Force download of user.js for Firefox ESR with version number VERSION for all
  304. profiles. Make sure the user.js for this version actually exists.
  305. Example: '-e 91' will download the user.js for Firefox ESR Version 91.x .
  306. Use '-e 0' to force the download of the user.js for the release update channel.
  307. Precedence to find Firefox update channel:
  308. -e > previous user.js > compatibility.ini > 'firefox --version'
  309. -g OVERRIDE Path to global overrides. These overrides get appended to every Firefox user.js, but
  310. not to unmanaged profiles (see '-c empty'). Per-profile overrides take precedence
  311. over these global ones. OVERRIDE may be a directory – see '-o'.
  312. The default path will always be applied, if present, even without this option:
  313. ~/.mozilla/firefox/global-user-overrides.js (Linux)
  314. ~/Library/Application Support/Firefox/global-user-overrides.js (MacOS)
  315. -h Show this help message and exit.
  316. -i Inspect the resulting user.js file.
  317. -l Interactively choose your profile from a list.
  318. -n Do not append any overrides even if user-overrides.js exists.
  319. -o OVERRIDE Filename or path to overrides file, needed if different than PROFILE/user-overrides.js .
  320. If used with -p, the path may be relative to PROFILE or an absolute path.
  321. If given a directory, all files inside ending in .js will be appended.
  322. -p PROFILE Path to your profile directory, needed if different than this script's location.
  323. May be used multiple times.
  324. IMPORTANT: If the profile path includes spaces, wrap whole path in quotes.
  325. -q Be quieter. Use multiple times to show less output: additional information,
  326. all information, warnings, errors will be silenced in this order. When completely
  327. silent, the outcome is still represented by the exit code.
  328. -r Only download user.js to a temporary file and open it. This requires -c .
  329. -s (Deprecated) This is an alias for -u .
  330. -t Dry-run: Perform a trial run that doesn't change anything and produces mostly
  331. the same output. Implies -y .
  332. -u Check for updates of this updater and install if available.
  333. -v Display updater version and exit.
  334. -y Update user.js without confirmation.
  335. "
  336. }
  337. #########################
  338. # (Pro)File Handling #
  339. #########################
  340. # Download a resource.
  341. # $1: the file name, where the resource will be saved to
  342. # $2: URL of the resource to download
  343. # Return: 1 on error, else 0
  344. download_file() {
  345. if [ "${2}" = 'empty' ]; then
  346. # This string makes parsing easy as it looks similar to a normal user.js.
  347. printf '// user_pref("_user.js.prhdb", "empty");\n' > "${1}" || return 1
  348. else
  349. ${download_command} "${1}" "${2}" || return 1
  350. fi
  351. }
  352. # Update $profile_path (especially with list/-l) or exit on error.
  353. get_profile_path() {
  354. if [ "${profile_path_option}" = 'list' ]; then
  355. if [ -n "${ff_file}" ]; then
  356. ini="${ff_file}"
  357. [ -n "${tb_file}" ] && ini='both' || printf 'Firefox detected.\n\n'
  358. elif [ -n "${tb_file}" ]; then
  359. ini="${tb_file}"
  360. printf 'Thunderbird detected.\n\n'
  361. else
  362. err_exit "Neither Thunderbird nor Firefox could be found, or '-l' is" \
  363. "not supported on your OS. Try '-p' option"
  364. fi
  365. if [ "${ini}" = 'both' ]; then
  366. while [ -z "${reply}" ]; do
  367. printf '%s' "${color_user_input}(F)irefox and (T)hunderbird were"
  368. printf '%s' " detected. Select one: f,t?${no_color_stdout} "
  369. read -r reply
  370. done
  371. reply="$(printf '%s' "${reply}" | tr '[:upper:]' '[:lower:]')"
  372. if [ -z "${reply##f*}" ]; then
  373. printf 'Firefox was selected.\n\n'
  374. read_ini_file "${ff_file}"
  375. elif [ -z "${reply##t*}" ]; then
  376. printf 'Thunderbird was selected.\n\n'
  377. read_ini_file "${tb_file}"
  378. else
  379. err_exit 'Could not parse your input'
  380. fi
  381. fi
  382. elif [ "${profile_path_option}" = 'all' ]; then
  383. for file in "${ff_file}" "${tb_file}"; do
  384. if [ -f "${file}" ]; then
  385. profile_path_temp="$(sed -ne 's/^Path=\(.*\)$/\1/p' "${file}")"
  386. else
  387. continue
  388. fi
  389. while read -r line; do
  390. if ! printf '%s' "${line}" | grep -q '^/'; then
  391. profile_path_to_update="${file%profiles.ini}${line}"
  392. else
  393. profile_path_to_update="${line}"
  394. fi
  395. update_profile_path "${profile_path_to_update}"
  396. # we don't want a subshell here
  397. done <<EOF
  398. ${profile_path_temp}
  399. EOF
  400. done
  401. elif [ "${profile_path_option}" = 'false' ]; then
  402. possible_userjs_dir="$(dirname "${script_path}")"
  403. # user.js in current working directory?
  404. if [ -e 'user.js' ] || [ -e "${compat_file}" ] || [ -e 'times.json' ]; then
  405. update_profile_path "$(pwd)"
  406. # user.js in same directory as script?
  407. elif [ -e "${possible_userjs_dir}/user.js" ]; then
  408. update_profile_path "${possible_userjs_dir}"
  409. else
  410. err_exit "Could not detect user.js location. Use '-p PROFILE'"
  411. fi
  412. fi
  413. if [ -z "${profile_path}" ]; then
  414. err_exit 'No valid profile path found'
  415. fi
  416. readonly profile_path
  417. }
  418. # Download user.js and put it to given path. Note the variables that should be
  419. # set for get_userjs_link(). Requires $profile_path_option to be set.
  420. # $1: path where the user.js should be saved
  421. # $2: set to 'true' to indicate a Firefox profile, otherwise no global
  422. # overrides will be appended
  423. get_userjs() {
  424. if [ ! -f "${1}" ]; then
  425. link="$(get_userjs_link)"
  426. if ! download_file "${1}" "${link}"; then
  427. if [ "${profile_path_option}" = 'all' ]; then
  428. print_warn 'Download of new user.js failed'
  429. return 1
  430. else
  431. err_exit 'Download of new user.js failed'
  432. fi
  433. fi
  434. [ "${2}" = 'true' ] && add_global_overrides "${1}"
  435. fi
  436. if ! new_version_long="$(get_userjs_version "${new_userjs}")"; then
  437. err_exit 'Could not parse new user.js'
  438. fi
  439. printv_info '0' ' new user.js' "${new_version_long}"
  440. }
  441. # Open a file for the user to view.
  442. # $1: path to file
  443. open_file() {
  444. "${open_command}" "${1}" || err_exit 'Cannot open files on your OS'
  445. }
  446. # Parse profiles.ini to find the correct profile for the user.js.
  447. # $1: absolute path of profiles.ini
  448. read_ini_file() {
  449. readonly ini_file="${1}"
  450. # only 1 profile found
  451. if [ "$(grep -c '^\[Profile' "${ini_file}")" -eq 1 ]; then
  452. ini_content="$(grep '^\[Profile' -A 4 "${ini_file}")"
  453. # more than 1 profile
  454. else
  455. printf 'Profiles found:\n'
  456. printf '————————————————————————————————————————————\n'
  457. grep -E 'Default=[^1]|\[Profile[0-9]*\]|Name=|Path=|^$' "${ini_file}"
  458. printf '————————————————————————————————————————————\n'
  459. printf '%s' "${color_user_input}Select the profile number"
  460. printf '%s' " (Profile(0), Profile(1) etc.): 1,2,…?${no_color_stdout} "
  461. read -r reply
  462. printf '\n'
  463. ini_content="$(grep '^\[Profile'"${reply}" -A 4 "${ini_file}")" \
  464. || err_exit "Profile${reply} does not exist"
  465. fi
  466. profile_path_to_add="$(printf '%s' "${ini_content}" \
  467. | sed -n 's/^Path=\(.*\)$/\1/p')"
  468. path_is_rel="$(printf '%s' "${ini_content}" \
  469. | sed -n 's/^IsRelative=\([01]\)$/\1/p')"
  470. # update global variable if path is relative
  471. [ "${path_is_rel}" -eq 1 ] \
  472. && profile_path_to_add="$(dirname "${ini_file}")/${profile_path_to_add}"
  473. update_profile_path "${profile_path_to_add}"
  474. }
  475. # Add a path to the list of profile paths, separated by newlines.
  476. # $1: one profile path
  477. update_profile_path() {
  478. if [ ! -d "${1}" ]; then
  479. print_warn "Invalid profile path: '${1}'"
  480. return 1
  481. fi
  482. if [ -e "${1}/user.js" ] && [ ! -w "${1}/user.js" ]; then
  483. print_warn "No permission to write file: '${1}/user.js'"
  484. return 1
  485. fi
  486. # if profile path empty, set variable to path
  487. if [ -z "${profile_path}" ]; then
  488. profile_path="${1}"
  489. # else append profile path
  490. else
  491. profile_path="$(printf '%s\n%s' "${profile_path}" "${1}")"
  492. fi
  493. }
  494. #########################
  495. # Update Updater #
  496. #########################
  497. # Update this updater. Call this function with "$@" as argument.
  498. update_updater() {
  499. # update check has not been activated
  500. [ "${update}" = 'false' ] && return 0
  501. readonly new_updater="${tmp_dir}/new_updater"
  502. if ! download_file "${new_updater}" \
  503. 'https://notabug.org/TotallyLeGIT/PH-userjs-updater/raw/main/updater'
  504. then
  505. print_warn 'Download of new updater failed'
  506. return 1
  507. fi
  508. chmod u+x "${new_updater}"
  509. current_updater_version="$("${script_path}" -v | cut -d ' ' -f 2)"
  510. new_updater_version="$("${new_updater}" -v | cut -d ' ' -f 2)"
  511. # compare updater versions, if different
  512. while [ "${current_updater_version}" != "${new_updater_version}" ]; do
  513. difference="$((${new_updater_version%%.*}-${current_updater_version%%.*}))"
  514. # newer version available
  515. if [ "${difference}" -gt 0 ]; then
  516. printv_info '0' 'updater' 'update available'
  517. # apply update
  518. if ! { cat "${new_updater}" > "${script_path}" ; } > /dev/null 2>&1; then
  519. print_warn "No permission to write file: '${script_path}'"
  520. return 1
  521. fi
  522. printv_info '1' 'changelog' \
  523. 'https://notabug.org/TotallyLeGIT/PH-userjs-updater/raw/main/CHANGELOG.md'
  524. # update user.js with new version
  525. "${script_path}" "${@}"
  526. # exit with exit code from updater call above
  527. exit "${?}"
  528. # version number is equal (go to next number)
  529. elif [ "${difference}" -eq 0 ]; then
  530. current_updater_version="${current_updater_version#*.}"
  531. new_updater_version="${new_updater_version#*.}"
  532. # this should not be happening
  533. else
  534. print_warn 'Your updater version seems odd. Check updater homepage' \
  535. 'and/or update manually.'
  536. return 2
  537. fi
  538. done
  539. printv_info '0' 'updater' 'up to date'
  540. }
  541. #########################
  542. # Update user.js #
  543. #########################
  544. # Add global overrides to a user.js.
  545. # $1: Path to a file which the overrides should be appended to
  546. add_global_overrides() {
  547. [ ! -e "${global_override_path}" ] && return 0
  548. printf '\n\n// global user-overrides\n' >> "${1}"
  549. append_overrides_content "${global_override_path}" "${1}" '1'
  550. }
  551. # Add local overrides to a user.js. Acts on a user.js whose path must be
  552. # stored in the variable $new_userjs which is changed if there are overrides.
  553. add_profile_overrides() {
  554. { [ "${override}" = 'false' ] || [ ! -e "${override_path}" ] ; } && return 0
  555. new_userjs_tmp="${new_userjs}_$(basename "${PWD}")"
  556. cp "${new_userjs}" "${new_userjs_tmp}"
  557. new_userjs="${new_userjs_tmp}"
  558. printf '\n\n// user-overrides\n' >> "${new_userjs}"
  559. append_overrides_content "${override_path}" "${new_userjs}"
  560. }
  561. # Append given override file or all files suffixed .js in given directory.
  562. # $1: Path to overrides
  563. # $2: Path to a file to append the overrides to
  564. # $3: Set to anything different than empty or 0 to indicate global overrides
  565. append_overrides_content() {
  566. input="${1}"
  567. output="${2}"
  568. if [ -f "${input}" ]; then
  569. printf '\n' >> "${output}"
  570. cat "${input}" >> "${output}"
  571. [ "${3:-0}" = 0 ] && printv_info '0' 'profile overrides' "${input}"
  572. elif [ -d "${input}" ]; then
  573. files="$(find "${input}" -mindepth 1 -maxdepth 1 -type f -name '*.js')"
  574. if [ -z "${files}" ]; then
  575. print_warn "No .js file in directory: '${input}'"
  576. return 1
  577. else
  578. printf '%s\n' "${files}" \
  579. | while read -r f
  580. do
  581. append_overrides_content "${f}" "${output}" "${3}"
  582. done
  583. fi
  584. else
  585. if [ "${override}" = 'soft' ]; then
  586. printv_info '0' 'profile overrides' 'none found'
  587. else
  588. print_warn "Invalid overrides path: '${input}'"
  589. return 1
  590. fi
  591. fi
  592. }
  593. # Perform checks whether the user.js to be downloaded seems sane given a
  594. # specific profile (profile = current working dir) and warn if not.
  595. check_userjs_against_profile() {
  596. unset print_warning
  597. [ -z "${version_profile_compat}" ] && return 0
  598. if [ "${ff_version_wanted_global}" != "${version_profile_compat}" ]
  599. then
  600. printv_info '0' 'profile used by' "${version_profile_compat}"
  601. fi
  602. # Thunderbird
  603. if [ -z "${is_config_wanted_profile_ff}" ]; then
  604. if [ "${version_profile_compat%% *}" = 'Firefox' ]; then
  605. print_warning='true'
  606. fi
  607. # Firefox
  608. else
  609. if [ "${version_profile_compat}" = 'Thunderbird' ]; then
  610. print_warning='true'
  611. # Firefox update channels do not match
  612. elif [ "${version_profile_compat#Firefox release}" != "$(printf \
  613. '%s' "${esr_to_dl}" | sed 's/ff/Firefox ESR /')" ]
  614. then
  615. print_warning='true'
  616. fi
  617. fi
  618. }
  619. # Return a user.js download link. Variables used here must be set beforehand.
  620. get_userjs_link() {
  621. case "${config_wanted_profile}" in
  622. empty)
  623. printf 'empty'
  624. return ;;
  625. arkenfox)
  626. printf 'https://raw.githubusercontent.com/arkenfox/user.js/master/user.js'
  627. return ;;
  628. HorlogeSkynet|horlogeskynet)
  629. printf 'https://raw.githubusercontent.com/HorlogeSkynet/thunderbird-user.js/master/user.js'
  630. return ;;
  631. esac
  632. printf '%s' "https://www.privacy-handbuch.de/download/"
  633. if [ -n "${is_config_wanted_profile_ff}" ]; then
  634. printf '%s' "${esr_to_dl:+$esr_to_dl/}"
  635. fi
  636. printf '%s' "${config_wanted_profile}/user.js"
  637. }
  638. # Print the version of a PH user.js file or return 1 if no user.js present.
  639. get_userjs_version() {
  640. [ -f "${1}" ] && userjs_version="$(grep -m1 \
  641. '_user\.js\.prhdb\|https://github\.com/\(arkenfox/\|HorlogeSkynet/thunderbird-\)user\.js' "${1}" \
  642. | sed -ne '/arkenfox/ s/.*/arkenfox/p' \
  643. -e '/HorlogeSkynet/ s/.*/HorlogeSkynet/p' \
  644. -e '/prhdb/ s/^[^"]*"[^"]*"[^"]*"\([^"]*\)"[^"]*$/\1/p')"
  645. [ -z "${userjs_version}" ] && return 1
  646. printf '%s' "${userjs_version}"
  647. }
  648. # Set $version_profile_compat to Thunderbird/Firefox version. Executable path
  649. # is taken from $compat_file. Current working directory needs to be the profile.
  650. #
  651. # Return: 1 if $compat_file doesn't exist or on error, else 0.
  652. get_version_compat() {
  653. if [ -e "${compat_file}" ]; then
  654. exec_dir_profile="$(sed \
  655. -ne 's/^LastPlatformDir=\([[:graph:]]*\)$/\1/p' "${compat_file}" \
  656. | sed -e "${sed_compat_os_script}")"
  657. if version_profile_compat="$(extract_version "${exec_dir_profile}/")"; then
  658. printf '%s' "${version_profile_compat}"
  659. return 0
  660. fi
  661. fi
  662. return 1
  663. }
  664. # Download a user.js, open it and exit.
  665. read_and_exit() {
  666. [ "${just_read}" = 'false' ] && return 0
  667. if ! set_config_wanted_profile; then
  668. err_exit "Could not determine which CONFIG to download. Use '-c CONFIG'"
  669. fi
  670. set_esr_to_dl
  671. userjs_tmp="$(mktemp -dt userjs.XXXXXX)/${esr_to_dl}${config_wanted_profile}"
  672. if ! download_file "${userjs_tmp}" "$(get_userjs_link)"; then
  673. err_exit 'Download of new user.js failed'
  674. fi
  675. printv_info '0' 'user.js saved to' "${userjs_tmp}"
  676. open_file "${userjs_tmp}"
  677. exit 0
  678. }
  679. # Set $config_wanted_profile to the CONFIG the user wants for this profile.
  680. set_config_wanted_profile() {
  681. unset is_config_wanted_profile_ff
  682. # 'keep' means to try autodetection
  683. if [ "${config_wanted_global}" = 'keep' ]; then
  684. # config detection impossible
  685. [ -z "${current_userjs_version_long}" ] && return 1
  686. config_wanted_profile="${current_userjs_version_long%_*}"
  687. # will be e. g. 'streng' or empty afterwards
  688. config_wanted_profile="${config_wanted_profile#*_}"
  689. # TODO: remove %2 in future version (tb2 leftover)
  690. config_wanted_profile="${config_wanted_profile%2}"
  691. else
  692. config_wanted_profile="${config_wanted_global}"
  693. fi
  694. if [ "${config_wanted_profile%-streng}" != 'tb' ]; then
  695. is_config_wanted_profile_ff='true'
  696. fi
  697. }
  698. # Set $esr_to_dl to something like 'ff91' or empty
  699. set_esr_to_dl() {
  700. # don't waste time if already globally forced by user
  701. [ "${ff_version_specified}" = 'true' ] && return 0
  702. unset esr_to_dl
  703. [ -z "${is_config_wanted_profile_ff}" ] && return 0
  704. if [ -n "${current_userjs_version_long}" ] \
  705. && [ -n "${current_userjs_version_long%%tb*}" ]
  706. then
  707. esr_to_dl="$(printf '%s' "${current_userjs_version_long}" \
  708. | sed -ne 's/\(ff[[:digit:]]*\)_.*/\1/p')"
  709. elif [ "${version_profile_compat%% *}" = 'Firefox' ]; then
  710. if [ "${version_profile_compat% *}" = 'Firefox ESR' ]; then
  711. esr_to_dl="ff${version_profile_compat##* }"
  712. fi
  713. elif [ "${ff_version_wanted_global%% *}" = 'Firefox' ]; then
  714. if [ "${ff_version_wanted_global% *}" = 'Firefox ESR' ]; then
  715. esr_to_dl="ff${ff_version_wanted_global##* }"
  716. fi
  717. # Could not find any update channel
  718. else
  719. err_exit "Could not determine Firefox update channel. Use '-e VERSION'"
  720. fi
  721. }
  722. # Apply chosen version of user.js and any custom overrides.
  723. update_userjs() {
  724. current_userjs_version_long="$(get_userjs_version 'user.js')"
  725. printv_info '1' 'profile path' "${PWD}"
  726. if ! set_config_wanted_profile; then
  727. if [ "${profile_path_option}" = 'all' ]; then
  728. printv_info '1' 'user.js status' 'none found – skipped'
  729. return 1
  730. else
  731. err_exit 'Could not determine which CONFIG to download. Use' \
  732. "'-c CONFIG' and/or '-p PROFILE'"
  733. fi
  734. fi
  735. version_profile_compat="$(get_version_compat)"
  736. set_esr_to_dl
  737. check_userjs_against_profile
  738. printv_info '0' 'current user.js' "${current_userjs_version_long:-not detected}"
  739. new_userjs="${tmp_dir}/userjs_${is_config_wanted_profile_ff:+$esr_to_dl}${config_wanted_profile}"
  740. get_userjs "${new_userjs}" "${is_config_wanted_profile_ff}" || return 1
  741. # if `check_userjs_against_profile` found something
  742. if [ -n "${print_warning}" ]; then
  743. print_warn "It seems the user.js won't fit this profile. Please" \
  744. 'recheck your configuration.'
  745. fi
  746. add_profile_overrides
  747. # Inspect (-i). If the user.js is identical, this script exits and deletes
  748. # the temporary user.js faster than open_file calling xdg-open opens it, so
  749. # it opens an already deleted, thus empty file. Sleep works around this.
  750. # It shouldn't be too bad, as the editor is opened in foreground anyway and
  751. # -i is probably not used a lot – not in a fast update run anyway.
  752. [ "${view}" = 'true' ] && open_file "${new_userjs}" && sleep 0.5
  753. # files are identical
  754. if cmp -s "${new_userjs}" 'user.js'; then
  755. printv_info '1' 'user.js status' 'identical – skipped'
  756. return 0
  757. fi
  758. if [ "${confirm}" = 'yes' ]; then
  759. printf '\n%s' "${color_user_input}Update user.js?"
  760. printf '\n%s' "(y)es / (n)o: Y,n?${no_color_stdout} "
  761. IFS='' read -r reply </dev/tty
  762. # y, j or empty → comfirmation
  763. if printf '%s' "${reply}" | grep -vq '^[yYjJ]*$'; then
  764. printf '%s\n' "${color_info}skipping profile${no_color_stdout}"
  765. return 1
  766. fi
  767. fi
  768. if [ -f 'user.js' ]; then
  769. status_output='backed up and updated'
  770. else
  771. status_output='installed'
  772. fi
  773. if [ "${dry_run}" = 'false' ]; then
  774. # create diff
  775. [ "${backup_multiple}" = 'true' ] && date_file="$(date '+%FT%T')_" \
  776. || date_file=''
  777. if [ "${compare}" = 'true' ]; then
  778. mkdir -p "${diff_dir}"
  779. diff -wBU 0 'user.js' "${new_userjs}" > "${diff_dir}/${date_file}diff.txt"
  780. printv_info '0' 'diff status' 'diff file created'
  781. fi
  782. # backup user.js
  783. mkdir -p "${backup_dir}"
  784. backup_file="${backup_dir}/${date_file}user.js"
  785. [ -f 'user.js' ] && cp -p 'user.js' "${backup_file}"
  786. # install newest user.js
  787. cat "${new_userjs}" > 'user.js'
  788. dry_run_output=''
  789. else
  790. dry_run_output=' (DRY-RUN)'
  791. fi
  792. printv_info '1' 'user.js status' "${status_output}${dry_run_output}"
  793. return 0
  794. }
  795. #########################
  796. # Main() #
  797. #########################
  798. set_variables
  799. parse_options "${@}"
  800. prepare "${0}"
  801. update_updater "${@}"
  802. abstract_os
  803. set_ff_version_wanted_global
  804. read_and_exit
  805. print_global_overrides
  806. get_profile_path
  807. while read -r line; do
  808. printv '0' '————————————————————————————————————————————\n'
  809. # this enables us to leave relative profile paths as they are
  810. { cd "${pwd_start}" && cd "${line}" ; } || exit 99
  811. update_userjs
  812. done <<EOF
  813. ${profile_path}
  814. EOF
  815. # vim: shiftwidth=2 tabstop=2 noexpandtab