diff options
Diffstat (limited to 'http.c')
-rw-r--r-- | http.c | 173 |
1 files changed, 167 insertions, 6 deletions
@@ -1,5 +1,6 @@ #include "git-compat-util.h" #include "git-curl-compat.h" +#include "hex.h" #include "http.h" #include "config.h" #include "pack.h" @@ -11,10 +12,12 @@ #include "version.h" #include "pkt-line.h" #include "gettext.h" +#include "trace.h" #include "transport.h" #include "packfile.h" #include "protocol.h" #include "string-list.h" +#include "object-file.h" #include "object-store.h" static struct trace_key trace_curl = TRACE_KEY_INIT(CURL); @@ -39,6 +42,7 @@ static int curl_ssl_verify = -1; static int curl_ssl_try; static const char *curl_http_version = NULL; static const char *ssl_cert; +static const char *ssl_cert_type; static const char *ssl_cipherlist; static const char *ssl_version; static struct { @@ -58,6 +62,7 @@ static struct { #endif }; static const char *ssl_key; +static const char *ssl_key_type; static const char *ssl_capath; static const char *curl_no_proxy; #ifdef GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY @@ -181,6 +186,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; @@ -264,8 +378,12 @@ static int http_options(const char *var, const char *value, void *cb) return git_config_string(&ssl_version, var, value); if (!strcmp("http.sslcert", var)) return git_config_pathname(&ssl_cert, var, value); + if (!strcmp("http.sslcerttype", var)) + return git_config_string(&ssl_cert_type, var, value); if (!strcmp("http.sslkey", var)) return git_config_pathname(&ssl_key, var, value); + if (!strcmp("http.sslkeytype", var)) + return git_config_string(&ssl_key_type, var, value); if (!strcmp("http.sslcapath", var)) return git_config_pathname(&ssl_capath, var, value); if (!strcmp("http.sslcainfo", var)) @@ -558,13 +676,15 @@ static void set_curl_keepalive(CURL *c) } #endif -static void redact_sensitive_header(struct strbuf *header) +/* Return 1 if redactions have been made, 0 otherwise. */ +static int redact_sensitive_header(struct strbuf *header, size_t offset) { + int ret = 0; const char *sensitive_header; if (trace_curl_redact && - (skip_iprefix(header->buf, "Authorization:", &sensitive_header) || - skip_iprefix(header->buf, "Proxy-Authorization:", &sensitive_header))) { + (skip_iprefix(header->buf + offset, "Authorization:", &sensitive_header) || + skip_iprefix(header->buf + offset, "Proxy-Authorization:", &sensitive_header))) { /* The first token is the type, which is OK to log */ while (isspace(*sensitive_header)) sensitive_header++; @@ -573,8 +693,9 @@ static void redact_sensitive_header(struct strbuf *header) /* Everything else is opaque and possibly sensitive */ strbuf_setlen(header, sensitive_header - header->buf); strbuf_addstr(header, " <redacted>"); + ret = 1; } else if (trace_curl_redact && - skip_iprefix(header->buf, "Cookie:", &sensitive_header)) { + skip_iprefix(header->buf + offset, "Cookie:", &sensitive_header)) { struct strbuf redacted_header = STRBUF_INIT; const char *cookie; @@ -610,6 +731,26 @@ static void redact_sensitive_header(struct strbuf *header) strbuf_setlen(header, sensitive_header - header->buf); strbuf_addbuf(header, &redacted_header); + ret = 1; + } + return ret; +} + +/* Redact headers in info */ +static void redact_sensitive_info_header(struct strbuf *header) +{ + const char *sensitive_header; + + /* + * curl's h2h3 prints headers in info, e.g.: + * h2h3 [<header-name>: <header-val>] + */ + if (trace_curl_redact && + skip_iprefix(header->buf, "h2h3 [", &sensitive_header)) { + if (redact_sensitive_header(header, sensitive_header - header->buf)) { + /* redaction ate our closing bracket */ + strbuf_addch(header, ']'); + } } } @@ -627,7 +768,7 @@ static void curl_dump_header(const char *text, unsigned char *ptr, size_t size, for (header = headers; *header; header++) { if (hide_sensitive_header) - redact_sensitive_header(*header); + redact_sensitive_header(*header, 0); strbuf_insertstr((*header), 0, text); strbuf_insertstr((*header), strlen(text), ": "); strbuf_rtrim((*header)); @@ -666,6 +807,18 @@ static void curl_dump_data(const char *text, unsigned char *ptr, size_t size) strbuf_release(&out); } +static void curl_dump_info(char *data, size_t size) +{ + struct strbuf buf = STRBUF_INIT; + + strbuf_add(&buf, data, size); + + redact_sensitive_info_header(&buf); + trace_printf_key(&trace_curl, "== Info: %s", buf.buf); + + strbuf_release(&buf); +} + static int curl_trace(CURL *handle, curl_infotype type, char *data, size_t size, void *userp) { const char *text; @@ -673,7 +826,7 @@ static int curl_trace(CURL *handle, curl_infotype type, char *data, size_t size, switch (type) { case CURLINFO_TEXT: - trace_printf_key(&trace_curl, "== Info: %s", data); + curl_dump_info(data, size); break; case CURLINFO_HEADER_OUT: text = "=> Send header"; @@ -869,10 +1022,14 @@ static CURL *get_curl_handle(void) if (ssl_cert) curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert); + if (ssl_cert_type) + curl_easy_setopt(result, CURLOPT_SSLCERTTYPE, ssl_cert_type); if (has_cert_password()) curl_easy_setopt(result, CURLOPT_KEYPASSWD, cert_auth.password); if (ssl_key) curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key); + if (ssl_key_type) + curl_easy_setopt(result, CURLOPT_SSLKEYTYPE, ssl_key_type); if (ssl_capath) curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath); #ifdef GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY @@ -1107,7 +1264,9 @@ void http_init(struct remote *remote, const char *url, int proactive_auth) curl_ssl_verify = 0; set_from_env(&ssl_cert, "GIT_SSL_CERT"); + set_from_env(&ssl_cert_type, "GIT_SSL_CERT_TYPE"); set_from_env(&ssl_key, "GIT_SSL_KEY"); + set_from_env(&ssl_key_type, "GIT_SSL_KEY_TYPE"); set_from_env(&ssl_capath, "GIT_SSL_CAPATH"); set_from_env(&ssl_cainfo, "GIT_SSL_CAINFO"); @@ -1860,6 +2019,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) |