Sync with 1.6.2.2
[git] / http.c
1 #include "http.h"
2
3 int data_received;
4 int active_requests;
5
6 #ifdef USE_CURL_MULTI
7 static int max_requests = -1;
8 static CURLM *curlm;
9 #endif
10 #ifndef NO_CURL_EASY_DUPHANDLE
11 static CURL *curl_default;
12 #endif
13 char curl_errorstr[CURL_ERROR_SIZE];
14
15 static int curl_ssl_verify = -1;
16 static const char *ssl_cert;
17 #if LIBCURL_VERSION_NUM >= 0x070902
18 static const char *ssl_key;
19 #endif
20 #if LIBCURL_VERSION_NUM >= 0x070908
21 static const char *ssl_capath;
22 #endif
23 static const char *ssl_cainfo;
24 static long curl_low_speed_limit = -1;
25 static long curl_low_speed_time = -1;
26 static int curl_ftp_no_epsv;
27 static const char *curl_http_proxy;
28 static char *user_name, *user_pass;
29
30 static struct curl_slist *pragma_header;
31
32 static struct active_request_slot *active_queue_head;
33
34 size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *buffer_)
35 {
36         size_t size = eltsize * nmemb;
37         struct buffer *buffer = buffer_;
38
39         if (size > buffer->buf.len - buffer->posn)
40                 size = buffer->buf.len - buffer->posn;
41         memcpy(ptr, buffer->buf.buf + buffer->posn, size);
42         buffer->posn += size;
43
44         return size;
45 }
46
47 size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *buffer_)
48 {
49         size_t size = eltsize * nmemb;
50         struct strbuf *buffer = buffer_;
51
52         strbuf_add(buffer, ptr, size);
53         data_received++;
54         return size;
55 }
56
57 size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf)
58 {
59         data_received++;
60         return eltsize * nmemb;
61 }
62
63 static void finish_active_slot(struct active_request_slot *slot);
64
65 #ifdef USE_CURL_MULTI
66 static void process_curl_messages(void)
67 {
68         int num_messages;
69         struct active_request_slot *slot;
70         CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages);
71
72         while (curl_message != NULL) {
73                 if (curl_message->msg == CURLMSG_DONE) {
74                         int curl_result = curl_message->data.result;
75                         slot = active_queue_head;
76                         while (slot != NULL &&
77                                slot->curl != curl_message->easy_handle)
78                                 slot = slot->next;
79                         if (slot != NULL) {
80                                 curl_multi_remove_handle(curlm, slot->curl);
81                                 slot->curl_result = curl_result;
82                                 finish_active_slot(slot);
83                         } else {
84                                 fprintf(stderr, "Received DONE message for unknown request!\n");
85                         }
86                 } else {
87                         fprintf(stderr, "Unknown CURL message received: %d\n",
88                                 (int)curl_message->msg);
89                 }
90                 curl_message = curl_multi_info_read(curlm, &num_messages);
91         }
92 }
93 #endif
94
95 static int http_options(const char *var, const char *value, void *cb)
96 {
97         if (!strcmp("http.sslverify", var)) {
98                 curl_ssl_verify = git_config_bool(var, value);
99                 return 0;
100         }
101         if (!strcmp("http.sslcert", var))
102                 return git_config_string(&ssl_cert, var, value);
103 #if LIBCURL_VERSION_NUM >= 0x070902
104         if (!strcmp("http.sslkey", var))
105                 return git_config_string(&ssl_key, var, value);
106 #endif
107 #if LIBCURL_VERSION_NUM >= 0x070908
108         if (!strcmp("http.sslcapath", var))
109                 return git_config_string(&ssl_capath, var, value);
110 #endif
111         if (!strcmp("http.sslcainfo", var))
112                 return git_config_string(&ssl_cainfo, var, value);
113 #ifdef USE_CURL_MULTI
114         if (!strcmp("http.maxrequests", var)) {
115                 max_requests = git_config_int(var, value);
116                 return 0;
117         }
118 #endif
119         if (!strcmp("http.lowspeedlimit", var)) {
120                 curl_low_speed_limit = (long)git_config_int(var, value);
121                 return 0;
122         }
123         if (!strcmp("http.lowspeedtime", var)) {
124                 curl_low_speed_time = (long)git_config_int(var, value);
125                 return 0;
126         }
127
128         if (!strcmp("http.noepsv", var)) {
129                 curl_ftp_no_epsv = git_config_bool(var, value);
130                 return 0;
131         }
132         if (!strcmp("http.proxy", var))
133                 return git_config_string(&curl_http_proxy, var, value);
134
135         /* Fall back on the default ones */
136         return git_default_config(var, value, cb);
137 }
138
139 static void init_curl_http_auth(CURL *result)
140 {
141         if (user_name) {
142                 struct strbuf up = STRBUF_INIT;
143                 if (!user_pass)
144                         user_pass = xstrdup(getpass("Password: "));
145                 strbuf_addf(&up, "%s:%s", user_name, user_pass);
146                 curl_easy_setopt(result, CURLOPT_USERPWD,
147                                  strbuf_detach(&up, NULL));
148         }
149 }
150
151 static CURL *get_curl_handle(void)
152 {
153         CURL *result = curl_easy_init();
154
155         if (!curl_ssl_verify) {
156                 curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 0);
157                 curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 0);
158         } else {
159                 /* Verify authenticity of the peer's certificate */
160                 curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 1);
161                 /* The name in the cert must match whom we tried to connect */
162                 curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2);
163         }
164
165 #if LIBCURL_VERSION_NUM >= 0x070907
166         curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
167 #endif
168
169         init_curl_http_auth(result);
170
171         if (ssl_cert != NULL)
172                 curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
173 #if LIBCURL_VERSION_NUM >= 0x070902
174         if (ssl_key != NULL)
175                 curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
176 #endif
177 #if LIBCURL_VERSION_NUM >= 0x070908
178         if (ssl_capath != NULL)
179                 curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath);
180 #endif
181         if (ssl_cainfo != NULL)
182                 curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
183         curl_easy_setopt(result, CURLOPT_FAILONERROR, 1);
184
185         if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {
186                 curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT,
187                                  curl_low_speed_limit);
188                 curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME,
189                                  curl_low_speed_time);
190         }
191
192         curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1);
193
194         if (getenv("GIT_CURL_VERBOSE"))
195                 curl_easy_setopt(result, CURLOPT_VERBOSE, 1);
196
197         curl_easy_setopt(result, CURLOPT_USERAGENT, GIT_USER_AGENT);
198
199         if (curl_ftp_no_epsv)
200                 curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0);
201
202         if (curl_http_proxy)
203                 curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy);
204
205         return result;
206 }
207
208 static void http_auth_init(const char *url)
209 {
210         char *at, *colon, *cp, *slash;
211         int len;
212
213         cp = strstr(url, "://");
214         if (!cp)
215                 return;
216
217         /*
218          * Ok, the URL looks like "proto://something".  Which one?
219          * "proto://<user>:<pass>@<host>/...",
220          * "proto://<user>@<host>/...", or just
221          * "proto://<host>/..."?
222          */
223         cp += 3;
224         at = strchr(cp, '@');
225         colon = strchr(cp, ':');
226         slash = strchrnul(cp, '/');
227         if (!at || slash <= at)
228                 return; /* No credentials */
229         if (!colon || at <= colon) {
230                 /* Only username */
231                 len = at - cp;
232                 user_name = xmalloc(len + 1);
233                 memcpy(user_name, cp, len);
234                 user_name[len] = '\0';
235                 user_pass = NULL;
236         } else {
237                 len = colon - cp;
238                 user_name = xmalloc(len + 1);
239                 memcpy(user_name, cp, len);
240                 user_name[len] = '\0';
241                 len = at - (colon + 1);
242                 user_pass = xmalloc(len + 1);
243                 memcpy(user_pass, colon + 1, len);
244                 user_pass[len] = '\0';
245         }
246 }
247
248 static void set_from_env(const char **var, const char *envname)
249 {
250         const char *val = getenv(envname);
251         if (val)
252                 *var = val;
253 }
254
255 void http_init(struct remote *remote)
256 {
257         char *low_speed_limit;
258         char *low_speed_time;
259
260         git_config(http_options, NULL);
261
262         curl_global_init(CURL_GLOBAL_ALL);
263
264         if (remote && remote->http_proxy)
265                 curl_http_proxy = xstrdup(remote->http_proxy);
266
267         pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache");
268
269 #ifdef USE_CURL_MULTI
270         {
271                 char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
272                 if (http_max_requests != NULL)
273                         max_requests = atoi(http_max_requests);
274         }
275
276         curlm = curl_multi_init();
277         if (curlm == NULL) {
278                 fprintf(stderr, "Error creating curl multi handle.\n");
279                 exit(1);
280         }
281 #endif
282
283         if (getenv("GIT_SSL_NO_VERIFY"))
284                 curl_ssl_verify = 0;
285
286         set_from_env(&ssl_cert, "GIT_SSL_CERT");
287 #if LIBCURL_VERSION_NUM >= 0x070902
288         set_from_env(&ssl_key, "GIT_SSL_KEY");
289 #endif
290 #if LIBCURL_VERSION_NUM >= 0x070908
291         set_from_env(&ssl_capath, "GIT_SSL_CAPATH");
292 #endif
293         set_from_env(&ssl_cainfo, "GIT_SSL_CAINFO");
294
295         low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
296         if (low_speed_limit != NULL)
297                 curl_low_speed_limit = strtol(low_speed_limit, NULL, 10);
298         low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME");
299         if (low_speed_time != NULL)
300                 curl_low_speed_time = strtol(low_speed_time, NULL, 10);
301
302         if (curl_ssl_verify == -1)
303                 curl_ssl_verify = 1;
304
305 #ifdef USE_CURL_MULTI
306         if (max_requests < 1)
307                 max_requests = DEFAULT_MAX_REQUESTS;
308 #endif
309
310         if (getenv("GIT_CURL_FTP_NO_EPSV"))
311                 curl_ftp_no_epsv = 1;
312
313         if (remote && remote->url && remote->url[0])
314                 http_auth_init(remote->url[0]);
315
316 #ifndef NO_CURL_EASY_DUPHANDLE
317         curl_default = get_curl_handle();
318 #endif
319 }
320
321 void http_cleanup(void)
322 {
323         struct active_request_slot *slot = active_queue_head;
324
325         while (slot != NULL) {
326                 struct active_request_slot *next = slot->next;
327                 if (slot->curl != NULL) {
328 #ifdef USE_CURL_MULTI
329                         curl_multi_remove_handle(curlm, slot->curl);
330 #endif
331                         curl_easy_cleanup(slot->curl);
332                 }
333                 free(slot);
334                 slot = next;
335         }
336         active_queue_head = NULL;
337
338 #ifndef NO_CURL_EASY_DUPHANDLE
339         curl_easy_cleanup(curl_default);
340 #endif
341
342 #ifdef USE_CURL_MULTI
343         curl_multi_cleanup(curlm);
344 #endif
345         curl_global_cleanup();
346
347         curl_slist_free_all(pragma_header);
348         pragma_header = NULL;
349
350         if (curl_http_proxy) {
351                 free((void *)curl_http_proxy);
352                 curl_http_proxy = NULL;
353         }
354 }
355
356 struct active_request_slot *get_active_slot(void)
357 {
358         struct active_request_slot *slot = active_queue_head;
359         struct active_request_slot *newslot;
360
361 #ifdef USE_CURL_MULTI
362         int num_transfers;
363
364         /* Wait for a slot to open up if the queue is full */
365         while (active_requests >= max_requests) {
366                 curl_multi_perform(curlm, &num_transfers);
367                 if (num_transfers < active_requests)
368                         process_curl_messages();
369         }
370 #endif
371
372         while (slot != NULL && slot->in_use)
373                 slot = slot->next;
374
375         if (slot == NULL) {
376                 newslot = xmalloc(sizeof(*newslot));
377                 newslot->curl = NULL;
378                 newslot->in_use = 0;
379                 newslot->next = NULL;
380
381                 slot = active_queue_head;
382                 if (slot == NULL) {
383                         active_queue_head = newslot;
384                 } else {
385                         while (slot->next != NULL)
386                                 slot = slot->next;
387                         slot->next = newslot;
388                 }
389                 slot = newslot;
390         }
391
392         if (slot->curl == NULL) {
393 #ifdef NO_CURL_EASY_DUPHANDLE
394                 slot->curl = get_curl_handle();
395 #else
396                 slot->curl = curl_easy_duphandle(curl_default);
397 #endif
398         }
399
400         active_requests++;
401         slot->in_use = 1;
402         slot->local = NULL;
403         slot->results = NULL;
404         slot->finished = NULL;
405         slot->callback_data = NULL;
406         slot->callback_func = NULL;
407         curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
408         curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
409         curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL);
410         curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, NULL);
411         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, NULL);
412         curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0);
413         curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
414
415         return slot;
416 }
417
418 int start_active_slot(struct active_request_slot *slot)
419 {
420 #ifdef USE_CURL_MULTI
421         CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
422         int num_transfers;
423
424         if (curlm_result != CURLM_OK &&
425             curlm_result != CURLM_CALL_MULTI_PERFORM) {
426                 active_requests--;
427                 slot->in_use = 0;
428                 return 0;
429         }
430
431         /*
432          * We know there must be something to do, since we just added
433          * something.
434          */
435         curl_multi_perform(curlm, &num_transfers);
436 #endif
437         return 1;
438 }
439
440 #ifdef USE_CURL_MULTI
441 struct fill_chain {
442         void *data;
443         int (*fill)(void *);
444         struct fill_chain *next;
445 };
446
447 static struct fill_chain *fill_cfg;
448
449 void add_fill_function(void *data, int (*fill)(void *))
450 {
451         struct fill_chain *new = xmalloc(sizeof(*new));
452         struct fill_chain **linkp = &fill_cfg;
453         new->data = data;
454         new->fill = fill;
455         new->next = NULL;
456         while (*linkp)
457                 linkp = &(*linkp)->next;
458         *linkp = new;
459 }
460
461 void fill_active_slots(void)
462 {
463         struct active_request_slot *slot = active_queue_head;
464
465         while (active_requests < max_requests) {
466                 struct fill_chain *fill;
467                 for (fill = fill_cfg; fill; fill = fill->next)
468                         if (fill->fill(fill->data))
469                                 break;
470
471                 if (!fill)
472                         break;
473         }
474
475         while (slot != NULL) {
476                 if (!slot->in_use && slot->curl != NULL) {
477                         curl_easy_cleanup(slot->curl);
478                         slot->curl = NULL;
479                 }
480                 slot = slot->next;
481         }
482 }
483
484 void step_active_slots(void)
485 {
486         int num_transfers;
487         CURLMcode curlm_result;
488
489         do {
490                 curlm_result = curl_multi_perform(curlm, &num_transfers);
491         } while (curlm_result == CURLM_CALL_MULTI_PERFORM);
492         if (num_transfers < active_requests) {
493                 process_curl_messages();
494                 fill_active_slots();
495         }
496 }
497 #endif
498
499 void run_active_slot(struct active_request_slot *slot)
500 {
501 #ifdef USE_CURL_MULTI
502         long last_pos = 0;
503         long current_pos;
504         fd_set readfds;
505         fd_set writefds;
506         fd_set excfds;
507         int max_fd;
508         struct timeval select_timeout;
509         int finished = 0;
510
511         slot->finished = &finished;
512         while (!finished) {
513                 data_received = 0;
514                 step_active_slots();
515
516                 if (!data_received && slot->local != NULL) {
517                         current_pos = ftell(slot->local);
518                         if (current_pos > last_pos)
519                                 data_received++;
520                         last_pos = current_pos;
521                 }
522
523                 if (slot->in_use && !data_received) {
524                         max_fd = 0;
525                         FD_ZERO(&readfds);
526                         FD_ZERO(&writefds);
527                         FD_ZERO(&excfds);
528                         select_timeout.tv_sec = 0;
529                         select_timeout.tv_usec = 50000;
530                         select(max_fd, &readfds, &writefds,
531                                &excfds, &select_timeout);
532                 }
533         }
534 #else
535         while (slot->in_use) {
536                 slot->curl_result = curl_easy_perform(slot->curl);
537                 finish_active_slot(slot);
538         }
539 #endif
540 }
541
542 static void closedown_active_slot(struct active_request_slot *slot)
543 {
544         active_requests--;
545         slot->in_use = 0;
546 }
547
548 void release_active_slot(struct active_request_slot *slot)
549 {
550         closedown_active_slot(slot);
551         if (slot->curl) {
552 #ifdef USE_CURL_MULTI
553                 curl_multi_remove_handle(curlm, slot->curl);
554 #endif
555                 curl_easy_cleanup(slot->curl);
556                 slot->curl = NULL;
557         }
558 #ifdef USE_CURL_MULTI
559         fill_active_slots();
560 #endif
561 }
562
563 static void finish_active_slot(struct active_request_slot *slot)
564 {
565         closedown_active_slot(slot);
566         curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code);
567
568         if (slot->finished != NULL)
569                 (*slot->finished) = 1;
570
571         /* Store slot results so they can be read after the slot is reused */
572         if (slot->results != NULL) {
573                 slot->results->curl_result = slot->curl_result;
574                 slot->results->http_code = slot->http_code;
575         }
576
577         /* Run callback if appropriate */
578         if (slot->callback_func != NULL)
579                 slot->callback_func(slot->callback_data);
580 }
581
582 void finish_all_active_slots(void)
583 {
584         struct active_request_slot *slot = active_queue_head;
585
586         while (slot != NULL)
587                 if (slot->in_use) {
588                         run_active_slot(slot);
589                         slot = active_queue_head;
590                 } else {
591                         slot = slot->next;
592                 }
593 }
594
595 static inline int needs_quote(int ch)
596 {
597         if (((ch >= 'A') && (ch <= 'Z'))
598                         || ((ch >= 'a') && (ch <= 'z'))
599                         || ((ch >= '0') && (ch <= '9'))
600                         || (ch == '/')
601                         || (ch == '-')
602                         || (ch == '.'))
603                 return 0;
604         return 1;
605 }
606
607 static inline int hex(int v)
608 {
609         if (v < 10)
610                 return '0' + v;
611         else
612                 return 'A' + v - 10;
613 }
614
615 static char *quote_ref_url(const char *base, const char *ref)
616 {
617         struct strbuf buf = STRBUF_INIT;
618         const char *cp;
619         int ch;
620
621         strbuf_addstr(&buf, base);
622         if (buf.len && buf.buf[buf.len - 1] != '/' && *ref != '/')
623                 strbuf_addstr(&buf, "/");
624
625         for (cp = ref; (ch = *cp) != 0; cp++)
626                 if (needs_quote(ch))
627                         strbuf_addf(&buf, "%%%02x", ch);
628                 else
629                         strbuf_addch(&buf, *cp);
630
631         return strbuf_detach(&buf, NULL);
632 }
633
634 int http_fetch_ref(const char *base, struct ref *ref)
635 {
636         char *url;
637         struct strbuf buffer = STRBUF_INIT;
638         struct active_request_slot *slot;
639         struct slot_results results;
640         int ret;
641
642         url = quote_ref_url(base, ref->name);
643         slot = get_active_slot();
644         slot->results = &results;
645         curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
646         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
647         curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
648         curl_easy_setopt(slot->curl, CURLOPT_URL, url);
649         if (start_active_slot(slot)) {
650                 run_active_slot(slot);
651                 if (results.curl_result == CURLE_OK) {
652                         strbuf_rtrim(&buffer);
653                         if (buffer.len == 40)
654                                 ret = get_sha1_hex(buffer.buf, ref->old_sha1);
655                         else if (!prefixcmp(buffer.buf, "ref: ")) {
656                                 ref->symref = xstrdup(buffer.buf + 5);
657                                 ret = 0;
658                         } else
659                                 ret = 1;
660                 } else {
661                         ret = error("Couldn't get %s for %s\n%s",
662                                     url, ref->name, curl_errorstr);
663                 }
664         } else {
665                 ret = error("Unable to start request");
666         }
667
668         strbuf_release(&buffer);
669         free(url);
670         return ret;
671 }