]> git.proxmox.com Git - libgit2.git/blob - src/libgit2/transports/httpclient.c
New upstream version 1.5.0+ds
[libgit2.git] / src / libgit2 / transports / httpclient.c
1 /*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
3 *
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
6 */
7
8 #include "common.h"
9 #include "git2.h"
10 #include "http_parser.h"
11 #include "vector.h"
12 #include "trace.h"
13 #include "httpclient.h"
14 #include "http.h"
15 #include "auth.h"
16 #include "auth_negotiate.h"
17 #include "auth_ntlm.h"
18 #include "git2/sys/credential.h"
19 #include "net.h"
20 #include "stream.h"
21 #include "streams/socket.h"
22 #include "streams/tls.h"
23 #include "auth.h"
24
25 static git_http_auth_scheme auth_schemes[] = {
26 { GIT_HTTP_AUTH_NEGOTIATE, "Negotiate", GIT_CREDENTIAL_DEFAULT, git_http_auth_negotiate },
27 { GIT_HTTP_AUTH_NTLM, "NTLM", GIT_CREDENTIAL_USERPASS_PLAINTEXT, git_http_auth_ntlm },
28 { GIT_HTTP_AUTH_BASIC, "Basic", GIT_CREDENTIAL_USERPASS_PLAINTEXT, git_http_auth_basic },
29 };
30
31 /*
32 * Use a 16kb read buffer to match the maximum size of a TLS packet. This
33 * is critical for compatibility with SecureTransport, which will always do
34 * a network read on every call, even if it has data buffered to return to
35 * you. That buffered data may be the _end_ of a keep-alive response, so
36 * if SecureTransport performs another network read, it will wait until the
37 * server ultimately times out before it returns that buffered data to you.
38 * Since SecureTransport only reads a single TLS packet at a time, by
39 * calling it with a read buffer that is the maximum size of a TLS packet,
40 * we ensure that it will never buffer.
41 */
42 #define GIT_READ_BUFFER_SIZE (16 * 1024)
43
44 typedef struct {
45 git_net_url url;
46 git_stream *stream;
47
48 git_vector auth_challenges;
49 git_http_auth_context *auth_context;
50 } git_http_server;
51
52 typedef enum {
53 PROXY = 1,
54 SERVER
55 } git_http_server_t;
56
57 typedef enum {
58 NONE = 0,
59 SENDING_REQUEST,
60 SENDING_BODY,
61 SENT_REQUEST,
62 HAS_EARLY_RESPONSE,
63 READING_RESPONSE,
64 READING_BODY,
65 DONE
66 } http_client_state;
67
68 /* Parser state */
69 typedef enum {
70 PARSE_HEADER_NONE = 0,
71 PARSE_HEADER_NAME,
72 PARSE_HEADER_VALUE,
73 PARSE_HEADER_COMPLETE
74 } parse_header_state;
75
76 typedef enum {
77 PARSE_STATUS_OK,
78 PARSE_STATUS_NO_OUTPUT,
79 PARSE_STATUS_ERROR
80 } parse_status;
81
82 typedef struct {
83 git_http_client *client;
84 git_http_response *response;
85
86 /* Temporary buffers to avoid extra mallocs */
87 git_str parse_header_name;
88 git_str parse_header_value;
89
90 /* Parser state */
91 int error;
92 parse_status parse_status;
93
94 /* Headers parsing */
95 parse_header_state parse_header_state;
96
97 /* Body parsing */
98 char *output_buf; /* Caller's output buffer */
99 size_t output_size; /* Size of caller's output buffer */
100 size_t output_written; /* Bytes we've written to output buffer */
101 } http_parser_context;
102
103 /* HTTP client connection */
104 struct git_http_client {
105 git_http_client_options opts;
106
107 /* Are we writing to the proxy or server, and state of the client. */
108 git_http_server_t current_server;
109 http_client_state state;
110
111 http_parser parser;
112
113 git_http_server server;
114 git_http_server proxy;
115
116 unsigned request_count;
117 unsigned connected : 1,
118 proxy_connected : 1,
119 keepalive : 1,
120 request_chunked : 1;
121
122 /* Temporary buffers to avoid extra mallocs */
123 git_str request_msg;
124 git_str read_buf;
125
126 /* A subset of information from the request */
127 size_t request_body_len,
128 request_body_remain;
129
130 /*
131 * When state == HAS_EARLY_RESPONSE, the response of our proxy
132 * that we have buffered and will deliver during read_response.
133 */
134 git_http_response early_response;
135 };
136
137 bool git_http_response_is_redirect(git_http_response *response)
138 {
139 return (response->status == GIT_HTTP_MOVED_PERMANENTLY ||
140 response->status == GIT_HTTP_FOUND ||
141 response->status == GIT_HTTP_SEE_OTHER ||
142 response->status == GIT_HTTP_TEMPORARY_REDIRECT ||
143 response->status == GIT_HTTP_PERMANENT_REDIRECT);
144 }
145
146 void git_http_response_dispose(git_http_response *response)
147 {
148 if (!response)
149 return;
150
151 git__free(response->content_type);
152 git__free(response->location);
153
154 memset(response, 0, sizeof(git_http_response));
155 }
156
157 static int on_header_complete(http_parser *parser)
158 {
159 http_parser_context *ctx = (http_parser_context *) parser->data;
160 git_http_client *client = ctx->client;
161 git_http_response *response = ctx->response;
162
163 git_str *name = &ctx->parse_header_name;
164 git_str *value = &ctx->parse_header_value;
165
166 if (!strcasecmp("Content-Type", name->ptr)) {
167 if (response->content_type) {
168 git_error_set(GIT_ERROR_HTTP,
169 "multiple content-type headers");
170 return -1;
171 }
172
173 response->content_type =
174 git__strndup(value->ptr, value->size);
175 GIT_ERROR_CHECK_ALLOC(ctx->response->content_type);
176 } else if (!strcasecmp("Content-Length", name->ptr)) {
177 int64_t len;
178
179 if (response->content_length) {
180 git_error_set(GIT_ERROR_HTTP,
181 "multiple content-length headers");
182 return -1;
183 }
184
185 if (git__strntol64(&len, value->ptr, value->size,
186 NULL, 10) < 0 || len < 0) {
187 git_error_set(GIT_ERROR_HTTP,
188 "invalid content-length");
189 return -1;
190 }
191
192 response->content_length = (size_t)len;
193 } else if (!strcasecmp("Transfer-Encoding", name->ptr) &&
194 !strcasecmp("chunked", value->ptr)) {
195 ctx->response->chunked = 1;
196 } else if (!strcasecmp("Proxy-Authenticate", git_str_cstr(name))) {
197 char *dup = git__strndup(value->ptr, value->size);
198 GIT_ERROR_CHECK_ALLOC(dup);
199
200 if (git_vector_insert(&client->proxy.auth_challenges, dup) < 0)
201 return -1;
202 } else if (!strcasecmp("WWW-Authenticate", name->ptr)) {
203 char *dup = git__strndup(value->ptr, value->size);
204 GIT_ERROR_CHECK_ALLOC(dup);
205
206 if (git_vector_insert(&client->server.auth_challenges, dup) < 0)
207 return -1;
208 } else if (!strcasecmp("Location", name->ptr)) {
209 if (response->location) {
210 git_error_set(GIT_ERROR_HTTP,
211 "multiple location headers");
212 return -1;
213 }
214
215 response->location = git__strndup(value->ptr, value->size);
216 GIT_ERROR_CHECK_ALLOC(response->location);
217 }
218
219 return 0;
220 }
221
222 static int on_header_field(http_parser *parser, const char *str, size_t len)
223 {
224 http_parser_context *ctx = (http_parser_context *) parser->data;
225
226 switch (ctx->parse_header_state) {
227 /*
228 * We last saw a header value, process the name/value pair and
229 * get ready to handle this new name.
230 */
231 case PARSE_HEADER_VALUE:
232 if (on_header_complete(parser) < 0)
233 return ctx->parse_status = PARSE_STATUS_ERROR;
234
235 git_str_clear(&ctx->parse_header_name);
236 git_str_clear(&ctx->parse_header_value);
237 /* Fall through */
238
239 case PARSE_HEADER_NONE:
240 case PARSE_HEADER_NAME:
241 ctx->parse_header_state = PARSE_HEADER_NAME;
242
243 if (git_str_put(&ctx->parse_header_name, str, len) < 0)
244 return ctx->parse_status = PARSE_STATUS_ERROR;
245
246 break;
247
248 default:
249 git_error_set(GIT_ERROR_HTTP,
250 "header name seen at unexpected time");
251 return ctx->parse_status = PARSE_STATUS_ERROR;
252 }
253
254 return 0;
255 }
256
257 static int on_header_value(http_parser *parser, const char *str, size_t len)
258 {
259 http_parser_context *ctx = (http_parser_context *) parser->data;
260
261 switch (ctx->parse_header_state) {
262 case PARSE_HEADER_NAME:
263 case PARSE_HEADER_VALUE:
264 ctx->parse_header_state = PARSE_HEADER_VALUE;
265
266 if (git_str_put(&ctx->parse_header_value, str, len) < 0)
267 return ctx->parse_status = PARSE_STATUS_ERROR;
268
269 break;
270
271 default:
272 git_error_set(GIT_ERROR_HTTP,
273 "header value seen at unexpected time");
274 return ctx->parse_status = PARSE_STATUS_ERROR;
275 }
276
277 return 0;
278 }
279
280 GIT_INLINE(bool) challenge_matches_scheme(
281 const char *challenge,
282 git_http_auth_scheme *scheme)
283 {
284 const char *scheme_name = scheme->name;
285 size_t scheme_len = strlen(scheme_name);
286
287 if (!strncasecmp(challenge, scheme_name, scheme_len) &&
288 (challenge[scheme_len] == '\0' || challenge[scheme_len] == ' '))
289 return true;
290
291 return false;
292 }
293
294 static git_http_auth_scheme *scheme_for_challenge(const char *challenge)
295 {
296 size_t i;
297
298 for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) {
299 if (challenge_matches_scheme(challenge, &auth_schemes[i]))
300 return &auth_schemes[i];
301 }
302
303 return NULL;
304 }
305
306 GIT_INLINE(void) collect_authinfo(
307 unsigned int *schemetypes,
308 unsigned int *credtypes,
309 git_vector *challenges)
310 {
311 git_http_auth_scheme *scheme;
312 const char *challenge;
313 size_t i;
314
315 *schemetypes = 0;
316 *credtypes = 0;
317
318 git_vector_foreach(challenges, i, challenge) {
319 if ((scheme = scheme_for_challenge(challenge)) != NULL) {
320 *schemetypes |= scheme->type;
321 *credtypes |= scheme->credtypes;
322 }
323 }
324 }
325
326 static int resend_needed(git_http_client *client, git_http_response *response)
327 {
328 git_http_auth_context *auth_context;
329
330 if (response->status == GIT_HTTP_STATUS_UNAUTHORIZED &&
331 (auth_context = client->server.auth_context) &&
332 auth_context->is_complete &&
333 !auth_context->is_complete(auth_context))
334 return 1;
335
336 if (response->status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED &&
337 (auth_context = client->proxy.auth_context) &&
338 auth_context->is_complete &&
339 !auth_context->is_complete(auth_context))
340 return 1;
341
342 return 0;
343 }
344
345 static int on_headers_complete(http_parser *parser)
346 {
347 http_parser_context *ctx = (http_parser_context *) parser->data;
348
349 /* Finalize the last seen header */
350 switch (ctx->parse_header_state) {
351 case PARSE_HEADER_VALUE:
352 if (on_header_complete(parser) < 0)
353 return ctx->parse_status = PARSE_STATUS_ERROR;
354
355 /* Fall through */
356
357 case PARSE_HEADER_NONE:
358 ctx->parse_header_state = PARSE_HEADER_COMPLETE;
359 break;
360
361 default:
362 git_error_set(GIT_ERROR_HTTP,
363 "header completion at unexpected time");
364 return ctx->parse_status = PARSE_STATUS_ERROR;
365 }
366
367 ctx->response->status = parser->status_code;
368 ctx->client->keepalive = http_should_keep_alive(parser);
369
370 /* Prepare for authentication */
371 collect_authinfo(&ctx->response->server_auth_schemetypes,
372 &ctx->response->server_auth_credtypes,
373 &ctx->client->server.auth_challenges);
374 collect_authinfo(&ctx->response->proxy_auth_schemetypes,
375 &ctx->response->proxy_auth_credtypes,
376 &ctx->client->proxy.auth_challenges);
377
378 ctx->response->resend_credentials = resend_needed(ctx->client,
379 ctx->response);
380
381 /* Stop parsing. */
382 http_parser_pause(parser, 1);
383
384 if (ctx->response->content_type || ctx->response->chunked)
385 ctx->client->state = READING_BODY;
386 else
387 ctx->client->state = DONE;
388
389 return 0;
390 }
391
392 static int on_body(http_parser *parser, const char *buf, size_t len)
393 {
394 http_parser_context *ctx = (http_parser_context *) parser->data;
395 size_t max_len;
396
397 /* Saw data when we expected not to (eg, in consume_response_body) */
398 if (ctx->output_buf == NULL || ctx->output_size == 0) {
399 ctx->parse_status = PARSE_STATUS_NO_OUTPUT;
400 return 0;
401 }
402
403 GIT_ASSERT(ctx->output_size >= ctx->output_written);
404
405 max_len = min(ctx->output_size - ctx->output_written, len);
406 max_len = min(max_len, INT_MAX);
407
408 memcpy(ctx->output_buf + ctx->output_written, buf, max_len);
409 ctx->output_written += max_len;
410
411 return 0;
412 }
413
414 static int on_message_complete(http_parser *parser)
415 {
416 http_parser_context *ctx = (http_parser_context *) parser->data;
417
418 ctx->client->state = DONE;
419 return 0;
420 }
421
422 GIT_INLINE(int) stream_write(
423 git_http_server *server,
424 const char *data,
425 size_t len)
426 {
427 git_trace(GIT_TRACE_TRACE,
428 "Sending request:\n%.*s", (int)len, data);
429
430 return git_stream__write_full(server->stream, data, len, 0);
431 }
432
433 GIT_INLINE(int) client_write_request(git_http_client *client)
434 {
435 git_stream *stream = client->current_server == PROXY ?
436 client->proxy.stream : client->server.stream;
437
438 git_trace(GIT_TRACE_TRACE,
439 "Sending request:\n%.*s",
440 (int)client->request_msg.size, client->request_msg.ptr);
441
442 return git_stream__write_full(stream,
443 client->request_msg.ptr,
444 client->request_msg.size,
445 0);
446 }
447
448 static const char *name_for_method(git_http_method method)
449 {
450 switch (method) {
451 case GIT_HTTP_METHOD_GET:
452 return "GET";
453 case GIT_HTTP_METHOD_POST:
454 return "POST";
455 case GIT_HTTP_METHOD_CONNECT:
456 return "CONNECT";
457 }
458
459 return NULL;
460 }
461
462 /*
463 * Find the scheme that is suitable for the given credentials, based on the
464 * server's auth challenges.
465 */
466 static bool best_scheme_and_challenge(
467 git_http_auth_scheme **scheme_out,
468 const char **challenge_out,
469 git_vector *challenges,
470 git_credential *credentials)
471 {
472 const char *challenge;
473 size_t i, j;
474
475 for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) {
476 git_vector_foreach(challenges, j, challenge) {
477 git_http_auth_scheme *scheme = &auth_schemes[i];
478
479 if (challenge_matches_scheme(challenge, scheme) &&
480 (scheme->credtypes & credentials->credtype)) {
481 *scheme_out = scheme;
482 *challenge_out = challenge;
483 return true;
484 }
485 }
486 }
487
488 return false;
489 }
490
491 /*
492 * Find the challenge from the server for our current auth context.
493 */
494 static const char *challenge_for_context(
495 git_vector *challenges,
496 git_http_auth_context *auth_ctx)
497 {
498 const char *challenge;
499 size_t i, j;
500
501 for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) {
502 if (auth_schemes[i].type == auth_ctx->type) {
503 git_http_auth_scheme *scheme = &auth_schemes[i];
504
505 git_vector_foreach(challenges, j, challenge) {
506 if (challenge_matches_scheme(challenge, scheme))
507 return challenge;
508 }
509 }
510 }
511
512 return NULL;
513 }
514
515 static const char *init_auth_context(
516 git_http_server *server,
517 git_vector *challenges,
518 git_credential *credentials)
519 {
520 git_http_auth_scheme *scheme;
521 const char *challenge;
522 int error;
523
524 if (!best_scheme_and_challenge(&scheme, &challenge, challenges, credentials)) {
525 git_error_set(GIT_ERROR_HTTP, "could not find appropriate mechanism for credentials");
526 return NULL;
527 }
528
529 error = scheme->init_context(&server->auth_context, &server->url);
530
531 if (error == GIT_PASSTHROUGH) {
532 git_error_set(GIT_ERROR_HTTP, "'%s' authentication is not supported", scheme->name);
533 return NULL;
534 }
535
536 return challenge;
537 }
538
539 static void free_auth_context(git_http_server *server)
540 {
541 if (!server->auth_context)
542 return;
543
544 if (server->auth_context->free)
545 server->auth_context->free(server->auth_context);
546
547 server->auth_context = NULL;
548 }
549
550 static int apply_credentials(
551 git_str *buf,
552 git_http_server *server,
553 const char *header_name,
554 git_credential *credentials)
555 {
556 git_http_auth_context *auth = server->auth_context;
557 git_vector *challenges = &server->auth_challenges;
558 const char *challenge;
559 git_str token = GIT_STR_INIT;
560 int error = 0;
561
562 /* We've started a new request without creds; free the context. */
563 if (auth && !credentials) {
564 free_auth_context(server);
565 return 0;
566 }
567
568 /* We haven't authenticated, nor were we asked to. Nothing to do. */
569 if (!auth && !git_vector_length(challenges))
570 return 0;
571
572 if (!auth) {
573 challenge = init_auth_context(server, challenges, credentials);
574 auth = server->auth_context;
575
576 if (!challenge || !auth) {
577 error = -1;
578 goto done;
579 }
580 } else if (auth->set_challenge) {
581 challenge = challenge_for_context(challenges, auth);
582 }
583
584 if (auth->set_challenge && challenge &&
585 (error = auth->set_challenge(auth, challenge)) < 0)
586 goto done;
587
588 if ((error = auth->next_token(&token, auth, credentials)) < 0)
589 goto done;
590
591 if (auth->is_complete && auth->is_complete(auth)) {
592 /*
593 * If we're done with an auth mechanism with connection affinity,
594 * we don't need to send any more headers and can dispose the context.
595 */
596 if (auth->connection_affinity)
597 free_auth_context(server);
598 } else if (!token.size) {
599 git_error_set(GIT_ERROR_HTTP, "failed to respond to authentication challenge");
600 error = GIT_EAUTH;
601 goto done;
602 }
603
604 if (token.size > 0)
605 error = git_str_printf(buf, "%s: %s\r\n", header_name, token.ptr);
606
607 done:
608 git_str_dispose(&token);
609 return error;
610 }
611
612 GIT_INLINE(int) apply_server_credentials(
613 git_str *buf,
614 git_http_client *client,
615 git_http_request *request)
616 {
617 return apply_credentials(buf,
618 &client->server,
619 "Authorization",
620 request->credentials);
621 }
622
623 GIT_INLINE(int) apply_proxy_credentials(
624 git_str *buf,
625 git_http_client *client,
626 git_http_request *request)
627 {
628 return apply_credentials(buf,
629 &client->proxy,
630 "Proxy-Authorization",
631 request->proxy_credentials);
632 }
633
634 static int puts_host_and_port(git_str *buf, git_net_url *url, bool force_port)
635 {
636 bool ipv6 = git_net_url_is_ipv6(url);
637
638 if (ipv6)
639 git_str_putc(buf, '[');
640
641 git_str_puts(buf, url->host);
642
643 if (ipv6)
644 git_str_putc(buf, ']');
645
646 if (force_port || !git_net_url_is_default_port(url)) {
647 git_str_putc(buf, ':');
648 git_str_puts(buf, url->port);
649 }
650
651 return git_str_oom(buf) ? -1 : 0;
652 }
653
654 static int generate_connect_request(
655 git_http_client *client,
656 git_http_request *request)
657 {
658 git_str *buf;
659 int error;
660
661 git_str_clear(&client->request_msg);
662 buf = &client->request_msg;
663
664 git_str_puts(buf, "CONNECT ");
665 puts_host_and_port(buf, &client->server.url, true);
666 git_str_puts(buf, " HTTP/1.1\r\n");
667
668 git_str_puts(buf, "User-Agent: ");
669 git_http__user_agent(buf);
670 git_str_puts(buf, "\r\n");
671
672 git_str_puts(buf, "Host: ");
673 puts_host_and_port(buf, &client->server.url, true);
674 git_str_puts(buf, "\r\n");
675
676 if ((error = apply_proxy_credentials(buf, client, request) < 0))
677 return -1;
678
679 git_str_puts(buf, "\r\n");
680
681 return git_str_oom(buf) ? -1 : 0;
682 }
683
684 static bool use_connect_proxy(git_http_client *client)
685 {
686 return client->proxy.url.host && !strcmp(client->server.url.scheme, "https");
687 }
688
689 static int generate_request(
690 git_http_client *client,
691 git_http_request *request)
692 {
693 git_str *buf;
694 size_t i;
695 int error;
696
697 GIT_ASSERT_ARG(client);
698 GIT_ASSERT_ARG(request);
699
700 git_str_clear(&client->request_msg);
701 buf = &client->request_msg;
702
703 /* GET|POST path HTTP/1.1 */
704 git_str_puts(buf, name_for_method(request->method));
705 git_str_putc(buf, ' ');
706
707 if (request->proxy && strcmp(request->url->scheme, "https"))
708 git_net_url_fmt(buf, request->url);
709 else
710 git_net_url_fmt_path(buf, request->url);
711
712 git_str_puts(buf, " HTTP/1.1\r\n");
713
714 git_str_puts(buf, "User-Agent: ");
715 git_http__user_agent(buf);
716 git_str_puts(buf, "\r\n");
717
718 git_str_puts(buf, "Host: ");
719 puts_host_and_port(buf, request->url, false);
720 git_str_puts(buf, "\r\n");
721
722 if (request->accept)
723 git_str_printf(buf, "Accept: %s\r\n", request->accept);
724 else
725 git_str_puts(buf, "Accept: */*\r\n");
726
727 if (request->content_type)
728 git_str_printf(buf, "Content-Type: %s\r\n",
729 request->content_type);
730
731 if (request->chunked)
732 git_str_puts(buf, "Transfer-Encoding: chunked\r\n");
733
734 if (request->content_length > 0)
735 git_str_printf(buf, "Content-Length: %"PRIuZ "\r\n",
736 request->content_length);
737
738 if (request->expect_continue)
739 git_str_printf(buf, "Expect: 100-continue\r\n");
740
741 if ((error = apply_server_credentials(buf, client, request)) < 0 ||
742 (!use_connect_proxy(client) &&
743 (error = apply_proxy_credentials(buf, client, request)) < 0))
744 return error;
745
746 if (request->custom_headers) {
747 for (i = 0; i < request->custom_headers->count; i++) {
748 const char *hdr = request->custom_headers->strings[i];
749
750 if (hdr)
751 git_str_printf(buf, "%s\r\n", hdr);
752 }
753 }
754
755 git_str_puts(buf, "\r\n");
756
757 if (git_str_oom(buf))
758 return -1;
759
760 return 0;
761 }
762
763 static int check_certificate(
764 git_stream *stream,
765 git_net_url *url,
766 int is_valid,
767 git_transport_certificate_check_cb cert_cb,
768 void *cert_cb_payload)
769 {
770 git_cert *cert;
771 git_error_state last_error = {0};
772 int error;
773
774 if ((error = git_stream_certificate(&cert, stream)) < 0)
775 return error;
776
777 git_error_state_capture(&last_error, GIT_ECERTIFICATE);
778
779 error = cert_cb(cert, is_valid, url->host, cert_cb_payload);
780
781 if (error == GIT_PASSTHROUGH && !is_valid)
782 return git_error_state_restore(&last_error);
783 else if (error == GIT_PASSTHROUGH)
784 error = 0;
785 else if (error && !git_error_last())
786 git_error_set(GIT_ERROR_HTTP,
787 "user rejected certificate for %s", url->host);
788
789 git_error_state_free(&last_error);
790 return error;
791 }
792
793 static int server_connect_stream(
794 git_http_server *server,
795 git_transport_certificate_check_cb cert_cb,
796 void *cb_payload)
797 {
798 int error;
799
800 GIT_ERROR_CHECK_VERSION(server->stream, GIT_STREAM_VERSION, "git_stream");
801
802 error = git_stream_connect(server->stream);
803
804 if (error && error != GIT_ECERTIFICATE)
805 return error;
806
807 if (git_stream_is_encrypted(server->stream) && cert_cb != NULL)
808 error = check_certificate(server->stream, &server->url, !error,
809 cert_cb, cb_payload);
810
811 return error;
812 }
813
814 static void reset_auth_connection(git_http_server *server)
815 {
816 /*
817 * If we've authenticated and we're doing "normal"
818 * authentication with a request affinity (Basic, Digest)
819 * then we want to _keep_ our context, since authentication
820 * survives even through non-keep-alive connections. If
821 * we've authenticated and we're doing connection-based
822 * authentication (NTLM, Negotiate) - indicated by the presence
823 * of an `is_complete` callback - then we need to restart
824 * authentication on a new connection.
825 */
826
827 if (server->auth_context &&
828 server->auth_context->connection_affinity)
829 free_auth_context(server);
830 }
831
832 /*
833 * Updates the server data structure with the new URL; returns 1 if the server
834 * has changed and we need to reconnect, returns 0 otherwise.
835 */
836 GIT_INLINE(int) server_setup_from_url(
837 git_http_server *server,
838 git_net_url *url)
839 {
840 if (!server->url.scheme || strcmp(server->url.scheme, url->scheme) ||
841 !server->url.host || strcmp(server->url.host, url->host) ||
842 !server->url.port || strcmp(server->url.port, url->port)) {
843 git__free(server->url.scheme);
844 git__free(server->url.host);
845 git__free(server->url.port);
846
847 server->url.scheme = git__strdup(url->scheme);
848 GIT_ERROR_CHECK_ALLOC(server->url.scheme);
849
850 server->url.host = git__strdup(url->host);
851 GIT_ERROR_CHECK_ALLOC(server->url.host);
852
853 server->url.port = git__strdup(url->port);
854 GIT_ERROR_CHECK_ALLOC(server->url.port);
855
856 return 1;
857 }
858
859 return 0;
860 }
861
862 static void reset_parser(git_http_client *client)
863 {
864 http_parser_init(&client->parser, HTTP_RESPONSE);
865 }
866
867 static int setup_hosts(
868 git_http_client *client,
869 git_http_request *request)
870 {
871 int ret, diff = 0;
872
873 GIT_ASSERT_ARG(client);
874 GIT_ASSERT_ARG(request);
875
876 GIT_ASSERT(request->url);
877
878 if ((ret = server_setup_from_url(&client->server, request->url)) < 0)
879 return ret;
880
881 diff |= ret;
882
883 if (request->proxy &&
884 (ret = server_setup_from_url(&client->proxy, request->proxy)) < 0)
885 return ret;
886
887 diff |= ret;
888
889 if (diff) {
890 free_auth_context(&client->server);
891 free_auth_context(&client->proxy);
892
893 client->connected = 0;
894 }
895
896 return 0;
897 }
898
899 GIT_INLINE(int) server_create_stream(git_http_server *server)
900 {
901 git_net_url *url = &server->url;
902
903 if (strcasecmp(url->scheme, "https") == 0)
904 return git_tls_stream_new(&server->stream, url->host, url->port);
905 else if (strcasecmp(url->scheme, "http") == 0)
906 return git_socket_stream_new(&server->stream, url->host, url->port);
907
908 git_error_set(GIT_ERROR_HTTP, "unknown http scheme '%s'", url->scheme);
909 return -1;
910 }
911
912 GIT_INLINE(void) save_early_response(
913 git_http_client *client,
914 git_http_response *response)
915 {
916 /* Buffer the response so we can return it in read_response */
917 client->state = HAS_EARLY_RESPONSE;
918
919 memcpy(&client->early_response, response, sizeof(git_http_response));
920 memset(response, 0, sizeof(git_http_response));
921 }
922
923 static int proxy_connect(
924 git_http_client *client,
925 git_http_request *request)
926 {
927 git_http_response response = {0};
928 int error;
929
930 if (!client->proxy_connected || !client->keepalive) {
931 git_trace(GIT_TRACE_DEBUG, "Connecting to proxy %s port %s",
932 client->proxy.url.host, client->proxy.url.port);
933
934 if ((error = server_create_stream(&client->proxy)) < 0 ||
935 (error = server_connect_stream(&client->proxy,
936 client->opts.proxy_certificate_check_cb,
937 client->opts.proxy_certificate_check_payload)) < 0)
938 goto done;
939
940 client->proxy_connected = 1;
941 }
942
943 client->current_server = PROXY;
944 client->state = SENDING_REQUEST;
945
946 if ((error = generate_connect_request(client, request)) < 0 ||
947 (error = client_write_request(client)) < 0)
948 goto done;
949
950 client->state = SENT_REQUEST;
951
952 if ((error = git_http_client_read_response(&response, client)) < 0 ||
953 (error = git_http_client_skip_body(client)) < 0)
954 goto done;
955
956 GIT_ASSERT(client->state == DONE);
957
958 if (response.status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
959 save_early_response(client, &response);
960
961 error = GIT_RETRY;
962 goto done;
963 } else if (response.status != GIT_HTTP_STATUS_OK) {
964 git_error_set(GIT_ERROR_HTTP, "proxy returned unexpected status: %d", response.status);
965 error = -1;
966 goto done;
967 }
968
969 reset_parser(client);
970 client->state = NONE;
971
972 done:
973 git_http_response_dispose(&response);
974 return error;
975 }
976
977 static int server_connect(git_http_client *client)
978 {
979 git_net_url *url = &client->server.url;
980 git_transport_certificate_check_cb cert_cb;
981 void *cert_payload;
982 int error;
983
984 client->current_server = SERVER;
985
986 if (client->proxy.stream)
987 error = git_tls_stream_wrap(&client->server.stream, client->proxy.stream, url->host);
988 else
989 error = server_create_stream(&client->server);
990
991 if (error < 0)
992 goto done;
993
994 cert_cb = client->opts.server_certificate_check_cb;
995 cert_payload = client->opts.server_certificate_check_payload;
996
997 error = server_connect_stream(&client->server, cert_cb, cert_payload);
998
999 done:
1000 return error;
1001 }
1002
1003 GIT_INLINE(void) close_stream(git_http_server *server)
1004 {
1005 if (server->stream) {
1006 git_stream_close(server->stream);
1007 git_stream_free(server->stream);
1008 server->stream = NULL;
1009 }
1010 }
1011
1012 static int http_client_connect(
1013 git_http_client *client,
1014 git_http_request *request)
1015 {
1016 bool use_proxy = false;
1017 int error;
1018
1019 if ((error = setup_hosts(client, request)) < 0)
1020 goto on_error;
1021
1022 /* We're connected to our destination server; no need to reconnect */
1023 if (client->connected && client->keepalive &&
1024 (client->state == NONE || client->state == DONE))
1025 return 0;
1026
1027 client->connected = 0;
1028 client->request_count = 0;
1029
1030 close_stream(&client->server);
1031 reset_auth_connection(&client->server);
1032
1033 reset_parser(client);
1034
1035 /* Reconnect to the proxy if necessary. */
1036 use_proxy = use_connect_proxy(client);
1037
1038 if (use_proxy) {
1039 if (!client->proxy_connected || !client->keepalive ||
1040 (client->state != NONE && client->state != DONE)) {
1041 close_stream(&client->proxy);
1042 reset_auth_connection(&client->proxy);
1043
1044 client->proxy_connected = 0;
1045 }
1046
1047 if ((error = proxy_connect(client, request)) < 0)
1048 goto on_error;
1049 }
1050
1051 git_trace(GIT_TRACE_DEBUG, "Connecting to remote %s port %s",
1052 client->server.url.host, client->server.url.port);
1053
1054 if ((error = server_connect(client)) < 0)
1055 goto on_error;
1056
1057 client->connected = 1;
1058 return error;
1059
1060 on_error:
1061 if (error != GIT_RETRY)
1062 close_stream(&client->proxy);
1063
1064 close_stream(&client->server);
1065 return error;
1066 }
1067
1068 GIT_INLINE(int) client_read(git_http_client *client)
1069 {
1070 http_parser_context *parser_context = client->parser.data;
1071 git_stream *stream;
1072 char *buf = client->read_buf.ptr + client->read_buf.size;
1073 size_t max_len;
1074 ssize_t read_len;
1075
1076 stream = client->current_server == PROXY ?
1077 client->proxy.stream : client->server.stream;
1078
1079 /*
1080 * We use a git_str for convenience, but statically allocate it and
1081 * don't resize. Limit our consumption to INT_MAX since calling
1082 * functions use an int return type to return number of bytes read.
1083 */
1084 max_len = client->read_buf.asize - client->read_buf.size;
1085 max_len = min(max_len, INT_MAX);
1086
1087 if (parser_context->output_size)
1088 max_len = min(max_len, parser_context->output_size);
1089
1090 if (max_len == 0) {
1091 git_error_set(GIT_ERROR_HTTP, "no room in output buffer");
1092 return -1;
1093 }
1094
1095 read_len = git_stream_read(stream, buf, max_len);
1096
1097 if (read_len >= 0) {
1098 client->read_buf.size += read_len;
1099
1100 git_trace(GIT_TRACE_TRACE, "Received:\n%.*s",
1101 (int)read_len, buf);
1102 }
1103
1104 return (int)read_len;
1105 }
1106
1107 static bool parser_settings_initialized;
1108 static http_parser_settings parser_settings;
1109
1110 GIT_INLINE(http_parser_settings *) http_client_parser_settings(void)
1111 {
1112 if (!parser_settings_initialized) {
1113 parser_settings.on_header_field = on_header_field;
1114 parser_settings.on_header_value = on_header_value;
1115 parser_settings.on_headers_complete = on_headers_complete;
1116 parser_settings.on_body = on_body;
1117 parser_settings.on_message_complete = on_message_complete;
1118
1119 parser_settings_initialized = true;
1120 }
1121
1122 return &parser_settings;
1123 }
1124
1125 GIT_INLINE(int) client_read_and_parse(git_http_client *client)
1126 {
1127 http_parser *parser = &client->parser;
1128 http_parser_context *ctx = (http_parser_context *) parser->data;
1129 unsigned char http_errno;
1130 int read_len;
1131 size_t parsed_len;
1132
1133 /*
1134 * If we have data in our read buffer, that means we stopped early
1135 * when parsing headers. Use the data in the read buffer instead of
1136 * reading more from the socket.
1137 */
1138 if (!client->read_buf.size && (read_len = client_read(client)) < 0)
1139 return read_len;
1140
1141 parsed_len = http_parser_execute(parser,
1142 http_client_parser_settings(),
1143 client->read_buf.ptr,
1144 client->read_buf.size);
1145 http_errno = client->parser.http_errno;
1146
1147 if (parsed_len > INT_MAX) {
1148 git_error_set(GIT_ERROR_HTTP, "unexpectedly large parse");
1149 return -1;
1150 }
1151
1152 if (ctx->parse_status == PARSE_STATUS_ERROR) {
1153 client->connected = 0;
1154 return ctx->error ? ctx->error : -1;
1155 }
1156
1157 /*
1158 * If we finished reading the headers or body, we paused parsing.
1159 * Otherwise the parser will start filling the body, or even parse
1160 * a new response if the server pipelined us multiple responses.
1161 * (This can happen in response to an expect/continue request,
1162 * where the server gives you a 100 and 200 simultaneously.)
1163 */
1164 if (http_errno == HPE_PAUSED) {
1165 /*
1166 * http-parser has a "feature" where it will not deliver the
1167 * final byte when paused in a callback. Consume that byte.
1168 * https://github.com/nodejs/http-parser/issues/97
1169 */
1170 GIT_ASSERT(client->read_buf.size > parsed_len);
1171
1172 http_parser_pause(parser, 0);
1173
1174 parsed_len += http_parser_execute(parser,
1175 http_client_parser_settings(),
1176 client->read_buf.ptr + parsed_len,
1177 1);
1178 }
1179
1180 /* Most failures will be reported in http_errno */
1181 else if (parser->http_errno != HPE_OK) {
1182 git_error_set(GIT_ERROR_HTTP, "http parser error: %s",
1183 http_errno_description(http_errno));
1184 return -1;
1185 }
1186
1187 /* Otherwise we should have consumed the entire buffer. */
1188 else if (parsed_len != client->read_buf.size) {
1189 git_error_set(GIT_ERROR_HTTP,
1190 "http parser did not consume entire buffer: %s",
1191 http_errno_description(http_errno));
1192 return -1;
1193 }
1194
1195 /* recv returned 0, the server hung up on us */
1196 else if (!parsed_len) {
1197 git_error_set(GIT_ERROR_HTTP, "unexpected EOF");
1198 return -1;
1199 }
1200
1201 git_str_consume_bytes(&client->read_buf, parsed_len);
1202
1203 return (int)parsed_len;
1204 }
1205
1206 /*
1207 * See if we've consumed the entire response body. If the client was
1208 * reading the body but did not consume it entirely, it's possible that
1209 * they knew that the stream had finished (in a git response, seeing a
1210 * final flush) and stopped reading. But if the response was chunked,
1211 * we may have not consumed the final chunk marker. Consume it to
1212 * ensure that we don't have it waiting in our socket. If there's
1213 * more than just a chunk marker, close the connection.
1214 */
1215 static void complete_response_body(git_http_client *client)
1216 {
1217 http_parser_context parser_context = {0};
1218
1219 /* If we're not keeping alive, don't bother. */
1220 if (!client->keepalive) {
1221 client->connected = 0;
1222 goto done;
1223 }
1224
1225 parser_context.client = client;
1226 client->parser.data = &parser_context;
1227
1228 /* If there was an error, just close the connection. */
1229 if (client_read_and_parse(client) < 0 ||
1230 parser_context.error != HPE_OK ||
1231 (parser_context.parse_status != PARSE_STATUS_OK &&
1232 parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) {
1233 git_error_clear();
1234 client->connected = 0;
1235 }
1236
1237 done:
1238 git_str_clear(&client->read_buf);
1239 }
1240
1241 int git_http_client_send_request(
1242 git_http_client *client,
1243 git_http_request *request)
1244 {
1245 git_http_response response = {0};
1246 int error = -1;
1247
1248 GIT_ASSERT_ARG(client);
1249 GIT_ASSERT_ARG(request);
1250
1251 /* If the client did not finish reading, clean up the stream. */
1252 if (client->state == READING_BODY)
1253 complete_response_body(client);
1254
1255 /* If we're waiting for proxy auth, don't sending more requests. */
1256 if (client->state == HAS_EARLY_RESPONSE)
1257 return 0;
1258
1259 if (git_trace_level() >= GIT_TRACE_DEBUG) {
1260 git_str url = GIT_STR_INIT;
1261 git_net_url_fmt(&url, request->url);
1262 git_trace(GIT_TRACE_DEBUG, "Sending %s request to %s",
1263 name_for_method(request->method),
1264 url.ptr ? url.ptr : "<invalid>");
1265 git_str_dispose(&url);
1266 }
1267
1268 if ((error = http_client_connect(client, request)) < 0 ||
1269 (error = generate_request(client, request)) < 0 ||
1270 (error = client_write_request(client)) < 0)
1271 goto done;
1272
1273 client->state = SENT_REQUEST;
1274
1275 if (request->expect_continue) {
1276 if ((error = git_http_client_read_response(&response, client)) < 0 ||
1277 (error = git_http_client_skip_body(client)) < 0)
1278 goto done;
1279
1280 error = 0;
1281
1282 if (response.status != GIT_HTTP_STATUS_CONTINUE) {
1283 save_early_response(client, &response);
1284 goto done;
1285 }
1286 }
1287
1288 if (request->content_length || request->chunked) {
1289 client->state = SENDING_BODY;
1290 client->request_body_len = request->content_length;
1291 client->request_body_remain = request->content_length;
1292 client->request_chunked = request->chunked;
1293 }
1294
1295 reset_parser(client);
1296
1297 done:
1298 if (error == GIT_RETRY)
1299 error = 0;
1300
1301 git_http_response_dispose(&response);
1302 return error;
1303 }
1304
1305 bool git_http_client_has_response(git_http_client *client)
1306 {
1307 return (client->state == HAS_EARLY_RESPONSE ||
1308 client->state > SENT_REQUEST);
1309 }
1310
1311 int git_http_client_send_body(
1312 git_http_client *client,
1313 const char *buffer,
1314 size_t buffer_len)
1315 {
1316 git_http_server *server;
1317 git_str hdr = GIT_STR_INIT;
1318 int error;
1319
1320 GIT_ASSERT_ARG(client);
1321
1322 /* If we're waiting for proxy auth, don't sending more requests. */
1323 if (client->state == HAS_EARLY_RESPONSE)
1324 return 0;
1325
1326 if (client->state != SENDING_BODY) {
1327 git_error_set(GIT_ERROR_HTTP, "client is in invalid state");
1328 return -1;
1329 }
1330
1331 if (!buffer_len)
1332 return 0;
1333
1334 server = &client->server;
1335
1336 if (client->request_body_len) {
1337 GIT_ASSERT(buffer_len <= client->request_body_remain);
1338
1339 if ((error = stream_write(server, buffer, buffer_len)) < 0)
1340 goto done;
1341
1342 client->request_body_remain -= buffer_len;
1343 } else {
1344 if ((error = git_str_printf(&hdr, "%" PRIxZ "\r\n", buffer_len)) < 0 ||
1345 (error = stream_write(server, hdr.ptr, hdr.size)) < 0 ||
1346 (error = stream_write(server, buffer, buffer_len)) < 0 ||
1347 (error = stream_write(server, "\r\n", 2)) < 0)
1348 goto done;
1349 }
1350
1351 done:
1352 git_str_dispose(&hdr);
1353 return error;
1354 }
1355
1356 static int complete_request(git_http_client *client)
1357 {
1358 int error = 0;
1359
1360 GIT_ASSERT_ARG(client);
1361 GIT_ASSERT(client->state == SENDING_BODY);
1362
1363 if (client->request_body_len && client->request_body_remain) {
1364 git_error_set(GIT_ERROR_HTTP, "truncated write");
1365 error = -1;
1366 } else if (client->request_chunked) {
1367 error = stream_write(&client->server, "0\r\n\r\n", 5);
1368 }
1369
1370 client->state = SENT_REQUEST;
1371 return error;
1372 }
1373
1374 int git_http_client_read_response(
1375 git_http_response *response,
1376 git_http_client *client)
1377 {
1378 http_parser_context parser_context = {0};
1379 int error;
1380
1381 GIT_ASSERT_ARG(response);
1382 GIT_ASSERT_ARG(client);
1383
1384 if (client->state == SENDING_BODY) {
1385 if ((error = complete_request(client)) < 0)
1386 goto done;
1387 }
1388
1389 if (client->state == HAS_EARLY_RESPONSE) {
1390 memcpy(response, &client->early_response, sizeof(git_http_response));
1391 memset(&client->early_response, 0, sizeof(git_http_response));
1392 client->state = DONE;
1393 return 0;
1394 }
1395
1396 if (client->state != SENT_REQUEST) {
1397 git_error_set(GIT_ERROR_HTTP, "client is in invalid state");
1398 error = -1;
1399 goto done;
1400 }
1401
1402 git_http_response_dispose(response);
1403
1404 if (client->current_server == PROXY) {
1405 git_vector_free_deep(&client->proxy.auth_challenges);
1406 } else if(client->current_server == SERVER) {
1407 git_vector_free_deep(&client->server.auth_challenges);
1408 }
1409
1410 client->state = READING_RESPONSE;
1411 client->keepalive = 0;
1412 client->parser.data = &parser_context;
1413
1414 parser_context.client = client;
1415 parser_context.response = response;
1416
1417 while (client->state == READING_RESPONSE) {
1418 if ((error = client_read_and_parse(client)) < 0)
1419 goto done;
1420 }
1421
1422 GIT_ASSERT(client->state == READING_BODY || client->state == DONE);
1423
1424 done:
1425 git_str_dispose(&parser_context.parse_header_name);
1426 git_str_dispose(&parser_context.parse_header_value);
1427
1428 return error;
1429 }
1430
1431 int git_http_client_read_body(
1432 git_http_client *client,
1433 char *buffer,
1434 size_t buffer_size)
1435 {
1436 http_parser_context parser_context = {0};
1437 int error = 0;
1438
1439 if (client->state == DONE)
1440 return 0;
1441
1442 if (client->state != READING_BODY) {
1443 git_error_set(GIT_ERROR_HTTP, "client is in invalid state");
1444 return -1;
1445 }
1446
1447 /*
1448 * Now we'll read from the socket and http_parser will pipeline the
1449 * data directly to the client.
1450 */
1451
1452 parser_context.client = client;
1453 parser_context.output_buf = buffer;
1454 parser_context.output_size = buffer_size;
1455
1456 client->parser.data = &parser_context;
1457
1458 /*
1459 * Clients expect to get a non-zero amount of data from us,
1460 * so we either block until we have data to return, until we
1461 * hit EOF or there's an error. Do this in a loop, since we
1462 * may end up reading only some stream metadata (like chunk
1463 * information).
1464 */
1465 while (!parser_context.output_written) {
1466 error = client_read_and_parse(client);
1467
1468 if (error <= 0)
1469 goto done;
1470
1471 if (client->state == DONE)
1472 break;
1473 }
1474
1475 GIT_ASSERT(parser_context.output_written <= INT_MAX);
1476 error = (int)parser_context.output_written;
1477
1478 done:
1479 if (error < 0)
1480 client->connected = 0;
1481
1482 return error;
1483 }
1484
1485 int git_http_client_skip_body(git_http_client *client)
1486 {
1487 http_parser_context parser_context = {0};
1488 int error;
1489
1490 if (client->state == DONE)
1491 return 0;
1492
1493 if (client->state != READING_BODY) {
1494 git_error_set(GIT_ERROR_HTTP, "client is in invalid state");
1495 return -1;
1496 }
1497
1498 parser_context.client = client;
1499 client->parser.data = &parser_context;
1500
1501 do {
1502 error = client_read_and_parse(client);
1503
1504 if (parser_context.error != HPE_OK ||
1505 (parser_context.parse_status != PARSE_STATUS_OK &&
1506 parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) {
1507 git_error_set(GIT_ERROR_HTTP,
1508 "unexpected data handled in callback");
1509 error = -1;
1510 }
1511 } while (error >= 0 && client->state != DONE);
1512
1513 if (error < 0)
1514 client->connected = 0;
1515
1516 return error;
1517 }
1518
1519 /*
1520 * Create an http_client capable of communicating with the given remote
1521 * host.
1522 */
1523 int git_http_client_new(
1524 git_http_client **out,
1525 git_http_client_options *opts)
1526 {
1527 git_http_client *client;
1528
1529 GIT_ASSERT_ARG(out);
1530
1531 client = git__calloc(1, sizeof(git_http_client));
1532 GIT_ERROR_CHECK_ALLOC(client);
1533
1534 git_str_init(&client->read_buf, GIT_READ_BUFFER_SIZE);
1535 GIT_ERROR_CHECK_ALLOC(client->read_buf.ptr);
1536
1537 if (opts)
1538 memcpy(&client->opts, opts, sizeof(git_http_client_options));
1539
1540 *out = client;
1541 return 0;
1542 }
1543
1544 GIT_INLINE(void) http_server_close(git_http_server *server)
1545 {
1546 if (server->stream) {
1547 git_stream_close(server->stream);
1548 git_stream_free(server->stream);
1549 server->stream = NULL;
1550 }
1551
1552 git_net_url_dispose(&server->url);
1553
1554 git_vector_free_deep(&server->auth_challenges);
1555 free_auth_context(server);
1556 }
1557
1558 static void http_client_close(git_http_client *client)
1559 {
1560 http_server_close(&client->server);
1561 http_server_close(&client->proxy);
1562
1563 git_str_dispose(&client->request_msg);
1564
1565 client->state = 0;
1566 client->request_count = 0;
1567 client->connected = 0;
1568 client->keepalive = 0;
1569 }
1570
1571 void git_http_client_free(git_http_client *client)
1572 {
1573 if (!client)
1574 return;
1575
1576 http_client_close(client);
1577 git_str_dispose(&client->read_buf);
1578 git__free(client);
1579 }