From 90ac75525a280ab662ece762e7e20186e6aaab4e Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Fri, 14 Aug 2026 17:58:48 +0100 Subject: [PATCH] fix(sync-php): read Authorization via getallheaders and tolerate ETag forms Apache commonly strips Authorization before PHP sees it, so every request looked unauthenticated on a stock XAMPP and answered 404. If-None-Match is now compared on the bare hash: a validator that lost its quotes, or a weak W/ tag, would never match and each request would silently re-download the whole snapshot - expensive on mobile data and invisible in the logs, a 200 looking perfectly normal. --- server-php/sync.php | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/server-php/sync.php b/server-php/sync.php index 06423bc..3957775 100644 --- a/server-php/sync.php +++ b/server-php/sync.php @@ -41,10 +41,22 @@ header('X-Content-Type-Options: nosniff'); // Authentication // --------------------------------------------------------------------------- -/** Bearer token, compared in constant time so a wrong guess leaks no timing. */ +/** + * Bearer token, compared in constant time so a wrong guess leaks no timing. + * + * Three sources on purpose: Apache commonly strips Authorization before PHP + * sees it (it is only forwarded with CGIPassAuth on, or through a rewrite), so + * $_SERVER alone answers empty on a stock XAMPP and every request looks + * unauthenticated. getallheaders() reads what the client actually sent. + */ function client_token(): string { $h = $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? ''; - if (stripos($h, 'Bearer ') === 0) return substr($h, 7); + if ($h === '' && function_exists('getallheaders')) { + foreach (getallheaders() as $name => $value) { + if (strcasecmp($name, 'Authorization') === 0) { $h = $value; break; } + } + } + if (stripos($h, 'Bearer ') === 0) return trim(substr($h, 7)); return ''; } @@ -85,7 +97,12 @@ if ($method === 'HEAD' || $method === 'GET') { header('Last-Modified: ' . gmdate('D, d M Y H:i:s', (int)filemtime(SYNC_FILE)) . ' GMT'); // Save the round trip when the client already holds this version. - if (trim($_SERVER['HTTP_IF_NONE_MATCH'] ?? '') === $etag) { + // Compared on the bare hash: a validator that loses its quotes or arrives + // as a weak tag (W/"…") would otherwise never match, and every request + // would silently download the whole snapshot again — costly on mobile data + // and invisible in the logs, since a 200 looks perfectly normal. + $sent = trim($_SERVER['HTTP_IF_NONE_MATCH'] ?? ''); + if ($sent !== '' && trim($sent, 'W/" ') === trim($etag, '" ')) { http_response_code(304); exit; }