]> mop.ddnsfree.com - git repositories - brisk.git/commitdiff
headers_render: three duplicated headers in every response
authorMatteo Nastasi <nastasi@alternativeoutput.it>
Sun, 13 Sep 2026 10:32:24 +0000 (12:32 +0200)
committerMatteo Nastasi <nastasi@alternativeoutput.it>
Sun, 13 Sep 2026 10:32:24 +0000 (12:32 +0200)
Http header names are case insensitive, php array keys are not. The
transports set "Content-type" with a lowercase t (transports.phh:568 and
:618, index.php:1025 and :1199) while headers_render() checked for
"Content-Type": the check never saw it and added the default anyway.

Same dynamic for Expires and Cache-Control, which force_no_cache() sets and
headers_render added again without checking at all.

Every response therefore went out with:
  Content-Type: text/html         +  Content-type: text/html; charset="utf-8"
  Expires: -1                     +  Expires: Mon, 26 Jul 1997 05:00:00 GMT
  Cache-Control: no-cache         +  Cache-Control: no-cache, must-revalidate

With apache this went unnoticed: the daemon wrote the bytes straight to the
client and the browser applied the last value. Behind a reverse proxy the
response is parsed instead, the first value wins and the second is dropped:
the charset was lost, and in transports.phh:568 an application/xml was
replaced by text/html.

Fixed at the root rather than in the five calling places: headers_render
builds a map of the keys normalised to lowercase and uses it for every
check, so the defect does not come back if somebody writes "Content-type"
again tomorrow.

Found by putting nginx in front of the daemon: it reported "upstream sent
duplicate header line" 123 times. After the fix: zero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014M1jiEq9cHdE5SE5j6vFuE

web/Obj/sac-a-push.phh

index 1f745e77649084b8fa95f636067f1676b36f15b0..25f5bfcd81395d8024aa0888e72fd5af219cc754 100644 (file)
@@ -237,6 +237,24 @@ function headers_render($header, $len)
 {
     $cookies = "";
 
+    /* Http header names are case insensitive, php array keys are not. The
+       transports set "Content-type" (lowercase t) while the code below
+       checked for "Content-Type": the check never saw it and the default was
+       added anyway, so two Content-Type headers went out in the same
+       response. Same story for Expires and Cache-Control, already set by
+       force_no_cache() and then added again unconditionally.
+       With apache this went unnoticed, because the daemon wrote the bytes
+       straight to the client and the browser applied the last value. A
+       reverse proxy instead parses the response, keeps the first one and
+       drops the second: the charset was lost, and an application/xml was
+       replaced by text/html.
+       Here a map of the normalised keys is built and used for every
+       check. */
+    $hset = array();
+    foreach ($header as $hk => $hv) {
+        $hset[strtolower($hk)] = TRUE;
+    }
+
     if (isset($header['cookies'])) {
         $cookies = $header['cookies']->render();
         unset($header['cookies']);
@@ -258,11 +276,11 @@ function headers_render($header, $len)
     else {
         $s = "HTTP/1.1 200 OK\r\n";
 
-        if (!isset($header['Date']))
+        if (!isset($hset['date']))
             $s .= sprintf("Date: %s\r\n", date(DATE_RFC822));
-        if (!isset($header['Connection']))
+        if (!isset($hset['connection']))
             $s .= "Connection: close\r\n";
-        if (!isset($header['Content-Type']))
+        if (!isset($hset['content-type']))
             $s .= "Content-Type: text/html\r\n";
         foreach($header as $key => $value) {
             $s .= sprintf("%s: %s\r\n", $key, $value);
@@ -271,9 +289,13 @@ function headers_render($header, $len)
             $s .= sprintf("Content-Length: %d\r\n", $len);
         }
         else {
-            $s .= "Cache-Control: no-cache, must-revalidate\r\n";
-            $s .= "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n";
-            if (!isset($header['Content-Encoding'])) {
+            if (!isset($hset['cache-control'])) {
+                $s .= "Cache-Control: no-cache, must-revalidate\r\n";
+            }
+            if (!isset($hset['expires'])) {
+                $s .= "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n";
+            }
+            if (!isset($hset['content-encoding'])) {
                 $s .= "Content-Encoding: chunked\r\n";
             }
             $s .= "Transfer-Encoding: chunked\r\n";