From 6dfd553ae03de1e7d31603411335a400876579fd Mon Sep 17 00:00:00 2001 From: polarathene <5098581+polarathene@users.noreply.github.com> Date: Mon, 4 Sep 2023 11:24:10 +1200 Subject: [PATCH 1/3] fix: Helper `_replace_by_env_in_file()` support delimiter parameter Minor change to add support for usage with `saslauthd.conf` which differs with `:` for it's key/value delimiter. Also adopts the`_escape_for_sed()` method, instead of the inline sed pattern (_which for some reason escaped `=` but not `(` + `)`, thus buggy if ever matching on input with those tokens_). `_escape_for_sed()` likewise wasn't escaping for `(` + `)` or even `|`, which are required for proper escape support if using `sed -E` / `sed -r` with this method. Additionally added escaping support for `&` replacement segment token, which seems valid. Increased verbosity to better grok pattern matching expression, clarified escaping concern with `sed` delimiter since the project is not consistent there. --- target/scripts/helpers/utils.sh | 40 ++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/target/scripts/helpers/utils.sh b/target/scripts/helpers/utils.sh index c74fd7e8..d2d31659 100644 --- a/target/scripts/helpers/utils.sh +++ b/target/scripts/helpers/utils.sh @@ -4,14 +4,30 @@ function _escape() { echo "${1//./\\.}" } -# TODO: Not in use currently. Maybe in the future: https://github.com/docker-mailserver/docker-mailserver/pull/3484/files#r1299410851 -# Replaces a string so that it can be used inside -# `sed` safely. +# Replaces a string so that it can be used inside the `sed` regex segment safely. +# WARNING: Only for use with `sed -E` / `sed -r` due to escaping additional tokens, +# which are valid tokens when escaped in basic regex mode. # # @param ${1} = string to escape # @output = prints the escaped string function _escape_for_sed() { - sed -E 's/[]\/$*.^[]/\\&/g' <<< "${1:?String to escape for sed is required}" + # Escapes all special tokens: + # - `/` should represent the sed delimiter (caution when using sed with a non-default delimiter). + # - `]` should be the first char, while `.` must not be the 2nd. + # - Within the `[ ... ]` scope, no escaping is needed regardless of basic vs extended regexp mode. + local REGEX_BASIC='][/\$*.^' + # In this mode (`-E` / `-r`), these tokens no longer require a preceding `\`, so must also be escaped: + local REGEX_EXTENDED='|?)(}{+' + # The replacement segment is compatible but most tokens do not require escaping. + # `&` is a token for this segment, and is compatible with escaping in the regex segment: + local TOKEN_REPLACEMENT='&' + local REGEX_SEGMENT="${REGEX_BASIC}${REGEX_EXTENDED}${TOKEN_REPLACEMENT}" + + # Output: `\\` prepends a `\` to a token matched by the regex segment (`&`) + local PREPEND_ESCAPE='\\&' + + # Full sed expression: sed -E 's/[][/\$*.^|?)(}{+&]/\\&/g' + sed -E "s/[${REGEX_SEGMENT}]/${PREPEND_ESCAPE}/g" <<< "${1:?String to escape for sed is required}" } # Returns input after filtering out lines that are: @@ -78,8 +94,9 @@ function _reload_postfix() { # calling this function. # # @option --shutdown-on-error = shutdown in case an error is detected -# @param ${1} = prefix for environment variables -# @param ${2} = file in which substitutions should take place +# @param ${1} = Prefix for selecting environment variables +# @param ${2} = File in which substitutions should take place +# @param ${3} = The key/value assignment operator used (default: `=`) [OPTIONAL] # # ## Example # @@ -87,7 +104,7 @@ function _reload_postfix() { # you can set the environment variable `POSTFIX_README_DIRECTORY='/new/dir/'` # (`POSTFIX_` is an arbitrary prefix, you can choose the one you like), # and then call this function: -# `_replace_by_env_in_file 'POSTFIX_' 'PATH TO POSTFIX's main.cf>` +# `_replace_by_env_in_file 'POSTFIX_' '/etc/postfix/main.cf` # # ## Panics # @@ -105,15 +122,16 @@ function _replace_by_env_in_file() { fi local ENV_PREFIX=${1} CONFIG_FILE=${2} + local KV_DELIMITER=${3:-'='} local ESCAPED_VALUE ESCAPED_KEY - while IFS='=' read -r KEY VALUE; do + while IFS="${KV_DELIMITER}" read -r KEY VALUE; do KEY=${KEY#"${ENV_PREFIX}"} # strip prefix - ESCAPED_KEY=$(sed -E 's#([\=\&\|\$\.\*\/\[\\^]|\])#\\\1#g' <<< "${KEY,,}") - ESCAPED_VALUE=$(sed -E 's#([\=\&\|\$\.\*\/\[\\^]|\])#\\\1#g' <<< "${VALUE}") + ESCAPED_KEY=$(_escape_for_sed "${KEY,,}") + ESCAPED_VALUE=$(_escape_for_sed "${VALUE}") [[ -n ${ESCAPED_VALUE} ]] && ESCAPED_VALUE=" ${ESCAPED_VALUE}" _log 'trace' "Setting value of '${KEY}' in '${CONFIG_FILE}' to '${VALUE}'" - sed -i -E "s#^${ESCAPED_KEY}[[:space:]]*=.*#${ESCAPED_KEY} =${ESCAPED_VALUE}#g" "${CONFIG_FILE}" + sed -i -E "s#^${ESCAPED_KEY}[[:space:]]*${KV_DELIMITER}.*#${ESCAPED_KEY} ${KV_DELIMITER}${ESCAPED_VALUE}#g" "${CONFIG_FILE}" done < <(env | grep "^${ENV_PREFIX}") } From 01f7db09656790208121726b72c7525a6f9c9364 Mon Sep 17 00:00:00 2001 From: polarathene <5098581+polarathene@users.noreply.github.com> Date: Mon, 4 Sep 2023 11:44:31 +1200 Subject: [PATCH 2/3] chore: Preserve K/V white-space of original source No need to check for a non-empty value to prepend a space (_since an empty value is used via sed anyway?_). Can also be a bit DRY with the sed pattern, matching the key + delimiter (_and all white-space before/after the delimiter until the value_), then capture that for the replacement left-side value while only actually swapping the value for the ENV input value. Should be an improvement, unless there is a scenario that would differ between `` and ` ` as valid value assignments? --- target/scripts/helpers/utils.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/target/scripts/helpers/utils.sh b/target/scripts/helpers/utils.sh index d2d31659..55ad87bf 100644 --- a/target/scripts/helpers/utils.sh +++ b/target/scripts/helpers/utils.sh @@ -129,9 +129,8 @@ function _replace_by_env_in_file() { KEY=${KEY#"${ENV_PREFIX}"} # strip prefix ESCAPED_KEY=$(_escape_for_sed "${KEY,,}") ESCAPED_VALUE=$(_escape_for_sed "${VALUE}") - [[ -n ${ESCAPED_VALUE} ]] && ESCAPED_VALUE=" ${ESCAPED_VALUE}" _log 'trace' "Setting value of '${KEY}' in '${CONFIG_FILE}' to '${VALUE}'" - sed -i -E "s#^${ESCAPED_KEY}[[:space:]]*${KV_DELIMITER}.*#${ESCAPED_KEY} ${KV_DELIMITER}${ESCAPED_VALUE}#g" "${CONFIG_FILE}" + sed -i -E "s#^(${ESCAPED_KEY}[[:space:]]*${KV_DELIMITER}[[:space:]]*).*#\1${ESCAPED_VALUE}#g" "${CONFIG_FILE}" done < <(env | grep "^${ENV_PREFIX}") } From 39fb585bfafc8735df7d49c12a410c9b97a755e5 Mon Sep 17 00:00:00 2001 From: Brennan Kinney <5098581+polarathene@users.noreply.github.com> Date: Mon, 11 Sep 2023 20:02:55 +1200 Subject: [PATCH 3/3] chore: Adopt `sedfile` + case-insensitive pattern matching Both should be improvements for usage Co-authored-by: Casper --- target/scripts/helpers/utils.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/scripts/helpers/utils.sh b/target/scripts/helpers/utils.sh index 55ad87bf..d0997d04 100644 --- a/target/scripts/helpers/utils.sh +++ b/target/scripts/helpers/utils.sh @@ -130,7 +130,7 @@ function _replace_by_env_in_file() { ESCAPED_KEY=$(_escape_for_sed "${KEY,,}") ESCAPED_VALUE=$(_escape_for_sed "${VALUE}") _log 'trace' "Setting value of '${KEY}' in '${CONFIG_FILE}' to '${VALUE}'" - sed -i -E "s#^(${ESCAPED_KEY}[[:space:]]*${KV_DELIMITER}[[:space:]]*).*#\1${ESCAPED_VALUE}#g" "${CONFIG_FILE}" + sedfile -i -E "s#^(${ESCAPED_KEY}[[:space:]]*${KV_DELIMITER}[[:space:]]*).*#\1${ESCAPED_VALUE}#gi" "${CONFIG_FILE}" done < <(env | grep "^${ENV_PREFIX}") }