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>
This commit is contained in:
Johannes Schindelin 2016-04-27 14:20:37 +02:00 коммит произвёл Junio C Hamano
Родитель 90f7b16b3a
Коммит 8cb01e2fd3
7 изменённых файлов: 61 добавлений и 10 удалений

Просмотреть файл

@ -1654,6 +1654,12 @@ http.emptyAuth::
a username in the URL, as libcurl normally requires a username for
authentication.
http.extraHeader::
Pass an additional HTTP header when communicating with a server. If
more than one such entry exists, all of them are added as extra
headers. To allow overriding the settings inherited from the system
config, an empty value will reset the extra headers to the empty list.
http.cookieFile::
File containing previously stored cookie lines which should be used
in the Git http session, if they match the server. The file format

Просмотреть файл

@ -211,7 +211,7 @@ static void curl_setup_http(CURL *curl, const char *url,
static struct curl_slist *get_dav_token_headers(struct remote_lock *lock, enum dav_header_flag options)
{
struct strbuf buf = STRBUF_INIT;
struct curl_slist *dav_headers = NULL;
struct curl_slist *dav_headers = http_copy_default_headers();
if (options & DAV_HEADER_IF) {
strbuf_addf(&buf, "If: (<%s>)", lock->token);
@ -417,7 +417,7 @@ static void start_put(struct transfer_request *request)
static void start_move(struct transfer_request *request)
{
struct active_request_slot *slot;
struct curl_slist *dav_headers = NULL;
struct curl_slist *dav_headers = http_copy_default_headers();
slot = get_active_slot();
slot->callback_func = process_response;
@ -845,7 +845,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
char *ep;
char timeout_header[25];
struct remote_lock *lock = NULL;
struct curl_slist *dav_headers = NULL;
struct curl_slist *dav_headers = http_copy_default_headers();
struct xml_ctx ctx;
char *escaped;
@ -1126,7 +1126,7 @@ static void remote_ls(const char *path, int flags,
struct slot_results results;
struct strbuf in_buffer = STRBUF_INIT;
struct buffer out_buffer = { STRBUF_INIT, 0 };
struct curl_slist *dav_headers = NULL;
struct curl_slist *dav_headers = http_copy_default_headers();
struct xml_ctx ctx;
struct remote_ls_ctx ls;
@ -1204,7 +1204,7 @@ static int locking_available(void)
struct slot_results results;
struct strbuf in_buffer = STRBUF_INIT;
struct buffer out_buffer = { STRBUF_INIT, 0 };
struct curl_slist *dav_headers = NULL;
struct curl_slist *dav_headers = http_copy_default_headers();
struct xml_ctx ctx;
int lock_flags = 0;
char *escaped;

35
http.c
Просмотреть файл

@ -114,6 +114,7 @@ static unsigned long http_auth_methods = CURLAUTH_ANY;
static struct curl_slist *pragma_header;
static struct curl_slist *no_pragma_header;
static struct curl_slist *extra_http_headers;
static struct active_request_slot *active_queue_head;
@ -323,6 +324,19 @@ static int http_options(const char *var, const char *value, void *cb)
#endif
}
if (!strcmp("http.extraheader", var)) {
if (!value) {
return config_error_nonbool(var);
} else if (!*value) {
curl_slist_free_all(extra_http_headers);
extra_http_headers = NULL;
} else {
extra_http_headers =
curl_slist_append(extra_http_headers, value);
}
return 0;
}
/* Fall back on the default ones */
return git_default_config(var, value, cb);
}
@ -675,8 +689,10 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
if (remote)
var_override(&http_proxy_authmethod, remote->http_proxy_authmethod);
pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache");
no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
pragma_header = curl_slist_append(http_copy_default_headers(),
"Pragma: no-cache");
no_pragma_header = curl_slist_append(http_copy_default_headers(),
"Pragma:");
#ifdef USE_CURL_MULTI
{
@ -762,6 +778,9 @@ void http_cleanup(void)
#endif
curl_global_cleanup();
curl_slist_free_all(extra_http_headers);
extra_http_headers = NULL;
curl_slist_free_all(pragma_header);
pragma_header = NULL;
@ -1160,6 +1179,16 @@ int run_one_slot(struct active_request_slot *slot,
return handle_curl_result(results);
}
struct curl_slist *http_copy_default_headers(void)
{
struct curl_slist *headers = NULL, *h;
for (h = extra_http_headers; h; h = h->next)
headers = curl_slist_append(headers, h->data);
return headers;
}
static CURLcode curlinfo_strbuf(CURL *curl, CURLINFO info, struct strbuf *buf)
{
char *ptr;
@ -1377,7 +1406,7 @@ static int http_request(const char *url,
{
struct active_request_slot *slot;
struct slot_results results;
struct curl_slist *headers = NULL;
struct curl_slist *headers = http_copy_default_headers();
struct strbuf buf = STRBUF_INIT;
const char *accept_language;
int ret;

1
http.h
Просмотреть файл

@ -106,6 +106,7 @@ extern void step_active_slots(void);
extern void http_init(struct remote *remote, const char *url,
int proactive_auth);
extern void http_cleanup(void);
extern struct curl_slist *http_copy_default_headers(void);
extern long int git_curl_ipresolve;
extern int active_requests;

Просмотреть файл

@ -474,7 +474,7 @@ static int run_slot(struct active_request_slot *slot,
static int probe_rpc(struct rpc_state *rpc, struct slot_results *results)
{
struct active_request_slot *slot;
struct curl_slist *headers = NULL;
struct curl_slist *headers = http_copy_default_headers();
struct strbuf buf = STRBUF_INIT;
int err;
@ -503,7 +503,7 @@ static int probe_rpc(struct rpc_state *rpc, struct slot_results *results)
static int post_rpc(struct rpc_state *rpc)
{
struct active_request_slot *slot;
struct curl_slist *headers = NULL;
struct curl_slist *headers = http_copy_default_headers();
int use_gzip = rpc->gzip_request;
char *gzip_body = NULL;
size_t gzip_size = 0;

Просмотреть файл

@ -101,6 +101,14 @@ Alias /auth/dumb/ www/auth/dumb/
SetEnv GIT_HTTP_EXPORT_ALL
Header set Set-Cookie name=value
</LocationMatch>
<LocationMatch /smart_headers/>
<RequireAll>
Require expr %{HTTP:x-magic-one} == 'abra'
Require expr %{HTTP:x-magic-two} == 'cadabra'
</RequireAll>
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
SetEnv GIT_HTTP_EXPORT_ALL
</LocationMatch>
ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1
ScriptAlias /broken_smart/ broken-smart-http.sh/
ScriptAlias /error/ error.sh/

Просмотреть файл

@ -282,5 +282,12 @@ test_expect_success EXPENSIVE 'http can handle enormous ref negotiation' '
test_line_count = 100000 tags
'
test_expect_success 'custom http headers' '
test_must_fail git fetch "$HTTPD_URL/smart_headers/repo.git" &&
git -c http.extraheader="x-magic-one: abra" \
-c http.extraheader="x-magic-two: cadabra" \
fetch "$HTTPD_URL/smart_headers/repo.git"
'
stop_httpd
test_done