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