http.con commit Merge branch 'mh/maint-http-proxy-fix' (7ab9f8f)
   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)
  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);
 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);
 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#ifdef USE_CURL_MULTI
 288        char *wait_url;
 289#endif
 290
 291        while (slot != NULL) {
 292                struct active_request_slot *next = slot->next;
 293#ifdef USE_CURL_MULTI
 294                if (slot->in_use) {
 295                        curl_easy_getinfo(slot->curl,
 296                                          CURLINFO_EFFECTIVE_URL,
 297                                          &wait_url);
 298                        fprintf(stderr, "Waiting for %s\n", wait_url);
 299                        run_active_slot(slot);
 300                }
 301#endif
 302                if (slot->curl != NULL)
 303                        curl_easy_cleanup(slot->curl);
 304                free(slot);
 305                slot = next;
 306        }
 307        active_queue_head = NULL;
 308
 309#ifndef NO_CURL_EASY_DUPHANDLE
 310        curl_easy_cleanup(curl_default);
 311#endif
 312
 313#ifdef USE_CURL_MULTI
 314        curl_multi_cleanup(curlm);
 315#endif
 316        curl_global_cleanup();
 317
 318        curl_slist_free_all(pragma_header);
 319        pragma_header = NULL;
 320
 321        if (curl_http_proxy) {
 322                free(curl_http_proxy);
 323                curl_http_proxy = NULL;
 324        }
 325}
 326
 327struct active_request_slot *get_active_slot(void)
 328{
 329        struct active_request_slot *slot = active_queue_head;
 330        struct active_request_slot *newslot;
 331
 332#ifdef USE_CURL_MULTI
 333        int num_transfers;
 334
 335        /* Wait for a slot to open up if the queue is full */
 336        while (active_requests >= max_requests) {
 337                curl_multi_perform(curlm, &num_transfers);
 338                if (num_transfers < active_requests) {
 339                        process_curl_messages();
 340                }
 341        }
 342#endif
 343
 344        while (slot != NULL && slot->in_use) {
 345                slot = slot->next;
 346        }
 347        if (slot == NULL) {
 348                newslot = xmalloc(sizeof(*newslot));
 349                newslot->curl = NULL;
 350                newslot->in_use = 0;
 351                newslot->next = NULL;
 352
 353                slot = active_queue_head;
 354                if (slot == NULL) {
 355                        active_queue_head = newslot;
 356                } else {
 357                        while (slot->next != NULL) {
 358                                slot = slot->next;
 359                        }
 360                        slot->next = newslot;
 361                }
 362                slot = newslot;
 363        }
 364
 365        if (slot->curl == NULL) {
 366#ifdef NO_CURL_EASY_DUPHANDLE
 367                slot->curl = get_curl_handle();
 368#else
 369                slot->curl = curl_easy_duphandle(curl_default);
 370#endif
 371        }
 372
 373        active_requests++;
 374        slot->in_use = 1;
 375        slot->local = NULL;
 376        slot->results = NULL;
 377        slot->finished = NULL;
 378        slot->callback_data = NULL;
 379        slot->callback_func = NULL;
 380        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
 381        curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
 382        curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL);
 383        curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, NULL);
 384        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, NULL);
 385        curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0);
 386        curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
 387
 388        return slot;
 389}
 390
 391int start_active_slot(struct active_request_slot *slot)
 392{
 393#ifdef USE_CURL_MULTI
 394        CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
 395        int num_transfers;
 396
 397        if (curlm_result != CURLM_OK &&
 398            curlm_result != CURLM_CALL_MULTI_PERFORM) {
 399                active_requests--;
 400                slot->in_use = 0;
 401                return 0;
 402        }
 403
 404        /*
 405         * We know there must be something to do, since we just added
 406         * something.
 407         */
 408        curl_multi_perform(curlm, &num_transfers);
 409#endif
 410        return 1;
 411}
 412
 413#ifdef USE_CURL_MULTI
 414struct fill_chain {
 415        void *data;
 416        int (*fill)(void *);
 417        struct fill_chain *next;
 418};
 419
 420static struct fill_chain *fill_cfg = NULL;
 421
 422void add_fill_function(void *data, int (*fill)(void *))
 423{
 424        struct fill_chain *new = malloc(sizeof(*new));
 425        struct fill_chain **linkp = &fill_cfg;
 426        new->data = data;
 427        new->fill = fill;
 428        new->next = NULL;
 429        while (*linkp)
 430                linkp = &(*linkp)->next;
 431        *linkp = new;
 432}
 433
 434void fill_active_slots(void)
 435{
 436        struct active_request_slot *slot = active_queue_head;
 437
 438        while (active_requests < max_requests) {
 439                struct fill_chain *fill;
 440                for (fill = fill_cfg; fill; fill = fill->next)
 441                        if (fill->fill(fill->data))
 442                                break;
 443
 444                if (!fill)
 445                        break;
 446        }
 447
 448        while (slot != NULL) {
 449                if (!slot->in_use && slot->curl != NULL) {
 450                        curl_easy_cleanup(slot->curl);
 451                        slot->curl = NULL;
 452                }
 453                slot = slot->next;
 454        }
 455}
 456
 457void step_active_slots(void)
 458{
 459        int num_transfers;
 460        CURLMcode curlm_result;
 461
 462        do {
 463                curlm_result = curl_multi_perform(curlm, &num_transfers);
 464        } while (curlm_result == CURLM_CALL_MULTI_PERFORM);
 465        if (num_transfers < active_requests) {
 466                process_curl_messages();
 467                fill_active_slots();
 468        }
 469}
 470#endif
 471
 472void run_active_slot(struct active_request_slot *slot)
 473{
 474#ifdef USE_CURL_MULTI
 475        long last_pos = 0;
 476        long current_pos;
 477        fd_set readfds;
 478        fd_set writefds;
 479        fd_set excfds;
 480        int max_fd;
 481        struct timeval select_timeout;
 482        int finished = 0;
 483
 484        slot->finished = &finished;
 485        while (!finished) {
 486                data_received = 0;
 487                step_active_slots();
 488
 489                if (!data_received && slot->local != NULL) {
 490                        current_pos = ftell(slot->local);
 491                        if (current_pos > last_pos)
 492                                data_received++;
 493                        last_pos = current_pos;
 494                }
 495
 496                if (slot->in_use && !data_received) {
 497                        max_fd = 0;
 498                        FD_ZERO(&readfds);
 499                        FD_ZERO(&writefds);
 500                        FD_ZERO(&excfds);
 501                        select_timeout.tv_sec = 0;
 502                        select_timeout.tv_usec = 50000;
 503                        select(max_fd, &readfds, &writefds,
 504                               &excfds, &select_timeout);
 505                }
 506        }
 507#else
 508        while (slot->in_use) {
 509                slot->curl_result = curl_easy_perform(slot->curl);
 510                finish_active_slot(slot);
 511        }
 512#endif
 513}
 514
 515static void closedown_active_slot(struct active_request_slot *slot)
 516{
 517        active_requests--;
 518        slot->in_use = 0;
 519}
 520
 521void release_active_slot(struct active_request_slot *slot)
 522{
 523        closedown_active_slot(slot);
 524        if (slot->curl) {
 525#ifdef USE_CURL_MULTI
 526                curl_multi_remove_handle(curlm, slot->curl);
 527#endif
 528                curl_easy_cleanup(slot->curl);
 529                slot->curl = NULL;
 530        }
 531#ifdef USE_CURL_MULTI
 532        fill_active_slots();
 533#endif
 534}
 535
 536static void finish_active_slot(struct active_request_slot *slot)
 537{
 538        closedown_active_slot(slot);
 539        curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code);
 540
 541        if (slot->finished != NULL)
 542                (*slot->finished) = 1;
 543
 544        /* Store slot results so they can be read after the slot is reused */
 545        if (slot->results != NULL) {
 546                slot->results->curl_result = slot->curl_result;
 547                slot->results->http_code = slot->http_code;
 548        }
 549
 550        /* Run callback if appropriate */
 551        if (slot->callback_func != NULL) {
 552                slot->callback_func(slot->callback_data);
 553        }
 554}
 555
 556void finish_all_active_slots(void)
 557{
 558        struct active_request_slot *slot = active_queue_head;
 559
 560        while (slot != NULL)
 561                if (slot->in_use) {
 562                        run_active_slot(slot);
 563                        slot = active_queue_head;
 564                } else {
 565                        slot = slot->next;
 566                }
 567}
 568
 569static inline int needs_quote(int ch)
 570{
 571        if (((ch >= 'A') && (ch <= 'Z'))
 572                        || ((ch >= 'a') && (ch <= 'z'))
 573                        || ((ch >= '0') && (ch <= '9'))
 574                        || (ch == '/')
 575                        || (ch == '-')
 576                        || (ch == '.'))
 577                return 0;
 578        return 1;
 579}
 580
 581static inline int hex(int v)
 582{
 583        if (v < 10) return '0' + v;
 584        else return 'A' + v - 10;
 585}
 586
 587static char *quote_ref_url(const char *base, const char *ref)
 588{
 589        const char *cp;
 590        char *dp, *qref;
 591        int len, baselen, ch;
 592
 593        baselen = strlen(base);
 594        len = baselen + 7; /* "/refs/" + NUL */
 595        for (cp = ref; (ch = *cp) != 0; cp++, len++)
 596                if (needs_quote(ch))
 597                        len += 2; /* extra two hex plus replacement % */
 598        qref = xmalloc(len);
 599        memcpy(qref, base, baselen);
 600        memcpy(qref + baselen, "/refs/", 6);
 601        for (cp = ref, dp = qref + baselen + 6; (ch = *cp) != 0; cp++) {
 602                if (needs_quote(ch)) {
 603                        *dp++ = '%';
 604                        *dp++ = hex((ch >> 4) & 0xF);
 605                        *dp++ = hex(ch & 0xF);
 606                }
 607                else
 608                        *dp++ = ch;
 609        }
 610        *dp = 0;
 611
 612        return qref;
 613}
 614
 615int http_fetch_ref(const char *base, const char *ref, unsigned char *sha1)
 616{
 617        char *url;
 618        struct strbuf buffer = STRBUF_INIT;
 619        struct active_request_slot *slot;
 620        struct slot_results results;
 621        int ret;
 622
 623        url = quote_ref_url(base, ref);
 624        slot = get_active_slot();
 625        slot->results = &results;
 626        curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
 627        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
 628        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
 629        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
 630        if (start_active_slot(slot)) {
 631                run_active_slot(slot);
 632                if (results.curl_result == CURLE_OK) {
 633                        strbuf_rtrim(&buffer);
 634                        if (buffer.len == 40)
 635                                ret = get_sha1_hex(buffer.buf, sha1);
 636                        else
 637                                ret = 1;
 638                } else {
 639                        ret = error("Couldn't get %s for %s\n%s",
 640                                    url, ref, curl_errorstr);
 641                }
 642        } else {
 643                ret = error("Unable to start request");
 644        }
 645
 646        strbuf_release(&buffer);
 647        free(url);
 648        return ret;
 649}