fix(lyrics): avoid shell injection in NetEase requests

This commit is contained in:
h465855hgg
2026-07-17 21:57:02 +08:00
parent 6b96da5a0c
commit 6dacdc7a78
4 changed files with 27 additions and 26 deletions
+5 -5
View File
@@ -12,7 +12,7 @@ highlighting, animated line changes, and configurable online or local sources.
## Requirements ## Requirements
Install `playerctl`, `python3`, `curl`, and `cp` on `PATH`. The active media Install `playerctl`, `python3`, and `cp` on `PATH`. The active media
player must expose MPRIS metadata for automatic track and playback detection. player must expose MPRIS metadata for automatic track and playback detection.
Noctalia installs the plugin files; it does not install system packages for you. Noctalia installs the plugin files; it does not install system packages for you.
@@ -103,7 +103,7 @@ HTTP mode contacts only the configured endpoint. The plugin never reads browser
cookies or player credentials. cookies or player credentials.
The service runs `playerctl` to read and control MPRIS playback, `python3` for the The service runs `playerctl` to read and control MPRIS playback, `python3` for the
LRCLIB helper and dynamic-lyric parser, `curl` for the public NetEase API, and LRCLIB helper and dynamic-lyric parser, and `cp` to preserve temporary local cover
`cp` to preserve temporary local cover files. Query scratch files and downloaded files. Public NetEase requests use Noctalia's HTTP API. Query scratch files and
cover images are written inside the plugin runtime directory. Remote code is downloaded cover images are written inside the plugin runtime directory. Remote
never downloaded or executed. code is never downloaded or executed.
+13 -10
View File
@@ -82,19 +82,18 @@ local function fetchLyricsNetEase(track, embeddedLyrics)
local function tryFetch(query, fallback) local function tryFetch(query, fallback)
local searchUrl = "https://music.163.com/api/search/get?type=1&s=" .. noctalia.string.urlEncode(query) .. "&limit=5" local searchUrl = "https://music.163.com/api/search/get?type=1&s=" .. noctalia.string.urlEncode(query) .. "&limit=5"
local cmd = 'curl -s --max-time 10 --connect-timeout 5 "' .. searchUrl .. '" -H "Referer: https://music.163.com"'
noctalia.runAsync(cmd, function(r1) noctalia.http({ url = searchUrl, headers = { "Referer: https://music.163.com" } }, function(r1)
if not inFlight then return end if not inFlight then return end
if tk ~= lastTrackKey then inFlight = nil; return end if tk ~= lastTrackKey then inFlight = nil; return end
if r1.exitCode ~= 0 or not r1.stdout or #r1.stdout == 0 then if not r1.ok or r1.status < 200 or r1.status >= 300 or not r1.body or #r1.body == 0 then
if fallback then fallback() if fallback then fallback()
else inFlight = nil; noctalia.state.set("lyrics", nil) end else inFlight = nil; noctalia.state.set("lyrics", nil) end
return return
end end
local data = noctalia.json.decode(r1.stdout) local data = noctalia.json.decode(r1.body)
if not data or not data.result or not data.result.songs or #data.result.songs == 0 then if not data or not data.result or not data.result.songs or #data.result.songs == 0 then
if fallback then fallback() if fallback then fallback()
else inFlight = nil; noctalia.state.set("lyrics", nil) end else inFlight = nil; noctalia.state.set("lyrics", nil) end
@@ -120,17 +119,21 @@ local function fetchLyricsNetEase(track, embeddedLyrics)
bestMatch = data.result.songs[1] bestMatch = data.result.songs[1]
end end
local songId = bestMatch.id local songId = tostring(bestMatch.id or "")
local lyricUrl = "https://music.163.com/api/song/lyric?id=" .. songId .. "&lv=1&kv=1&tv=-1" if not songId:match("^%d+$") then
local lCmd = 'curl -s --max-time 10 --connect-timeout 5 "' .. lyricUrl .. '" -H "Referer: https://music.163.com"' if fallback then fallback()
else inFlight = nil; noctalia.state.set("lyrics", nil) end
return
end
local lyricUrl = "https://music.163.com/api/song/lyric?id=" .. noctalia.string.urlEncode(songId) .. "&lv=1&kv=1&tv=-1"
noctalia.runAsync(lCmd, function(r2) noctalia.http({ url = lyricUrl, headers = { "Referer: https://music.163.com" } }, function(r2)
if not inFlight then return end if not inFlight then return end
if tk ~= lastTrackKey then inFlight = nil; return end if tk ~= lastTrackKey then inFlight = nil; return end
local lyrics = nil local lyrics = nil
if r2.exitCode == 0 and r2.stdout and #r2.stdout > 0 then if r2.ok and r2.status >= 200 and r2.status < 300 and r2.body and #r2.body > 0 then
local ldata = noctalia.json.decode(r2.stdout) local ldata = noctalia.json.decode(r2.body)
local klyricStr = "" local klyricStr = ""
if ldata and ldata.klyric then if ldata and ldata.klyric then
local k = ldata.klyric local k = ldata.klyric
+1 -1
View File
@@ -4,7 +4,7 @@ version = "1.1.0"
plugin_api = 3 plugin_api = 3
author = "h465855hgg" author = "h465855hgg"
license = "MIT" license = "MIT"
dependencies = ["playerctl", "python3", "curl", "cp"] dependencies = ["playerctl", "python3", "cp"]
tags = ["bar", "service", "music", "media", "animation"] tags = ["bar", "service", "music", "media", "animation"]
icon = "music" icon = "music"
description = "Synchronized lyrics with karaoke highlighting, animated transitions, and flexible lyric sources." description = "Synchronized lyrics with karaoke highlighting, animated transitions, and flexible lyric sources."
+8 -10
View File
@@ -45,11 +45,10 @@ need_command() {
MISSING_COMMANDS="" MISSING_COMMANDS=""
need_command playerctl need_command playerctl
need_command python3 need_command python3
need_command curl
need_command cp need_command cp
if [ -z "$MISSING_COMMANDS" ]; then if [ -z "$MISSING_COMMANDS" ]; then
echo "All runtime commands are installed: playerctl python3 curl cp" echo "All runtime commands are installed: playerctl python3 cp"
exit 0 exit 0
fi fi
@@ -90,28 +89,28 @@ PM="$(detect_pm)"
case "$PM" in case "$PM" in
apt) apt)
INSTALL_CMD="$SUDO apt-get update && $SUDO apt-get install -y playerctl python3 curl coreutils" INSTALL_CMD="$SUDO apt-get update && $SUDO apt-get install -y playerctl python3 coreutils"
;; ;;
dnf) dnf)
INSTALL_CMD="$SUDO dnf install -y playerctl python3 curl coreutils" INSTALL_CMD="$SUDO dnf install -y playerctl python3 coreutils"
;; ;;
pacman) pacman)
INSTALL_CMD="$SUDO pacman -S --needed playerctl python curl coreutils" INSTALL_CMD="$SUDO pacman -S --needed playerctl python coreutils"
;; ;;
zypper) zypper)
INSTALL_CMD="$SUDO zypper install -y playerctl python3 curl coreutils" INSTALL_CMD="$SUDO zypper install -y playerctl python3 coreutils"
;; ;;
apk) apk)
INSTALL_CMD="$SUDO apk add playerctl python3 curl coreutils" INSTALL_CMD="$SUDO apk add playerctl python3 coreutils"
;; ;;
xbps) xbps)
INSTALL_CMD="$SUDO xbps-install -Sy playerctl python3 curl coreutils" INSTALL_CMD="$SUDO xbps-install -Sy playerctl python3 coreutils"
;; ;;
*) *)
cat >&2 <<'EOF' cat >&2 <<'EOF'
Could not detect a supported package manager. Could not detect a supported package manager.
Install these packages manually with your distribution package manager: Install these packages manually with your distribution package manager:
playerctl python3 curl coreutils playerctl python3 coreutils
EOF EOF
exit 1 exit 1
;; ;;
@@ -138,7 +137,6 @@ sh -c "$INSTALL_CMD"
MISSING_COMMANDS="" MISSING_COMMANDS=""
need_command playerctl need_command playerctl
need_command python3 need_command python3
need_command curl
need_command cp need_command cp
if [ -n "$MISSING_COMMANDS" ]; then if [ -n "$MISSING_COMMANDS" ]; then