diff options
Diffstat (limited to 'http.c')
-rw-r--r-- | http.c | 1027 |
1 files changed, 912 insertions, 115 deletions
@@ -1,70 +1,104 @@ #include "http.h" +#include "pack.h" +#include "sideband.h" int data_received; -int active_requests = 0; +int active_requests; +int http_is_verbose; +size_t http_post_buffer = 16 * LARGE_PACKET_MAX; #ifdef USE_CURL_MULTI -int max_requests = -1; -CURLM *curlm; +static int max_requests = -1; +static CURLM *curlm; #endif #ifndef NO_CURL_EASY_DUPHANDLE -CURL *curl_default; +static CURL *curl_default; #endif + +#define PREV_BUF_SIZE 4096 +#define RANGE_HEADER_SIZE 30 + char curl_errorstr[CURL_ERROR_SIZE]; -int curl_ssl_verify = -1; -char *ssl_cert = NULL; -#if LIBCURL_VERSION_NUM >= 0x070902 -char *ssl_key = NULL; +static int curl_ssl_verify = -1; +static const char *ssl_cert; +#if LIBCURL_VERSION_NUM >= 0x070903 +static const char *ssl_key; #endif #if LIBCURL_VERSION_NUM >= 0x070908 -char *ssl_capath = NULL; +static const char *ssl_capath; +#endif +static const char *ssl_cainfo; +static long curl_low_speed_limit = -1; +static long curl_low_speed_time = -1; +static int curl_ftp_no_epsv; +static const char *curl_http_proxy; +static char *user_name, *user_pass; + +#if LIBCURL_VERSION_NUM >= 0x071700 +/* Use CURLOPT_KEYPASSWD as is */ +#elif LIBCURL_VERSION_NUM >= 0x070903 +#define CURLOPT_KEYPASSWD CURLOPT_SSLKEYPASSWD +#else +#define CURLOPT_KEYPASSWD CURLOPT_SSLCERTPASSWD #endif -char *ssl_cainfo = NULL; -long curl_low_speed_limit = -1; -long curl_low_speed_time = -1; -int curl_ftp_no_epsv = 0; -struct curl_slist *pragma_header; +static char *ssl_cert_password; +static int ssl_cert_password_required; -struct active_request_slot *active_queue_head = NULL; +static struct curl_slist *pragma_header; +static struct curl_slist *no_pragma_header; -size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, - struct buffer *buffer) +static struct active_request_slot *active_queue_head; + +size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *buffer_) { size_t size = eltsize * nmemb; - if (size > buffer->size - buffer->posn) - size = buffer->size - buffer->posn; - memcpy(ptr, (char *) buffer->buffer + buffer->posn, size); + struct buffer *buffer = buffer_; + + if (size > buffer->buf.len - buffer->posn) + size = buffer->buf.len - buffer->posn; + memcpy(ptr, buffer->buf.buf + buffer->posn, size); buffer->posn += size; + return size; } -size_t fwrite_buffer(const void *ptr, size_t eltsize, - size_t nmemb, struct buffer *buffer) +#ifndef NO_CURL_IOCTL +curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp) { - size_t size = eltsize * nmemb; - if (size > buffer->size - buffer->posn) { - buffer->size = buffer->size * 3 / 2; - if (buffer->size < buffer->posn + size) - buffer->size = buffer->posn + size; - buffer->buffer = xrealloc(buffer->buffer, buffer->size); + struct buffer *buffer = clientp; + + switch (cmd) { + case CURLIOCMD_NOP: + return CURLIOE_OK; + + case CURLIOCMD_RESTARTREAD: + buffer->posn = 0; + return CURLIOE_OK; + + default: + return CURLIOE_UNKNOWNCMD; } - memcpy((char *) buffer->buffer + buffer->posn, ptr, size); - buffer->posn += size; +} +#endif + +size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *buffer_) +{ + size_t size = eltsize * nmemb; + struct strbuf *buffer = buffer_; + + strbuf_add(buffer, ptr, size); data_received++; return size; } -size_t fwrite_null(const void *ptr, size_t eltsize, - size_t nmemb, struct buffer *buffer) +size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf) { data_received++; return eltsize * nmemb; } -static void finish_active_slot(struct active_request_slot *slot); - #ifdef USE_CURL_MULTI static void process_curl_messages(void) { @@ -95,64 +129,41 @@ static void process_curl_messages(void) } #endif -static int http_options(const char *var, const char *value) +static int http_options(const char *var, const char *value, void *cb) { if (!strcmp("http.sslverify", var)) { - if (curl_ssl_verify == -1) { - curl_ssl_verify = git_config_bool(var, value); - } - return 0; - } - - if (!strcmp("http.sslcert", var)) { - if (ssl_cert == NULL) { - ssl_cert = xmalloc(strlen(value)+1); - strcpy(ssl_cert, value); - } - return 0; - } -#if LIBCURL_VERSION_NUM >= 0x070902 - if (!strcmp("http.sslkey", var)) { - if (ssl_key == NULL) { - ssl_key = xmalloc(strlen(value)+1); - strcpy(ssl_key, value); - } + curl_ssl_verify = git_config_bool(var, value); return 0; } + if (!strcmp("http.sslcert", var)) + return git_config_string(&ssl_cert, var, value); +#if LIBCURL_VERSION_NUM >= 0x070903 + if (!strcmp("http.sslkey", var)) + return git_config_string(&ssl_key, var, value); #endif #if LIBCURL_VERSION_NUM >= 0x070908 - if (!strcmp("http.sslcapath", var)) { - if (ssl_capath == NULL) { - ssl_capath = xmalloc(strlen(value)+1); - strcpy(ssl_capath, value); - } - return 0; - } + if (!strcmp("http.sslcapath", var)) + return git_config_string(&ssl_capath, var, value); #endif - if (!strcmp("http.sslcainfo", var)) { - if (ssl_cainfo == NULL) { - ssl_cainfo = xmalloc(strlen(value)+1); - strcpy(ssl_cainfo, value); - } + if (!strcmp("http.sslcainfo", var)) + return git_config_string(&ssl_cainfo, var, value); + if (!strcmp("http.sslcertpasswordprotected", var)) { + if (git_config_bool(var, value)) + ssl_cert_password_required = 1; return 0; } - #ifdef USE_CURL_MULTI if (!strcmp("http.maxrequests", var)) { - if (max_requests == -1) - max_requests = git_config_int(var, value); + max_requests = git_config_int(var, value); return 0; } #endif - if (!strcmp("http.lowspeedlimit", var)) { - if (curl_low_speed_limit == -1) - curl_low_speed_limit = (long)git_config_int(var, value); + curl_low_speed_limit = (long)git_config_int(var, value); return 0; } if (!strcmp("http.lowspeedtime", var)) { - if (curl_low_speed_time == -1) - curl_low_speed_time = (long)git_config_int(var, value); + curl_low_speed_time = (long)git_config_int(var, value); return 0; } @@ -160,23 +171,73 @@ static int http_options(const char *var, const char *value) curl_ftp_no_epsv = git_config_bool(var, value); return 0; } + if (!strcmp("http.proxy", var)) + return git_config_string(&curl_http_proxy, var, value); + + if (!strcmp("http.postbuffer", var)) { + http_post_buffer = git_config_int(var, value); + if (http_post_buffer < LARGE_PACKET_MAX) + http_post_buffer = LARGE_PACKET_MAX; + return 0; + } /* Fall back on the default ones */ - return git_default_config(var, value); + return git_default_config(var, value, cb); +} + +static void init_curl_http_auth(CURL *result) +{ + if (user_name) { + struct strbuf up = STRBUF_INIT; + if (!user_pass) + user_pass = xstrdup(getpass("Password: ")); + strbuf_addf(&up, "%s:%s", user_name, user_pass); + curl_easy_setopt(result, CURLOPT_USERPWD, + strbuf_detach(&up, NULL)); + } +} + +static int has_cert_password(void) +{ + if (ssl_cert_password != NULL) + return 1; + if (ssl_cert == NULL || ssl_cert_password_required != 1) + return 0; + /* Only prompt the user once. */ + ssl_cert_password_required = -1; + ssl_cert_password = getpass("Certificate Password: "); + if (ssl_cert_password != NULL) { + ssl_cert_password = xstrdup(ssl_cert_password); + return 1; + } else + return 0; } -static CURL* get_curl_handle(void) +static CURL *get_curl_handle(void) { - CURL* result = curl_easy_init(); + CURL *result = curl_easy_init(); + + if (!curl_ssl_verify) { + curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 0); + } else { + /* Verify authenticity of the peer's certificate */ + curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 1); + /* The name in the cert must match whom we tried to connect */ + curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2); + } - curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify); #if LIBCURL_VERSION_NUM >= 0x070907 curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL); #endif + init_curl_http_auth(result); + if (ssl_cert != NULL) curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert); -#if LIBCURL_VERSION_NUM >= 0x070902 + if (has_cert_password()) + curl_easy_setopt(result, CURLOPT_KEYPASSWD, ssl_cert_password); +#if LIBCURL_VERSION_NUM >= 0x070903 if (ssl_key != NULL) curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key); #endif @@ -205,17 +266,75 @@ static CURL* get_curl_handle(void) if (curl_ftp_no_epsv) curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0); + if (curl_http_proxy) + curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy); + return result; } -void http_init(void) +static void http_auth_init(const char *url) +{ + char *at, *colon, *cp, *slash; + int len; + + cp = strstr(url, "://"); + if (!cp) + return; + + /* + * Ok, the URL looks like "proto://something". Which one? + * "proto://<user>:<pass>@<host>/...", + * "proto://<user>@<host>/...", or just + * "proto://<host>/..."? + */ + cp += 3; + at = strchr(cp, '@'); + colon = strchr(cp, ':'); + slash = strchrnul(cp, '/'); + if (!at || slash <= at) + return; /* No credentials */ + if (!colon || at <= colon) { + /* Only username */ + len = at - cp; + user_name = xmalloc(len + 1); + memcpy(user_name, cp, len); + user_name[len] = '\0'; + user_pass = NULL; + } else { + len = colon - cp; + user_name = xmalloc(len + 1); + memcpy(user_name, cp, len); + user_name[len] = '\0'; + len = at - (colon + 1); + user_pass = xmalloc(len + 1); + memcpy(user_pass, colon + 1, len); + user_pass[len] = '\0'; + } +} + +static void set_from_env(const char **var, const char *envname) +{ + const char *val = getenv(envname); + if (val) + *var = val; +} + +void http_init(struct remote *remote) { char *low_speed_limit; char *low_speed_time; + http_is_verbose = 0; + + git_config(http_options, NULL); + curl_global_init(CURL_GLOBAL_ALL); + if (remote && remote->http_proxy) + curl_http_proxy = xstrdup(remote->http_proxy); + pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache"); + no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:"); #ifdef USE_CURL_MULTI { @@ -234,14 +353,14 @@ void http_init(void) if (getenv("GIT_SSL_NO_VERIFY")) curl_ssl_verify = 0; - ssl_cert = getenv("GIT_SSL_CERT"); -#if LIBCURL_VERSION_NUM >= 0x070902 - ssl_key = getenv("GIT_SSL_KEY"); + set_from_env(&ssl_cert, "GIT_SSL_CERT"); +#if LIBCURL_VERSION_NUM >= 0x070903 + set_from_env(&ssl_key, "GIT_SSL_KEY"); #endif #if LIBCURL_VERSION_NUM >= 0x070908 - ssl_capath = getenv("GIT_SSL_CAPATH"); + set_from_env(&ssl_capath, "GIT_SSL_CAPATH"); #endif - ssl_cainfo = getenv("GIT_SSL_CAINFO"); + set_from_env(&ssl_cainfo, "GIT_SSL_CAINFO"); low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT"); if (low_speed_limit != NULL) @@ -250,8 +369,6 @@ void http_init(void) if (low_speed_time != NULL) curl_low_speed_time = strtol(low_speed_time, NULL, 10); - git_config(http_options); - if (curl_ssl_verify == -1) curl_ssl_verify = 1; @@ -263,6 +380,14 @@ void http_init(void) if (getenv("GIT_CURL_FTP_NO_EPSV")) curl_ftp_no_epsv = 1; + if (remote && remote->url && remote->url[0]) { + http_auth_init(remote->url[0]); + if (!ssl_cert_password_required && + getenv("GIT_SSL_CERT_PASSWORD_PROTECTED") && + !prefixcmp(remote->url[0], "https://")) + ssl_cert_password_required = 1; + } + #ifndef NO_CURL_EASY_DUPHANDLE curl_default = get_curl_handle(); #endif @@ -271,24 +396,19 @@ void http_init(void) void http_cleanup(void) { struct active_request_slot *slot = active_queue_head; -#ifdef USE_CURL_MULTI - char *wait_url; -#endif while (slot != NULL) { + struct active_request_slot *next = slot->next; + if (slot->curl != NULL) { #ifdef USE_CURL_MULTI - if (slot->in_use) { - curl_easy_getinfo(slot->curl, - CURLINFO_EFFECTIVE_URL, - &wait_url); - fprintf(stderr, "Waiting for %s\n", wait_url); - run_active_slot(slot); - } + curl_multi_remove_handle(curlm, slot->curl); #endif - if (slot->curl != NULL) curl_easy_cleanup(slot->curl); - slot = slot->next; + } + free(slot); + slot = next; } + active_queue_head = NULL; #ifndef NO_CURL_EASY_DUPHANDLE curl_easy_cleanup(curl_default); @@ -300,7 +420,22 @@ void http_cleanup(void) curl_global_cleanup(); curl_slist_free_all(pragma_header); - pragma_header = NULL; + pragma_header = NULL; + + curl_slist_free_all(no_pragma_header); + no_pragma_header = NULL; + + if (curl_http_proxy) { + free((void *)curl_http_proxy); + curl_http_proxy = NULL; + } + + if (ssl_cert_password != NULL) { + memset(ssl_cert_password, 0, strlen(ssl_cert_password)); + free(ssl_cert_password); + ssl_cert_password = NULL; + } + ssl_cert_password_required = 0; } struct active_request_slot *get_active_slot(void) @@ -314,15 +449,14 @@ struct active_request_slot *get_active_slot(void) /* Wait for a slot to open up if the queue is full */ while (active_requests >= max_requests) { curl_multi_perform(curlm, &num_transfers); - if (num_transfers < active_requests) { + if (num_transfers < active_requests) process_curl_messages(); - } } #endif - while (slot != NULL && slot->in_use) { + while (slot != NULL && slot->in_use) slot = slot->next; - } + if (slot == NULL) { newslot = xmalloc(sizeof(*newslot)); newslot->curl = NULL; @@ -333,9 +467,8 @@ struct active_request_slot *get_active_slot(void) if (slot == NULL) { active_queue_head = newslot; } else { - while (slot->next != NULL) { + while (slot->next != NULL) slot = slot->next; - } slot->next = newslot; } slot = newslot; @@ -356,7 +489,6 @@ struct active_request_slot *get_active_slot(void) slot->finished = NULL; slot->callback_data = NULL; slot->callback_func = NULL; - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header); curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr); curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL); @@ -372,6 +504,7 @@ int start_active_slot(struct active_request_slot *slot) { #ifdef USE_CURL_MULTI CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl); + int num_transfers; if (curlm_result != CURLM_OK && curlm_result != CURLM_CALL_MULTI_PERFORM) { @@ -379,11 +512,60 @@ int start_active_slot(struct active_request_slot *slot) slot->in_use = 0; return 0; } + + /* + * We know there must be something to do, since we just added + * something. + */ + curl_multi_perform(curlm, &num_transfers); #endif return 1; } #ifdef USE_CURL_MULTI +struct fill_chain { + void *data; + int (*fill)(void *); + struct fill_chain *next; +}; + +static struct fill_chain *fill_cfg; + +void add_fill_function(void *data, int (*fill)(void *)) +{ + struct fill_chain *new = xmalloc(sizeof(*new)); + struct fill_chain **linkp = &fill_cfg; + new->data = data; + new->fill = fill; + new->next = NULL; + while (*linkp) + linkp = &(*linkp)->next; + *linkp = new; +} + +void fill_active_slots(void) +{ + struct active_request_slot *slot = active_queue_head; + + while (active_requests < max_requests) { + struct fill_chain *fill; + for (fill = fill_cfg; fill; fill = fill->next) + if (fill->fill(fill->data)) + break; + + if (!fill) + break; + } + + while (slot != NULL) { + if (!slot->in_use && slot->curl != NULL) { + curl_easy_cleanup(slot->curl); + slot->curl = NULL; + } + slot = slot->next; + } +} + void step_active_slots(void) { int num_transfers; @@ -444,8 +626,8 @@ void run_active_slot(struct active_request_slot *slot) static void closedown_active_slot(struct active_request_slot *slot) { - active_requests--; - slot->in_use = 0; + active_requests--; + slot->in_use = 0; } void release_active_slot(struct active_request_slot *slot) @@ -463,10 +645,10 @@ void release_active_slot(struct active_request_slot *slot) #endif } -static void finish_active_slot(struct active_request_slot *slot) +void finish_active_slot(struct active_request_slot *slot) { closedown_active_slot(slot); - curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code); + curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code); if (slot->finished != NULL) (*slot->finished) = 1; @@ -477,10 +659,9 @@ static void finish_active_slot(struct active_request_slot *slot) slot->results->http_code = slot->http_code; } - /* Run callback if appropriate */ - if (slot->callback_func != NULL) { - slot->callback_func(slot->callback_data); - } + /* Run callback if appropriate */ + if (slot->callback_func != NULL) + slot->callback_func(slot->callback_data); } void finish_all_active_slots(void) @@ -495,3 +676,619 @@ void finish_all_active_slots(void) slot = slot->next; } } + +/* Helpers for modifying and creating URLs */ +static inline int needs_quote(int ch) +{ + if (((ch >= 'A') && (ch <= 'Z')) + || ((ch >= 'a') && (ch <= 'z')) + || ((ch >= '0') && (ch <= '9')) + || (ch == '/') + || (ch == '-') + || (ch == '.')) + return 0; + return 1; +} + +static inline int hex(int v) +{ + if (v < 10) + return '0' + v; + else + return 'A' + v - 10; +} + +static void end_url_with_slash(struct strbuf *buf, const char *url) +{ + strbuf_addstr(buf, url); + if (buf->len && buf->buf[buf->len - 1] != '/') + strbuf_addstr(buf, "/"); +} + +static char *quote_ref_url(const char *base, const char *ref) +{ + struct strbuf buf = STRBUF_INIT; + const char *cp; + int ch; + + end_url_with_slash(&buf, base); + + for (cp = ref; (ch = *cp) != 0; cp++) + if (needs_quote(ch)) + strbuf_addf(&buf, "%%%02x", ch); + else + strbuf_addch(&buf, *cp); + + return strbuf_detach(&buf, NULL); +} + +void append_remote_object_url(struct strbuf *buf, const char *url, + const char *hex, + int only_two_digit_prefix) +{ + end_url_with_slash(buf, url); + + strbuf_addf(buf, "objects/%.*s/", 2, hex); + if (!only_two_digit_prefix) + strbuf_addf(buf, "%s", hex+2); +} + +char *get_remote_object_url(const char *url, const char *hex, + int only_two_digit_prefix) +{ + struct strbuf buf = STRBUF_INIT; + append_remote_object_url(&buf, url, hex, only_two_digit_prefix); + return strbuf_detach(&buf, NULL); +} + +/* http_request() targets */ +#define HTTP_REQUEST_STRBUF 0 +#define HTTP_REQUEST_FILE 1 + +static int http_request(const char *url, void *result, int target, int options) +{ + struct active_request_slot *slot; + struct slot_results results; + struct curl_slist *headers = NULL; + struct strbuf buf = STRBUF_INIT; + int ret; + + slot = get_active_slot(); + slot->results = &results; + curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); + + if (result == NULL) { + curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1); + } else { + curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); + curl_easy_setopt(slot->curl, CURLOPT_FILE, result); + + if (target == HTTP_REQUEST_FILE) { + long posn = ftell(result); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, + fwrite); + if (posn > 0) { + strbuf_addf(&buf, "Range: bytes=%ld-", posn); + headers = curl_slist_append(headers, buf.buf); + strbuf_reset(&buf); + } + slot->local = result; + } else + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, + fwrite_buffer); + } + + strbuf_addstr(&buf, "Pragma:"); + if (options & HTTP_NO_CACHE) + strbuf_addstr(&buf, " no-cache"); + + headers = curl_slist_append(headers, buf.buf); + + curl_easy_setopt(slot->curl, CURLOPT_URL, url); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers); + + if (start_active_slot(slot)) { + run_active_slot(slot); + if (results.curl_result == CURLE_OK) + ret = HTTP_OK; + else if (missing_target(&results)) + ret = HTTP_MISSING_TARGET; + else + ret = HTTP_ERROR; + } else { + error("Unable to start HTTP request for %s", url); + ret = HTTP_START_FAILED; + } + + slot->local = NULL; + curl_slist_free_all(headers); + strbuf_release(&buf); + + return ret; +} + +int http_get_strbuf(const char *url, struct strbuf *result, int options) +{ + return http_request(url, result, HTTP_REQUEST_STRBUF, options); +} + +int http_get_file(const char *url, const char *filename, int options) +{ + int ret; + struct strbuf tmpfile = STRBUF_INIT; + FILE *result; + + strbuf_addf(&tmpfile, "%s.temp", filename); + result = fopen(tmpfile.buf, "a"); + if (! result) { + error("Unable to open local file %s", tmpfile.buf); + ret = HTTP_ERROR; + goto cleanup; + } + + ret = http_request(url, result, HTTP_REQUEST_FILE, options); + fclose(result); + + if ((ret == HTTP_OK) && move_temp_to_file(tmpfile.buf, filename)) + ret = HTTP_ERROR; +cleanup: + strbuf_release(&tmpfile); + return ret; +} + +int http_error(const char *url, int ret) +{ + /* http_request has already handled HTTP_START_FAILED. */ + if (ret != HTTP_START_FAILED) + error("%s while accessing %s\n", curl_errorstr, url); + + return ret; +} + +int http_fetch_ref(const char *base, struct ref *ref) +{ + char *url; + struct strbuf buffer = STRBUF_INIT; + int ret = -1; + + url = quote_ref_url(base, ref->name); + if (http_get_strbuf(url, &buffer, HTTP_NO_CACHE) == HTTP_OK) { + strbuf_rtrim(&buffer); + if (buffer.len == 40) + ret = get_sha1_hex(buffer.buf, ref->old_sha1); + else if (!prefixcmp(buffer.buf, "ref: ")) { + ref->symref = xstrdup(buffer.buf + 5); + ret = 0; + } + } + + strbuf_release(&buffer); + free(url); + return ret; +} + +/* Helpers for fetching packs */ +static int fetch_pack_index(unsigned char *sha1, const char *base_url) +{ + int ret = 0; + char *hex = xstrdup(sha1_to_hex(sha1)); + char *filename; + char *url = NULL; + struct strbuf buf = STRBUF_INIT; + + if (has_pack_index(sha1)) { + ret = 0; + goto cleanup; + } + + if (http_is_verbose) + fprintf(stderr, "Getting index for pack %s\n", hex); + + end_url_with_slash(&buf, base_url); + strbuf_addf(&buf, "objects/pack/pack-%s.idx", hex); + url = strbuf_detach(&buf, NULL); + + filename = sha1_pack_index_name(sha1); + if (http_get_file(url, filename, 0) != HTTP_OK) + ret = error("Unable to get pack index %s\n", url); + +cleanup: + free(hex); + free(url); + return ret; +} + +static int fetch_and_setup_pack_index(struct packed_git **packs_head, + unsigned char *sha1, const char *base_url) +{ + struct packed_git *new_pack; + + if (fetch_pack_index(sha1, base_url)) + return -1; + + new_pack = parse_pack_index(sha1); + if (!new_pack) + return -1; /* parse_pack_index() already issued error message */ + new_pack->next = *packs_head; + *packs_head = new_pack; + return 0; +} + +int http_get_info_packs(const char *base_url, struct packed_git **packs_head) +{ + int ret = 0, i = 0; + char *url, *data; + struct strbuf buf = STRBUF_INIT; + unsigned char sha1[20]; + + end_url_with_slash(&buf, base_url); + strbuf_addstr(&buf, "objects/info/packs"); + url = strbuf_detach(&buf, NULL); + + ret = http_get_strbuf(url, &buf, HTTP_NO_CACHE); + if (ret != HTTP_OK) + goto cleanup; + + data = buf.buf; + while (i < buf.len) { + switch (data[i]) { + case 'P': + i++; + if (i + 52 <= buf.len && + !prefixcmp(data + i, " pack-") && + !prefixcmp(data + i + 46, ".pack\n")) { + get_sha1_hex(data + i + 6, sha1); + fetch_and_setup_pack_index(packs_head, sha1, + base_url); + i += 51; + break; + } + default: + while (i < buf.len && data[i] != '\n') + i++; + } + i++; + } + +cleanup: + free(url); + return ret; +} + +void release_http_pack_request(struct http_pack_request *preq) +{ + if (preq->packfile != NULL) { + fclose(preq->packfile); + preq->packfile = NULL; + preq->slot->local = NULL; + } + if (preq->range_header != NULL) { + curl_slist_free_all(preq->range_header); + preq->range_header = NULL; + } + preq->slot = NULL; + free(preq->url); +} + +int finish_http_pack_request(struct http_pack_request *preq) +{ + int ret; + struct packed_git **lst; + + preq->target->pack_size = ftell(preq->packfile); + + if (preq->packfile != NULL) { + fclose(preq->packfile); + preq->packfile = NULL; + preq->slot->local = NULL; + } + + ret = move_temp_to_file(preq->tmpfile, preq->filename); + if (ret) + return ret; + + lst = preq->lst; + while (*lst != preq->target) + lst = &((*lst)->next); + *lst = (*lst)->next; + + if (verify_pack(preq->target)) + return -1; + install_packed_git(preq->target); + + return 0; +} + +struct http_pack_request *new_http_pack_request( + struct packed_git *target, const char *base_url) +{ + char *filename; + long prev_posn = 0; + char range[RANGE_HEADER_SIZE]; + struct strbuf buf = STRBUF_INIT; + struct http_pack_request *preq; + + preq = xmalloc(sizeof(*preq)); + preq->target = target; + preq->range_header = NULL; + + end_url_with_slash(&buf, base_url); + strbuf_addf(&buf, "objects/pack/pack-%s.pack", + sha1_to_hex(target->sha1)); + preq->url = strbuf_detach(&buf, NULL); + + filename = sha1_pack_name(target->sha1); + snprintf(preq->filename, sizeof(preq->filename), "%s", filename); + snprintf(preq->tmpfile, sizeof(preq->tmpfile), "%s.temp", filename); + preq->packfile = fopen(preq->tmpfile, "a"); + if (!preq->packfile) { + error("Unable to open local file %s for pack", + preq->tmpfile); + goto abort; + } + + preq->slot = get_active_slot(); + preq->slot->local = preq->packfile; + curl_easy_setopt(preq->slot->curl, CURLOPT_FILE, preq->packfile); + curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite); + curl_easy_setopt(preq->slot->curl, CURLOPT_URL, preq->url); + curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER, + no_pragma_header); + + /* + * If there is data present from a previous transfer attempt, + * resume where it left off + */ + prev_posn = ftell(preq->packfile); + if (prev_posn>0) { + if (http_is_verbose) + fprintf(stderr, + "Resuming fetch of pack %s at byte %ld\n", + sha1_to_hex(target->sha1), prev_posn); + sprintf(range, "Range: bytes=%ld-", prev_posn); + preq->range_header = curl_slist_append(NULL, range); + curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER, + preq->range_header); + } + + return preq; + +abort: + free(filename); + free(preq->url); + free(preq); + return NULL; +} + +/* Helpers for fetching objects (loose) */ +static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, + void *data) +{ + unsigned char expn[4096]; + size_t size = eltsize * nmemb; + int posn = 0; + struct http_object_request *freq = + (struct http_object_request *)data; + do { + ssize_t retval = xwrite(freq->localfile, + (char *) ptr + posn, size - posn); + if (retval < 0) + return posn; + posn += retval; + } while (posn < size); + + freq->stream.avail_in = size; + freq->stream.next_in = ptr; + do { + freq->stream.next_out = expn; + freq->stream.avail_out = sizeof(expn); + freq->zret = git_inflate(&freq->stream, Z_SYNC_FLUSH); + git_SHA1_Update(&freq->c, expn, + sizeof(expn) - freq->stream.avail_out); + } while (freq->stream.avail_in && freq->zret == Z_OK); + data_received++; + return size; +} + +struct http_object_request *new_http_object_request(const char *base_url, + unsigned char *sha1) +{ + char *hex = sha1_to_hex(sha1); + char *filename; + char prevfile[PATH_MAX]; + int prevlocal; + unsigned char prev_buf[PREV_BUF_SIZE]; + ssize_t prev_read = 0; + long prev_posn = 0; + char range[RANGE_HEADER_SIZE]; + struct curl_slist *range_header = NULL; + struct http_object_request *freq; + + freq = xmalloc(sizeof(*freq)); + hashcpy(freq->sha1, sha1); + freq->localfile = -1; + + filename = sha1_file_name(sha1); + snprintf(freq->filename, sizeof(freq->filename), "%s", filename); + snprintf(freq->tmpfile, sizeof(freq->tmpfile), + "%s.temp", filename); + + snprintf(prevfile, sizeof(prevfile), "%s.prev", filename); + unlink_or_warn(prevfile); + rename(freq->tmpfile, prevfile); + unlink_or_warn(freq->tmpfile); + + if (freq->localfile != -1) + error("fd leakage in start: %d", freq->localfile); + freq->localfile = open(freq->tmpfile, + O_WRONLY | O_CREAT | O_EXCL, 0666); + /* + * This could have failed due to the "lazy directory creation"; + * try to mkdir the last path component. + */ + if (freq->localfile < 0 && errno == ENOENT) { + char *dir = strrchr(freq->tmpfile, '/'); + if (dir) { + *dir = 0; + mkdir(freq->tmpfile, 0777); + *dir = '/'; + } + freq->localfile = open(freq->tmpfile, + O_WRONLY | O_CREAT | O_EXCL, 0666); + } + + if (freq->localfile < 0) { + error("Couldn't create temporary file %s for %s: %s", + freq->tmpfile, freq->filename, strerror(errno)); + goto abort; + } + + memset(&freq->stream, 0, sizeof(freq->stream)); + + git_inflate_init(&freq->stream); + + git_SHA1_Init(&freq->c); + + freq->url = get_remote_object_url(base_url, hex, 0); + + /* + * If a previous temp file is present, process what was already + * fetched. + */ + prevlocal = open(prevfile, O_RDONLY); + if (prevlocal != -1) { + do { + prev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE); + if (prev_read>0) { + if (fwrite_sha1_file(prev_buf, + 1, + prev_read, + freq) == prev_read) { + prev_posn += prev_read; + } else { + prev_read = -1; + } + } + } while (prev_read > 0); + close(prevlocal); + } + unlink_or_warn(prevfile); + + /* + * Reset inflate/SHA1 if there was an error reading the previous temp + * file; also rewind to the beginning of the local file. + */ + if (prev_read == -1) { + memset(&freq->stream, 0, sizeof(freq->stream)); + git_inflate_init(&freq->stream); + git_SHA1_Init(&freq->c); + if (prev_posn>0) { + prev_posn = 0; + lseek(freq->localfile, 0, SEEK_SET); + if (ftruncate(freq->localfile, 0) < 0) { + error("Couldn't truncate temporary file %s for %s: %s", + freq->tmpfile, freq->filename, strerror(errno)); + goto abort; + } + } + } + + freq->slot = get_active_slot(); + + curl_easy_setopt(freq->slot->curl, CURLOPT_FILE, freq); + curl_easy_setopt(freq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file); + curl_easy_setopt(freq->slot->curl, CURLOPT_ERRORBUFFER, freq->errorstr); + curl_easy_setopt(freq->slot->curl, CURLOPT_URL, freq->url); + curl_easy_setopt(freq->slot->curl, CURLOPT_HTTPHEADER, no_pragma_header); + + /* + * If we have successfully processed data from a previous fetch + * attempt, only fetch the data we don't already have. + */ + if (prev_posn>0) { + if (http_is_verbose) + fprintf(stderr, + "Resuming fetch of object %s at byte %ld\n", + hex, prev_posn); + sprintf(range, "Range: bytes=%ld-", prev_posn); + range_header = curl_slist_append(range_header, range); + curl_easy_setopt(freq->slot->curl, + CURLOPT_HTTPHEADER, range_header); + } + + return freq; + +abort: + free(filename); + free(freq->url); + free(freq); + return NULL; +} + +void process_http_object_request(struct http_object_request *freq) +{ + if (freq->slot == NULL) + return; + freq->curl_result = freq->slot->curl_result; + freq->http_code = freq->slot->http_code; + freq->slot = NULL; +} + +int finish_http_object_request(struct http_object_request *freq) +{ + struct stat st; + + close(freq->localfile); + freq->localfile = -1; + + process_http_object_request(freq); + + if (freq->http_code == 416) { + fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n"); + } else if (freq->curl_result != CURLE_OK) { + if (stat(freq->tmpfile, &st) == 0) + if (st.st_size == 0) + unlink_or_warn(freq->tmpfile); + return -1; + } + + git_inflate_end(&freq->stream); + git_SHA1_Final(freq->real_sha1, &freq->c); + if (freq->zret != Z_STREAM_END) { + unlink_or_warn(freq->tmpfile); + return -1; + } + if (hashcmp(freq->sha1, freq->real_sha1)) { + unlink_or_warn(freq->tmpfile); + return -1; + } + freq->rename = + move_temp_to_file(freq->tmpfile, freq->filename); + + return freq->rename; +} + +void abort_http_object_request(struct http_object_request *freq) +{ + unlink_or_warn(freq->tmpfile); + + release_http_object_request(freq); +} + +void release_http_object_request(struct http_object_request *freq) +{ + if (freq->localfile != -1) { + close(freq->localfile); + freq->localfile = -1; + } + if (freq->url != NULL) { + free(freq->url); + freq->url = NULL; + } + if (freq->slot != NULL) { + freq->slot->callback_func = NULL; + freq->slot->callback_data = NULL; + release_active_slot(freq->slot); + freq->slot = NULL; + } +} |