2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
10 #include "http_parser.h"
13 #include "httpclient.h"
16 #include "auth_negotiate.h"
17 #include "auth_ntlm.h"
18 #include "git2/sys/credential.h"
21 #include "streams/socket.h"
22 #include "streams/tls.h"
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
},
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.
42 #define GIT_READ_BUFFER_SIZE (16 * 1024)
48 git_vector auth_challenges
;
49 git_http_auth_context
*auth_context
;
70 PARSE_HEADER_NONE
= 0,
78 PARSE_STATUS_NO_OUTPUT
,
83 git_http_client
*client
;
84 git_http_response
*response
;
86 /* Temporary buffers to avoid extra mallocs */
87 git_str parse_header_name
;
88 git_str parse_header_value
;
92 parse_status parse_status
;
95 parse_header_state parse_header_state
;
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
;
103 /* HTTP client connection */
104 struct git_http_client
{
105 git_http_client_options opts
;
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
;
113 git_http_server server
;
114 git_http_server proxy
;
116 unsigned request_count
;
117 unsigned connected
: 1,
122 /* Temporary buffers to avoid extra mallocs */
126 /* A subset of information from the request */
127 size_t request_body_len
,
131 * When state == HAS_EARLY_RESPONSE, the response of our proxy
132 * that we have buffered and will deliver during read_response.
134 git_http_response early_response
;
137 bool git_http_response_is_redirect(git_http_response
*response
)
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
);
146 void git_http_response_dispose(git_http_response
*response
)
151 git__free(response
->content_type
);
152 git__free(response
->location
);
154 memset(response
, 0, sizeof(git_http_response
));
157 static int on_header_complete(http_parser
*parser
)
159 http_parser_context
*ctx
= (http_parser_context
*) parser
->data
;
160 git_http_client
*client
= ctx
->client
;
161 git_http_response
*response
= ctx
->response
;
163 git_str
*name
= &ctx
->parse_header_name
;
164 git_str
*value
= &ctx
->parse_header_value
;
166 if (!strcasecmp("Content-Type", name
->ptr
)) {
167 if (response
->content_type
) {
168 git_error_set(GIT_ERROR_HTTP
,
169 "multiple content-type headers");
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
)) {
179 if (response
->content_length
) {
180 git_error_set(GIT_ERROR_HTTP
,
181 "multiple content-length headers");
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");
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
);
200 if (git_vector_insert(&client
->proxy
.auth_challenges
, dup
) < 0)
202 } else if (!strcasecmp("WWW-Authenticate", name
->ptr
)) {
203 char *dup
= git__strndup(value
->ptr
, value
->size
);
204 GIT_ERROR_CHECK_ALLOC(dup
);
206 if (git_vector_insert(&client
->server
.auth_challenges
, dup
) < 0)
208 } else if (!strcasecmp("Location", name
->ptr
)) {
209 if (response
->location
) {
210 git_error_set(GIT_ERROR_HTTP
,
211 "multiple location headers");
215 response
->location
= git__strndup(value
->ptr
, value
->size
);
216 GIT_ERROR_CHECK_ALLOC(response
->location
);
222 static int on_header_field(http_parser
*parser
, const char *str
, size_t len
)
224 http_parser_context
*ctx
= (http_parser_context
*) parser
->data
;
226 switch (ctx
->parse_header_state
) {
228 * We last saw a header value, process the name/value pair and
229 * get ready to handle this new name.
231 case PARSE_HEADER_VALUE
:
232 if (on_header_complete(parser
) < 0)
233 return ctx
->parse_status
= PARSE_STATUS_ERROR
;
235 git_str_clear(&ctx
->parse_header_name
);
236 git_str_clear(&ctx
->parse_header_value
);
239 case PARSE_HEADER_NONE
:
240 case PARSE_HEADER_NAME
:
241 ctx
->parse_header_state
= PARSE_HEADER_NAME
;
243 if (git_str_put(&ctx
->parse_header_name
, str
, len
) < 0)
244 return ctx
->parse_status
= PARSE_STATUS_ERROR
;
249 git_error_set(GIT_ERROR_HTTP
,
250 "header name seen at unexpected time");
251 return ctx
->parse_status
= PARSE_STATUS_ERROR
;
257 static int on_header_value(http_parser
*parser
, const char *str
, size_t len
)
259 http_parser_context
*ctx
= (http_parser_context
*) parser
->data
;
261 switch (ctx
->parse_header_state
) {
262 case PARSE_HEADER_NAME
:
263 case PARSE_HEADER_VALUE
:
264 ctx
->parse_header_state
= PARSE_HEADER_VALUE
;
266 if (git_str_put(&ctx
->parse_header_value
, str
, len
) < 0)
267 return ctx
->parse_status
= PARSE_STATUS_ERROR
;
272 git_error_set(GIT_ERROR_HTTP
,
273 "header value seen at unexpected time");
274 return ctx
->parse_status
= PARSE_STATUS_ERROR
;
280 GIT_INLINE(bool) challenge_matches_scheme(
281 const char *challenge
,
282 git_http_auth_scheme
*scheme
)
284 const char *scheme_name
= scheme
->name
;
285 size_t scheme_len
= strlen(scheme_name
);
287 if (!strncasecmp(challenge
, scheme_name
, scheme_len
) &&
288 (challenge
[scheme_len
] == '\0' || challenge
[scheme_len
] == ' '))
294 static git_http_auth_scheme
*scheme_for_challenge(const char *challenge
)
298 for (i
= 0; i
< ARRAY_SIZE(auth_schemes
); i
++) {
299 if (challenge_matches_scheme(challenge
, &auth_schemes
[i
]))
300 return &auth_schemes
[i
];
306 GIT_INLINE(void) collect_authinfo(
307 unsigned int *schemetypes
,
308 unsigned int *credtypes
,
309 git_vector
*challenges
)
311 git_http_auth_scheme
*scheme
;
312 const char *challenge
;
318 git_vector_foreach(challenges
, i
, challenge
) {
319 if ((scheme
= scheme_for_challenge(challenge
)) != NULL
) {
320 *schemetypes
|= scheme
->type
;
321 *credtypes
|= scheme
->credtypes
;
326 static int resend_needed(git_http_client
*client
, git_http_response
*response
)
328 git_http_auth_context
*auth_context
;
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
))
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
))
345 static int on_headers_complete(http_parser
*parser
)
347 http_parser_context
*ctx
= (http_parser_context
*) parser
->data
;
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
;
357 case PARSE_HEADER_NONE
:
358 ctx
->parse_header_state
= PARSE_HEADER_COMPLETE
;
362 git_error_set(GIT_ERROR_HTTP
,
363 "header completion at unexpected time");
364 return ctx
->parse_status
= PARSE_STATUS_ERROR
;
367 ctx
->response
->status
= parser
->status_code
;
368 ctx
->client
->keepalive
= http_should_keep_alive(parser
);
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
);
378 ctx
->response
->resend_credentials
= resend_needed(ctx
->client
,
382 http_parser_pause(parser
, 1);
384 if (ctx
->response
->content_type
|| ctx
->response
->chunked
)
385 ctx
->client
->state
= READING_BODY
;
387 ctx
->client
->state
= DONE
;
392 static int on_body(http_parser
*parser
, const char *buf
, size_t len
)
394 http_parser_context
*ctx
= (http_parser_context
*) parser
->data
;
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
;
403 GIT_ASSERT(ctx
->output_size
>= ctx
->output_written
);
405 max_len
= min(ctx
->output_size
- ctx
->output_written
, len
);
406 max_len
= min(max_len
, INT_MAX
);
408 memcpy(ctx
->output_buf
+ ctx
->output_written
, buf
, max_len
);
409 ctx
->output_written
+= max_len
;
414 static int on_message_complete(http_parser
*parser
)
416 http_parser_context
*ctx
= (http_parser_context
*) parser
->data
;
418 ctx
->client
->state
= DONE
;
422 GIT_INLINE(int) stream_write(
423 git_http_server
*server
,
427 git_trace(GIT_TRACE_TRACE
,
428 "Sending request:\n%.*s", (int)len
, data
);
430 return git_stream__write_full(server
->stream
, data
, len
, 0);
433 GIT_INLINE(int) client_write_request(git_http_client
*client
)
435 git_stream
*stream
= client
->current_server
== PROXY
?
436 client
->proxy
.stream
: client
->server
.stream
;
438 git_trace(GIT_TRACE_TRACE
,
439 "Sending request:\n%.*s",
440 (int)client
->request_msg
.size
, client
->request_msg
.ptr
);
442 return git_stream__write_full(stream
,
443 client
->request_msg
.ptr
,
444 client
->request_msg
.size
,
448 static const char *name_for_method(git_http_method method
)
451 case GIT_HTTP_METHOD_GET
:
453 case GIT_HTTP_METHOD_POST
:
455 case GIT_HTTP_METHOD_CONNECT
:
463 * Find the scheme that is suitable for the given credentials, based on the
464 * server's auth challenges.
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
)
472 const char *challenge
;
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
];
479 if (challenge_matches_scheme(challenge
, scheme
) &&
480 (scheme
->credtypes
& credentials
->credtype
)) {
481 *scheme_out
= scheme
;
482 *challenge_out
= challenge
;
492 * Find the challenge from the server for our current auth context.
494 static const char *challenge_for_context(
495 git_vector
*challenges
,
496 git_http_auth_context
*auth_ctx
)
498 const char *challenge
;
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
];
505 git_vector_foreach(challenges
, j
, challenge
) {
506 if (challenge_matches_scheme(challenge
, scheme
))
515 static const char *init_auth_context(
516 git_http_server
*server
,
517 git_vector
*challenges
,
518 git_credential
*credentials
)
520 git_http_auth_scheme
*scheme
;
521 const char *challenge
;
524 if (!best_scheme_and_challenge(&scheme
, &challenge
, challenges
, credentials
)) {
525 git_error_set(GIT_ERROR_HTTP
, "could not find appropriate mechanism for credentials");
529 error
= scheme
->init_context(&server
->auth_context
, &server
->url
);
531 if (error
== GIT_PASSTHROUGH
) {
532 git_error_set(GIT_ERROR_HTTP
, "'%s' authentication is not supported", scheme
->name
);
539 static void free_auth_context(git_http_server
*server
)
541 if (!server
->auth_context
)
544 if (server
->auth_context
->free
)
545 server
->auth_context
->free(server
->auth_context
);
547 server
->auth_context
= NULL
;
550 static int apply_credentials(
552 git_http_server
*server
,
553 const char *header_name
,
554 git_credential
*credentials
)
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
;
562 /* We've started a new request without creds; free the context. */
563 if (auth
&& !credentials
) {
564 free_auth_context(server
);
568 /* We haven't authenticated, nor were we asked to. Nothing to do. */
569 if (!auth
&& !git_vector_length(challenges
))
573 challenge
= init_auth_context(server
, challenges
, credentials
);
574 auth
= server
->auth_context
;
576 if (!challenge
|| !auth
) {
580 } else if (auth
->set_challenge
) {
581 challenge
= challenge_for_context(challenges
, auth
);
584 if (auth
->set_challenge
&& challenge
&&
585 (error
= auth
->set_challenge(auth
, challenge
)) < 0)
588 if ((error
= auth
->next_token(&token
, auth
, credentials
)) < 0)
591 if (auth
->is_complete
&& auth
->is_complete(auth
)) {
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.
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");
605 error
= git_str_printf(buf
, "%s: %s\r\n", header_name
, token
.ptr
);
608 git_str_dispose(&token
);
612 GIT_INLINE(int) apply_server_credentials(
614 git_http_client
*client
,
615 git_http_request
*request
)
617 return apply_credentials(buf
,
620 request
->credentials
);
623 GIT_INLINE(int) apply_proxy_credentials(
625 git_http_client
*client
,
626 git_http_request
*request
)
628 return apply_credentials(buf
,
630 "Proxy-Authorization",
631 request
->proxy_credentials
);
634 static int puts_host_and_port(git_str
*buf
, git_net_url
*url
, bool force_port
)
636 bool ipv6
= git_net_url_is_ipv6(url
);
639 git_str_putc(buf
, '[');
641 git_str_puts(buf
, url
->host
);
644 git_str_putc(buf
, ']');
646 if (force_port
|| !git_net_url_is_default_port(url
)) {
647 git_str_putc(buf
, ':');
648 git_str_puts(buf
, url
->port
);
651 return git_str_oom(buf
) ? -1 : 0;
654 static int generate_connect_request(
655 git_http_client
*client
,
656 git_http_request
*request
)
661 git_str_clear(&client
->request_msg
);
662 buf
= &client
->request_msg
;
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");
668 git_str_puts(buf
, "User-Agent: ");
669 git_http__user_agent(buf
);
670 git_str_puts(buf
, "\r\n");
672 git_str_puts(buf
, "Host: ");
673 puts_host_and_port(buf
, &client
->server
.url
, true);
674 git_str_puts(buf
, "\r\n");
676 if ((error
= apply_proxy_credentials(buf
, client
, request
) < 0))
679 git_str_puts(buf
, "\r\n");
681 return git_str_oom(buf
) ? -1 : 0;
684 static bool use_connect_proxy(git_http_client
*client
)
686 return client
->proxy
.url
.host
&& !strcmp(client
->server
.url
.scheme
, "https");
689 static int generate_request(
690 git_http_client
*client
,
691 git_http_request
*request
)
697 GIT_ASSERT_ARG(client
);
698 GIT_ASSERT_ARG(request
);
700 git_str_clear(&client
->request_msg
);
701 buf
= &client
->request_msg
;
703 /* GET|POST path HTTP/1.1 */
704 git_str_puts(buf
, name_for_method(request
->method
));
705 git_str_putc(buf
, ' ');
707 if (request
->proxy
&& strcmp(request
->url
->scheme
, "https"))
708 git_net_url_fmt(buf
, request
->url
);
710 git_net_url_fmt_path(buf
, request
->url
);
712 git_str_puts(buf
, " HTTP/1.1\r\n");
714 git_str_puts(buf
, "User-Agent: ");
715 git_http__user_agent(buf
);
716 git_str_puts(buf
, "\r\n");
718 git_str_puts(buf
, "Host: ");
719 puts_host_and_port(buf
, request
->url
, false);
720 git_str_puts(buf
, "\r\n");
723 git_str_printf(buf
, "Accept: %s\r\n", request
->accept
);
725 git_str_puts(buf
, "Accept: */*\r\n");
727 if (request
->content_type
)
728 git_str_printf(buf
, "Content-Type: %s\r\n",
729 request
->content_type
);
731 if (request
->chunked
)
732 git_str_puts(buf
, "Transfer-Encoding: chunked\r\n");
734 if (request
->content_length
> 0)
735 git_str_printf(buf
, "Content-Length: %"PRIuZ
"\r\n",
736 request
->content_length
);
738 if (request
->expect_continue
)
739 git_str_printf(buf
, "Expect: 100-continue\r\n");
741 if ((error
= apply_server_credentials(buf
, client
, request
)) < 0 ||
742 (!use_connect_proxy(client
) &&
743 (error
= apply_proxy_credentials(buf
, client
, request
)) < 0))
746 if (request
->custom_headers
) {
747 for (i
= 0; i
< request
->custom_headers
->count
; i
++) {
748 const char *hdr
= request
->custom_headers
->strings
[i
];
751 git_str_printf(buf
, "%s\r\n", hdr
);
755 git_str_puts(buf
, "\r\n");
757 if (git_str_oom(buf
))
763 static int check_certificate(
767 git_transport_certificate_check_cb cert_cb
,
768 void *cert_cb_payload
)
771 git_error_state last_error
= {0};
774 if ((error
= git_stream_certificate(&cert
, stream
)) < 0)
777 git_error_state_capture(&last_error
, GIT_ECERTIFICATE
);
779 error
= cert_cb(cert
, is_valid
, url
->host
, cert_cb_payload
);
781 if (error
== GIT_PASSTHROUGH
&& !is_valid
)
782 return git_error_state_restore(&last_error
);
783 else if (error
== GIT_PASSTHROUGH
)
785 else if (error
&& !git_error_last())
786 git_error_set(GIT_ERROR_HTTP
,
787 "user rejected certificate for %s", url
->host
);
789 git_error_state_free(&last_error
);
793 static int server_connect_stream(
794 git_http_server
*server
,
795 git_transport_certificate_check_cb cert_cb
,
800 GIT_ERROR_CHECK_VERSION(server
->stream
, GIT_STREAM_VERSION
, "git_stream");
802 error
= git_stream_connect(server
->stream
);
804 if (error
&& error
!= GIT_ECERTIFICATE
)
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
);
814 static void reset_auth_connection(git_http_server
*server
)
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.
827 if (server
->auth_context
&&
828 server
->auth_context
->connection_affinity
)
829 free_auth_context(server
);
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.
836 GIT_INLINE(int) server_setup_from_url(
837 git_http_server
*server
,
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
);
847 server
->url
.scheme
= git__strdup(url
->scheme
);
848 GIT_ERROR_CHECK_ALLOC(server
->url
.scheme
);
850 server
->url
.host
= git__strdup(url
->host
);
851 GIT_ERROR_CHECK_ALLOC(server
->url
.host
);
853 server
->url
.port
= git__strdup(url
->port
);
854 GIT_ERROR_CHECK_ALLOC(server
->url
.port
);
862 static void reset_parser(git_http_client
*client
)
864 http_parser_init(&client
->parser
, HTTP_RESPONSE
);
867 static int setup_hosts(
868 git_http_client
*client
,
869 git_http_request
*request
)
873 GIT_ASSERT_ARG(client
);
874 GIT_ASSERT_ARG(request
);
876 GIT_ASSERT(request
->url
);
878 if ((ret
= server_setup_from_url(&client
->server
, request
->url
)) < 0)
883 if (request
->proxy
&&
884 (ret
= server_setup_from_url(&client
->proxy
, request
->proxy
)) < 0)
890 free_auth_context(&client
->server
);
891 free_auth_context(&client
->proxy
);
893 client
->connected
= 0;
899 GIT_INLINE(int) server_create_stream(git_http_server
*server
)
901 git_net_url
*url
= &server
->url
;
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
);
908 git_error_set(GIT_ERROR_HTTP
, "unknown http scheme '%s'", url
->scheme
);
912 GIT_INLINE(void) save_early_response(
913 git_http_client
*client
,
914 git_http_response
*response
)
916 /* Buffer the response so we can return it in read_response */
917 client
->state
= HAS_EARLY_RESPONSE
;
919 memcpy(&client
->early_response
, response
, sizeof(git_http_response
));
920 memset(response
, 0, sizeof(git_http_response
));
923 static int proxy_connect(
924 git_http_client
*client
,
925 git_http_request
*request
)
927 git_http_response response
= {0};
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
);
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)
940 client
->proxy_connected
= 1;
943 client
->current_server
= PROXY
;
944 client
->state
= SENDING_REQUEST
;
946 if ((error
= generate_connect_request(client
, request
)) < 0 ||
947 (error
= client_write_request(client
)) < 0)
950 client
->state
= SENT_REQUEST
;
952 if ((error
= git_http_client_read_response(&response
, client
)) < 0 ||
953 (error
= git_http_client_skip_body(client
)) < 0)
956 GIT_ASSERT(client
->state
== DONE
);
958 if (response
.status
== GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED
) {
959 save_early_response(client
, &response
);
963 } else if (response
.status
!= GIT_HTTP_STATUS_OK
) {
964 git_error_set(GIT_ERROR_HTTP
, "proxy returned unexpected status: %d", response
.status
);
969 reset_parser(client
);
970 client
->state
= NONE
;
973 git_http_response_dispose(&response
);
977 static int server_connect(git_http_client
*client
)
979 git_net_url
*url
= &client
->server
.url
;
980 git_transport_certificate_check_cb cert_cb
;
984 client
->current_server
= SERVER
;
986 if (client
->proxy
.stream
)
987 error
= git_tls_stream_wrap(&client
->server
.stream
, client
->proxy
.stream
, url
->host
);
989 error
= server_create_stream(&client
->server
);
994 cert_cb
= client
->opts
.server_certificate_check_cb
;
995 cert_payload
= client
->opts
.server_certificate_check_payload
;
997 error
= server_connect_stream(&client
->server
, cert_cb
, cert_payload
);
1003 GIT_INLINE(void) close_stream(git_http_server
*server
)
1005 if (server
->stream
) {
1006 git_stream_close(server
->stream
);
1007 git_stream_free(server
->stream
);
1008 server
->stream
= NULL
;
1012 static int http_client_connect(
1013 git_http_client
*client
,
1014 git_http_request
*request
)
1016 bool use_proxy
= false;
1019 if ((error
= setup_hosts(client
, request
)) < 0)
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
))
1027 client
->connected
= 0;
1028 client
->request_count
= 0;
1030 close_stream(&client
->server
);
1031 reset_auth_connection(&client
->server
);
1033 reset_parser(client
);
1035 /* Reconnect to the proxy if necessary. */
1036 use_proxy
= use_connect_proxy(client
);
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
);
1044 client
->proxy_connected
= 0;
1047 if ((error
= proxy_connect(client
, request
)) < 0)
1051 git_trace(GIT_TRACE_DEBUG
, "Connecting to remote %s port %s",
1052 client
->server
.url
.host
, client
->server
.url
.port
);
1054 if ((error
= server_connect(client
)) < 0)
1057 client
->connected
= 1;
1061 if (error
!= GIT_RETRY
)
1062 close_stream(&client
->proxy
);
1064 close_stream(&client
->server
);
1068 GIT_INLINE(int) client_read(git_http_client
*client
)
1070 http_parser_context
*parser_context
= client
->parser
.data
;
1072 char *buf
= client
->read_buf
.ptr
+ client
->read_buf
.size
;
1076 stream
= client
->current_server
== PROXY
?
1077 client
->proxy
.stream
: client
->server
.stream
;
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.
1084 max_len
= client
->read_buf
.asize
- client
->read_buf
.size
;
1085 max_len
= min(max_len
, INT_MAX
);
1087 if (parser_context
->output_size
)
1088 max_len
= min(max_len
, parser_context
->output_size
);
1091 git_error_set(GIT_ERROR_HTTP
, "no room in output buffer");
1095 read_len
= git_stream_read(stream
, buf
, max_len
);
1097 if (read_len
>= 0) {
1098 client
->read_buf
.size
+= read_len
;
1100 git_trace(GIT_TRACE_TRACE
, "Received:\n%.*s",
1101 (int)read_len
, buf
);
1104 return (int)read_len
;
1107 static bool parser_settings_initialized
;
1108 static http_parser_settings parser_settings
;
1110 GIT_INLINE(http_parser_settings
*) http_client_parser_settings(void)
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
;
1119 parser_settings_initialized
= true;
1122 return &parser_settings
;
1125 GIT_INLINE(int) client_read_and_parse(git_http_client
*client
)
1127 http_parser
*parser
= &client
->parser
;
1128 http_parser_context
*ctx
= (http_parser_context
*) parser
->data
;
1129 unsigned char http_errno
;
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.
1138 if (!client
->read_buf
.size
&& (read_len
= client_read(client
)) < 0)
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
;
1147 if (parsed_len
> INT_MAX
) {
1148 git_error_set(GIT_ERROR_HTTP
, "unexpectedly large parse");
1152 if (ctx
->parse_status
== PARSE_STATUS_ERROR
) {
1153 client
->connected
= 0;
1154 return ctx
->error
? ctx
->error
: -1;
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.)
1164 if (http_errno
== HPE_PAUSED
) {
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
1170 GIT_ASSERT(client
->read_buf
.size
> parsed_len
);
1172 http_parser_pause(parser
, 0);
1174 parsed_len
+= http_parser_execute(parser
,
1175 http_client_parser_settings(),
1176 client
->read_buf
.ptr
+ parsed_len
,
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
));
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
));
1195 /* recv returned 0, the server hung up on us */
1196 else if (!parsed_len
) {
1197 git_error_set(GIT_ERROR_HTTP
, "unexpected EOF");
1201 git_str_consume_bytes(&client
->read_buf
, parsed_len
);
1203 return (int)parsed_len
;
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.
1215 static void complete_response_body(git_http_client
*client
)
1217 http_parser_context parser_context
= {0};
1219 /* If we're not keeping alive, don't bother. */
1220 if (!client
->keepalive
) {
1221 client
->connected
= 0;
1225 parser_context
.client
= client
;
1226 client
->parser
.data
= &parser_context
;
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
)) {
1234 client
->connected
= 0;
1238 git_str_clear(&client
->read_buf
);
1241 int git_http_client_send_request(
1242 git_http_client
*client
,
1243 git_http_request
*request
)
1245 git_http_response response
= {0};
1248 GIT_ASSERT_ARG(client
);
1249 GIT_ASSERT_ARG(request
);
1251 /* If the client did not finish reading, clean up the stream. */
1252 if (client
->state
== READING_BODY
)
1253 complete_response_body(client
);
1255 /* If we're waiting for proxy auth, don't sending more requests. */
1256 if (client
->state
== HAS_EARLY_RESPONSE
)
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
);
1268 if ((error
= http_client_connect(client
, request
)) < 0 ||
1269 (error
= generate_request(client
, request
)) < 0 ||
1270 (error
= client_write_request(client
)) < 0)
1273 client
->state
= SENT_REQUEST
;
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)
1282 if (response
.status
!= GIT_HTTP_STATUS_CONTINUE
) {
1283 save_early_response(client
, &response
);
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
;
1295 reset_parser(client
);
1298 if (error
== GIT_RETRY
)
1301 git_http_response_dispose(&response
);
1305 bool git_http_client_has_response(git_http_client
*client
)
1307 return (client
->state
== HAS_EARLY_RESPONSE
||
1308 client
->state
> SENT_REQUEST
);
1311 int git_http_client_send_body(
1312 git_http_client
*client
,
1316 git_http_server
*server
;
1317 git_str hdr
= GIT_STR_INIT
;
1320 GIT_ASSERT_ARG(client
);
1322 /* If we're waiting for proxy auth, don't sending more requests. */
1323 if (client
->state
== HAS_EARLY_RESPONSE
)
1326 if (client
->state
!= SENDING_BODY
) {
1327 git_error_set(GIT_ERROR_HTTP
, "client is in invalid state");
1334 server
= &client
->server
;
1336 if (client
->request_body_len
) {
1337 GIT_ASSERT(buffer_len
<= client
->request_body_remain
);
1339 if ((error
= stream_write(server
, buffer
, buffer_len
)) < 0)
1342 client
->request_body_remain
-= buffer_len
;
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)
1352 git_str_dispose(&hdr
);
1356 static int complete_request(git_http_client
*client
)
1360 GIT_ASSERT_ARG(client
);
1361 GIT_ASSERT(client
->state
== SENDING_BODY
);
1363 if (client
->request_body_len
&& client
->request_body_remain
) {
1364 git_error_set(GIT_ERROR_HTTP
, "truncated write");
1366 } else if (client
->request_chunked
) {
1367 error
= stream_write(&client
->server
, "0\r\n\r\n", 5);
1370 client
->state
= SENT_REQUEST
;
1374 int git_http_client_read_response(
1375 git_http_response
*response
,
1376 git_http_client
*client
)
1378 http_parser_context parser_context
= {0};
1381 GIT_ASSERT_ARG(response
);
1382 GIT_ASSERT_ARG(client
);
1384 if (client
->state
== SENDING_BODY
) {
1385 if ((error
= complete_request(client
)) < 0)
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
;
1396 if (client
->state
!= SENT_REQUEST
) {
1397 git_error_set(GIT_ERROR_HTTP
, "client is in invalid state");
1402 git_http_response_dispose(response
);
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
);
1410 client
->state
= READING_RESPONSE
;
1411 client
->keepalive
= 0;
1412 client
->parser
.data
= &parser_context
;
1414 parser_context
.client
= client
;
1415 parser_context
.response
= response
;
1417 while (client
->state
== READING_RESPONSE
) {
1418 if ((error
= client_read_and_parse(client
)) < 0)
1422 GIT_ASSERT(client
->state
== READING_BODY
|| client
->state
== DONE
);
1425 git_str_dispose(&parser_context
.parse_header_name
);
1426 git_str_dispose(&parser_context
.parse_header_value
);
1431 int git_http_client_read_body(
1432 git_http_client
*client
,
1436 http_parser_context parser_context
= {0};
1439 if (client
->state
== DONE
)
1442 if (client
->state
!= READING_BODY
) {
1443 git_error_set(GIT_ERROR_HTTP
, "client is in invalid state");
1448 * Now we'll read from the socket and http_parser will pipeline the
1449 * data directly to the client.
1452 parser_context
.client
= client
;
1453 parser_context
.output_buf
= buffer
;
1454 parser_context
.output_size
= buffer_size
;
1456 client
->parser
.data
= &parser_context
;
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
1465 while (!parser_context
.output_written
) {
1466 error
= client_read_and_parse(client
);
1471 if (client
->state
== DONE
)
1475 GIT_ASSERT(parser_context
.output_written
<= INT_MAX
);
1476 error
= (int)parser_context
.output_written
;
1480 client
->connected
= 0;
1485 int git_http_client_skip_body(git_http_client
*client
)
1487 http_parser_context parser_context
= {0};
1490 if (client
->state
== DONE
)
1493 if (client
->state
!= READING_BODY
) {
1494 git_error_set(GIT_ERROR_HTTP
, "client is in invalid state");
1498 parser_context
.client
= client
;
1499 client
->parser
.data
= &parser_context
;
1502 error
= client_read_and_parse(client
);
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");
1511 } while (error
>= 0 && client
->state
!= DONE
);
1514 client
->connected
= 0;
1520 * Create an http_client capable of communicating with the given remote
1523 int git_http_client_new(
1524 git_http_client
**out
,
1525 git_http_client_options
*opts
)
1527 git_http_client
*client
;
1529 GIT_ASSERT_ARG(out
);
1531 client
= git__calloc(1, sizeof(git_http_client
));
1532 GIT_ERROR_CHECK_ALLOC(client
);
1534 git_str_init(&client
->read_buf
, GIT_READ_BUFFER_SIZE
);
1535 GIT_ERROR_CHECK_ALLOC(client
->read_buf
.ptr
);
1538 memcpy(&client
->opts
, opts
, sizeof(git_http_client_options
));
1544 GIT_INLINE(void) http_server_close(git_http_server
*server
)
1546 if (server
->stream
) {
1547 git_stream_close(server
->stream
);
1548 git_stream_free(server
->stream
);
1549 server
->stream
= NULL
;
1552 git_net_url_dispose(&server
->url
);
1554 git_vector_free_deep(&server
->auth_challenges
);
1555 free_auth_context(server
);
1558 static void http_client_close(git_http_client
*client
)
1560 http_server_close(&client
->server
);
1561 http_server_close(&client
->proxy
);
1563 git_str_dispose(&client
->request_msg
);
1566 client
->request_count
= 0;
1567 client
->connected
= 0;
1568 client
->keepalive
= 0;
1571 void git_http_client_free(git_http_client
*client
)
1576 http_client_close(client
);
1577 git_str_dispose(&client
->read_buf
);