Skip to content

Commit 83881e9

Browse files
authored
Merge pull request #7984 from Chessing234/fix/webapp-name-slashes
Keep a web app name out of the launcher's directory structure
2 parents 946704f + 9ece53c commit 83881e9

3 files changed

Lines changed: 169 additions & 9 deletions

File tree

bin/omarchy-webapp-install

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@ safe_icon_name() {
1313
| sed 's/[^[:alnum:]]\+/-/g; s/^-//; s/-$//'
1414
}
1515

16+
require_plain_name() {
17+
# The name becomes a filename. A slash would turn it into directory levels, so
18+
# the launcher lands somewhere omarchy-webapp-remove cannot address and the app
19+
# is stuck in the launcher; a leading ../ leaves the applications directory
20+
# altogether. Refuse rather than silently renaming what the user typed -- most
21+
# often it is a URL entered in the name field.
22+
if [[ $1 == */* ]]; then
23+
echo "App name cannot contain '/': $1"
24+
exit 1
25+
fi
26+
}
27+
1628
icon_name_from_ref() {
1729
local ref="$1"
1830
local name
@@ -68,6 +80,7 @@ fetch_site_icon() {
6880
if (( $# < 3 )); then
6981
echo -e "\e[32mLet's create a new web app you can start with the app launcher.\n\e[0m"
7082
APP_NAME=$(gum input --prompt "Name> " --placeholder "My favorite web app")
83+
require_plain_name "$APP_NAME"
7184
APP_URL=$(gum input --prompt "URL> " --placeholder "https://example.com")
7285
if [[ ! $APP_URL =~ ^[a-zA-Z][a-zA-Z0-9+.-]*: ]]; then
7386
APP_URL="https://$APP_URL"
@@ -104,6 +117,8 @@ if [[ -z $APP_NAME || -z $APP_URL ]]; then
104117
exit 1
105118
fi
106119

120+
require_plain_name "$APP_NAME"
121+
107122
if [[ -z $ICON_REF ]]; then
108123
ICON_VALUE=$(safe_icon_name "$APP_NAME")
109124
mkdir -p "$ICON_DIR"
@@ -132,8 +147,9 @@ fi
132147
EXEC_COMMAND="${CUSTOM_EXEC:-omarchy-launch-webapp $APP_URL}"
133148

134149
# Create application .desktop file
135-
DESKTOP_FILE="$HOME/.local/share/applications/$APP_NAME.desktop"
136-
mkdir -p "$(dirname "$DESKTOP_FILE")"
150+
DESKTOP_DIR="$HOME/.local/share/applications"
151+
DESKTOP_FILE="$DESKTOP_DIR/$APP_NAME.desktop"
152+
mkdir -p "$DESKTOP_DIR"
137153

138154
cat >"$DESKTOP_FILE" <<EOF
139155
[Desktop Entry]

bin/omarchy-webapp-remove

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,31 @@ ICON_DIR="$HOME/.local/share/icons/hicolor/256x256/apps"
99
OLD_ICON_DIR="$HOME/.local/share/applications/icons"
1010
DESKTOP_DIR="$HOME/.local/share/applications/"
1111

12-
if (( $# == 0 )); then
13-
# Find all web apps
14-
while IFS= read -r -d '' file; do
15-
if grep -q '^Exec=.*\(omarchy-launch-webapp\|omarchy-webapp-handler\).*' "$file"; then
16-
WEB_APPS+=("$(basename "${file%.desktop}")")
12+
# Always index the launchers, so removal deletes the file that was found rather
13+
# than a path rebuilt from the displayed name. Installs predating the name
14+
# validation could nest the launcher inside directories, and those are exactly
15+
# the ones a reconstructed path cannot reach.
16+
WEB_APP_PATHS=()
17+
while IFS= read -r -d '' file; do
18+
if grep -q '^Exec=.*\(omarchy-launch-webapp\|omarchy-webapp-handler\).*' "$file"; then
19+
WEB_APPS+=("$(basename "${file%.desktop}")")
20+
WEB_APP_PATHS+=("$file")
21+
fi
22+
done < <(find "$DESKTOP_DIR" -name '*.desktop' -print0 2>/dev/null)
23+
24+
# The launcher matching a chosen name, or empty when nothing was indexed under
25+
# it (an app removed between the scan and the pick, say).
26+
path_for_web_app() {
27+
local wanted="$1" i
28+
for i in "${!WEB_APPS[@]}"; do
29+
if [[ ${WEB_APPS[$i]} == "$wanted" ]]; then
30+
printf '%s\n' "${WEB_APP_PATHS[$i]}"
31+
return 0
1732
fi
18-
done < <(find "$DESKTOP_DIR" -name '*.desktop' -print0)
33+
done
34+
}
1935

36+
if (( $# == 0 )); then
2037
if ((${#WEB_APPS[@]})); then
2138
mapfile -t SORTED_WEB_APPS < <(printf '%s\n' "${WEB_APPS[@]}" | sort)
2239
APP_NAME=$(omarchy-menu-select "Select web app to remove" "${SORTED_WEB_APPS[@]}" -- --width 520 --maxheight 520)
@@ -34,7 +51,8 @@ if [[ -z $APP_NAME ]]; then
3451
fi
3552

3653
icon_name=$(printf '%s\n' "$APP_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^[:alnum:]]\+/-/g; s/^-//; s/-$//')
37-
rm -f "$DESKTOP_DIR/$APP_NAME.desktop"
54+
desktop_file=$(path_for_web_app "$APP_NAME")
55+
rm -f "${desktop_file:-$DESKTOP_DIR/$APP_NAME.desktop}"
3856
rm -f "$ICON_DIR/$icon_name.png" "$ICON_DIR/$APP_NAME.png" "$OLD_ICON_DIR/$APP_NAME.png"
3957

4058
if [[ ${OMARCHY_REMOVE_NOTIFY:-true} != "false" ]]; then

test/shell.d/webapp-name-test.sh

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
6+
7+
tmp_dir=$(mktemp -d)
8+
trap 'rm -rf "$tmp_dir"' EXIT
9+
mkdir -p "$tmp_dir/bin" "$tmp_dir/home"
10+
11+
for stub in gtk-update-icon-cache update-desktop-database omarchy-notification-send; do
12+
printf '#!/bin/bash\n:\n' >"$tmp_dir/bin/$stub"
13+
chmod +x "$tmp_dir/bin/$stub"
14+
done
15+
16+
run_install() {
17+
HOME="$tmp_dir/home" PATH="$tmp_dir/bin:$PATH" \
18+
"$ROOT/bin/omarchy-webapp-install" "$@"
19+
}
20+
21+
run_remove() {
22+
HOME="$tmp_dir/home" PATH="$tmp_dir/bin:$PATH" OMARCHY_REMOVE_NOTIFY=false \
23+
"$ROOT/bin/omarchy-webapp-remove" "$@"
24+
}
25+
26+
apps_dir="$tmp_dir/home/.local/share/applications"
27+
icons_dir="$tmp_dir/home/.local/share/icons/hicolor/256x256/apps"
28+
29+
# A URL typed into the name field is the reported way in. Every slash used to
30+
# become a directory level, leaving a launcher nothing could address. Assert on
31+
# the message: creating the launcher directly in the applications directory
32+
# already makes the redirect fail on its own, so a bare non-zero exit would pass
33+
# just as well with no validation at all.
34+
output=$(run_install "http://example.test/oops" "https://example.com" hey 2>&1) &&
35+
fail "webapp install rejects a name containing a slash"
36+
[[ $output == *"App name cannot contain '/'"* ]] ||
37+
fail "webapp install says why it refused a slashed name" "$output"
38+
[[ -e "$apps_dir/http:" ]] &&
39+
fail "webapp install does not create a directory from a slashed name"
40+
pass "webapp install rejects a name that would nest the launcher"
41+
42+
# The name was a path fragment until something said otherwise, so ../ climbed
43+
# out of the applications directory entirely and wrote wherever it landed.
44+
if run_install "../../../../escaped" "https://example.com" hey >/dev/null 2>&1; then
45+
fail "webapp install rejects a name that climbs out of the applications directory"
46+
fi
47+
[[ -e "$tmp_dir/escaped.desktop" ]] &&
48+
fail "webapp install writes no launcher outside the applications directory"
49+
pass "webapp install refuses a name that would escape the applications directory"
50+
51+
# The interactive prompt reads the name long before it is used as a path, and
52+
# fetches the site icon in between. Rejecting only at the write leaves that icon
53+
# behind in the user's icon theme, once per attempt.
54+
mkdir -p "$tmp_dir/ibin"
55+
cp "$tmp_dir/bin"/* "$tmp_dir/ibin/"
56+
cat >"$tmp_dir/ibin/gum" <<'STUB'
57+
#!/bin/bash
58+
count_file="${GUM_STUB_COUNT:?}"
59+
count=$(cat "$count_file" 2>/dev/null || echo 0)
60+
count=$((count + 1))
61+
echo "$count" >"$count_file"
62+
if (( count == 1 )); then
63+
echo "http://example.test/oops"
64+
else
65+
echo "https://example.com"
66+
fi
67+
STUB
68+
cat >"$tmp_dir/ibin/curl" <<'STUB'
69+
#!/bin/bash
70+
# Answer any download with a real PNG so the icon fetch reports success.
71+
out=""
72+
prev=""
73+
for arg in "$@"; do
74+
[[ $prev == "-o" ]] && out="$arg"
75+
prev="$arg"
76+
done
77+
if [[ -n $out ]]; then
78+
printf '%s' 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==' | base64 -d >"$out"
79+
fi
80+
STUB
81+
chmod +x "$tmp_dir/ibin/gum" "$tmp_dir/ibin/curl"
82+
83+
if HOME="$tmp_dir/home" PATH="$tmp_dir/ibin:$PATH" \
84+
GUM_STUB_COUNT="$tmp_dir/gum-count" \
85+
"$ROOT/bin/omarchy-webapp-install" >/dev/null 2>&1; then
86+
fail "interactive webapp install rejects a name containing a slash"
87+
fi
88+
if compgen -G "$icons_dir/*.png" >/dev/null; then
89+
fail "interactive webapp install downloads no icon for a name it refuses" \
90+
"$(ls "$icons_dir")"
91+
fi
92+
pass "webapp install refuses a slashed name before fetching its icon"
93+
94+
# A normal name still installs and removes.
95+
run_install "Example App" "https://example.com" hey >/dev/null
96+
[[ -f "$apps_dir/Example App.desktop" ]] ||
97+
fail "webapp install writes the launcher for an ordinary name"
98+
run_remove "Example App" >/dev/null
99+
[[ -f "$apps_dir/Example App.desktop" ]] &&
100+
fail "webapp remove deletes the launcher it installed"
101+
pass "webapp install and remove round-trip an ordinary name"
102+
103+
# Anything installed by an older version can still be nested. Removal has to
104+
# reach it, which a path rebuilt from the displayed name never could.
105+
mkdir -p "$apps_dir/http:/127.0.0.1:4000"
106+
cat >"$apps_dir/http:/127.0.0.1:4000/.desktop" <<'DESKTOP'
107+
[Desktop Entry]
108+
Name=http://127.0.0.1:4000
109+
Exec=omarchy-launch-webapp https://127.0.0.1:4000
110+
Type=Application
111+
DESKTOP
112+
113+
# This is the name the picker shows for that file: the script strips .desktop
114+
# from the path and then takes the basename, which lands on the directory.
115+
run_remove "127.0.0.1:4000" >/dev/null
116+
[[ -f "$apps_dir/http:/127.0.0.1:4000/.desktop" ]] &&
117+
fail "webapp remove deletes a launcher left nested by an older install"
118+
pass "webapp remove reaches a nested legacy launcher"
119+
120+
# Removing by name on a machine with no applications directory yet must stay
121+
# quiet: omarchy-remove-gaming-xbox-cloud calls it without hiding stderr.
122+
noise=$(HOME="$tmp_dir/empty" PATH="$tmp_dir/bin:$PATH" OMARCHY_REMOVE_NOTIFY=false \
123+
"$ROOT/bin/omarchy-webapp-remove" "Xbox Cloud Gaming" 2>&1 >/dev/null)
124+
[[ -n $noise ]] &&
125+
fail "webapp remove stays quiet with no applications directory" "$noise"
126+
pass "webapp remove stays quiet when there is no applications directory"

0 commit comments

Comments
 (0)