]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
http: convert parsers to strparse
authorDaniel Stenberg <daniel@haxx.se>
Thu, 20 Feb 2025 15:55:13 +0000 (16:55 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 24 Feb 2025 09:02:40 +0000 (10:02 +0100)
Closes #16436

lib/http.c
lib/strdup.c
lib/strparse.c
lib/strparse.h

index 76cd9bcc6be449e4d78ba37ab1c900682c38264a..b273059d2d8dc73ad6a22ff37047c5c0f35c31b1 100644 (file)
@@ -273,46 +273,28 @@ char *Curl_checkProxyheaders(struct Curl_easy *data,
 #endif
 
 /*
- * Strip off leading and trailing whitespace from the value in the
- * given HTTP header line and return a strdupped copy. Returns NULL in
- * case of allocation failure. Returns an empty string if the header value
- * consists entirely of whitespace.
+ * Strip off leading and trailing whitespace from the value in the given HTTP
+ * header line and return a strdup()ed copy. Returns NULL in case of
+ * allocation failure or bad input. Returns an empty string if the header
+ * value consists entirely of whitespace.
+ *
+ * If the header is provided as "name;", ending with a semicolon, it must
+ * return a blank string.
  */
 char *Curl_copy_header_value(const char *header)
 {
-  const char *start;
-  const char *end;
-  size_t len;
-
-  /* Find the end of the header name */
-  while(*header && (*header != ':'))
-    ++header;
-
-  if(*header)
-    /* Skip over colon */
-    ++header;
-
-  /* Find the first non-space letter */
-  start = header;
-  while(ISSPACE(*start))
-    start++;
+  struct Curl_str out;
 
-  end = strchr(start, '\r');
-  if(!end)
-    end = strchr(start, '\n');
-  if(!end)
-    end = strchr(start, '\0');
-  if(!end)
-    return NULL;
+  /* find the end of the header name */
+  if(!Curl_str_cspn(&header, &out, ";:") &&
+     (!Curl_str_single(&header, ':') || !Curl_str_single(&header, ';'))) {
+    Curl_str_untilnl(&header, &out, MAX_HTTP_RESP_HEADER_SIZE);
+    Curl_str_trimblanks(&out);
 
-  /* skip all trailing space letters */
-  while((end > start) && ISSPACE(*end))
-    end--;
-
-  /* get length of the type */
-  len = end - start + 1;
-
-  return Curl_memdup0(start, len);
+    return Curl_memdup0(Curl_str(&out), Curl_strlen(&out));
+  }
+  /* bad input */
+  return NULL;
 }
 
 #ifndef CURL_DISABLE_HTTP_AUTH
@@ -1468,9 +1450,8 @@ Curl_compareheader(const char *headerline, /* line to check */
    * The field value MAY be preceded by any amount of LWS, though a single SP
    * is preferred." */
 
-  size_t len;
-  const char *start;
-  const char *end;
+  const char *p;
+  struct Curl_str val;
   DEBUGASSERT(hlen);
   DEBUGASSERT(clen);
   DEBUGASSERT(header);
@@ -1480,31 +1461,21 @@ Curl_compareheader(const char *headerline, /* line to check */
     return FALSE; /* does not start with header */
 
   /* pass the header */
-  start = &headerline[hlen];
-
-  /* pass all whitespace */
-  while(ISSPACE(*start))
-    start++;
-
-  /* find the end of the header line */
-  end = strchr(start, '\r'); /* lines end with CRLF */
-  if(!end) {
-    /* in case there is a non-standard compliant line here */
-    end = strchr(start, '\n');
-
-    if(!end)
-      /* hm, there is no line ending here, use the zero byte! */
-      end = strchr(start, '\0');
-  }
+  p = &headerline[hlen];
 
-  len = end-start; /* length of the content part of the input line */
+  if(Curl_str_untilnl(&p, &val, MAX_HTTP_RESP_HEADER_SIZE))
+    return FALSE;
+  Curl_str_trimblanks(&val);
 
   /* find the content string in the rest of the line */
-  for(; len >= clen; len--, start++) {
-    if(strncasecompare(start, content, clen))
-      return TRUE; /* match! */
+  if(Curl_strlen(&val) >= clen) {
+    size_t len;
+    p = Curl_str(&val);
+    for(len = Curl_strlen(&val); len >= Curl_strlen(&val); len--, p++) {
+      if(strncasecompare(p, content, clen))
+        return TRUE; /* match! */
+    }
   }
-
   return FALSE; /* no match */
 }
 
@@ -1623,7 +1594,6 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data,
                                  bool is_connect, int httpversion,
                                  struct dynbuf *req)
 {
-  char *ptr;
   struct curl_slist *h[2];
   struct curl_slist *headers;
   int numlists = 1; /* by default */
@@ -1663,98 +1633,81 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data,
 
   /* loop through one or two lists */
   for(i = 0; i < numlists; i++) {
-    headers = h[i];
-
-    while(headers) {
-      char *semicolonp = NULL;
-      ptr = strchr(headers->data, ':');
-      if(!ptr) {
-        char *optr;
-        /* no colon, semicolon? */
-        ptr = strchr(headers->data, ';');
-        if(ptr) {
-          optr = ptr;
-          ptr++; /* pass the semicolon */
-          while(ISSPACE(*ptr))
-            ptr++;
-
-          if(*ptr) {
-            /* this may be used for something else in the future */
-            optr = NULL;
-          }
-          else {
-            if(*(--ptr) == ';') {
-              /* copy the source */
-              semicolonp = strdup(headers->data);
-              if(!semicolonp) {
-                Curl_dyn_free(req);
-                return CURLE_OUT_OF_MEMORY;
-              }
-              /* put a colon where the semicolon is */
-              semicolonp[ptr - headers->data] = ':';
-              /* point at the colon */
-              optr = &semicolonp [ptr - headers->data];
-            }
-          }
-          ptr = optr;
-        }
-      }
-      if(ptr && (ptr != headers->data)) {
-        /* we require a colon for this to be a true header */
-
-        ptr++; /* pass the colon */
-        while(ISSPACE(*ptr))
-          ptr++;
-
-        if(*ptr || semicolonp) {
-          /* only send this if the contents was non-blank or done special */
-          CURLcode result = CURLE_OK;
-          char *compare = semicolonp ? semicolonp : headers->data;
-
-          if(data->state.aptr.host &&
-             /* a Host: header was sent already, do not pass on any custom
-                Host: header as that will produce *two* in the same
-                request! */
-             checkprefix("Host:", compare))
-            ;
-          else if(data->state.httpreq == HTTPREQ_POST_FORM &&
-                  /* this header (extended by formdata.c) is sent later */
-                  checkprefix("Content-Type:", compare))
-            ;
-          else if(data->state.httpreq == HTTPREQ_POST_MIME &&
-                  /* this header is sent later */
-                  checkprefix("Content-Type:", compare))
-            ;
-          else if(data->req.authneg &&
-                  /* while doing auth neg, do not allow the custom length since
-                     we will force length zero then */
-                  checkprefix("Content-Length:", compare))
-            ;
-          else if(data->state.aptr.te &&
-                  /* when asking for Transfer-Encoding, do not pass on a custom
-                     Connection: */
-                  checkprefix("Connection:", compare))
-            ;
-          else if((httpversion >= 20) &&
-                  checkprefix("Transfer-Encoding:", compare))
-            /* HTTP/2 does not support chunked requests */
-            ;
-          else if((checkprefix("Authorization:", compare) ||
-                   checkprefix("Cookie:", compare)) &&
-                  /* be careful of sending this potentially sensitive header to
-                     other hosts */
-                  !Curl_auth_allowed_to_host(data))
-            ;
-          else {
-            result = Curl_dyn_addf(req, "%s\r\n", compare);
-          }
-          if(semicolonp)
-            free(semicolonp);
-          if(result)
-            return result;
+    for(headers = h[i]; headers; headers = headers->next) {
+      CURLcode result = CURLE_OK;
+      bool blankheader = FALSE;
+      struct Curl_str name;
+      const char *p = headers->data;
+      const char *origp = p;
+
+      /* explicitly asked to send header without content is done by a header
+         that ends with a semicolon, but there must be no colon present in the
+         name */
+      if(!Curl_str_until(&p, &name, MAX_HTTP_RESP_HEADER_SIZE, ';') &&
+         !Curl_str_single(&p, ';') &&
+         !Curl_str_single(&p, '\0') &&
+         !memchr(Curl_str(&name), ':', Curl_strlen(&name)))
+        blankheader = TRUE;
+      else {
+        p = origp;
+        if(!Curl_str_until(&p, &name, MAX_HTTP_RESP_HEADER_SIZE, ':') &&
+           !Curl_str_single(&p, ':')) {
+          struct Curl_str val;
+          Curl_str_untilnl(&p, &val, MAX_HTTP_RESP_HEADER_SIZE);
+          Curl_str_trimblanks(&val);
+          if(!Curl_strlen(&val))
+            /* no content, don't send this */
+            continue;
         }
+        else
+          /* no colon */
+          continue;
       }
-      headers = headers->next;
+
+      /* only send this if the contents was non-blank or done special */
+
+      if(data->state.aptr.host &&
+         /* a Host: header was sent already, do not pass on any custom
+            Host: header as that will produce *two* in the same
+            request! */
+         Curl_str_casecompare(&name, "Host"))
+        ;
+      else if(data->state.httpreq == HTTPREQ_POST_FORM &&
+              /* this header (extended by formdata.c) is sent later */
+              Curl_str_casecompare(&name, "Content-Type"))
+        ;
+      else if(data->state.httpreq == HTTPREQ_POST_MIME &&
+              /* this header is sent later */
+              Curl_str_casecompare(&name, "Content-Type"))
+        ;
+      else if(data->req.authneg &&
+              /* while doing auth neg, do not allow the custom length since
+                 we will force length zero then */
+              Curl_str_casecompare(&name, "Content-Length"))
+        ;
+      else if(data->state.aptr.te &&
+              /* when asking for Transfer-Encoding, do not pass on a custom
+                 Connection: */
+              Curl_str_casecompare(&name, "Connection"))
+        ;
+      else if((httpversion >= 20) &&
+              Curl_str_casecompare(&name, "Transfer-Encoding"))
+        /* HTTP/2 does not support chunked requests */
+        ;
+      else if((Curl_str_casecompare(&name, "Authorization") ||
+               Curl_str_casecompare(&name, "Cookie")) &&
+              /* be careful of sending this potentially sensitive header to
+                 other hosts */
+              !Curl_auth_allowed_to_host(data))
+        ;
+      else if(blankheader)
+        result = Curl_dyn_addf(req, "%.*s:\r\n", (int)Curl_strlen(&name),
+                               Curl_str(&name));
+      else
+        result = Curl_dyn_addf(req, "%s\r\n", origp);
+
+      if(result)
+        return result;
     }
   }
 
index 299c9cc36b7a306b94d71d3673157a87d967147b..9fceaea892905522a4310c05daadfa41a39e44c9 100644 (file)
@@ -114,7 +114,10 @@ void *Curl_memdup0(const char *src, size_t length)
   char *buf = malloc(length + 1);
   if(!buf)
     return NULL;
-  memcpy(buf, src, length);
+  if(length) {
+    DEBUGASSERT(src); /* must never be NULL */
+    memcpy(buf, src, length);
+  }
   buf[length] = 0;
   return buf;
 }
index 97f70ba7b221cb182aca77774deb179617ee06d8..8a4e08e2bd20e7217e2e13488b7eecd60bdf2237 100644 (file)
@@ -63,6 +63,29 @@ int Curl_str_word(const char **linep, struct Curl_str *out,
   return Curl_str_until(linep, out, max, ' ');
 }
 
+/* Get a word until a newline byte or end of string. At least one byte long.
+   return non-zero on error */
+int Curl_str_untilnl(const char **linep, struct Curl_str *out,
+                     const size_t max)
+{
+  const char *s = *linep;
+  size_t len = 0;
+  DEBUGASSERT(linep && *linep && out && max);
+
+  Curl_str_init(out);
+  while(*s && !ISNEWLINE(*s)) {
+    s++;
+    if(++len > max)
+      return STRE_BIG;
+  }
+  if(!len)
+    return STRE_SHORT;
+  out->str = *linep;
+  out->len = len;
+  *linep = s; /* point to the first byte after the word */
+  return STRE_OK;
+}
+
 
 /* Get a "quoted" word. No escaping possible.
    return non-zero on error */
index 3dbbf9a2623846d7e48100bf1ab4c5ed8c009f74..5dcd537bd9a2a5a727511d9a8ad6d4b95e858309 100644 (file)
@@ -56,6 +56,11 @@ int Curl_str_word(const char **linep, struct Curl_str *out, const size_t max);
 int Curl_str_until(const char **linep, struct Curl_str *out, const size_t max,
                    char delim);
 
+/* Get a word until a newline byte or end of string. At least one byte long.
+   return non-zero on error */
+int Curl_str_untilnl(const char **linep, struct Curl_str *out,
+                     const size_t max);
+
 /* Get a "quoted" word. No escaping possible.
    return non-zero on error */
 int Curl_str_quotedword(const char **linep, struct Curl_str *out,