From eaf91b157b63423b6eea600fffdfc827a21db5eb Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Sat, 15 Aug 2026 21:40:06 -0400 Subject: [PATCH] Fix poster scan losing items whose title contains a quote MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit posters_scan passed each line to the worker with `xargs -I{}`, which splices the text straight into the shell command. Any title containing an apostrophe or a quote became a shell syntax error and that item was dropped — silently, since the error went to stderr while the audit counted only what came back. No show title happened to trigger it, so this survived the show-level work. The first episode-level scan hit it immediately: titles like "That's a shirt?" and "WAIT A MINUTE, ..." produced `unexpected EOF while looking for matching quote`, which means that episode run reported nothing trustworthy. Lines are now passed as a positional argument, so the shell never parses their content. Regression-tested with titles containing apostrophes, double quotes, commas, backticks and $(...) — all handled, and the $(...) case confirmed not to execute. Also tightens the check itself: a poster is OK only if the response is 200 *and* an image content-type. Plex answers some paths with 200 and an XML body, which the status-only check counted as a working poster. Co-Authored-By: Claude Opus 5 (1M context) --- plex/lib/posters.sh | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/plex/lib/posters.sh b/plex/lib/posters.sh index ca1c9ab..fcba0dd 100644 --- a/plex/lib/posters.sh +++ b/plex/lib/posters.sh @@ -57,19 +57,27 @@ posters_scan() { # Items with no thumb field at all need no HTTP request. jq -r '.MediaContainer.Metadata[]?|select(has("thumb")|not)|"\(.ratingKey)\tMISSING\t\(.title)"' "$items_json" + # Each line is passed to the worker as a positional ARGUMENT ($1), never + # substituted into the command text. `xargs -I{}` would splice the title + # straight into the shell string, so a title containing an apostrophe or a + # quote — "That's", 'MINUTE,' — becomes a syntax error and that item is + # silently lost. Real titles at episode scale are full of them. jq -r '.MediaContainer.Metadata[]?|select(has("thumb"))|"\(.ratingKey)\t\(.thumb)\t\(.title)"' "$items_json" \ | PLEX_URL="$PLEX_URL" PLEX_TOKEN="$PLEX_TOKEN" PLEX_INSECURE="${PLEX_INSECURE:-0}" \ - xargs -P "$jobs" -d '\n' -I{} bash -c ' - IFS=$'"'"'\t'"'"' read -r rk thumb title <<<"{}" + xargs -P "$jobs" -d '\n' -n 1 bash -c ' + IFS=" " read -r rk thumb title <<<"$1" flags=(--silent --show-error --max-time 30) [[ "${PLEX_INSECURE:-0}" == "1" ]] && flags+=(--insecure) - code=$(curl "${flags[@]}" -o /dev/null -w "%{http_code}" \ + # Status alone is not proof of a poster: Plex answers some paths with + # HTTP 200 and an XML or HTML body. Require an image content-type. + read -r code ctype < <(curl "${flags[@]}" -o /dev/null \ + -w "%{http_code} %{content_type}" \ -H "X-Plex-Token: ${PLEX_TOKEN}" "${PLEX_URL%/}${thumb}") - if [[ "$code" == "200" ]]; then + if [[ "$code" == "200" && "$ctype" == image/* ]]; then printf "%s\tOK\t%s\n" "$rk" "$title" else printf "%s\tBROKEN\t%s\n" "$rk" "$title" - fi' + fi' _ } # Echo the best poster candidate URL for an item, or nothing if there is none.