http.con commit Merge branch 'maint' (442dd42)
   1#include "http.h"
   2
   3int data_received;
   4int active_requests;
   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 const char *ssl_cert;
  17#if LIBCURL_VERSION_NUM >= 0x070902
  18static const char *ssl_key;
  19#endif
  20#if LIBCURL_VERSION_NUM >= 0x070908
  21static const char *ssl_capath;
  22#endif
  23static const char *ssl_cainfo;
  24static long curl_low_speed_limit = -1;
  25static long curl_low_speed_time = -1;
  26static int curl_ftp_no_epsv;
  27static const char *curl_http_proxy;
  28static char *user_name, *user_pass;
  29
  30static struct curl_slist *pragma_header;
  31
  32static struct active_request_slot *active_queue_head;
  33
  34size_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
  47size_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
  57size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf)
  58{
  59        data_received++;
  60        return eltsize * nmemb;
  61}
  62
  63static void finish_active_slot(struct active_request_slot *slot);
  64
  65#ifdef USE_CURL_MULTI
  66static 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
  95static 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
 139static 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
 151static 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
 208static 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
 248static 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
 255void 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
 321void 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
 356struct 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
 418int 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
 441struct fill_chain {
 442        void *data;
 443        int (*fill)(void *);
 444        struct fill_chain *next;
 445};
 446
 447static struct fill_chain *fill_cfg;
 448
 449void 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
 461void 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
 484void 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
 499void 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
 542static void closedown_active_slot(struct active_request_slot *slot)
 543{
 544        active_requests--;
 545        slot->in_use = 0;
 546}
 547
 548void 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
 563static 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
 582void 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
 595static 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
 607static inline int hex(int v)
 608{
 609        if (v < 10)
 610                return '0' + v;
 611        else
 612                return 'A' + v - 10;
 613}
 614
 615static 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
 634int 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}