diff --git a/bin/omarchy-cmd-missing b/bin/omarchy-cmd-missing index 470d69c3..2b48be81 100755 --- a/bin/omarchy-cmd-missing +++ b/bin/omarchy-cmd-missing @@ -3,7 +3,7 @@ # omarchy:summary=Check whether any required commands are missing for cmd in "$@"; do - if ! command -v "$cmd" &>/dev/null; then + if ! command -v -- "$cmd" &>/dev/null; then exit 0 fi done diff --git a/bin/omarchy-cmd-present b/bin/omarchy-cmd-present index 4e758760..57b05102 100755 --- a/bin/omarchy-cmd-present +++ b/bin/omarchy-cmd-present @@ -3,7 +3,7 @@ # omarchy:summary=Check whether all required commands are available for cmd in "$@"; do - command -v "$cmd" &>/dev/null || exit 1 + command -v -- "$cmd" &>/dev/null || exit 1 done exit 0 diff --git a/bin/omarchy-hook-install b/bin/omarchy-hook-install index 6e954bd1..08b82e58 100755 --- a/bin/omarchy-hook-install +++ b/bin/omarchy-hook-install @@ -27,7 +27,7 @@ if [[ -z $HOOK_TYPE || $HOOK_TYPE == */* || $HOOK_TYPE == "." || $HOOK_TYPE == " fi HOOK_DIR="$HOME/.config/omarchy/hooks/$HOOK_TYPE.d" -HOOK_NAME=$(basename "$HOOK_FILE") +HOOK_NAME=$(basename -- "$HOOK_FILE") HOOK_PATH="$HOOK_DIR/$HOOK_NAME" if [[ ! -f $HOOK_FILE ]]; then @@ -36,7 +36,7 @@ if [[ ! -f $HOOK_FILE ]]; then fi mkdir -p "$HOOK_DIR" -cp "$HOOK_FILE" "$HOOK_PATH" +cp -- "$HOOK_FILE" "$HOOK_PATH" chmod 755 "$HOOK_PATH" echo "Installed $HOOK_TYPE hook: $HOOK_PATH" diff --git a/bin/omarchy-hw-match b/bin/omarchy-hw-match index 318ca7e8..cecca3b0 100755 --- a/bin/omarchy-hw-match +++ b/bin/omarchy-hw-match @@ -3,5 +3,5 @@ # omarchy:summary=Match against the computer's DMI product name or product family (case-insensitive). # omarchy:args= -grep -qi "$1" /sys/class/dmi/id/product_name 2>/dev/null || -grep -qi "$1" /sys/class/dmi/id/product_family 2>/dev/null +grep -qi -- "$1" /sys/class/dmi/id/product_name 2>/dev/null || +grep -qi -- "$1" /sys/class/dmi/id/product_family 2>/dev/null diff --git a/bin/omarchy-launch-editor b/bin/omarchy-launch-editor index 52999719..cbf12c78 100755 --- a/bin/omarchy-launch-editor +++ b/bin/omarchy-launch-editor @@ -26,12 +26,12 @@ omarchy-cmd-present "$editor" || editor="nvim" case "${editor##*/}" in nvim | vim | nano | micro | hx | helix | fresh) if [[ $inline == "true" ]]; then - exec "$editor" "$@" + exec "$editor" -- "$@" else - exec omarchy-launch-tui "$editor" "$@" + exec omarchy-launch-tui "$editor" -- "$@" fi ;; *) - exec setsid uwsm-app -- "$editor" "$@" + exec setsid uwsm-app -- "$editor" -- "$@" ;; esac diff --git a/bin/omarchy-pkg-add b/bin/omarchy-pkg-add index 977d7efd..1f5e4091 100755 --- a/bin/omarchy-pkg-add +++ b/bin/omarchy-pkg-add @@ -7,15 +7,15 @@ if omarchy-pkg-missing "$@"; then if (( EUID == 0 )); then - pacman -S --noconfirm --needed "$@" || exit 1 + pacman -S --noconfirm --needed -- "$@" || exit 1 else - sudo pacman -S --noconfirm --needed "$@" || exit 1 + sudo pacman -S --noconfirm --needed -- "$@" || exit 1 fi fi for pkg in "$@"; do # Secondary check to handle states where pacman doesn't actually register an error - if ! pacman -Q "$pkg" &>/dev/null; then + if ! pacman -Q -- "$pkg" &>/dev/null; then echo -e "\033[31mError: Package '$pkg' did not install\033[0m" >&2 exit 1 fi diff --git a/bin/omarchy-pkg-aur-add b/bin/omarchy-pkg-aur-add index 7a152be7..64ad097c 100755 --- a/bin/omarchy-pkg-aur-add +++ b/bin/omarchy-pkg-aur-add @@ -4,12 +4,12 @@ # omarchy:args= if omarchy-pkg-missing "$@"; then - yay -S --noconfirm --needed "$@" || exit 1 + yay -S --noconfirm --needed -- "$@" || exit 1 fi for pkg in "$@"; do # Secondary check to handle states where pacman doesn't actually register an error - if ! pacman -Q "$pkg" &>/dev/null; then + if ! pacman -Q -- "$pkg" &>/dev/null; then echo -e "\033[31mError: Package '$pkg' did not install\033[0m" >&2 exit 1 fi diff --git a/bin/omarchy-pkg-missing b/bin/omarchy-pkg-missing index c4059dff..b282d2f3 100755 --- a/bin/omarchy-pkg-missing +++ b/bin/omarchy-pkg-missing @@ -4,7 +4,7 @@ # omarchy:args= for pkg in "$@"; do - if ! pacman -Q "$pkg" &>/dev/null; then + if ! pacman -Q -- "$pkg" &>/dev/null; then exit 0 fi done diff --git a/bin/omarchy-pkg-present b/bin/omarchy-pkg-present index 8c4340d6..0a0645bd 100755 --- a/bin/omarchy-pkg-present +++ b/bin/omarchy-pkg-present @@ -4,7 +4,7 @@ # omarchy:args= for pkg in "$@"; do - pacman -Q "$pkg" &>/dev/null || exit 1 + pacman -Q -- "$pkg" &>/dev/null || exit 1 done exit 0 diff --git a/bin/omarchy-restart-app b/bin/omarchy-restart-app index d250650d..e7d7ce5d 100755 --- a/bin/omarchy-restart-app +++ b/bin/omarchy-restart-app @@ -3,5 +3,5 @@ # omarchy:summary=Restart an application by killing it and relaunching via uwsm. # omarchy:args= [application-args...] -pkill -x $1 +pkill -x -- "$1" setsid uwsm-app -- "$@" >/dev/null 2>&1 & diff --git a/bin/omarchy-setup-security-fingerprint b/bin/omarchy-setup-security-fingerprint index e46aaed9..a57dea4a 100755 --- a/bin/omarchy-setup-security-fingerprint +++ b/bin/omarchy-setup-security-fingerprint @@ -80,7 +80,7 @@ fi # one transaction, so a failed install leaves the existing driver in place. if omarchy-pkg-missing libfprint-git fprintd usbutils; then echo "Installing required packages..." - sudo pacman -S --needed --noconfirm --ask 4 libfprint-git fprintd usbutils + sudo pacman -S --needed --noconfirm --ask 4 -- libfprint-git fprintd usbutils fi # Enroll first fingerprint diff --git a/test/shell.d/command-operand-boundaries-test.sh b/test/shell.d/command-operand-boundaries-test.sh new file mode 100755 index 00000000..652b1ce7 --- /dev/null +++ b/test/shell.d/command-operand-boundaries-test.sh @@ -0,0 +1,259 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +export OMARCHY_PATH="$ROOT" + +if "$ROOT/bin/omarchy-cmd-present" -p; then + fail "cmd-present treats an option-shaped command name literally" +fi +pass "cmd-present treats an option-shaped command name literally" + +"$ROOT/bin/omarchy-cmd-missing" -p || + fail "cmd-missing treats an option-shaped command name literally" +pass "cmd-missing treats an option-shaped command name literally" + +"$ROOT/bin/omarchy-cmd-present" bash || fail "cmd-present still finds an ordinary command" +pass "cmd-present still finds an ordinary command" + +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT + +mock_bin="$test_tmp/bin" +mkdir -p "$mock_bin" + +cat >"$mock_bin/pacman" <<'STUB' +#!/bin/bash + +if [[ -n ${OMARCHY_TEST_PACKAGE_CALLS:-} ]]; then + { + printf 'pacman' + printf ' <%s>' "$@" + printf '\n' + } >>"$OMARCHY_TEST_PACKAGE_CALLS" +fi + +if [[ $1 == "-S" ]]; then + exit 0 +elif [[ $1 != "-Q" ]]; then + exit 2 +fi +shift + +if [[ -n ${OMARCHY_TEST_PACKAGE_CALLS:-} ]]; then + exit 0 +elif [[ ${1:-} == "--" ]]; then + shift + [[ ${1:-} == "installed" ]] +elif [[ ${1:-} == "--help" ]]; then + exit 0 +else + [[ ${1:-} == "installed" ]] +fi +STUB +chmod +x "$mock_bin/pacman" + +if PATH="$mock_bin:$PATH" "$ROOT/bin/omarchy-pkg-present" --help; then + fail "pkg-present treats an option-shaped package name literally" +fi +pass "pkg-present treats an option-shaped package name literally" + +PATH="$mock_bin:$PATH" "$ROOT/bin/omarchy-pkg-missing" --help || + fail "pkg-missing treats an option-shaped package name literally" +pass "pkg-missing treats an option-shaped package name literally" + +PATH="$mock_bin:$PATH" "$ROOT/bin/omarchy-pkg-present" installed || + fail "pkg-present still finds an installed package" +pass "pkg-present still finds an installed package" + +package_calls="$test_tmp/package-calls" +cat >"$mock_bin/omarchy-pkg-missing" <<'STUB' +#!/bin/bash + +exit 0 +STUB +cat >"$mock_bin/sudo" <<'STUB' +#!/bin/bash + +exec "$@" +STUB +cat >"$mock_bin/yay" <<'STUB' +#!/bin/bash + +: "${OMARCHY_TEST_PACKAGE_CALLS:?}" +{ + printf 'yay' + printf ' <%s>' "$@" + printf '\n' +} >>"$OMARCHY_TEST_PACKAGE_CALLS" +STUB +chmod +x "$mock_bin/omarchy-pkg-missing" "$mock_bin/sudo" "$mock_bin/yay" + +: >"$package_calls" +PATH="$mock_bin:$ROOT/bin:$PATH" OMARCHY_TEST_PACKAGE_CALLS="$package_calls" \ + "$ROOT/bin/omarchy-pkg-add" --hookdir=/tmp/hooks + +grep -Fx 'pacman <-S> <--noconfirm> <--needed> <--> <--hookdir=/tmp/hooks>' "$package_calls" >/dev/null || + fail "pkg-add separates package operands from pacman options" "$(<"$package_calls")" +grep -Fx 'pacman <-Q> <--> <--hookdir=/tmp/hooks>' "$package_calls" >/dev/null || + fail "pkg-add verifies an option-shaped package name literally" "$(<"$package_calls")" +pass "pkg-add keeps option-shaped package names out of pacman option parsing" + +: >"$package_calls" +PATH="$mock_bin:$ROOT/bin:$PATH" OMARCHY_TEST_PACKAGE_CALLS="$package_calls" \ + "$ROOT/bin/omarchy-pkg-aur-add" --hookdir=/tmp/hooks + +grep -Fx 'yay <-S> <--noconfirm> <--needed> <--> <--hookdir=/tmp/hooks>' "$package_calls" >/dev/null || + fail "pkg-aur-add separates package operands from yay options" "$(<"$package_calls")" +grep -Fx 'pacman <-Q> <--> <--hookdir=/tmp/hooks>' "$package_calls" >/dev/null || + fail "pkg-aur-add verifies an option-shaped package name literally" "$(<"$package_calls")" +pass "pkg-aur-add keeps option-shaped package names out of package-manager option parsing" + +cat >"$mock_bin/grep" <<'STUB' +#!/bin/bash + +if [[ $1 != "-qi" ]]; then + exit 2 +fi +shift + +if [[ ${1:-} == "--help" ]]; then + exit 0 +elif [[ ${1:-} == "--" ]]; then + shift +fi +[[ ${1:-} == "known-hardware" ]] +STUB +chmod +x "$mock_bin/grep" + +if PATH="$mock_bin:$PATH" "$ROOT/bin/omarchy-hw-match" --help; then + fail "hw-match treats an option-shaped hardware pattern literally" +fi +pass "hw-match treats an option-shaped hardware pattern literally" + +PATH="$mock_bin:$PATH" "$ROOT/bin/omarchy-hw-match" known-hardware || + fail "hw-match still accepts an ordinary hardware pattern" +pass "hw-match still accepts an ordinary hardware pattern" + +pkill_calls="$test_tmp/pkill-calls" +cat >"$mock_bin/pkill" <<'STUB' +#!/bin/bash + +: "${OMARCHY_TEST_PKILL_CALLS:?}" +printf '<%s>\n' "$@" >"$OMARCHY_TEST_PKILL_CALLS" +STUB +cat >"$mock_bin/setsid" <<'STUB' +#!/bin/bash + +if [[ -n ${OMARCHY_TEST_SETSID_CALLS:-} ]]; then + printf '<%s>\n' "$@" >"$OMARCHY_TEST_SETSID_CALLS" +fi +exit 0 +STUB +chmod +x "$mock_bin/pkill" "$mock_bin/setsid" + +PATH="$mock_bin:$PATH" OMARCHY_TEST_PKILL_CALLS="$pkill_calls" \ + "$ROOT/bin/omarchy-restart-app" "-9 kitty" + +pkill_argv=$(<"$pkill_calls") +[[ $pkill_argv == $'<-x>\n<-->\n<-9 kitty>' ]] || + fail "restart-app treats the application name as one literal pkill pattern" "$pkill_argv" +pass "restart-app treats the application name as one literal pkill pattern" + +fake_home="$test_tmp/home" +editor_calls="$test_tmp/editor-calls" +mkdir -p "$fake_home/.local/state/omarchy/defaults" +printf 'nvim\n' >"$fake_home/.local/state/omarchy/defaults/editor" + +cat >"$mock_bin/nvim" <<'STUB' +#!/bin/bash + +: "${OMARCHY_TEST_EDITOR_CALLS:?}" +printf '<%s>\n' "$@" >"$OMARCHY_TEST_EDITOR_CALLS" +STUB +chmod +x "$mock_bin/nvim" + +HOME="$fake_home" PATH="$mock_bin:$ROOT/bin:$PATH" OMARCHY_TEST_EDITOR_CALLS="$editor_calls" \ + "$ROOT/bin/omarchy-launch-editor" --inline -cquit + +editor_argv=$(<"$editor_calls") +[[ $editor_argv == $'<-->\n<-cquit>' ]] || + fail "launch-editor separates an option-shaped path from editor options" "$editor_argv" +pass "launch-editor separates an option-shaped path from editor options" + +cat >"$mock_bin/omarchy-launch-tui" <<'STUB' +#!/bin/bash + +: "${OMARCHY_TEST_EDITOR_CALLS:?}" +printf '<%s>\n' "$@" >"$OMARCHY_TEST_EDITOR_CALLS" +STUB +chmod +x "$mock_bin/omarchy-launch-tui" + +HOME="$fake_home" PATH="$mock_bin:$ROOT/bin:$PATH" OMARCHY_TEST_EDITOR_CALLS="$editor_calls" \ + "$ROOT/bin/omarchy-launch-editor" -cquit + +editor_argv=$(<"$editor_calls") +[[ $editor_argv == $'\n<-->\n<-cquit>' ]] || + fail "launch-editor separates a terminal editor path from editor options" "$editor_argv" +pass "launch-editor separates a terminal editor path from editor options" + +setsid_calls="$test_tmp/setsid-calls" +printf 'code\n' >"$fake_home/.local/state/omarchy/defaults/editor" +cat >"$mock_bin/code" <<'STUB' +#!/bin/bash + +exit 0 +STUB +chmod +x "$mock_bin/code" + +HOME="$fake_home" PATH="$mock_bin:$ROOT/bin:$PATH" OMARCHY_TEST_SETSID_CALLS="$setsid_calls" \ + "$ROOT/bin/omarchy-launch-editor" -cquit + +setsid_argv=$(<"$setsid_calls") +[[ $setsid_argv == $'\n<-->\n\n<-->\n<-cquit>' ]] || + fail "launch-editor separates a graphical editor path from editor options" "$setsid_argv" +pass "launch-editor separates a graphical editor path from editor options" + +hook_calls="$test_tmp/hook-calls" +hook_home="$test_tmp/hook-home" +printf '#!/bin/bash\n' >"$test_tmp/--help" +cat >"$mock_bin/basename" <<'STUB' +#!/bin/bash + +: "${OMARCHY_TEST_HOOK_CALLS:?}" +{ + printf 'basename' + printf ' <%s>' "$@" + printf '\n' +} >>"$OMARCHY_TEST_HOOK_CALLS" +printf 'literal-hook\n' +STUB +cat >"$mock_bin/cp" <<'STUB' +#!/bin/bash + +: "${OMARCHY_TEST_HOOK_CALLS:?}" +{ + printf 'cp' + printf ' <%s>' "$@" + printf '\n' +} >>"$OMARCHY_TEST_HOOK_CALLS" +STUB +for command in mkdir chmod; do + printf '#!/bin/bash\nexit 0\n' >"$mock_bin/$command" +done +chmod +x "$mock_bin/basename" "$mock_bin/cp" "$mock_bin/mkdir" "$mock_bin/chmod" + +: >"$hook_calls" +( + cd -- "$test_tmp" + HOME="$hook_home" PATH="$mock_bin:$PATH" OMARCHY_TEST_HOOK_CALLS="$hook_calls" \ + "$ROOT/bin/omarchy-hook-install" post-update --help >/dev/null +) + +hook_argv=$(<"$hook_calls") +expected_hook_argv=$'basename <--> <--help>\ncp <--> <--help> <'"$hook_home"'/.config/omarchy/hooks/post-update.d/literal-hook>' +[[ $hook_argv == $expected_hook_argv ]] || + fail "hook-install treats its source file as an operand" "$hook_argv" +pass "hook-install treats its source file as an operand" diff --git a/test/shell.d/fingerprint-driver-migration-test.sh b/test/shell.d/fingerprint-driver-migration-test.sh index 6b3f523c..3164c2dd 100755 --- a/test/shell.d/fingerprint-driver-migration-test.sh +++ b/test/shell.d/fingerprint-driver-migration-test.sh @@ -24,12 +24,26 @@ STUB cat > "$scratch/bin/pacman" <<'STUB' #!/bin/bash case "$1" in - -Q) grep -qx "$2" <<< "${INSTALLED:-}" || grep -qx "$2" "$INSTALLED_LOG" ;; + -Q) + if [[ $2 == "--" ]]; then + shift 2 + else + shift + fi + if grep -qx -- "$1" <<< "${INSTALLED:-}"; then + : + else + grep -qx -- "$1" "$INSTALLED_LOG" + fi + ;; -S) printf 'pacman %s\n' "$*" >> "$CALL_LOG" - for arg in "$@"; do - [[ $arg == -* ]] || printf '%s\n' "$arg" >> "$INSTALLED_LOG" - done + if [[ $4 == "--" ]]; then + shift 4 + for arg in "$@"; do + printf '%s\n' "$arg" >> "$INSTALLED_LOG" + done + fi ;; *) printf 'pacman %s\n' "$*" >> "$CALL_LOG" ;; esac @@ -44,7 +58,7 @@ run_migration() { } INSTALLED='fprintd' run_migration -grep -qx 'pacman -S --noconfirm --needed libfprint-git' "$CALL_LOG" || fail "fprintd without a library gets libfprint-git" +grep -qx 'pacman -S --noconfirm --needed -- libfprint-git' "$CALL_LOG" || fail "fprintd without a library gets libfprint-git" pass "fprintd without a library gets libfprint-git" INSTALLED=$'libfprint-git\nfprintd' run_migration diff --git a/test/shell.d/fingerprint-package-test.sh b/test/shell.d/fingerprint-package-test.sh index 70329982..efc14fcc 100755 --- a/test/shell.d/fingerprint-package-test.sh +++ b/test/shell.d/fingerprint-package-test.sh @@ -30,7 +30,14 @@ STUB cat > "$scratch/bin/pacman" <<'STUB' #!/bin/bash case "$1" in - -Q) grep -qx "$2" <<< "${INSTALLED:-}" ;; + -Q) + if [[ $2 == "--" ]]; then + shift 2 + else + shift + fi + grep -qx -- "$1" <<< "${INSTALLED:-}" + ;; -S) printf 'pacman %s\n' "$*" >> "$CALL_LOG" exit "${INSTALL_STATUS:-0}" @@ -62,7 +69,7 @@ run_setup() { } assert_installs() { - grep -qx 'pacman -S --needed --noconfirm --ask 4 libfprint-git fprintd usbutils' "$CALL_LOG" || fail "$1" + grep -qx 'pacman -S --needed --noconfirm --ask 4 -- libfprint-git fprintd usbutils' "$CALL_LOG" || fail "$1" (( $(grep -c '^pacman ' "$CALL_LOG") == 1 )) || fail "$1: one pacman transaction" } diff --git a/test/shell.d/menu-guards-test.sh b/test/shell.d/menu-guards-test.sh index 660d77c5..546596ce 100755 --- a/test/shell.d/menu-guards-test.sh +++ b/test/shell.d/menu-guards-test.sh @@ -118,7 +118,11 @@ Version : 9.2-1 INFO ;; -Q) - shift + if [[ $2 == "--" ]]; then + shift 2 + else + shift + fi for want in "$@"; do case "${want%%[<>=]*}" in bash | gvim | sh | vim | xxd) ;; *) exit 1 ;; esac done