]> git.ipfire.org Git - thirdparty/git.git/blobdiff - http.c
http: read HTTP WWW-Authenticate response headers
[thirdparty/git.git] / http.c
diff --git a/http.c b/http.c
index c4b6ddef287b90f6492706cdaa483c1832d8b06c..32906eb2ac50a8fb76c9ad441316122dbf207e67 100644 (file)
--- a/http.c
+++ b/http.c
@@ -181,6 +181,115 @@ size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)
        return nmemb;
 }
 
+/*
+ * A folded header continuation line starts with any number of spaces or
+ * horizontal tab characters (SP or HTAB) as per RFC 7230 section 3.2.
+ * It is not a continuation line if the line starts with any other character.
+ */
+static inline int is_hdr_continuation(const char *ptr, const size_t size)
+{
+       return size && (*ptr == ' ' || *ptr == '\t');
+}
+
+static size_t fwrite_wwwauth(char *ptr, size_t eltsize, size_t nmemb, void *p)
+{
+       size_t size = eltsize * nmemb;
+       struct strvec *values = &http_auth.wwwauth_headers;
+       struct strbuf buf = STRBUF_INIT;
+       const char *val;
+       size_t val_len;
+
+       /*
+        * Header lines may not come NULL-terminated from libcurl so we must
+        * limit all scans to the maximum length of the header line, or leverage
+        * strbufs for all operations.
+        *
+        * In addition, it is possible that header values can be split over
+        * multiple lines as per RFC 7230. 'Line folding' has been deprecated
+        * but older servers may still emit them. A continuation header field
+        * value is identified as starting with a space or horizontal tab.
+        *
+        * The formal definition of a header field as given in RFC 7230 is:
+        *
+        * header-field   = field-name ":" OWS field-value OWS
+        *
+        * field-name     = token
+        * field-value    = *( field-content / obs-fold )
+        * field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
+        * field-vchar    = VCHAR / obs-text
+        *
+        * obs-fold       = CRLF 1*( SP / HTAB )
+        *                ; obsolete line folding
+        *                ; see Section 3.2.4
+        */
+
+       /* Start of a new WWW-Authenticate header */
+       if (skip_iprefix_mem(ptr, size, "www-authenticate:", &val, &val_len)) {
+               strbuf_add(&buf, val, val_len);
+
+               /*
+                * Strip the CRLF that should be present at the end of each
+                * field as well as any trailing or leading whitespace from the
+                * value.
+                */
+               strbuf_trim(&buf);
+
+               strvec_push(values, buf.buf);
+               http_auth.header_is_last_match = 1;
+               goto exit;
+       }
+
+       /*
+        * This line could be a continuation of the previously matched header
+        * field. If this is the case then we should append this value to the
+        * end of the previously consumed value.
+        */
+       if (http_auth.header_is_last_match && is_hdr_continuation(ptr, size)) {
+               /*
+                * Trim the CRLF and any leading or trailing from this line.
+                */
+               strbuf_add(&buf, ptr, size);
+               strbuf_trim(&buf);
+
+               /*
+                * At this point we should always have at least one existing
+                * value, even if it is empty. Do not bother appending the new
+                * value if this continuation header is itself empty.
+                */
+               if (!values->nr) {
+                       BUG("should have at least one existing header value");
+               } else if (buf.len) {
+                       char *prev = xstrdup(values->v[values->nr - 1]);
+
+                       /* Join two non-empty values with a single space. */
+                       const char *const sp = *prev ? " " : "";
+
+                       strvec_pop(values);
+                       strvec_pushf(values, "%s%s%s", prev, sp, buf.buf);
+                       free(prev);
+               }
+
+               goto exit;
+       }
+
+       /* Not a continuation of a previously matched auth header line. */
+       http_auth.header_is_last_match = 0;
+
+       /*
+        * If this is a HTTP status line and not a header field, this signals
+        * a different HTTP response. libcurl writes all the output of all
+        * response headers of all responses, including redirects.
+        * We only care about the last HTTP request response's headers so clear
+        * the existing array.
+        */
+       if (skip_iprefix_mem(ptr, size, "http/", &val, &val_len))
+               strvec_clear(values);
+
+exit:
+       strbuf_release(&buf);
+       return size;
+}
+
 size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf)
 {
        return nmemb;
@@ -1895,6 +2004,8 @@ static int http_request(const char *url,
                                         fwrite_buffer);
        }
 
+       curl_easy_setopt(slot->curl, CURLOPT_HEADERFUNCTION, fwrite_wwwauth);
+
        accept_language = http_get_accept_language_header();
 
        if (accept_language)