Patch 0001

Add shared asl3-ini.sh helpers for safe config parsing.

0001-Add-shared-asl3-ini.sh-helpers-for-safe-config-parsi.patch

0001 0002 0003 0004 0005 0006 0007
From 2fb0858d022426c68596b85e64ec182db6d76f9b Mon Sep 17 00:00:00 2001
From: "Jory A. Pratt" <geekypenguin@gmail.com>
Date: Thu, 2 Jul 2026 07:25:51 -0500
Subject: [PATCH 1/7] Add shared asl3-ini.sh helpers for safe config parsing.

Introduce reusable functions for reading and writing KEY=VALUE files
without sourcing them, validating directory paths, quoting cron args,
and rejecting unsafe ZIP member paths.
---
 bin/asl3-ini.sh | 136 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 136 insertions(+)
 create mode 100644 bin/asl3-ini.sh

diff --git a/bin/asl3-ini.sh b/bin/asl3-ini.sh
new file mode 100644
index 0000000..6e7548f
--- /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
+}
-- 
2.47.3