]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
altsvc: accept ma/persist per alternative entry
authorDaniel Stenberg <daniel@haxx.se>
Thu, 1 Jan 2026 16:46:04 +0000 (17:46 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 2 Jan 2026 22:50:21 +0000 (23:50 +0100)
The 'ma' and 'persist' keywords should be considered per list entry, not
once per header.

Expand test 1654 to verify such headers

Reported-by: Hunt Darlener
Closes #20160

lib/altsvc.c
tests/data/test1654
tests/unit/unit1654.c

index 3b99c145908262e292fac789ed06b28d1b675524..7006333b0700f54f1e11ba82fee761b1810ff0b2 100644 (file)
@@ -459,9 +459,6 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data,
   unsigned short dstport = srcport; /* the same by default */
   size_t entries = 0;
   struct Curl_str alpn;
-  const char *sp;
-  time_t maxage = 24 * 3600; /* default is 24 hours */
-  bool persist = FALSE;
 #ifdef CURL_DISABLE_VERBOSE_STRINGS
   (void)data;
 #endif
@@ -486,44 +483,10 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data,
 
   curlx_str_trimblanks(&alpn);
 
-  /* Handle the optional 'ma' and 'persist' flags once first, as they need to
-     be known for each alternative service. Unknown flags are skipped. */
-  sp = strchr(p, ';');
-  if(sp) {
-    sp++; /* pass the semicolon */
-    for(;;) {
-      struct Curl_str name;
-      struct Curl_str val;
-      const char *vp;
-      curl_off_t num;
-      bool quoted;
-      /* allow some extra whitespaces around name and value */
-      if(curlx_str_until(&sp, &name, 20, '=') ||
-         curlx_str_single(&sp, '=') ||
-         curlx_str_until(&sp, &val, 80, ';'))
-        break;
-      curlx_str_trimblanks(&name);
-      curlx_str_trimblanks(&val);
-      /* the value might be quoted */
-      vp = curlx_str(&val);
-      quoted = (*vp == '\"');
-      if(quoted)
-        vp++;
-      if(!curlx_str_number(&vp, &num, TIME_T_MAX)) {
-        if(curlx_str_casecompare(&name, "ma"))
-          maxage = (time_t)num;
-        else if(curlx_str_casecompare(&name, "persist") && (num == 1))
-          persist = TRUE;
-      }
-      if(quoted && curlx_str_single(&sp, '\"'))
-        break;
-      if(curlx_str_single(&sp, ';'))
-        break;
-    }
-  }
-
   do {
     if(!curlx_str_single(&p, '=')) {
+      time_t maxage = 24 * 3600; /* default is 24 hours */
+      bool persist = FALSE;
       /* [protocol]="[host][:port], [protocol]="[host][:port]" */
       enum alpnid dstalpnid = Curl_str2alpnid(&alpn);
       if(!curlx_str_single(&p, '\"')) {
@@ -562,6 +525,45 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data,
         if(curlx_str_single(&p, '\"'))
           break;
 
+        /* Handle the optional 'ma' and 'persist' flags. Unknown flags are
+           skipped. */
+        curlx_str_passblanks(&p);
+        if(!curlx_str_single(&p, ';')) {
+          for(;;) {
+            struct Curl_str name;
+            struct Curl_str val;
+            const char *vp;
+            curl_off_t num;
+            bool quoted;
+            /* allow some extra whitespaces around name and value */
+            if(curlx_str_until(&p, &name, 20, '=') ||
+               curlx_str_single(&p, '=') ||
+               curlx_str_cspn(&p, &val, ",;"))
+              break;
+            curlx_str_trimblanks(&name);
+            curlx_str_trimblanks(&val);
+            /* the value might be quoted */
+            vp = curlx_str(&val);
+            quoted = (*vp == '\"');
+            if(quoted)
+              vp++;
+            if(!curlx_str_number(&vp, &num, TIME_T_MAX)) {
+              if(curlx_str_casecompare(&name, "ma"))
+                maxage = (time_t)num;
+              else if(curlx_str_casecompare(&name, "persist") && (num == 1))
+                persist = TRUE;
+            }
+            else
+              break;
+            p = vp; /* point to the byte ending the value */
+            curlx_str_passblanks(&p);
+            if(quoted && curlx_str_single(&p, '\"'))
+              break;
+            curlx_str_passblanks(&p);
+            if(curlx_str_single(&p, ';'))
+              break;
+          }
+        }
         if(dstalpnid) {
           if(!entries++)
             /* Flush cached alternatives for this source origin, if any - when
index a946e552cca65a2d446ee3cad25d30f8691891f8..3d85404abae0e250f3af7955eddfba4391b12bb7 100644 (file)
@@ -48,6 +48,8 @@ h1 3.example.org 8080 h2 example.com 8080 "20190125 22:34:21" 0 0
 h1 3.example.org 8080 h3 yesyes.com 8080 "20190125 22:34:21" 0 0
 h2 example.org 80 h2 example.com 443 "20190124 22:36:21" 0 0
 h2 example.net 80 h2 example.net 443 "20190124 22:37:21" 0 0
+h2 test.se 443 h2 test2.se 443 "20190124 22:37:21" 0 0
+h2 test.se 443 h2 test3.se 443 "20190124 22:36:21" 0 0
 </file>
 </verify>
 </testcase>
index c16a9ebfd6ff3b7c809af6a96f1e94c986588c5e..0c8adcded6bf8005d346c51ee8d82e8486231387 100644 (file)
@@ -82,7 +82,7 @@ static CURLcode test_unit1654(const char *arg)
   fail_unless(Curl_llist_count(&asi->list) == 10, "wrong number of entries");
 
   res = Curl_altsvc_parse(curl, asi,
-                          "h2=\":443\", h3=\":443\"; "
+                          "h2=\":443\"; ma=180, h3=\":443\"; "
                           "persist = \"1\"; ma = 120;\r\n",
                           ALPN_h1, "curl.se", 80);
   fail_if(res, "Curl_altsvc_parse(6) failed!");
@@ -131,6 +131,12 @@ static CURLcode test_unit1654(const char *arg)
                           ALPN_h2, "8.example.net", 80);
   fail_if(res, "Curl_altsvc_parse(11) failed!");
 
+  res = Curl_altsvc_parse(curl, asi,
+                          "h2=\"test2.se:443\"; ma=\"180 \" ; unknown=2, "
+                          "h2=\"test3.se:443\"; ma = 120;\r\n",
+                          ALPN_h2, "test.se", 443);
+  fail_if(res, "Curl_altsvc_parse(12) failed!");
+
   Curl_altsvc_save(curl, asi, outname);
 
   curl_easy_cleanup(curl);