From 3a1eda9c88075827b88c56524a9a81e738b8828b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=BCegg?= Date: Fri, 25 Sep 2026 21:43:03 +0200 Subject: [PATCH] fix(sync): cache screenshots under the key the app store hands out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The app store now serves screenshot URLs already rewritten to point at this proxy. sync.php encoded that proxy URL a second time and fetched the proxy itself, so no screenshot published since the switch was ever fetched, and requests for it answer "File not found". Take the cache key from the proxy URL's path and fetch its decoded form; URLs not in proxy form are handled as before. Assisted-by: Claude Code:Claude Opus 5.5 Signed-off-by: Martin Rüegg --- sync.php | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/sync.php b/sync.php index 781270f..eb84ace 100644 --- a/sync.php +++ b/sync.php @@ -29,6 +29,34 @@ const MAX_SCREENSHOT_SIZE = 2 * 1024 * 1024; // 2 MiB const HTTP_TIMEOUT_S = 30; const RETRY_BACKOFF_S = 6 * 3600; // don't retry a failed fetch more often than this +const PROXY_URL_PREFIX = 'https://usercontent.apps.nextcloud.com/'; + +/** + * The app store serves screenshot URLs already pointing at this proxy, so the + * cache key is the path of that URL and the source URL is its decoded form. + * URLs not in that form are the source URL itself, as the app store served + * them before. + * + * @param string $url screenshot URL as served by the app store + * @return array{string, string}|null cache key and source URL, or null if the key is not valid base64url + */ +function resolveScreenshotUrl(string $url): ?array { + if (!str_starts_with($url, PROXY_URL_PREFIX)) { + return [strtr(base64_encode($url), '+/', '-_'), $url]; + } + + $base64Url = substr($url, strlen(PROXY_URL_PREFIX)); + if (preg_match('/^[A-Za-z0-9_-]+={0,2}$/', $base64Url) !== 1) { + return null; + } + + $sourceUrl = base64_decode(strtr($base64Url, '-_', '+/'), true); + if ($sourceUrl === false) { + return null; + } + + return [$base64Url, $sourceUrl]; +} /** * @param string $cacheUrl path to screenshot in cache @@ -169,14 +197,18 @@ function (string $ip) use ($host, $port): string { * @param array $screenshot decoded JSON of a single screenshot entry */ function handleScreenshot(UrlValidator $validator, array $screenshot): void { - $url = $screenshot['url']; + $resolved = resolveScreenshotUrl($screenshot['url']); + if ($resolved === null) { + return; + } + + [$base64Url, $url] = $resolved; $trimmedUrl = trim($url); if (!str_starts_with($trimmedUrl, 'https://')) { return; } - $base64Url = strtr(base64_encode($url), '+/', '-_'); $cacheUrl = __DIR__ . '/cache/' . $base64Url; $failMarker = __DIR__ . '/cache-failed/' . $base64Url;