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"
14 #include "httpclient.h"
17 #include "auth_negotiate.h"
18 #include "auth_ntlm.h"
19 #include "git2/sys/credential.h"
22 #include "streams/socket.h"
23 #include "streams/tls.h"
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
},
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.
43 #define GIT_READ_BUFFER_SIZE (16 * 1024)
49 git_vector auth_challenges
;
50 git_http_auth_context
*auth_context
;
71 PARSE_HEADER_NONE
= 0,
79 PARSE_STATUS_NO_OUTPUT
,
84 git_http_client
*client
;
85 git_http_response
*response
;
87 /* Temporary buffers to avoid extra mallocs */
88 git_buf parse_header_name
;
89 git_buf parse_header_value
;
93 parse_status parse_status
;
96 parse_header_state parse_header_state
;
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
;
104 /* HTTP client connection */
105 struct git_http_client
{
106 git_http_client_options opts
;
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
;
114 git_http_server server
;
115 git_http_server proxy
;
117 unsigned request_count
;
118 unsigned connected
: 1,
123 /* Temporary buffers to avoid extra mallocs */
127 /* A subset of information from the request */
128 size_t request_body_len
,
132 * When state == HAS_EARLY_RESPONSE, the response of our proxy
133 * that we have buffered and will deliver during read_response.
135 git_http_response early_response
;
138 bool git_http_response_is_redirect(git_http_response
*response
)
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
);
147 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_buf
*name
= &ctx
->parse_header_name
;
164 git_buf
*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_buf_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_buf_clear(&ctx
->parse_header_name
);
236 git_buf_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_buf_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_buf_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 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 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_buf token
= GIT_BUF_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_buf_printf(buf
, "%s: %s\r\n", header_name
, token
.ptr
);
608 git_buf_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 generate_connect_request(
635 git_http_client
*client
,
636 git_http_request
*request
)
641 git_buf_clear(&client
->request_msg
);
642 buf
= &client
->request_msg
;
644 git_buf_printf(buf
, "CONNECT %s:%s HTTP/1.1\r\n",
645 client
->server
.url
.host
, client
->server
.url
.port
);
647 git_buf_puts(buf
, "User-Agent: ");
648 git_http__user_agent(buf
);
649 git_buf_puts(buf
, "\r\n");
651 git_buf_printf(buf
, "Host: %s\r\n", client
->proxy
.url
.host
);
653 if ((error
= apply_proxy_credentials(buf
, client
, request
) < 0))
656 git_buf_puts(buf
, "\r\n");
658 return git_buf_oom(buf
) ? -1 : 0;
661 static int generate_request(
662 git_http_client
*client
,
663 git_http_request
*request
)
669 assert(client
&& request
);
671 git_buf_clear(&client
->request_msg
);
672 buf
= &client
->request_msg
;
674 /* GET|POST path HTTP/1.1 */
675 git_buf_puts(buf
, name_for_method(request
->method
));
676 git_buf_putc(buf
, ' ');
678 if (request
->proxy
&& strcmp(request
->url
->scheme
, "https"))
679 git_net_url_fmt(buf
, request
->url
);
681 git_net_url_fmt_path(buf
, request
->url
);
683 git_buf_puts(buf
, " HTTP/1.1\r\n");
685 git_buf_puts(buf
, "User-Agent: ");
686 git_http__user_agent(buf
);
687 git_buf_puts(buf
, "\r\n");
689 git_buf_printf(buf
, "Host: %s", request
->url
->host
);
691 if (!git_net_url_is_default_port(request
->url
))
692 git_buf_printf(buf
, ":%s", request
->url
->port
);
694 git_buf_puts(buf
, "\r\n");
697 git_buf_printf(buf
, "Accept: %s\r\n", request
->accept
);
699 git_buf_puts(buf
, "Accept: */*\r\n");
701 if (request
->content_type
)
702 git_buf_printf(buf
, "Content-Type: %s\r\n",
703 request
->content_type
);
705 if (request
->chunked
)
706 git_buf_puts(buf
, "Transfer-Encoding: chunked\r\n");
708 if (request
->content_length
> 0)
709 git_buf_printf(buf
, "Content-Length: %"PRIuZ
"\r\n",
710 request
->content_length
);
712 if (request
->expect_continue
)
713 git_buf_printf(buf
, "Expect: 100-continue\r\n");
715 if ((error
= apply_server_credentials(buf
, client
, request
)) < 0 ||
716 (error
= apply_proxy_credentials(buf
, client
, request
)) < 0)
719 if (request
->custom_headers
) {
720 for (i
= 0; i
< request
->custom_headers
->count
; i
++) {
721 const char *hdr
= request
->custom_headers
->strings
[i
];
724 git_buf_printf(buf
, "%s\r\n", hdr
);
728 git_buf_puts(buf
, "\r\n");
730 if (git_buf_oom(buf
))
736 static int check_certificate(
740 git_transport_certificate_check_cb cert_cb
,
741 void *cert_cb_payload
)
744 git_error_state last_error
= {0};
747 if ((error
= git_stream_certificate(&cert
, stream
)) < 0)
750 git_error_state_capture(&last_error
, GIT_ECERTIFICATE
);
752 error
= cert_cb(cert
, is_valid
, url
->host
, cert_cb_payload
);
754 if (error
== GIT_PASSTHROUGH
&& !is_valid
)
755 return git_error_state_restore(&last_error
);
756 else if (error
== GIT_PASSTHROUGH
)
758 else if (error
&& !git_error_last())
759 git_error_set(GIT_ERROR_HTTP
,
760 "user rejected certificate for %s", url
->host
);
762 git_error_state_free(&last_error
);
766 static int server_connect_stream(
767 git_http_server
*server
,
768 git_transport_certificate_check_cb cert_cb
,
773 GIT_ERROR_CHECK_VERSION(server
->stream
, GIT_STREAM_VERSION
, "git_stream");
775 error
= git_stream_connect(server
->stream
);
777 if (error
&& error
!= GIT_ECERTIFICATE
)
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
);
787 static void reset_auth_connection(git_http_server
*server
)
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.
800 if (server
->auth_context
&&
801 server
->auth_context
->connection_affinity
)
802 free_auth_context(server
);
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.
809 GIT_INLINE(int) server_setup_from_url(
810 git_http_server
*server
,
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
);
820 server
->url
.scheme
= git__strdup(url
->scheme
);
821 GIT_ERROR_CHECK_ALLOC(server
->url
.scheme
);
823 server
->url
.host
= git__strdup(url
->host
);
824 GIT_ERROR_CHECK_ALLOC(server
->url
.host
);
826 server
->url
.port
= git__strdup(url
->port
);
827 GIT_ERROR_CHECK_ALLOC(server
->url
.port
);
835 static void reset_parser(git_http_client
*client
)
837 http_parser_init(&client
->parser
, HTTP_RESPONSE
);
840 static int setup_hosts(
841 git_http_client
*client
,
842 git_http_request
*request
)
846 assert(client
&& request
&& request
->url
);
848 if ((ret
= server_setup_from_url(&client
->server
, request
->url
)) < 0)
853 if (request
->proxy
&&
854 (ret
= server_setup_from_url(&client
->proxy
, request
->proxy
)) < 0)
860 free_auth_context(&client
->server
);
861 free_auth_context(&client
->proxy
);
863 client
->connected
= 0;
869 GIT_INLINE(int) server_create_stream(git_http_server
*server
)
871 git_net_url
*url
= &server
->url
;
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
);
878 git_error_set(GIT_ERROR_HTTP
, "unknown http scheme '%s'", url
->scheme
);
882 GIT_INLINE(void) save_early_response(
883 git_http_client
*client
,
884 git_http_response
*response
)
886 /* Buffer the response so we can return it in read_response */
887 client
->state
= HAS_EARLY_RESPONSE
;
889 memcpy(&client
->early_response
, response
, sizeof(git_http_response
));
890 memset(response
, 0, sizeof(git_http_response
));
893 static int proxy_connect(
894 git_http_client
*client
,
895 git_http_request
*request
)
897 git_http_response response
= {0};
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
);
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)
910 client
->proxy_connected
= 1;
913 client
->current_server
= PROXY
;
914 client
->state
= SENDING_REQUEST
;
916 if ((error
= generate_connect_request(client
, request
)) < 0 ||
917 (error
= client_write_request(client
)) < 0)
920 client
->state
= SENT_REQUEST
;
922 if ((error
= git_http_client_read_response(&response
, client
)) < 0 ||
923 (error
= git_http_client_skip_body(client
)) < 0)
926 assert(client
->state
== DONE
);
928 if (response
.status
== GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED
) {
929 save_early_response(client
, &response
);
933 } else if (response
.status
!= GIT_HTTP_STATUS_OK
) {
934 git_error_set(GIT_ERROR_HTTP
, "proxy returned unexpected status: %d", response
.status
);
939 reset_parser(client
);
940 client
->state
= NONE
;
943 git_http_response_dispose(&response
);
947 static int server_connect(git_http_client
*client
)
949 git_net_url
*url
= &client
->server
.url
;
950 git_transport_certificate_check_cb cert_cb
;
954 client
->current_server
= SERVER
;
956 if (client
->proxy
.stream
)
957 error
= git_tls_stream_wrap(&client
->server
.stream
, client
->proxy
.stream
, url
->host
);
959 error
= server_create_stream(&client
->server
);
964 cert_cb
= client
->opts
.server_certificate_check_cb
;
965 cert_payload
= client
->opts
.server_certificate_check_payload
;
967 error
= server_connect_stream(&client
->server
, cert_cb
, cert_payload
);
973 GIT_INLINE(void) close_stream(git_http_server
*server
)
975 if (server
->stream
) {
976 git_stream_close(server
->stream
);
977 git_stream_free(server
->stream
);
978 server
->stream
= NULL
;
982 static int http_client_connect(
983 git_http_client
*client
,
984 git_http_request
*request
)
986 bool use_proxy
= false;
989 if ((error
= setup_hosts(client
, request
)) < 0)
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
))
997 client
->connected
= 0;
998 client
->request_count
= 0;
1000 close_stream(&client
->server
);
1001 reset_auth_connection(&client
->server
);
1003 reset_parser(client
);
1005 /* Reconnect to the proxy if necessary. */
1006 use_proxy
= client
->proxy
.url
.host
&&
1007 !strcmp(client
->server
.url
.scheme
, "https");
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
);
1015 client
->proxy_connected
= 0;
1018 if ((error
= proxy_connect(client
, request
)) < 0)
1022 git_trace(GIT_TRACE_DEBUG
, "Connecting to remote %s:%s",
1023 client
->server
.url
.host
, client
->server
.url
.port
);
1025 if ((error
= server_connect(client
)) < 0)
1028 client
->connected
= 1;
1032 if (error
!= GIT_RETRY
)
1033 close_stream(&client
->proxy
);
1035 close_stream(&client
->server
);
1039 GIT_INLINE(int) client_read(git_http_client
*client
)
1041 http_parser_context
*parser_context
= client
->parser
.data
;
1043 char *buf
= client
->read_buf
.ptr
+ client
->read_buf
.size
;
1047 stream
= client
->current_server
== PROXY
?
1048 client
->proxy
.stream
: client
->server
.stream
;
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.
1055 max_len
= client
->read_buf
.asize
- client
->read_buf
.size
;
1056 max_len
= min(max_len
, INT_MAX
);
1058 if (parser_context
->output_size
)
1059 max_len
= min(max_len
, parser_context
->output_size
);
1062 git_error_set(GIT_ERROR_HTTP
, "no room in output buffer");
1066 read_len
= git_stream_read(stream
, buf
, max_len
);
1068 if (read_len
>= 0) {
1069 client
->read_buf
.size
+= read_len
;
1071 git_trace(GIT_TRACE_TRACE
, "Received:\n%.*s",
1072 (int)read_len
, buf
);
1075 return (int)read_len
;
1078 static bool parser_settings_initialized
;
1079 static http_parser_settings parser_settings
;
1081 GIT_INLINE(http_parser_settings
*) http_client_parser_settings(void)
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
;
1090 parser_settings_initialized
= true;
1093 return &parser_settings
;
1096 GIT_INLINE(int) client_read_and_parse(git_http_client
*client
)
1098 http_parser
*parser
= &client
->parser
;
1099 http_parser_context
*ctx
= (http_parser_context
*) parser
->data
;
1100 unsigned char http_errno
;
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.
1109 if (!client
->read_buf
.size
&& (read_len
= client_read(client
)) < 0)
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
;
1118 if (parsed_len
> INT_MAX
) {
1119 git_error_set(GIT_ERROR_HTTP
, "unexpectedly large parse");
1123 if (parser
->upgrade
) {
1124 git_error_set(GIT_ERROR_HTTP
, "server requested upgrade");
1128 if (ctx
->parse_status
== PARSE_STATUS_ERROR
) {
1129 client
->connected
= 0;
1130 return ctx
->error
? ctx
->error
: -1;
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.)
1140 if (http_errno
== HPE_PAUSED
) {
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
1146 assert(client
->read_buf
.size
> parsed_len
);
1148 http_parser_pause(parser
, 0);
1150 parsed_len
+= http_parser_execute(parser
,
1151 http_client_parser_settings(),
1152 client
->read_buf
.ptr
+ parsed_len
,
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
));
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
));
1171 /* recv returned 0, the server hung up on us */
1172 else if (!parsed_len
) {
1173 git_error_set(GIT_ERROR_HTTP
, "unexpected EOF");
1177 git_buf_consume_bytes(&client
->read_buf
, parsed_len
);
1179 return (int)parsed_len
;
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.
1191 static void complete_response_body(git_http_client
*client
)
1193 http_parser_context parser_context
= {0};
1195 /* If we're not keeping alive, don't bother. */
1196 if (!client
->keepalive
) {
1197 client
->connected
= 0;
1201 parser_context
.client
= client
;
1202 client
->parser
.data
= &parser_context
;
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
)) {
1210 client
->connected
= 0;
1214 git_buf_clear(&client
->read_buf
);
1217 int git_http_client_send_request(
1218 git_http_client
*client
,
1219 git_http_request
*request
)
1221 git_http_response response
= {0};
1224 assert(client
&& request
);
1226 /* If the client did not finish reading, clean up the stream. */
1227 if (client
->state
== READING_BODY
)
1228 complete_response_body(client
);
1230 /* If we're waiting for proxy auth, don't sending more requests. */
1231 if (client
->state
== HAS_EARLY_RESPONSE
)
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
);
1243 if ((error
= http_client_connect(client
, request
)) < 0 ||
1244 (error
= generate_request(client
, request
)) < 0 ||
1245 (error
= client_write_request(client
)) < 0)
1248 client
->state
= SENT_REQUEST
;
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)
1257 if (response
.status
!= GIT_HTTP_STATUS_CONTINUE
) {
1258 save_early_response(client
, &response
);
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
;
1270 reset_parser(client
);
1273 if (error
== GIT_RETRY
)
1276 git_http_response_dispose(&response
);
1280 bool git_http_client_has_response(git_http_client
*client
)
1282 return (client
->state
== HAS_EARLY_RESPONSE
||
1283 client
->state
> SENT_REQUEST
);
1286 int git_http_client_send_body(
1287 git_http_client
*client
,
1291 git_http_server
*server
;
1292 git_buf hdr
= GIT_BUF_INIT
;
1297 /* If we're waiting for proxy auth, don't sending more requests. */
1298 if (client
->state
== HAS_EARLY_RESPONSE
)
1301 if (client
->state
!= SENDING_BODY
) {
1302 git_error_set(GIT_ERROR_HTTP
, "client is in invalid state");
1309 server
= &client
->server
;
1311 if (client
->request_body_len
) {
1312 assert(buffer_len
<= client
->request_body_remain
);
1314 if ((error
= stream_write(server
, buffer
, buffer_len
)) < 0)
1317 client
->request_body_remain
-= buffer_len
;
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)
1327 git_buf_dispose(&hdr
);
1331 static int complete_request(git_http_client
*client
)
1335 assert(client
&& client
->state
== SENDING_BODY
);
1337 if (client
->request_body_len
&& client
->request_body_remain
) {
1338 git_error_set(GIT_ERROR_HTTP
, "truncated write");
1340 } else if (client
->request_chunked
) {
1341 error
= stream_write(&client
->server
, "0\r\n\r\n", 5);
1344 client
->state
= SENT_REQUEST
;
1348 int git_http_client_read_response(
1349 git_http_response
*response
,
1350 git_http_client
*client
)
1352 http_parser_context parser_context
= {0};
1355 assert(response
&& client
);
1357 if (client
->state
== SENDING_BODY
) {
1358 if ((error
= complete_request(client
)) < 0)
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
;
1369 if (client
->state
!= SENT_REQUEST
) {
1370 git_error_set(GIT_ERROR_HTTP
, "client is in invalid state");
1375 git_http_response_dispose(response
);
1377 git_vector_free_deep(&client
->server
.auth_challenges
);
1378 git_vector_free_deep(&client
->proxy
.auth_challenges
);
1380 client
->state
= READING_RESPONSE
;
1381 client
->keepalive
= 0;
1382 client
->parser
.data
= &parser_context
;
1384 parser_context
.client
= client
;
1385 parser_context
.response
= response
;
1387 while (client
->state
== READING_RESPONSE
) {
1388 if ((error
= client_read_and_parse(client
)) < 0)
1392 assert(client
->state
== READING_BODY
|| client
->state
== DONE
);
1395 git_buf_dispose(&parser_context
.parse_header_name
);
1396 git_buf_dispose(&parser_context
.parse_header_value
);
1401 int git_http_client_read_body(
1402 git_http_client
*client
,
1406 http_parser_context parser_context
= {0};
1409 if (client
->state
== DONE
)
1412 if (client
->state
!= READING_BODY
) {
1413 git_error_set(GIT_ERROR_HTTP
, "client is in invalid state");
1418 * Now we'll read from the socket and http_parser will pipeline the
1419 * data directly to the client.
1422 parser_context
.client
= client
;
1423 parser_context
.output_buf
= buffer
;
1424 parser_context
.output_size
= buffer_size
;
1426 client
->parser
.data
= &parser_context
;
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
1435 while (!parser_context
.output_written
) {
1436 error
= client_read_and_parse(client
);
1441 if (client
->state
== DONE
)
1445 assert(parser_context
.output_written
<= INT_MAX
);
1446 error
= (int)parser_context
.output_written
;
1450 client
->connected
= 0;
1455 int git_http_client_skip_body(git_http_client
*client
)
1457 http_parser_context parser_context
= {0};
1460 if (client
->state
== DONE
)
1463 if (client
->state
!= READING_BODY
) {
1464 git_error_set(GIT_ERROR_HTTP
, "client is in invalid state");
1468 parser_context
.client
= client
;
1469 client
->parser
.data
= &parser_context
;
1472 error
= client_read_and_parse(client
);
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");
1484 client
->connected
= 0;
1490 * Create an http_client capable of communicating with the given remote
1493 int git_http_client_new(
1494 git_http_client
**out
,
1495 git_http_client_options
*opts
)
1497 git_http_client
*client
;
1501 client
= git__calloc(1, sizeof(git_http_client
));
1502 GIT_ERROR_CHECK_ALLOC(client
);
1504 git_buf_init(&client
->read_buf
, GIT_READ_BUFFER_SIZE
);
1505 GIT_ERROR_CHECK_ALLOC(client
->read_buf
.ptr
);
1508 memcpy(&client
->opts
, opts
, sizeof(git_http_client_options
));
1514 GIT_INLINE(void) http_server_close(git_http_server
*server
)
1516 if (server
->stream
) {
1517 git_stream_close(server
->stream
);
1518 git_stream_free(server
->stream
);
1519 server
->stream
= NULL
;
1522 git_net_url_dispose(&server
->url
);
1524 git_vector_free_deep(&server
->auth_challenges
);
1525 free_auth_context(server
);
1528 static void http_client_close(git_http_client
*client
)
1530 http_server_close(&client
->server
);
1531 http_server_close(&client
->proxy
);
1533 git_buf_dispose(&client
->request_msg
);
1536 client
->request_count
= 0;
1537 client
->connected
= 0;
1538 client
->keepalive
= 0;
1541 void git_http_client_free(git_http_client
*client
)
1546 http_client_close(client
);
1547 git_buf_dispose(&client
->read_buf
);