]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
digest: handle quotes in the path
authorDaniel Stenberg <daniel@haxx.se>
Tue, 13 Jan 2026 14:31:06 +0000 (15:31 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 14 Jan 2026 08:57:00 +0000 (09:57 +0100)
- The 'uri' component needs to be escaped as well
- Rewrote the quote function to use dynbuf
- Build the digest at least partly with dynbuf
- Use goto as a general error mechanism
- Make test 64 use a double quote in the URL

Closes #20295

lib/vauth/digest.c
tests/data/test64

index a1fd248617a40759a8a032fdd58957d4b185d356..790240837b1da4820cc25d21f1d25f3ecef8b835 100644 (file)
@@ -148,35 +148,26 @@ static void auth_digest_sha256_to_ascii(unsigned char *source, /* 32 bytes */
 }
 
 /* Perform quoted-string escaping as described in RFC2616 and its errata */
-static char *auth_digest_string_quoted(const char *source)
+static char *auth_digest_string_quoted(const char *s)
 {
-  char *dest;
-  const char *s = source;
-  size_t n = 1; /* null-terminator */
-
-  /* Calculate size needed */
+  struct dynbuf out;
+  curlx_dyn_init(&out, 2048);
+  if(!*s) /* for zero length input, make sure we return an empty string */
+    return curlx_strdup("");
   while(*s) {
-    ++n;
+    CURLcode result;
     if(*s == '"' || *s == '\\') {
-      ++n;
-    }
-    ++s;
-  }
-
-  dest = curlx_malloc(n);
-  if(dest) {
-    char *d = dest;
-    s = source;
-    while(*s) {
-      if(*s == '"' || *s == '\\') {
-        *d++ = '\\';
-      }
-      *d++ = *s++;
+      result = curlx_dyn_addn(&out, "\\", 1);
+      if(!result)
+        result = curlx_dyn_addn(&out, s, 1);
     }
-    *d = '\0';
+    else
+      result = curlx_dyn_addn(&out, s, 1);
+    if(result)
+      return NULL;
+    s++;
   }
-
-  return dest;
+  return curlx_dyn_ptr(&out);
 }
 
 /* Retrieves the value for a corresponding key from the challenge string
@@ -672,16 +663,15 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg,
  * Returns CURLE_OK on success.
  */
 static CURLcode auth_create_digest_http_message(
-                  struct Curl_easy *data,
-                  const char *userp,
-                  const char *passwdp,
-                  const unsigned char *request,
-                  const unsigned char *uripath,
-                  struct digestdata *digest,
-                  char **outptr, size_t *outlen,
-                  void (*convert_to_ascii)(unsigned char *, unsigned char *),
-                  CURLcode (*hash)(unsigned char *, const unsigned char *,
-                                   const size_t))
+  struct Curl_easy *data,
+  const char *userp,
+  const char *passwdp,
+  const unsigned char *request,
+  const unsigned char *uripath,
+  struct digestdata *digest,
+  char **outptr, size_t *outlen,
+  void (*convert_to_ascii)(unsigned char *, unsigned char *),
+  CURLcode (*hash)(unsigned char *, const unsigned char *, const size_t))
 {
   CURLcode result;
   unsigned char hashbuf[32]; /* 32 bytes/256 bits */
@@ -691,12 +681,15 @@ static CURLcode auth_create_digest_http_message(
   char userh[65];
   char *cnonce = NULL;
   size_t cnonce_sz = 0;
-  char *userp_quoted;
-  char *realm_quoted;
-  char *nonce_quoted;
-  char *response = NULL;
+  char *userp_quoted = NULL;
+  char *realm_quoted = NULL;
+  char *nonce_quoted = NULL;
   char *hashthis = NULL;
-  char *tmp = NULL;
+  char *uri_quoted = NULL;
+  struct dynbuf response;
+  *outptr = NULL;
+
+  curlx_dyn_init(&response, 4096); /* arbitrary max */
 
   memset(hashbuf, 0, sizeof(hashbuf));
   if(!digest->nc)
@@ -710,27 +703,27 @@ static CURLcode auth_create_digest_http_message(
 #endif
                              (unsigned char *)cnoncebuf,
                              sizeof(cnoncebuf));
+    if(!result)
+      result = curlx_base64_encode((uint8_t *)cnoncebuf, sizeof(cnoncebuf),
+                                   &cnonce, &cnonce_sz);
     if(result)
-      return result;
-
-    result = curlx_base64_encode((uint8_t *)cnoncebuf, sizeof(cnoncebuf),
-                                 &cnonce, &cnonce_sz);
-    if(result)
-      return result;
+      goto oom;
 
     digest->cnonce = cnonce;
   }
 
   if(digest->userhash) {
-    hashthis = curl_maprintf("%s:%s", userp,
-                             digest->realm ? digest->realm : "");
-    if(!hashthis)
-      return CURLE_OUT_OF_MEMORY;
+    char *hasht = curl_maprintf("%s:%s", userp,
+                                digest->realm ? digest->realm : "");
+    if(!hasht) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto oom;
+    }
 
-    result = hash(hashbuf, (unsigned char *)hashthis, strlen(hashthis));
-    curlx_free(hashthis);
+    result = hash(hashbuf, (unsigned char *)hasht, strlen(hasht));
+    curlx_free(hasht);
     if(result)
-      return result;
+      goto oom;
     convert_to_ascii(hashbuf, (unsigned char *)userh);
   }
 
@@ -745,27 +738,31 @@ static CURLcode auth_create_digest_http_message(
            unq(nonce-value) ":" unq(cnonce-value)
   */
 
-  hashthis = curl_maprintf("%s:%s:%s", userp,
-                           digest->realm ? digest->realm : "", passwdp);
-  if(!hashthis)
-    return CURLE_OUT_OF_MEMORY;
+  hashthis = curl_maprintf("%s:%s:%s", userp, digest->realm ?
+                           digest->realm : "", passwdp);
+  if(!hashthis) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto oom;
+  }
 
   result = hash(hashbuf, (unsigned char *)hashthis, strlen(hashthis));
   curlx_free(hashthis);
   if(result)
-    return result;
+    goto oom;
   convert_to_ascii(hashbuf, ha1);
 
   if(digest->algo & SESSION_ALGO) {
     /* nonce and cnonce are OUTSIDE the hash */
-    tmp = curl_maprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce);
-    if(!tmp)
-      return CURLE_OUT_OF_MEMORY;
+    char *tmp = curl_maprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce);
+    if(!tmp) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto oom;
+    }
 
     result = hash(hashbuf, (unsigned char *)tmp, strlen(tmp));
     curlx_free(tmp);
     if(result)
-      return result;
+      goto oom;
     convert_to_ascii(hashbuf, ha1);
   }
 
@@ -782,9 +779,17 @@ static CURLcode auth_create_digest_http_message(
     5.1.1 of RFC 2616)
   */
 
+  uri_quoted = auth_digest_string_quoted((const char *)uripath);
+  if(!uri_quoted) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto oom;
+  }
+
   hashthis = curl_maprintf("%s:%s", request, uripath);
-  if(!hashthis)
-    return CURLE_OUT_OF_MEMORY;
+  if(!hashthis) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto oom;
+  }
 
   if(digest->qop && curl_strequal(digest->qop, "auth-int")) {
     /* We do not support auth-int for PUT or POST */
@@ -792,41 +797,40 @@ static CURLcode auth_create_digest_http_message(
     char *hashthis2;
 
     result = hash(hashbuf, (const unsigned char *)"", 0);
-    if(result) {
-      curlx_free(hashthis);
-      return result;
-    }
+    if(result)
+      goto oom;
     convert_to_ascii(hashbuf, (unsigned char *)hashed);
 
     hashthis2 = curl_maprintf("%s:%s", hashthis, hashed);
     curlx_free(hashthis);
     hashthis = hashthis2;
+    if(!hashthis) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto oom;
+    }
   }
 
-  if(!hashthis)
-    return CURLE_OUT_OF_MEMORY;
-
   result = hash(hashbuf, (unsigned char *)hashthis, strlen(hashthis));
   curlx_free(hashthis);
   if(result)
-    return result;
+    goto oom;
   convert_to_ascii(hashbuf, ha2);
 
-  if(digest->qop) {
+  if(digest->qop)
     hashthis = curl_maprintf("%s:%s:%08x:%s:%s:%s", ha1, digest->nonce,
                              digest->nc, digest->cnonce, digest->qop, ha2);
-  }
-  else {
+  else
     hashthis = curl_maprintf("%s:%s:%s", ha1, digest->nonce, ha2);
-  }
 
-  if(!hashthis)
-    return CURLE_OUT_OF_MEMORY;
+  if(!hashthis) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto oom;
+  }
 
   result = hash(hashbuf, (unsigned char *)hashthis, strlen(hashthis));
   curlx_free(hashthis);
   if(result)
-    return result;
+    goto oom;
   convert_to_ascii(hashbuf, request_digest);
 
   /* For test case 64 (snooped from a Mozilla 1.3a request)
@@ -843,8 +847,10 @@ static CURLcode auth_create_digest_http_message(
      characters.
   */
   userp_quoted = auth_digest_string_quoted(digest->userhash ? userh : userp);
-  if(!userp_quoted)
-    return CURLE_OUT_OF_MEMORY;
+  if(!userp_quoted) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto oom;
+  }
   if(digest->realm)
     realm_quoted = auth_digest_string_quoted(digest->realm);
   else {
@@ -853,98 +859,93 @@ static CURLcode auth_create_digest_http_message(
       realm_quoted[0] = 0;
   }
   if(!realm_quoted) {
-    curlx_free(userp_quoted);
-    return CURLE_OUT_OF_MEMORY;
+    result = CURLE_OUT_OF_MEMORY;
+    goto oom;
   }
+
   nonce_quoted = auth_digest_string_quoted(digest->nonce);
   if(!nonce_quoted) {
-    curlx_free(realm_quoted);
-    curlx_free(userp_quoted);
-    return CURLE_OUT_OF_MEMORY;
+    result = CURLE_OUT_OF_MEMORY;
+    goto oom;
   }
 
   if(digest->qop) {
-    response = curl_maprintf("username=\"%s\", "
-                             "realm=\"%s\", "
-                             "nonce=\"%s\", "
-                             "uri=\"%s\", "
-                             "cnonce=\"%s\", "
-                             "nc=%08x, "
-                             "qop=%s, "
-                             "response=\"%s\"",
-                             userp_quoted,
-                             realm_quoted,
-                             nonce_quoted,
-                             uripath,
-                             digest->cnonce,
-                             digest->nc,
-                             digest->qop,
-                             request_digest);
+    result = curlx_dyn_addf(&response, "username=\"%s\", "
+                            "realm=\"%s\", "
+                            "nonce=\"%s\", "
+                            "uri=\"%s\", "
+                            "cnonce=\"%s\", "
+                            "nc=%08x, "
+                            "qop=%s, "
+                            "response=\"%s\"",
+                            userp_quoted,
+                            realm_quoted,
+                            nonce_quoted,
+                            uri_quoted,
+                            digest->cnonce,
+                            digest->nc,
+                            digest->qop,
+                            request_digest);
 
     /* Increment nonce-count to use another nc value for the next request */
     digest->nc++;
   }
   else {
-    response = curl_maprintf("username=\"%s\", "
-                             "realm=\"%s\", "
-                             "nonce=\"%s\", "
-                             "uri=\"%s\", "
-                             "response=\"%s\"",
-                             userp_quoted,
-                             realm_quoted,
-                             nonce_quoted,
-                             uripath,
-                             request_digest);
+    result = curlx_dyn_addf(&response, "username=\"%s\", "
+                            "realm=\"%s\", "
+                            "nonce=\"%s\", "
+                            "uri=\"%s\", "
+                            "response=\"%s\"",
+                            userp_quoted,
+                            realm_quoted,
+                            nonce_quoted,
+                            uri_quoted,
+                            request_digest);
   }
-  curlx_free(nonce_quoted);
-  curlx_free(realm_quoted);
-  curlx_free(userp_quoted);
-  if(!response)
-    return CURLE_OUT_OF_MEMORY;
+  if(result)
+    goto oom;
 
   /* Add the optional fields */
   if(digest->opaque) {
-    char *opaque_quoted;
     /* Append the opaque */
-    opaque_quoted = auth_digest_string_quoted(digest->opaque);
+    char *opaque_quoted = auth_digest_string_quoted(digest->opaque);
     if(!opaque_quoted) {
-      curlx_free(response);
-      return CURLE_OUT_OF_MEMORY;
+      result = CURLE_OUT_OF_MEMORY;
+      goto oom;
     }
-    tmp = curl_maprintf("%s, opaque=\"%s\"", response, opaque_quoted);
-    curlx_free(response);
+    result = curlx_dyn_addf(&response, ", opaque=\"%s\"", opaque_quoted);
     curlx_free(opaque_quoted);
-    if(!tmp)
-      return CURLE_OUT_OF_MEMORY;
-
-    response = tmp;
+    if(result)
+      goto oom;
   }
 
   if(digest->algorithm) {
     /* Append the algorithm */
-    tmp = curl_maprintf("%s, algorithm=%s", response, digest->algorithm);
-    curlx_free(response);
-    if(!tmp)
-      return CURLE_OUT_OF_MEMORY;
-
-    response = tmp;
+    result = curlx_dyn_addf(&response, ", algorithm=%s", digest->algorithm);
+    if(result)
+      goto oom;
   }
 
   if(digest->userhash) {
     /* Append the userhash */
-    tmp = curl_maprintf("%s, userhash=true", response);
-    curlx_free(response);
-    if(!tmp)
-      return CURLE_OUT_OF_MEMORY;
-
-    response = tmp;
+    result = curlx_dyn_add(&response, ", userhash=true");
+    if(result)
+      goto oom;
   }
 
   /* Return the output */
-  *outptr = response;
-  *outlen = strlen(response);
+  *outptr = curlx_dyn_ptr(&response);
+  *outlen = curlx_dyn_len(&response);
+  result = CURLE_OK;
 
-  return CURLE_OK;
+oom:
+  curlx_free(nonce_quoted);
+  curlx_free(realm_quoted);
+  curlx_free(uri_quoted);
+  curlx_free(userp_quoted);
+  if(result)
+    curlx_dyn_free(&response);
+  return result;
 }
 
 /*
index 716212a051a118459c05aad1896bb3d17b92cc35..d45cd9b22052cc85d068691b1f60917c8d6b97fb 100644 (file)
@@ -61,21 +61,21 @@ digest
 HTTP with Digest authorization
 </name>
 <command>
-http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u testuser:testpass --digest
+'http://%HOSTIP:%HTTPPORT/%TESTNUMBER"' -u testuser:testpass --digest
 </command>
 </client>
 
 # Verify data after the test has been "shot"
 <verify>
 <protocol crlf="headers">
-GET /%TESTNUMBER HTTP/1.1
+GET /%TESTNUMBER" HTTP/1.1
 Host: %HOSTIP:%HTTPPORT
 User-Agent: curl/%VERSION
 Accept: */*
 
-GET /%TESTNUMBER HTTP/1.1
+GET /%TESTNUMBER" HTTP/1.1
 Host: %HOSTIP:%HTTPPORT
-Authorization: Digest username="testuser", realm="testrealm", nonce="1053604145", uri="/%TESTNUMBER", response="c55f7f30d83d774a3d2dcacf725abaca"
+Authorization: Digest username="testuser", realm="testrealm", nonce="1053604145", uri="/%TESTNUMBER\"", response="1ee14b238b3259f17602e9ce41491ef9"
 User-Agent: curl/%VERSION
 Accept: */*