http: support sending custom HTTP headers

We introduce a way to send custom HTTP headers with all requests. This allows us, for example, to send an extra token from build agents for temporary access to private repositories. (This is the use case that triggered this patch.) This feature can be used like this: git -c http.extraheader='Secret: sssh!' fetch $URL $REF Note that `curl_easy_setopt(..., CURLOPT_HTTPHEADER, ...)` takes only a single list, overriding any previous call. This means we have to collect _all_ of the headers we want to use into a single list, and feed it to cURL in one shot. Since we already unconditionally set a "pragma" header when initializing the curl handles, we can add our new headers to that list. For callers which override the default header list (like probe_rpc), we provide `http_copy_default_headers()` so they can do the same trick. Big thanks to Jeff King and Junio Hamano for their outstanding help and patient reviews. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Reviewed-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Schindelin committed Apr 27, 2016 at 14:20 UTC 8cb01e2fd3a50b6d0893dfb066183f16a3c7a355
7 files changed +61 -10
Documentation/config.txt
+6
@@ -1654,6 +1654,12 @@ http.emptyAuth::
1654 a username in the URL, as libcurl normally requires a username for
1655 authentication.
1656
1657 +http.extraHeader::
1658 + Pass an additional HTTP header when communicating with a server. If
1659 + more than one such entry exists, all of them are added as extra
1660 + headers. To allow overriding the settings inherited from the system
1661 + config, an empty value will reset the extra headers to the empty list.
1662 +
1663 http.cookieFile::
1664 File containing previously stored cookie lines which should be used
1665 in the Git http session, if they match the server. The file format
http-push.c
+5 -5
@@ -211,7 +211,7 @@ static void curl_setup_http(CURL *curl, const char *url,
211 static struct curl_slist *get_dav_token_headers(struct remote_lock *lock, enum dav_header_flag options)
212 {
213 struct strbuf buf = STRBUF_INIT;
214 - struct curl_slist *dav_headers = NULL;
214 + struct curl_slist *dav_headers = http_copy_default_headers();
215
216 if (options & DAV_HEADER_IF) {
217 strbuf_addf(&buf, "If: (<%s>)", lock->token);
@@ -417,7 +417,7 @@ static void start_put(struct transfer_request *request)
417 static void start_move(struct transfer_request *request)
418 {
419 struct active_request_slot *slot;
420 - struct curl_slist *dav_headers = NULL;
420 + struct curl_slist *dav_headers = http_copy_default_headers();
421
422 slot = get_active_slot();
423 slot->callback_func = process_response;
@@ -845,7 +845,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
845 char *ep;
846 char timeout_header[25];
847 struct remote_lock *lock = NULL;
848 - struct curl_slist *dav_headers = NULL;
848 + struct curl_slist *dav_headers = http_copy_default_headers();
849 struct xml_ctx ctx;
850 char *escaped;
851
@@ -1126,7 +1126,7 @@ static void remote_ls(const char *path, int flags,
1126 struct slot_results results;
1127 struct strbuf in_buffer = STRBUF_INIT;
1128 struct buffer out_buffer = { STRBUF_INIT, 0 };
1129 - struct curl_slist *dav_headers = NULL;
1129 + struct curl_slist *dav_headers = http_copy_default_headers();
1130 struct xml_ctx ctx;
1131 struct remote_ls_ctx ls;
1132
@@ -1204,7 +1204,7 @@ static int locking_available(void)
1204 struct slot_results results;
1205 struct strbuf in_buffer = STRBUF_INIT;
1206 struct buffer out_buffer = { STRBUF_INIT, 0 };
1207 - struct curl_slist *dav_headers = NULL;
1207 + struct curl_slist *dav_headers = http_copy_default_headers();
1208 struct xml_ctx ctx;
1209 int lock_flags = 0;
1210 char *escaped;
http.c
+32 -3
@@ -114,6 +114,7 @@ static unsigned long http_auth_methods = CURLAUTH_ANY;
114
115 static struct curl_slist *pragma_header;
116 static struct curl_slist *no_pragma_header;
117 +static struct curl_slist *extra_http_headers;
118
119 static struct active_request_slot *active_queue_head;
120
@@ -323,6 +324,19 @@ static int http_options(const char *var, const char *value, void *cb)
324 #endif
325 }
326
327 + if (!strcmp("http.extraheader", var)) {
328 + if (!value) {
329 + return config_error_nonbool(var);
330 + } else if (!*value) {
331 + curl_slist_free_all(extra_http_headers);
332 + extra_http_headers = NULL;
333 + } else {
334 + extra_http_headers =
335 + curl_slist_append(extra_http_headers, value);
336 + }
337 + return 0;
338 + }
339 +
340 /* Fall back on the default ones */
341 return git_default_config(var, value, cb);
342 }
@@ -675,8 +689,10 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
689 if (remote)
690 var_override(&http_proxy_authmethod, remote->http_proxy_authmethod);
691
678 - pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache");
679 - no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
692 + pragma_header = curl_slist_append(http_copy_default_headers(),
693 + "Pragma: no-cache");
694 + no_pragma_header = curl_slist_append(http_copy_default_headers(),
695 + "Pragma:");
696
697 #ifdef USE_CURL_MULTI
698 {
@@ -762,6 +778,9 @@ void http_cleanup(void)
778 #endif
779 curl_global_cleanup();
780
781 + curl_slist_free_all(extra_http_headers);
782 + extra_http_headers = NULL;
783 +
784 curl_slist_free_all(pragma_header);
785 pragma_header = NULL;
786
@@ -1160,6 +1179,16 @@ int run_one_slot(struct active_request_slot *slot,
1179 return handle_curl_result(results);
1180 }
1181
1182 +struct curl_slist *http_copy_default_headers(void)
1183 +{
1184 + struct curl_slist *headers = NULL, *h;
1185 +
1186 + for (h = extra_http_headers; h; h = h->next)
1187 + headers = curl_slist_append(headers, h->data);
1188 +
1189 + return headers;
1190 +}
1191 +
1192 static CURLcode curlinfo_strbuf(CURL *curl, CURLINFO info, struct strbuf *buf)
1193 {
1194 char *ptr;
@@ -1377,7 +1406,7 @@ static int http_request(const char *url,
1406 {
1407 struct active_request_slot *slot;
1408 struct slot_results results;
1380 - struct curl_slist *headers = NULL;
1409 + struct curl_slist *headers = http_copy_default_headers();
1410 struct strbuf buf = STRBUF_INIT;
1411 const char *accept_language;
1412 int ret;
http.h
+1
@@ -106,6 +106,7 @@ extern void step_active_slots(void);
106 extern void http_init(struct remote *remote, const char *url,
107 int proactive_auth);
108 extern void http_cleanup(void);
109 +extern struct curl_slist *http_copy_default_headers(void);
110
111 extern long int git_curl_ipresolve;
112 extern int active_requests;
remote-curl.c
+2 -2
@@ -474,7 +474,7 @@ static int run_slot(struct active_request_slot *slot,
474 static int probe_rpc(struct rpc_state *rpc, struct slot_results *results)
475 {
476 struct active_request_slot *slot;
477 - struct curl_slist *headers = NULL;
477 + struct curl_slist *headers = http_copy_default_headers();
478 struct strbuf buf = STRBUF_INIT;
479 int err;
480
@@ -503,7 +503,7 @@ static int probe_rpc(struct rpc_state *rpc, struct slot_results *results)
503 static int post_rpc(struct rpc_state *rpc)
504 {
505 struct active_request_slot *slot;
506 - struct curl_slist *headers = NULL;
506 + struct curl_slist *headers = http_copy_default_headers();
507 int use_gzip = rpc->gzip_request;
508 char *gzip_body = NULL;
509 size_t gzip_size = 0;
t/lib-httpd/apache.conf
+8
@@ -101,6 +101,14 @@ Alias /auth/dumb/ www/auth/dumb/
101 SetEnv GIT_HTTP_EXPORT_ALL
102 Header set Set-Cookie name=value
103 </LocationMatch>
104 +<LocationMatch /smart_headers/>
105 + <RequireAll>
106 + Require expr %{HTTP:x-magic-one} == 'abra'
107 + Require expr %{HTTP:x-magic-two} == 'cadabra'
108 + </RequireAll>
109 + SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
110 + SetEnv GIT_HTTP_EXPORT_ALL
111 +</LocationMatch>
112 ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1
113 ScriptAlias /broken_smart/ broken-smart-http.sh/
114 ScriptAlias /error/ error.sh/
t/t5551-http-fetch-smart.sh
+7
@@ -282,5 +282,12 @@ test_expect_success EXPENSIVE 'http can handle enormous ref negotiation' '
282 test_line_count = 100000 tags
283 '
284
285 +test_expect_success 'custom http headers' '
286 + test_must_fail git fetch "$HTTPD_URL/smart_headers/repo.git" &&
287 + git -c http.extraheader="x-magic-one: abra" \
288 + -c http.extraheader="x-magic-two: cadabra" \
289 + fetch "$HTTPD_URL/smart_headers/repo.git"
290 +'
291 +
292 stop_httpd
293 test_done