Add shared asl3-ini.sh helpers for safe config parsing.
0001-Add-shared-asl3-ini.sh-helpers-for-safe-config-parsi.patch
--- diff --git a/bin/asl3-ini.sh b/bin/asl3-ini.sh --- /dev/null +++ b/bin/asl3-ini.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +# Shared helpers for safely reading KEY=VALUE config files without sourcing them. + +asl3_ini_trim() { + local value="$1" + value="${value//$'\r'/}" + value="${value#"${value%%[![:space:]]*}"}" + value="${value%"${value##*[![:space:]]}"}" + printf '%s' "$value" +} + +asl3_ini_unquote() { + local value + value="$(asl3_ini_trim "$1")" + if [[ "$value" == \"*\" && "$value" == *\" ]]; then + value="${value:1:${#value}-2}" + elif [[ "$value" == \'*\' && "$value" == *\' ]]; then + value="${value:1:${#value}-2}" + fi + printf '%s' "$value" +} + +# Usage: asl3_ini_get FILE KEY +# Prints the value for KEY, or an empty string when missing. +asl3_ini_get() { + local file="$1" + local want="$2" + local line key value + + [ -f "$file" ] || return 0 + + while IFS= read -r line || [ -n "$line" ]; do + line="${line%%$'\r'}" + [[ "$line" =~ ^[[:space:]]*(#|$) ]] && continue + [[ "$line" =~ ^[[:space:]]*([A-Za-z0-9_]+)[[:space:]]*=[[:space:]]*(.*)$ ]] || continue + key="${BASH_REMATCH[1]}" + value="$(asl3_ini_unquote "${BASH_REMATCH[2]}")" + if [ "$key" = "$want" ]; then + printf '%s' "$value" + return 0 + fi + done < "$file" + + return 0 +} + +# Usage: asl3_ini_load FILE [PREFIX] +# Exports every KEY as PREFIXKEY (default PREFIX is empty). +asl3_ini_load() { + local file="$1" + local prefix="${2:-}" + local line key value + + [ -f "$file" ] || return 0 + + while IFS= read -r line || [ -n "$line" ]; do + line="${line%%$'\r'}" + [[ "$line" =~ ^[[:space:]]*(#|$) ]] && continue + [[ "$line" =~ ^[[:space:]]*([A-Za-z0-9_]+)[[:space:]]*=[[:space:]]*(.*)$ ]] || continue + key="${BASH_REMATCH[1]}" + value="$(asl3_ini_unquote "${BASH_REMATCH[2]}")" + printf -v "${prefix}${key}" '%s' "$value" + export "${prefix}${key}" + done < "$file" +} + +# Usage: asl3_ini_set FILE KEY VALUE +# Creates or updates KEY with a safely quoted shell-style value. +asl3_ini_set() { + local file="$1" + local key="$2" + local value="$3" + local tmp line found=0 + local escaped="${value//\\/\\\\}" + escaped="${escaped//\"/\\\"}" + + tmp="$(mktemp "${file}.XXXXXX")" + + if [ -f "$file" ]; then + while IFS= read -r line || [ -n "$line" ]; do + if [[ "$line" =~ ^[[:space:]]*${key}[[:space:]]*= ]]; then + printf '%s="%s"\n' "$key" "$escaped" >> "$tmp" + found=1 + else + printf '%s\n' "$line" >> "$tmp" + fi + done < "$file" + fi + + if [ "$found" -eq 0 ]; then + printf '%s="%s"\n' "$key" "$escaped" >> "$tmp" + fi + + mv "$tmp" "$file" +} + +# Reject empty paths, /, and parent-directory traversal. +asl3_validate_dir_path() { + local path="$1" + local label="${2:-path}" + + if [ -z "$path" ]; then + echo "${label} must not be empty" >&2 + return 1 + fi + if [ "$path" = "/" ]; then + echo "${label} must not be /" >&2 + return 1 + fi + if [[ "$path" != /* ]]; then + echo "${label} must be an absolute path: $path" >&2 + return 1 + fi + if [[ "$path" =~ (^|/)\.\.(/|$) ]]; then + echo "${label} must not contain .. segments: $path" >&2 + return 1 + fi + return 0 +} + +# Quote a value for use in a root crontab line. +asl3_quote_cron_arg() { + local value="${1//\'/\'\\\'\'}" + printf "'%s'" "$value" +} + +# Reject ZIP member paths that could escape the target directory. +asl3_zip_entry_is_safe() { + local relative_file="$1" + + [ -n "$relative_file" ] || return 1 + case "$relative_file" in + /*|../*|*/../*|*/..|..) return 1 ;; + esac + return 0 +} --