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