From e1205c294bff8718f82e6daf3f3d828febbcc03f Mon Sep 17 00:00:00 2001 From: tkrmagid Date: Sun, 14 Jun 2026 02:17:07 +0900 Subject: [PATCH] v0.4.37: percent-encode preload download URL so non-ASCII paths cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VideoCache.download() opened the connection with URI.create(url).toURL() on the raw URL. URLs whose path has non-ASCII segments (e.g. the Korean ".../음악퀴즈/...") were sent unencoded, so the server answered HTTP 400 and every preload aborted — the disk cache stayed empty and clients fell back to live streaming for every video (instant on a fast host, 10s+/no-show on a slower remote client). FFmpeg tolerates the raw URL, masking the bug at playback time. Encode only the bytes put on the wire; the READY key, sha256 filename, and lookup() all keep the original url string so cache hits still match the anchor's raw URL. Verified: raw URL -> HTTP 400, encoded -> 200. Co-Authored-By: Claude Opus 4.7 --- gradle.properties | 2 +- .../client/playback/VideoCache.java | 27 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 73e85e5..0950c49 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.configuration-cache=false # Mod mod_id=video_player -mod_version=0.4.36 +mod_version=0.4.37 maven_group=com.ejclaw.videoplayer archives_base_name=video_player diff --git a/src/main/java/com/ejclaw/videoplayer/client/playback/VideoCache.java b/src/main/java/com/ejclaw/videoplayer/client/playback/VideoCache.java index 97ba5c0..a4049ae 100644 --- a/src/main/java/com/ejclaw/videoplayer/client/playback/VideoCache.java +++ b/src/main/java/com/ejclaw/videoplayer/client/playback/VideoCache.java @@ -381,7 +381,7 @@ public final class VideoCache { return; } - URLConnection raw = URI.create(url).toURL().openConnection(); + URLConnection raw = URI.create(encodeForRequest(url)).toURL().openConnection(); raw.setConnectTimeout(10_000); raw.setReadTimeout(30_000); raw.setRequestProperty("User-Agent", "video_player/" + VideoPlayerMod.MOD_ID); @@ -522,6 +522,31 @@ public final class VideoCache { } } + /** + * Percent-encode any non-ASCII characters in the URL so Java's {@link HttpURLConnection} + * puts a valid request line on the wire. The stored cache key, the {@link #sha256(String)} + * filename, and the {@link #READY} map all keep the ORIGINAL {@code url} string — only the + * bytes actually sent in the HTTP request are encoded — so {@link #lookup(String)} still + * matches the anchor's raw URL. + * + *

Without this, a URL with a non-ASCII path segment (e.g. {@code .../음악퀴즈/...}) is sent + * verbatim and the server answers HTTP 400, so every preload fails silently and the disk + * cache stays empty — clients then fall back to live streaming for every video. FFmpeg + * (playback) tolerates the raw URL, which is why playback "works" while the cache never + * fills. The multi-arg {@link URI} constructor encodes each component; an already-encoded + * URL round-trips unchanged (decode-then-encode is idempotent for these paths). On any + * parse failure we fall back to the raw string rather than dropping the download. + */ + private static String encodeForRequest(String url) { + try { + URI u = URI.create(url); + URI enc = new URI(u.getScheme(), u.getAuthority(), u.getPath(), u.getQuery(), u.getFragment()); + return enc.toASCIIString(); + } catch (Throwable t) { + return url; + } + } + /** * Best-effort filename extension from the URL path so FFmpeg's container probe gets a hint * (e.g. {@code .webm} for a webm stream). Falls back to {@code .bin}.