]> git.proxmox.com Git - libgit2.git/blob - src/transports/httpclient.c
New upstream version 1.0.1+dfsg.1
[libgit2.git] / src / 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 "global.h"
14 #include "httpclient.h"
15 #include "http.h"
16 #include "auth.h"
17 #include "auth_negotiate.h"
18 #include "auth_ntlm.h"
19 #include "git2/sys/credential.h"
20 #include "net.h"
21 #include "stream.h"
22 #include "streams/socket.h"
23 #include "streams/tls.h"
24 #include "auth.h"
25
26 static git_http_auth_scheme auth_schemes[] = {
27 { GIT_HTTP_AUTH_NEGOTIATE, "Negotiate", GIT_CREDENTIAL_DEFAULT, git_http_auth_negotiate },
28 { GIT_HTTP_AUTH_NTLM, "NTLM", GIT_CREDENTIAL_USERPASS_PLAINTEXT, git_http_auth_ntlm },
29 { GIT_HTTP_AUTH_BASIC, "Basic", GIT_CREDENTIAL_USERPASS_PLAINTEXT, git_http_auth_basic },
30 };
31
32 /*
33 * Use a 16kb read buffer to match the maximum size of a TLS packet. This
34 * is critical for compatibility with SecureTransport, which will always do
35 * a network read on every call, even if it has data buffered to return to
36 * you. That buffered data may be the _end_ of a keep-alive response, so
37 * if SecureTransport performs another network read, it will wait until the
38 * server ultimately times out before it returns that buffered data to you.
39 * Since SecureTransport only reads a single TLS packet at a time, by
40 * calling it with a read buffer that is the maximum size of a TLS packet,
41 * we ensure that it will never buffer.
42 */
43 #define GIT_READ_BUFFER_SIZE (16 * 1024)
44
45 typedef struct {
46 git_net_url url;
47 git_stream *stream;
48
49 git_vector auth_challenges;
50 git_http_auth_context *auth_context;
51 } git_http_server;
52
53 typedef enum {
54 PROXY = 1,
55 SERVER
56 } git_http_server_t;
57
58 typedef enum {
59 NONE = 0,
60 SENDING_REQUEST,
61 SENDING_BODY,
62 SENT_REQUEST,
63 HAS_EARLY_RESPONSE,
64 READING_RESPONSE,
65 READING_BODY,
66 DONE
67 } http_client_state;
68
69 /* Parser state */
70 typedef enum {
71 PARSE_HEADER_NONE = 0,
72 PARSE_HEADER_NAME,
73 PARSE_HEADER_VALUE,
74 PARSE_HEADER_COMPLETE
75 } parse_header_state;
76
77 typedef enum {
78 PARSE_STATUS_OK,
79 PARSE_STATUS_NO_OUTPUT,
80 PARSE_STATUS_ERROR
81 } parse_status;
82
83 typedef struct {
84 git_http_client *client;
85 git_http_response *response;
86
87 /* Temporary buffers to avoid extra mallocs */
88 git_buf parse_header_name;
89 git_buf parse_header_value;
90
91 /* Parser state */
92 int error;
93 parse_status parse_status;
94
95 /* Headers parsing */
96 parse_header_state parse_header_state;
97
98 /* Body parsing */
99 char *output_buf; /* Caller's output buffer */
100 size_t output_size; /* Size of caller's output buffer */
101 size_t output_written; /* Bytes we've written to output buffer */
102 } http_parser_context;
103
104 /* HTTP client connection */
105 struct git_http_client {
106 git_http_client_options opts;
107
108 /* Are we writing to the proxy or server, and state of the client. */
109 git_http_server_t current_server;
110 http_client_state state;
111
112 http_parser parser;
113
114 git_http_server server;
115 git_http_server proxy;
116
117 unsigned request_count;
118 unsigned connected : 1,
119 proxy_connected : 1,
120 keepalive : 1,
121 request_chunked : 1;
122
123 /* Temporary buffers to avoid extra mallocs */
124 git_buf request_msg;
125 git_buf read_buf;
126
127 /* A subset of information from the request */
128 size_t request_body_len,
129 request_body_remain;
130
131 /*
132 * When state == HAS_EARLY_RESPONSE, the response of our proxy
133 * that we have buffered and will deliver during read_response.
134 */
135 git_http_response early_response;
136 };
137
138 bool git_http_response_is_redirect(git_http_response *response)
139 {
140 return (response->status == GIT_HTTP_MOVED_PERMANENTLY ||
141 response->status == GIT_HTTP_FOUND ||
142 response->status == GIT_HTTP_SEE_OTHER ||
143 response->status == GIT_HTTP_TEMPORARY_REDIRECT ||
144 response->status == GIT_HTTP_PERMANENT_REDIRECT);
145 }
146
147 void git_http_response_dispose(git_http_response *response)
148 {
149 assert(response);
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_buf *name = &ctx->parse_header_name;
164 git_buf *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_buf_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_buf_clear(&ctx->parse_header_name);
236 git_buf_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_buf_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_buf_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 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 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_buf *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_buf token = GIT_BUF_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 = -1;
601 goto done;
602 }
603
604 if (token.size > 0)
605 error = git_buf_printf(buf, "%s: %s\r\n", header_name, token.ptr);
606
607 done:
608 git_buf_dispose(&token);
609 return error;
610 }
611
612 GIT_INLINE(int) apply_server_credentials(
613 git_buf *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_buf *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 generate_connect_request(
635 git_http_client *client,
636 git_http_request *request)
637 {
638 git_buf *buf;
639 int error;
640
641 git_buf_clear(&client->request_msg);
642 buf = &client->request_msg;
643
644 git_buf_printf(buf, "CONNECT %s:%s HTTP/1.1\r\n",
645 client->server.url.host, client->server.url.port);
646
647 git_buf_puts(buf, "User-Agent: ");
648 git_http__user_agent(buf);
649 git_buf_puts(buf, "\r\n");
650
651 git_buf_printf(buf, "Host: %s\r\n", client->proxy.url.host);
652
653 if ((error = apply_proxy_credentials(buf, client, request) < 0))
654 return -1;
655
656 git_buf_puts(buf, "\r\n");
657
658 return git_buf_oom(buf) ? -1 : 0;
659 }
660
661 static int generate_request(
662 git_http_client *client,
663 git_http_request *request)
664 {
665 git_buf *buf;
666 size_t i;
667 int error;
668
669 assert(client && request);
670
671 git_buf_clear(&client->request_msg);
672 buf = &client->request_msg;
673
674 /* GET|POST path HTTP/1.1 */
675 git_buf_puts(buf, name_for_method(request->method));
676 git_buf_putc(buf, ' ');
677
678 if (request->proxy && strcmp(request->url->scheme, "https"))
679 git_net_url_fmt(buf, request->url);
680 else
681 git_net_url_fmt_path(buf, request->url);
682
683 git_buf_puts(buf, " HTTP/1.1\r\n");
684
685 git_buf_puts(buf, "User-Agent: ");
686 git_http__user_agent(buf);
687 git_buf_puts(buf, "\r\n");
688
689 git_buf_printf(buf, "Host: %s", request->url->host);
690
691 if (!git_net_url_is_default_port(request->url))
692 git_buf_printf(buf, ":%s", request->url->port);
693
694 git_buf_puts(buf, "\r\n");
695
696 if (request->accept)
697 git_buf_printf(buf, "Accept: %s\r\n", request->accept);
698 else
699 git_buf_puts(buf, "Accept: */*\r\n");
700
701 if (request->content_type)
702 git_buf_printf(buf, "Content-Type: %s\r\n",
703 request->content_type);
704
705 if (request->chunked)
706 git_buf_puts(buf, "Transfer-Encoding: chunked\r\n");
707
708 if (request->content_length > 0)
709 git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n",
710 request->content_length);
711
712 if (request->expect_continue)
713 git_buf_printf(buf, "Expect: 100-continue\r\n");
714
715 if ((error = apply_server_credentials(buf, client, request)) < 0 ||
716 (error = apply_proxy_credentials(buf, client, request)) < 0)
717 return error;
718
719 if (request->custom_headers) {
720 for (i = 0; i < request->custom_headers->count; i++) {
721 const char *hdr = request->custom_headers->strings[i];
722
723 if (hdr)
724 git_buf_printf(buf, "%s\r\n", hdr);
725 }
726 }
727
728 git_buf_puts(buf, "\r\n");
729
730 if (git_buf_oom(buf))
731 return -1;
732
733 return 0;
734 }
735
736 static int check_certificate(
737 git_stream *stream,
738 git_net_url *url,
739 int is_valid,
740 git_transport_certificate_check_cb cert_cb,
741 void *cert_cb_payload)
742 {
743 git_cert *cert;
744 git_error_state last_error = {0};
745 int error;
746
747 if ((error = git_stream_certificate(&cert, stream)) < 0)
748 return error;
749
750 git_error_state_capture(&last_error, GIT_ECERTIFICATE);
751
752 error = cert_cb(cert, is_valid, url->host, cert_cb_payload);
753
754 if (error == GIT_PASSTHROUGH && !is_valid)
755 return git_error_state_restore(&last_error);
756 else if (error == GIT_PASSTHROUGH)
757 error = 0;
758 else if (error && !git_error_last())
759 git_error_set(GIT_ERROR_HTTP,
760 "user rejected certificate for %s", url->host);
761
762 git_error_state_free(&last_error);
763 return error;
764 }
765
766 static int server_connect_stream(
767 git_http_server *server,
768 git_transport_certificate_check_cb cert_cb,
769 void *cb_payload)
770 {
771 int error;
772
773 GIT_ERROR_CHECK_VERSION(server->stream, GIT_STREAM_VERSION, "git_stream");
774
775 error = git_stream_connect(server->stream);
776
777 if (error && error != GIT_ECERTIFICATE)
778 return error;
779
780 if (git_stream_is_encrypted(server->stream) && cert_cb != NULL)
781 error = check_certificate(server->stream, &server->url, !error,
782 cert_cb, cb_payload);
783
784 return error;
785 }
786
787 static void reset_auth_connection(git_http_server *server)
788 {
789 /*
790 * If we've authenticated and we're doing "normal"
791 * authentication with a request affinity (Basic, Digest)
792 * then we want to _keep_ our context, since authentication
793 * survives even through non-keep-alive connections. If
794 * we've authenticated and we're doing connection-based
795 * authentication (NTLM, Negotiate) - indicated by the presence
796 * of an `is_complete` callback - then we need to restart
797 * authentication on a new connection.
798 */
799
800 if (server->auth_context &&
801 server->auth_context->connection_affinity)
802 free_auth_context(server);
803 }
804
805 /*
806 * Updates the server data structure with the new URL; returns 1 if the server
807 * has changed and we need to reconnect, returns 0 otherwise.
808 */
809 GIT_INLINE(int) server_setup_from_url(
810 git_http_server *server,
811 git_net_url *url)
812 {
813 if (!server->url.scheme || strcmp(server->url.scheme, url->scheme) ||
814 !server->url.host || strcmp(server->url.host, url->host) ||
815 !server->url.port || strcmp(server->url.port, url->port)) {
816 git__free(server->url.scheme);
817 git__free(server->url.host);
818 git__free(server->url.port);
819
820 server->url.scheme = git__strdup(url->scheme);
821 GIT_ERROR_CHECK_ALLOC(server->url.scheme);
822
823 server->url.host = git__strdup(url->host);
824 GIT_ERROR_CHECK_ALLOC(server->url.host);
825
826 server->url.port = git__strdup(url->port);
827 GIT_ERROR_CHECK_ALLOC(server->url.port);
828
829 return 1;
830 }
831
832 return 0;
833 }
834
835 static void reset_parser(git_http_client *client)
836 {
837 http_parser_init(&client->parser, HTTP_RESPONSE);
838 }
839
840 static int setup_hosts(
841 git_http_client *client,
842 git_http_request *request)
843 {
844 int ret, diff = 0;
845
846 assert(client && request && request->url);
847
848 if ((ret = server_setup_from_url(&client->server, request->url)) < 0)
849 return ret;
850
851 diff |= ret;
852
853 if (request->proxy &&
854 (ret = server_setup_from_url(&client->proxy, request->proxy)) < 0)
855 return ret;
856
857 diff |= ret;
858
859 if (diff) {
860 free_auth_context(&client->server);
861 free_auth_context(&client->proxy);
862
863 client->connected = 0;
864 }
865
866 return 0;
867 }
868
869 GIT_INLINE(int) server_create_stream(git_http_server *server)
870 {
871 git_net_url *url = &server->url;
872
873 if (strcasecmp(url->scheme, "https") == 0)
874 return git_tls_stream_new(&server->stream, url->host, url->port);
875 else if (strcasecmp(url->scheme, "http") == 0)
876 return git_socket_stream_new(&server->stream, url->host, url->port);
877
878 git_error_set(GIT_ERROR_HTTP, "unknown http scheme '%s'", url->scheme);
879 return -1;
880 }
881
882 GIT_INLINE(void) save_early_response(
883 git_http_client *client,
884 git_http_response *response)
885 {
886 /* Buffer the response so we can return it in read_response */
887 client->state = HAS_EARLY_RESPONSE;
888
889 memcpy(&client->early_response, response, sizeof(git_http_response));
890 memset(response, 0, sizeof(git_http_response));
891 }
892
893 static int proxy_connect(
894 git_http_client *client,
895 git_http_request *request)
896 {
897 git_http_response response = {0};
898 int error;
899
900 if (!client->proxy_connected || !client->keepalive) {
901 git_trace(GIT_TRACE_DEBUG, "Connecting to proxy %s:%s",
902 client->proxy.url.host, client->proxy.url.port);
903
904 if ((error = server_create_stream(&client->proxy)) < 0 ||
905 (error = server_connect_stream(&client->proxy,
906 client->opts.proxy_certificate_check_cb,
907 client->opts.proxy_certificate_check_payload)) < 0)
908 goto done;
909
910 client->proxy_connected = 1;
911 }
912
913 client->current_server = PROXY;
914 client->state = SENDING_REQUEST;
915
916 if ((error = generate_connect_request(client, request)) < 0 ||
917 (error = client_write_request(client)) < 0)
918 goto done;
919
920 client->state = SENT_REQUEST;
921
922 if ((error = git_http_client_read_response(&response, client)) < 0 ||
923 (error = git_http_client_skip_body(client)) < 0)
924 goto done;
925
926 assert(client->state == DONE);
927
928 if (response.status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
929 save_early_response(client, &response);
930
931 error = GIT_RETRY;
932 goto done;
933 } else if (response.status != GIT_HTTP_STATUS_OK) {
934 git_error_set(GIT_ERROR_HTTP, "proxy returned unexpected status: %d", response.status);
935 error = -1;
936 goto done;
937 }
938
939 reset_parser(client);
940 client->state = NONE;
941
942 done:
943 git_http_response_dispose(&response);
944 return error;
945 }
946
947 static int server_connect(git_http_client *client)
948 {
949 git_net_url *url = &client->server.url;
950 git_transport_certificate_check_cb cert_cb;
951 void *cert_payload;
952 int error;
953
954 client->current_server = SERVER;
955
956 if (client->proxy.stream)
957 error = git_tls_stream_wrap(&client->server.stream, client->proxy.stream, url->host);
958 else
959 error = server_create_stream(&client->server);
960
961 if (error < 0)
962 goto done;
963
964 cert_cb = client->opts.server_certificate_check_cb;
965 cert_payload = client->opts.server_certificate_check_payload;
966
967 error = server_connect_stream(&client->server, cert_cb, cert_payload);
968
969 done:
970 return error;
971 }
972
973 GIT_INLINE(void) close_stream(git_http_server *server)
974 {
975 if (server->stream) {
976 git_stream_close(server->stream);
977 git_stream_free(server->stream);
978 server->stream = NULL;
979 }
980 }
981
982 static int http_client_connect(
983 git_http_client *client,
984 git_http_request *request)
985 {
986 bool use_proxy = false;
987 int error;
988
989 if ((error = setup_hosts(client, request)) < 0)
990 goto on_error;
991
992 /* We're connected to our destination server; no need to reconnect */
993 if (client->connected && client->keepalive &&
994 (client->state == NONE || client->state == DONE))
995 return 0;
996
997 client->connected = 0;
998 client->request_count = 0;
999
1000 close_stream(&client->server);
1001 reset_auth_connection(&client->server);
1002
1003 reset_parser(client);
1004
1005 /* Reconnect to the proxy if necessary. */
1006 use_proxy = client->proxy.url.host &&
1007 !strcmp(client->server.url.scheme, "https");
1008
1009 if (use_proxy) {
1010 if (!client->proxy_connected || !client->keepalive ||
1011 (client->state != NONE && client->state != DONE)) {
1012 close_stream(&client->proxy);
1013 reset_auth_connection(&client->proxy);
1014
1015 client->proxy_connected = 0;
1016 }
1017
1018 if ((error = proxy_connect(client, request)) < 0)
1019 goto on_error;
1020 }
1021
1022 git_trace(GIT_TRACE_DEBUG, "Connecting to remote %s:%s",
1023 client->server.url.host, client->server.url.port);
1024
1025 if ((error = server_connect(client)) < 0)
1026 goto on_error;
1027
1028 client->connected = 1;
1029 return error;
1030
1031 on_error:
1032 if (error != GIT_RETRY)
1033 close_stream(&client->proxy);
1034
1035 close_stream(&client->server);
1036 return error;
1037 }
1038
1039 GIT_INLINE(int) client_read(git_http_client *client)
1040 {
1041 http_parser_context *parser_context = client->parser.data;
1042 git_stream *stream;
1043 char *buf = client->read_buf.ptr + client->read_buf.size;
1044 size_t max_len;
1045 ssize_t read_len;
1046
1047 stream = client->current_server == PROXY ?
1048 client->proxy.stream : client->server.stream;
1049
1050 /*
1051 * We use a git_buf for convenience, but statically allocate it and
1052 * don't resize. Limit our consumption to INT_MAX since calling
1053 * functions use an int return type to return number of bytes read.
1054 */
1055 max_len = client->read_buf.asize - client->read_buf.size;
1056 max_len = min(max_len, INT_MAX);
1057
1058 if (parser_context->output_size)
1059 max_len = min(max_len, parser_context->output_size);
1060
1061 if (max_len == 0) {
1062 git_error_set(GIT_ERROR_HTTP, "no room in output buffer");
1063 return -1;
1064 }
1065
1066 read_len = git_stream_read(stream, buf, max_len);
1067
1068 if (read_len >= 0) {
1069 client->read_buf.size += read_len;
1070
1071 git_trace(GIT_TRACE_TRACE, "Received:\n%.*s",
1072 (int)read_len, buf);
1073 }
1074
1075 return (int)read_len;
1076 }
1077
1078 static bool parser_settings_initialized;
1079 static http_parser_settings parser_settings;
1080
1081 GIT_INLINE(http_parser_settings *) http_client_parser_settings(void)
1082 {
1083 if (!parser_settings_initialized) {
1084 parser_settings.on_header_field = on_header_field;
1085 parser_settings.on_header_value = on_header_value;
1086 parser_settings.on_headers_complete = on_headers_complete;
1087 parser_settings.on_body = on_body;
1088 parser_settings.on_message_complete = on_message_complete;
1089
1090 parser_settings_initialized = true;
1091 }
1092
1093 return &parser_settings;
1094 }
1095
1096 GIT_INLINE(int) client_read_and_parse(git_http_client *client)
1097 {
1098 http_parser *parser = &client->parser;
1099 http_parser_context *ctx = (http_parser_context *) parser->data;
1100 unsigned char http_errno;
1101 int read_len;
1102 size_t parsed_len;
1103
1104 /*
1105 * If we have data in our read buffer, that means we stopped early
1106 * when parsing headers. Use the data in the read buffer instead of
1107 * reading more from the socket.
1108 */
1109 if (!client->read_buf.size && (read_len = client_read(client)) < 0)
1110 return read_len;
1111
1112 parsed_len = http_parser_execute(parser,
1113 http_client_parser_settings(),
1114 client->read_buf.ptr,
1115 client->read_buf.size);
1116 http_errno = client->parser.http_errno;
1117
1118 if (parsed_len > INT_MAX) {
1119 git_error_set(GIT_ERROR_HTTP, "unexpectedly large parse");
1120 return -1;
1121 }
1122
1123 if (parser->upgrade) {
1124 git_error_set(GIT_ERROR_HTTP, "server requested upgrade");
1125 return -1;
1126 }
1127
1128 if (ctx->parse_status == PARSE_STATUS_ERROR) {
1129 client->connected = 0;
1130 return ctx->error ? ctx->error : -1;
1131 }
1132
1133 /*
1134 * If we finished reading the headers or body, we paused parsing.
1135 * Otherwise the parser will start filling the body, or even parse
1136 * a new response if the server pipelined us multiple responses.
1137 * (This can happen in response to an expect/continue request,
1138 * where the server gives you a 100 and 200 simultaneously.)
1139 */
1140 if (http_errno == HPE_PAUSED) {
1141 /*
1142 * http-parser has a "feature" where it will not deliver the
1143 * final byte when paused in a callback. Consume that byte.
1144 * https://github.com/nodejs/http-parser/issues/97
1145 */
1146 assert(client->read_buf.size > parsed_len);
1147
1148 http_parser_pause(parser, 0);
1149
1150 parsed_len += http_parser_execute(parser,
1151 http_client_parser_settings(),
1152 client->read_buf.ptr + parsed_len,
1153 1);
1154 }
1155
1156 /* Most failures will be reported in http_errno */
1157 else if (parser->http_errno != HPE_OK) {
1158 git_error_set(GIT_ERROR_HTTP, "http parser error: %s",
1159 http_errno_description(http_errno));
1160 return -1;
1161 }
1162
1163 /* Otherwise we should have consumed the entire buffer. */
1164 else if (parsed_len != client->read_buf.size) {
1165 git_error_set(GIT_ERROR_HTTP,
1166 "http parser did not consume entire buffer: %s",
1167 http_errno_description(http_errno));
1168 return -1;
1169 }
1170
1171 /* recv returned 0, the server hung up on us */
1172 else if (!parsed_len) {
1173 git_error_set(GIT_ERROR_HTTP, "unexpected EOF");
1174 return -1;
1175 }
1176
1177 git_buf_consume_bytes(&client->read_buf, parsed_len);
1178
1179 return (int)parsed_len;
1180 }
1181
1182 /*
1183 * See if we've consumed the entire response body. If the client was
1184 * reading the body but did not consume it entirely, it's possible that
1185 * they knew that the stream had finished (in a git response, seeing a
1186 * final flush) and stopped reading. But if the response was chunked,
1187 * we may have not consumed the final chunk marker. Consume it to
1188 * ensure that we don't have it waiting in our socket. If there's
1189 * more than just a chunk marker, close the connection.
1190 */
1191 static void complete_response_body(git_http_client *client)
1192 {
1193 http_parser_context parser_context = {0};
1194
1195 /* If we're not keeping alive, don't bother. */
1196 if (!client->keepalive) {
1197 client->connected = 0;
1198 goto done;
1199 }
1200
1201 parser_context.client = client;
1202 client->parser.data = &parser_context;
1203
1204 /* If there was an error, just close the connection. */
1205 if (client_read_and_parse(client) < 0 ||
1206 parser_context.error != HPE_OK ||
1207 (parser_context.parse_status != PARSE_STATUS_OK &&
1208 parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) {
1209 git_error_clear();
1210 client->connected = 0;
1211 }
1212
1213 done:
1214 git_buf_clear(&client->read_buf);
1215 }
1216
1217 int git_http_client_send_request(
1218 git_http_client *client,
1219 git_http_request *request)
1220 {
1221 git_http_response response = {0};
1222 int error = -1;
1223
1224 assert(client && request);
1225
1226 /* If the client did not finish reading, clean up the stream. */
1227 if (client->state == READING_BODY)
1228 complete_response_body(client);
1229
1230 /* If we're waiting for proxy auth, don't sending more requests. */
1231 if (client->state == HAS_EARLY_RESPONSE)
1232 return 0;
1233
1234 if (git_trace_level() >= GIT_TRACE_DEBUG) {
1235 git_buf url = GIT_BUF_INIT;
1236 git_net_url_fmt(&url, request->url);
1237 git_trace(GIT_TRACE_DEBUG, "Sending %s request to %s",
1238 name_for_method(request->method),
1239 url.ptr ? url.ptr : "<invalid>");
1240 git_buf_dispose(&url);
1241 }
1242
1243 if ((error = http_client_connect(client, request)) < 0 ||
1244 (error = generate_request(client, request)) < 0 ||
1245 (error = client_write_request(client)) < 0)
1246 goto done;
1247
1248 client->state = SENT_REQUEST;
1249
1250 if (request->expect_continue) {
1251 if ((error = git_http_client_read_response(&response, client)) < 0 ||
1252 (error = git_http_client_skip_body(client)) < 0)
1253 goto done;
1254
1255 error = 0;
1256
1257 if (response.status != GIT_HTTP_STATUS_CONTINUE) {
1258 save_early_response(client, &response);
1259 goto done;
1260 }
1261 }
1262
1263 if (request->content_length || request->chunked) {
1264 client->state = SENDING_BODY;
1265 client->request_body_len = request->content_length;
1266 client->request_body_remain = request->content_length;
1267 client->request_chunked = request->chunked;
1268 }
1269
1270 reset_parser(client);
1271
1272 done:
1273 if (error == GIT_RETRY)
1274 error = 0;
1275
1276 git_http_response_dispose(&response);
1277 return error;
1278 }
1279
1280 bool git_http_client_has_response(git_http_client *client)
1281 {
1282 return (client->state == HAS_EARLY_RESPONSE ||
1283 client->state > SENT_REQUEST);
1284 }
1285
1286 int git_http_client_send_body(
1287 git_http_client *client,
1288 const char *buffer,
1289 size_t buffer_len)
1290 {
1291 git_http_server *server;
1292 git_buf hdr = GIT_BUF_INIT;
1293 int error;
1294
1295 assert(client);
1296
1297 /* If we're waiting for proxy auth, don't sending more requests. */
1298 if (client->state == HAS_EARLY_RESPONSE)
1299 return 0;
1300
1301 if (client->state != SENDING_BODY) {
1302 git_error_set(GIT_ERROR_HTTP, "client is in invalid state");
1303 return -1;
1304 }
1305
1306 if (!buffer_len)
1307 return 0;
1308
1309 server = &client->server;
1310
1311 if (client->request_body_len) {
1312 assert(buffer_len <= client->request_body_remain);
1313
1314 if ((error = stream_write(server, buffer, buffer_len)) < 0)
1315 goto done;
1316
1317 client->request_body_remain -= buffer_len;
1318 } else {
1319 if ((error = git_buf_printf(&hdr, "%" PRIxZ "\r\n", buffer_len)) < 0 ||
1320 (error = stream_write(server, hdr.ptr, hdr.size)) < 0 ||
1321 (error = stream_write(server, buffer, buffer_len)) < 0 ||
1322 (error = stream_write(server, "\r\n", 2)) < 0)
1323 goto done;
1324 }
1325
1326 done:
1327 git_buf_dispose(&hdr);
1328 return error;
1329 }
1330
1331 static int complete_request(git_http_client *client)
1332 {
1333 int error = 0;
1334
1335 assert(client && client->state == SENDING_BODY);
1336
1337 if (client->request_body_len && client->request_body_remain) {
1338 git_error_set(GIT_ERROR_HTTP, "truncated write");
1339 error = -1;
1340 } else if (client->request_chunked) {
1341 error = stream_write(&client->server, "0\r\n\r\n", 5);
1342 }
1343
1344 client->state = SENT_REQUEST;
1345 return error;
1346 }
1347
1348 int git_http_client_read_response(
1349 git_http_response *response,
1350 git_http_client *client)
1351 {
1352 http_parser_context parser_context = {0};
1353 int error;
1354
1355 assert(response && client);
1356
1357 if (client->state == SENDING_BODY) {
1358 if ((error = complete_request(client)) < 0)
1359 goto done;
1360 }
1361
1362 if (client->state == HAS_EARLY_RESPONSE) {
1363 memcpy(response, &client->early_response, sizeof(git_http_response));
1364 memset(&client->early_response, 0, sizeof(git_http_response));
1365 client->state = DONE;
1366 return 0;
1367 }
1368
1369 if (client->state != SENT_REQUEST) {
1370 git_error_set(GIT_ERROR_HTTP, "client is in invalid state");
1371 error = -1;
1372 goto done;
1373 }
1374
1375 git_http_response_dispose(response);
1376
1377 git_vector_free_deep(&client->server.auth_challenges);
1378 git_vector_free_deep(&client->proxy.auth_challenges);
1379
1380 client->state = READING_RESPONSE;
1381 client->keepalive = 0;
1382 client->parser.data = &parser_context;
1383
1384 parser_context.client = client;
1385 parser_context.response = response;
1386
1387 while (client->state == READING_RESPONSE) {
1388 if ((error = client_read_and_parse(client)) < 0)
1389 goto done;
1390 }
1391
1392 assert(client->state == READING_BODY || client->state == DONE);
1393
1394 done:
1395 git_buf_dispose(&parser_context.parse_header_name);
1396 git_buf_dispose(&parser_context.parse_header_value);
1397
1398 return error;
1399 }
1400
1401 int git_http_client_read_body(
1402 git_http_client *client,
1403 char *buffer,
1404 size_t buffer_size)
1405 {
1406 http_parser_context parser_context = {0};
1407 int error = 0;
1408
1409 if (client->state == DONE)
1410 return 0;
1411
1412 if (client->state != READING_BODY) {
1413 git_error_set(GIT_ERROR_HTTP, "client is in invalid state");
1414 return -1;
1415 }
1416
1417 /*
1418 * Now we'll read from the socket and http_parser will pipeline the
1419 * data directly to the client.
1420 */
1421
1422 parser_context.client = client;
1423 parser_context.output_buf = buffer;
1424 parser_context.output_size = buffer_size;
1425
1426 client->parser.data = &parser_context;
1427
1428 /*
1429 * Clients expect to get a non-zero amount of data from us,
1430 * so we either block until we have data to return, until we
1431 * hit EOF or there's an error. Do this in a loop, since we
1432 * may end up reading only some stream metadata (like chunk
1433 * information).
1434 */
1435 while (!parser_context.output_written) {
1436 error = client_read_and_parse(client);
1437
1438 if (error <= 0)
1439 goto done;
1440
1441 if (client->state == DONE)
1442 break;
1443 }
1444
1445 assert(parser_context.output_written <= INT_MAX);
1446 error = (int)parser_context.output_written;
1447
1448 done:
1449 if (error < 0)
1450 client->connected = 0;
1451
1452 return error;
1453 }
1454
1455 int git_http_client_skip_body(git_http_client *client)
1456 {
1457 http_parser_context parser_context = {0};
1458 int error;
1459
1460 if (client->state == DONE)
1461 return 0;
1462
1463 if (client->state != READING_BODY) {
1464 git_error_set(GIT_ERROR_HTTP, "client is in invalid state");
1465 return -1;
1466 }
1467
1468 parser_context.client = client;
1469 client->parser.data = &parser_context;
1470
1471 do {
1472 error = client_read_and_parse(client);
1473
1474 if (parser_context.error != HPE_OK ||
1475 (parser_context.parse_status != PARSE_STATUS_OK &&
1476 parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) {
1477 git_error_set(GIT_ERROR_HTTP,
1478 "unexpected data handled in callback");
1479 error = -1;
1480 }
1481 } while (!error);
1482
1483 if (error < 0)
1484 client->connected = 0;
1485
1486 return error;
1487 }
1488
1489 /*
1490 * Create an http_client capable of communicating with the given remote
1491 * host.
1492 */
1493 int git_http_client_new(
1494 git_http_client **out,
1495 git_http_client_options *opts)
1496 {
1497 git_http_client *client;
1498
1499 assert(out);
1500
1501 client = git__calloc(1, sizeof(git_http_client));
1502 GIT_ERROR_CHECK_ALLOC(client);
1503
1504 git_buf_init(&client->read_buf, GIT_READ_BUFFER_SIZE);
1505 GIT_ERROR_CHECK_ALLOC(client->read_buf.ptr);
1506
1507 if (opts)
1508 memcpy(&client->opts, opts, sizeof(git_http_client_options));
1509
1510 *out = client;
1511 return 0;
1512 }
1513
1514 GIT_INLINE(void) http_server_close(git_http_server *server)
1515 {
1516 if (server->stream) {
1517 git_stream_close(server->stream);
1518 git_stream_free(server->stream);
1519 server->stream = NULL;
1520 }
1521
1522 git_net_url_dispose(&server->url);
1523
1524 git_vector_free_deep(&server->auth_challenges);
1525 free_auth_context(server);
1526 }
1527
1528 static void http_client_close(git_http_client *client)
1529 {
1530 http_server_close(&client->server);
1531 http_server_close(&client->proxy);
1532
1533 git_buf_dispose(&client->request_msg);
1534
1535 client->state = 0;
1536 client->request_count = 0;
1537 client->connected = 0;
1538 client->keepalive = 0;
1539 }
1540
1541 void git_http_client_free(git_http_client *client)
1542 {
1543 if (!client)
1544 return;
1545
1546 http_client_close(client);
1547 git_buf_dispose(&client->read_buf);
1548 git__free(client);
1549 }