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