Skip to content

Commit 6b8dda9

Browse files
mjcheethamgitster
authored andcommitted
http: read HTTP WWW-Authenticate response headers
Read and store the HTTP WWW-Authenticate response headers made for a particular request. This will allow us to pass important authentication challenge information to credential helpers or others that would otherwise have been lost. libcurl only provides us with the ability to read all headers recieved for a particular request, including any intermediate redirect requests or proxies. The lines returned by libcurl include HTTP status lines delinating any intermediate requests such as "HTTP/1.1 200". We use these lines to reset the strvec of WWW-Authenticate header values as we encounter them in order to only capture the final response headers. The collection of all header values matching the WWW-Authenticate header is complicated by the fact that it is legal for header fields to be continued over multiple lines, but libcurl only gives us each physical line a time, not each logical header. This line folding feature is deprecated in RFC 7230 [1] but older servers may still emit them, so we need to handle them. In the future [2] we may be able to leverage functions to read headers from libcurl itself, but as of today we must do this ourselves. [1] https://www.rfc-editor.org/rfc/rfc7230#section-3.2 [2] https://daniel.haxx.se/blog/2022/03/22/a-headers-api-for-libcurl/ Signed-off-by: Matthew John Cheetham <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 988aad9 commit 6b8dda9

File tree

4 files changed

+147
-0
lines changed

4 files changed

+147
-0
lines changed

credential.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ void credential_clear(struct credential *c)
2222
free(c->username);
2323
free(c->password);
2424
string_list_clear(&c->helpers, 0);
25+
strvec_clear(&c->wwwauth_headers);
2526

2627
credential_init(c);
2728
}

credential.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define CREDENTIAL_H
33

44
#include "string-list.h"
5+
#include "strvec.h"
56

67
/**
78
* The credentials API provides an abstracted way of gathering username and
@@ -115,6 +116,20 @@ struct credential {
115116
*/
116117
struct string_list helpers;
117118

119+
/**
120+
* A `strvec` of WWW-Authenticate header values. Each string
121+
* is the value of a WWW-Authenticate header in an HTTP response,
122+
* in the order they were received in the response.
123+
*/
124+
struct strvec wwwauth_headers;
125+
126+
/**
127+
* Internal use only. Keeps track of if we previously matched against a
128+
* WWW-Authenticate header line in order to re-fold future continuation
129+
* lines into one value.
130+
*/
131+
unsigned header_is_last_match:1;
132+
118133
unsigned approved:1,
119134
configured:1,
120135
quit:1,
@@ -130,6 +145,7 @@ struct credential {
130145

131146
#define CREDENTIAL_INIT { \
132147
.helpers = STRING_LIST_INIT_DUP, \
148+
.wwwauth_headers = STRVEC_INIT, \
133149
}
134150

135151
/* Initialize a credential structure, setting all fields to empty. */

git-compat-util.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,25 @@ static inline int skip_iprefix(const char *str, const char *prefix,
12871287
return 0;
12881288
}
12891289

1290+
/*
1291+
* Like skip_prefix_mem, but compare case-insensitively. Note that the
1292+
* comparison is done via tolower(), so it is strictly ASCII (no multi-byte
1293+
* characters or locale-specific conversions).
1294+
*/
1295+
static inline int skip_iprefix_mem(const char *buf, size_t len,
1296+
const char *prefix,
1297+
const char **out, size_t *outlen)
1298+
{
1299+
do {
1300+
if (!*prefix) {
1301+
*out = buf;
1302+
*outlen = len;
1303+
return 1;
1304+
}
1305+
} while (len-- > 0 && tolower(*buf++) == tolower(*prefix++));
1306+
return 0;
1307+
}
1308+
12901309
static inline int strtoul_ui(char const *s, int base, unsigned int *result)
12911310
{
12921311
unsigned long ul;

http.c

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,115 @@ size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)
181181
return nmemb;
182182
}
183183

184+
/*
185+
* A folded header continuation line starts with any number of spaces or
186+
* horizontal tab characters (SP or HTAB) as per RFC 7230 section 3.2.
187+
* It is not a continuation line if the line starts with any other character.
188+
*/
189+
static inline int is_hdr_continuation(const char *ptr, const size_t size)
190+
{
191+
return size && (*ptr == ' ' || *ptr == '\t');
192+
}
193+
194+
static size_t fwrite_wwwauth(char *ptr, size_t eltsize, size_t nmemb, void *p)
195+
{
196+
size_t size = eltsize * nmemb;
197+
struct strvec *values = &http_auth.wwwauth_headers;
198+
struct strbuf buf = STRBUF_INIT;
199+
const char *val;
200+
size_t val_len;
201+
202+
/*
203+
* Header lines may not come NULL-terminated from libcurl so we must
204+
* limit all scans to the maximum length of the header line, or leverage
205+
* strbufs for all operations.
206+
*
207+
* In addition, it is possible that header values can be split over
208+
* multiple lines as per RFC 7230. 'Line folding' has been deprecated
209+
* but older servers may still emit them. A continuation header field
210+
* value is identified as starting with a space or horizontal tab.
211+
*
212+
* The formal definition of a header field as given in RFC 7230 is:
213+
*
214+
* header-field = field-name ":" OWS field-value OWS
215+
*
216+
* field-name = token
217+
* field-value = *( field-content / obs-fold )
218+
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
219+
* field-vchar = VCHAR / obs-text
220+
*
221+
* obs-fold = CRLF 1*( SP / HTAB )
222+
* ; obsolete line folding
223+
* ; see Section 3.2.4
224+
*/
225+
226+
/* Start of a new WWW-Authenticate header */
227+
if (skip_iprefix_mem(ptr, size, "www-authenticate:", &val, &val_len)) {
228+
strbuf_add(&buf, val, val_len);
229+
230+
/*
231+
* Strip the CRLF that should be present at the end of each
232+
* field as well as any trailing or leading whitespace from the
233+
* value.
234+
*/
235+
strbuf_trim(&buf);
236+
237+
strvec_push(values, buf.buf);
238+
http_auth.header_is_last_match = 1;
239+
goto exit;
240+
}
241+
242+
/*
243+
* This line could be a continuation of the previously matched header
244+
* field. If this is the case then we should append this value to the
245+
* end of the previously consumed value.
246+
*/
247+
if (http_auth.header_is_last_match && is_hdr_continuation(ptr, size)) {
248+
/*
249+
* Trim the CRLF and any leading or trailing from this line.
250+
*/
251+
strbuf_add(&buf, ptr, size);
252+
strbuf_trim(&buf);
253+
254+
/*
255+
* At this point we should always have at least one existing
256+
* value, even if it is empty. Do not bother appending the new
257+
* value if this continuation header is itself empty.
258+
*/
259+
if (!values->nr) {
260+
BUG("should have at least one existing header value");
261+
} else if (buf.len) {
262+
char *prev = xstrdup(values->v[values->nr - 1]);
263+
264+
/* Join two non-empty values with a single space. */
265+
const char *const sp = *prev ? " " : "";
266+
267+
strvec_pop(values);
268+
strvec_pushf(values, "%s%s%s", prev, sp, buf.buf);
269+
free(prev);
270+
}
271+
272+
goto exit;
273+
}
274+
275+
/* Not a continuation of a previously matched auth header line. */
276+
http_auth.header_is_last_match = 0;
277+
278+
/*
279+
* If this is a HTTP status line and not a header field, this signals
280+
* a different HTTP response. libcurl writes all the output of all
281+
* response headers of all responses, including redirects.
282+
* We only care about the last HTTP request response's headers so clear
283+
* the existing array.
284+
*/
285+
if (skip_iprefix_mem(ptr, size, "http/", &val, &val_len))
286+
strvec_clear(values);
287+
288+
exit:
289+
strbuf_release(&buf);
290+
return size;
291+
}
292+
184293
size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf)
185294
{
186295
return nmemb;
@@ -1895,6 +2004,8 @@ static int http_request(const char *url,
18952004
fwrite_buffer);
18962005
}
18972006

2007+
curl_easy_setopt(slot->curl, CURLOPT_HEADERFUNCTION, fwrite_wwwauth);
2008+
18982009
accept_language = http_get_accept_language_header();
18992010

19002011
if (accept_language)

0 commit comments

Comments
 (0)