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.
13 #include "http_parser.h"
18 #include "git2/sys/credential.h"
22 #include "auth_negotiate.h"
23 #include "auth_ntlm.h"
25 #include "streams/tls.h"
26 #include "streams/socket.h"
27 #include "httpclient.h"
29 bool git_http__expect_continue
= false;
33 HTTP_STATE_SENDING_REQUEST
,
34 HTTP_STATE_RECEIVING_RESPONSE
,
39 git_http_method method
;
41 const char *request_type
;
42 const char *response_type
;
47 git_smart_subtransport_stream parent
;
48 const http_service
*service
;
50 unsigned replay_count
;
57 unsigned auth_schemetypes
;
58 unsigned url_cred_presented
: 1;
62 git_smart_subtransport parent
;
63 transport_smart
*owner
;
68 git_http_client
*http_client
;
71 static const http_service upload_pack_ls_service
= {
72 GIT_HTTP_METHOD_GET
, "/info/refs?service=git-upload-pack",
74 "application/x-git-upload-pack-advertisement",
77 static const http_service upload_pack_service
= {
78 GIT_HTTP_METHOD_POST
, "/git-upload-pack",
79 "application/x-git-upload-pack-request",
80 "application/x-git-upload-pack-result",
83 static const http_service receive_pack_ls_service
= {
84 GIT_HTTP_METHOD_GET
, "/info/refs?service=git-receive-pack",
86 "application/x-git-receive-pack-advertisement",
89 static const http_service receive_pack_service
= {
90 GIT_HTTP_METHOD_POST
, "/git-receive-pack",
91 "application/x-git-receive-pack-request",
92 "application/x-git-receive-pack-result",
96 #define SERVER_TYPE_REMOTE "remote"
97 #define SERVER_TYPE_PROXY "proxy"
99 #define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
101 static int apply_url_credentials(
102 git_credential
**cred
,
103 unsigned int allowed_types
,
104 const char *username
,
105 const char *password
)
107 GIT_ASSERT_ARG(username
);
112 if (allowed_types
& GIT_CREDENTIAL_USERPASS_PLAINTEXT
)
113 return git_credential_userpass_plaintext_new(cred
, username
, password
);
115 if ((allowed_types
& GIT_CREDENTIAL_DEFAULT
) && *username
== '\0' && *password
== '\0')
116 return git_credential_default_new(cred
);
118 return GIT_PASSTHROUGH
;
121 GIT_INLINE(void) free_cred(git_credential
**cred
)
124 git_credential_free(*cred
);
129 static int handle_auth(
131 const char *server_type
,
133 unsigned int allowed_schemetypes
,
134 unsigned int allowed_credtypes
,
135 git_credential_acquire_cb callback
,
136 void *callback_payload
)
141 free_cred(&server
->cred
);
143 /* Start with URL-specified credentials, if there were any. */
144 if ((allowed_credtypes
& GIT_CREDENTIAL_USERPASS_PLAINTEXT
) &&
145 !server
->url_cred_presented
&&
146 server
->url
.username
) {
147 error
= apply_url_credentials(&server
->cred
, allowed_credtypes
, server
->url
.username
, server
->url
.password
);
148 server
->url_cred_presented
= 1;
150 /* treat GIT_PASSTHROUGH as if callback isn't set */
151 if (error
== GIT_PASSTHROUGH
)
155 if (error
> 0 && callback
) {
156 error
= callback(&server
->cred
, url
, server
->url
.username
, allowed_credtypes
, callback_payload
);
158 /* treat GIT_PASSTHROUGH as if callback isn't set */
159 if (error
== GIT_PASSTHROUGH
)
164 git_error_set(GIT_ERROR_HTTP
, "%s authentication required but no callback set", server_type
);
169 server
->auth_schemetypes
= allowed_schemetypes
;
174 GIT_INLINE(int) handle_remote_auth(
176 git_http_response
*response
)
178 http_subtransport
*transport
= OWNING_SUBTRANSPORT(stream
);
180 if (response
->server_auth_credtypes
== 0) {
181 git_error_set(GIT_ERROR_HTTP
, "server requires authentication that we do not support");
185 /* Otherwise, prompt for credentials. */
189 transport
->owner
->url
,
190 response
->server_auth_schemetypes
,
191 response
->server_auth_credtypes
,
192 transport
->owner
->cred_acquire_cb
,
193 transport
->owner
->cred_acquire_payload
);
196 GIT_INLINE(int) handle_proxy_auth(
198 git_http_response
*response
)
200 http_subtransport
*transport
= OWNING_SUBTRANSPORT(stream
);
202 if (response
->proxy_auth_credtypes
== 0) {
203 git_error_set(GIT_ERROR_HTTP
, "proxy requires authentication that we do not support");
207 /* Otherwise, prompt for credentials. */
211 transport
->owner
->proxy
.url
,
212 response
->server_auth_schemetypes
,
213 response
->proxy_auth_credtypes
,
214 transport
->owner
->proxy
.credentials
,
215 transport
->owner
->proxy
.payload
);
219 static int handle_response(
222 git_http_response
*response
,
225 http_subtransport
*transport
= OWNING_SUBTRANSPORT(stream
);
230 if (allow_replay
&& git_http_response_is_redirect(response
)) {
231 if (!response
->location
) {
232 git_error_set(GIT_ERROR_HTTP
, "redirect without location");
236 if (git_net_url_apply_redirect(&transport
->server
.url
, response
->location
, stream
->service
->url
) < 0) {
241 } else if (git_http_response_is_redirect(response
)) {
242 git_error_set(GIT_ERROR_HTTP
, "unexpected redirect");
246 /* If we're in the middle of challenge/response auth, continue. */
247 if (allow_replay
&& response
->resend_credentials
) {
249 } else if (allow_replay
&& response
->status
== GIT_HTTP_STATUS_UNAUTHORIZED
) {
250 if ((error
= handle_remote_auth(stream
, response
)) < 0)
253 return git_http_client_skip_body(transport
->http_client
);
254 } else if (allow_replay
&& response
->status
== GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED
) {
255 if ((error
= handle_proxy_auth(stream
, response
)) < 0)
258 return git_http_client_skip_body(transport
->http_client
);
259 } else if (response
->status
== GIT_HTTP_STATUS_UNAUTHORIZED
||
260 response
->status
== GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED
) {
261 git_error_set(GIT_ERROR_HTTP
, "unexpected authentication failure");
265 if (response
->status
!= GIT_HTTP_STATUS_OK
) {
266 git_error_set(GIT_ERROR_HTTP
, "unexpected http status code: %d", response
->status
);
270 /* The response must contain a Content-Type header. */
271 if (!response
->content_type
) {
272 git_error_set(GIT_ERROR_HTTP
, "no content-type header in response");
276 /* The Content-Type header must match our expectation. */
277 if (strcmp(response
->content_type
, stream
->service
->response_type
) != 0) {
278 git_error_set(GIT_ERROR_HTTP
, "invalid content-type: '%s'", response
->content_type
);
283 stream
->state
= HTTP_STATE_RECEIVING_RESPONSE
;
287 static int lookup_proxy(
289 http_subtransport
*transport
)
297 git_net_url_dispose(&transport
->proxy
.url
);
299 switch (transport
->owner
->proxy
.type
) {
300 case GIT_PROXY_SPECIFIED
:
301 proxy
= transport
->owner
->proxy
.url
;
305 remote
= transport
->owner
->owner
;
307 error
= git_remote__http_proxy(&config
, remote
, &transport
->server
.url
);
309 if (error
|| !config
)
320 (error
= git_net_url_parse(&transport
->proxy
.url
, proxy
)) < 0)
330 static int generate_request(
332 git_http_request
*request
,
336 http_subtransport
*transport
= OWNING_SUBTRANSPORT(stream
);
337 bool use_proxy
= false;
340 if ((error
= git_net_url_joinpath(url
,
341 &transport
->server
.url
, stream
->service
->url
)) < 0 ||
342 (error
= lookup_proxy(&use_proxy
, transport
)) < 0)
345 request
->method
= stream
->service
->method
;
347 request
->credentials
= transport
->server
.cred
;
348 request
->proxy
= use_proxy
? &transport
->proxy
.url
: NULL
;
349 request
->proxy_credentials
= transport
->proxy
.cred
;
350 request
->custom_headers
= &transport
->owner
->custom_headers
;
352 if (stream
->service
->method
== GIT_HTTP_METHOD_POST
) {
353 request
->chunked
= stream
->service
->chunked
;
354 request
->content_length
= stream
->service
->chunked
? 0 : len
;
355 request
->content_type
= stream
->service
->request_type
;
356 request
->accept
= stream
->service
->response_type
;
357 request
->expect_continue
= git_http__expect_continue
;
364 * Read from an HTTP transport - for the first invocation of this function
365 * (ie, when stream->state == HTTP_STATE_NONE), we'll send a GET request
366 * to the remote host. We will stream that data back on all subsequent
369 static int http_stream_read(
370 git_smart_subtransport_stream
*s
,
375 http_stream
*stream
= (http_stream
*)s
;
376 http_subtransport
*transport
= OWNING_SUBTRANSPORT(stream
);
377 git_net_url url
= GIT_NET_URL_INIT
;
378 git_net_url proxy_url
= GIT_NET_URL_INIT
;
379 git_http_request request
= {0};
380 git_http_response response
= {0};
386 if (stream
->state
== HTTP_STATE_NONE
) {
387 stream
->state
= HTTP_STATE_SENDING_REQUEST
;
388 stream
->replay_count
= 0;
392 * Formulate the URL, send the request and read the response
393 * headers. Some of the request body may also be read.
395 while (stream
->state
== HTTP_STATE_SENDING_REQUEST
&&
396 stream
->replay_count
< GIT_HTTP_REPLAY_MAX
) {
397 git_net_url_dispose(&url
);
398 git_net_url_dispose(&proxy_url
);
399 git_http_response_dispose(&response
);
401 if ((error
= generate_request(&url
, &request
, stream
, 0)) < 0 ||
402 (error
= git_http_client_send_request(
403 transport
->http_client
, &request
)) < 0 ||
404 (error
= git_http_client_read_response(
405 &response
, transport
->http_client
)) < 0 ||
406 (error
= handle_response(&complete
, stream
, &response
, true)) < 0)
412 stream
->replay_count
++;
415 if (stream
->state
== HTTP_STATE_SENDING_REQUEST
) {
416 git_error_set(GIT_ERROR_HTTP
, "too many redirects or authentication replays");
417 error
= GIT_ERROR
; /* not GIT_EAUTH, because the exact cause is unclear */
421 GIT_ASSERT(stream
->state
== HTTP_STATE_RECEIVING_RESPONSE
);
423 error
= git_http_client_read_body(transport
->http_client
, buffer
, buffer_size
);
431 git_net_url_dispose(&url
);
432 git_net_url_dispose(&proxy_url
);
433 git_http_response_dispose(&response
);
438 static bool needs_probe(http_stream
*stream
)
440 http_subtransport
*transport
= OWNING_SUBTRANSPORT(stream
);
442 return (transport
->server
.auth_schemetypes
== GIT_HTTP_AUTH_NTLM
||
443 transport
->server
.auth_schemetypes
== GIT_HTTP_AUTH_NEGOTIATE
);
446 static int send_probe(http_stream
*stream
)
448 http_subtransport
*transport
= OWNING_SUBTRANSPORT(stream
);
449 git_http_client
*client
= transport
->http_client
;
450 const char *probe
= "0000";
452 git_net_url url
= GIT_NET_URL_INIT
;
453 git_http_request request
= {0};
454 git_http_response response
= {0};
455 bool complete
= false;
456 size_t step
, steps
= 1;
459 /* NTLM requires a full challenge/response */
460 if (transport
->server
.auth_schemetypes
== GIT_HTTP_AUTH_NTLM
)
461 steps
= GIT_AUTH_STEPS_NTLM
;
464 * Send at most two requests: one without any authentication to see
465 * if we get prompted to authenticate. If we do, send a second one
466 * with the first authentication message. The final authentication
467 * message with the response will occur with the *actual* POST data.
469 for (step
= 0; step
< steps
&& !complete
; step
++) {
470 git_net_url_dispose(&url
);
471 git_http_response_dispose(&response
);
473 if ((error
= generate_request(&url
, &request
, stream
, len
)) < 0 ||
474 (error
= git_http_client_send_request(client
, &request
)) < 0 ||
475 (error
= git_http_client_send_body(client
, probe
, len
)) < 0 ||
476 (error
= git_http_client_read_response(&response
, client
)) < 0 ||
477 (error
= git_http_client_skip_body(client
)) < 0 ||
478 (error
= handle_response(&complete
, stream
, &response
, true)) < 0)
483 git_http_response_dispose(&response
);
484 git_net_url_dispose(&url
);
489 * Write to an HTTP transport - for the first invocation of this function
490 * (ie, when stream->state == HTTP_STATE_NONE), we'll send a POST request
491 * to the remote host. If we're sending chunked data, then subsequent calls
492 * will write the additional data given in the buffer. If we're not chunking,
493 * then the caller should have given us all the data in the original call.
494 * The caller should call http_stream_read_response to get the result.
496 static int http_stream_write(
497 git_smart_subtransport_stream
*s
,
501 http_stream
*stream
= GIT_CONTAINER_OF(s
, http_stream
, parent
);
502 http_subtransport
*transport
= OWNING_SUBTRANSPORT(stream
);
503 git_net_url url
= GIT_NET_URL_INIT
;
504 git_http_request request
= {0};
505 git_http_response response
= {0};
508 while (stream
->state
== HTTP_STATE_NONE
&&
509 stream
->replay_count
< GIT_HTTP_REPLAY_MAX
) {
511 git_net_url_dispose(&url
);
512 git_http_response_dispose(&response
);
515 * If we're authenticating with a connection-based mechanism
516 * (NTLM, Kerberos), send a "probe" packet. Servers SHOULD
517 * authenticate an entire keep-alive connection, so ideally
518 * we should not need to authenticate but some servers do
519 * not support this. By sending a probe packet, we'll be
520 * able to follow up with a second POST using the actual
521 * data (and, in the degenerate case, the authentication
524 if (needs_probe(stream
) && (error
= send_probe(stream
)) < 0)
527 /* Send the regular POST request. */
528 if ((error
= generate_request(&url
, &request
, stream
, len
)) < 0 ||
529 (error
= git_http_client_send_request(
530 transport
->http_client
, &request
)) < 0)
533 if (request
.expect_continue
&&
534 git_http_client_has_response(transport
->http_client
)) {
538 * If we got a response to an expect/continue, then
539 * it's something other than a 100 and we should
540 * deal with the response somehow.
542 if ((error
= git_http_client_read_response(&response
, transport
->http_client
)) < 0 ||
543 (error
= handle_response(&complete
, stream
, &response
, true)) < 0)
546 stream
->state
= HTTP_STATE_SENDING_REQUEST
;
549 stream
->replay_count
++;
552 if (stream
->state
== HTTP_STATE_NONE
) {
553 git_error_set(GIT_ERROR_HTTP
,
554 "too many redirects or authentication replays");
555 error
= GIT_ERROR
; /* not GIT_EAUTH because the exact cause is unclear */
559 GIT_ASSERT(stream
->state
== HTTP_STATE_SENDING_REQUEST
);
561 error
= git_http_client_send_body(transport
->http_client
, buffer
, len
);
564 git_http_response_dispose(&response
);
565 git_net_url_dispose(&url
);
570 * Read from an HTTP transport after it has been written to. This is the
571 * response from a POST request made by http_stream_write.
573 static int http_stream_read_response(
574 git_smart_subtransport_stream
*s
,
579 http_stream
*stream
= (http_stream
*)s
;
580 http_subtransport
*transport
= OWNING_SUBTRANSPORT(stream
);
581 git_http_client
*client
= transport
->http_client
;
582 git_http_response response
= {0};
588 if (stream
->state
== HTTP_STATE_SENDING_REQUEST
) {
589 if ((error
= git_http_client_read_response(&response
, client
)) < 0 ||
590 (error
= handle_response(&complete
, stream
, &response
, false)) < 0)
593 GIT_ASSERT(complete
);
594 stream
->state
= HTTP_STATE_RECEIVING_RESPONSE
;
597 error
= git_http_client_read_body(client
, buffer
, buffer_size
);
605 git_http_response_dispose(&response
);
609 static void http_stream_free(git_smart_subtransport_stream
*stream
)
611 http_stream
*s
= GIT_CONTAINER_OF(stream
, http_stream
, parent
);
615 static const http_service
*select_service(git_smart_service_t action
)
618 case GIT_SERVICE_UPLOADPACK_LS
:
619 return &upload_pack_ls_service
;
620 case GIT_SERVICE_UPLOADPACK
:
621 return &upload_pack_service
;
622 case GIT_SERVICE_RECEIVEPACK_LS
:
623 return &receive_pack_ls_service
;
624 case GIT_SERVICE_RECEIVEPACK
:
625 return &receive_pack_service
;
631 static int http_action(
632 git_smart_subtransport_stream
**out
,
633 git_smart_subtransport
*t
,
635 git_smart_service_t action
)
637 http_subtransport
*transport
= GIT_CONTAINER_OF(t
, http_subtransport
, parent
);
639 const http_service
*service
;
648 * If we've seen a redirect then preserve the location that we've
649 * been given. This is important to continue authorization against
650 * the redirect target, not the user-given source; the endpoint may
651 * have redirected us from HTTP->HTTPS and is using an auth mechanism
652 * that would be insecure in plaintext (eg, HTTP Basic).
654 if (!git_net_url_valid(&transport
->server
.url
) &&
655 (error
= git_net_url_parse(&transport
->server
.url
, url
)) < 0)
658 if ((service
= select_service(action
)) == NULL
) {
659 git_error_set(GIT_ERROR_HTTP
, "invalid action");
663 stream
= git__calloc(sizeof(http_stream
), 1);
664 GIT_ERROR_CHECK_ALLOC(stream
);
666 if (!transport
->http_client
) {
667 git_http_client_options opts
= {0};
669 opts
.server_certificate_check_cb
= transport
->owner
->certificate_check_cb
;
670 opts
.server_certificate_check_payload
= transport
->owner
->message_cb_payload
;
671 opts
.proxy_certificate_check_cb
= transport
->owner
->proxy
.certificate_check
;
672 opts
.proxy_certificate_check_payload
= transport
->owner
->proxy
.payload
;
674 if (git_http_client_new(&transport
->http_client
, &opts
) < 0)
678 stream
->service
= service
;
679 stream
->parent
.subtransport
= &transport
->parent
;
681 if (service
->method
== GIT_HTTP_METHOD_GET
) {
682 stream
->parent
.read
= http_stream_read
;
684 stream
->parent
.write
= http_stream_write
;
685 stream
->parent
.read
= http_stream_read_response
;
688 stream
->parent
.free
= http_stream_free
;
690 *out
= (git_smart_subtransport_stream
*)stream
;
694 static int http_close(git_smart_subtransport
*t
)
696 http_subtransport
*transport
= GIT_CONTAINER_OF(t
, http_subtransport
, parent
);
698 free_cred(&transport
->server
.cred
);
699 free_cred(&transport
->proxy
.cred
);
701 transport
->server
.url_cred_presented
= false;
702 transport
->proxy
.url_cred_presented
= false;
704 git_net_url_dispose(&transport
->server
.url
);
705 git_net_url_dispose(&transport
->proxy
.url
);
710 static void http_free(git_smart_subtransport
*t
)
712 http_subtransport
*transport
= GIT_CONTAINER_OF(t
, http_subtransport
, parent
);
714 git_http_client_free(transport
->http_client
);
717 git__free(transport
);
720 int git_smart_subtransport_http(git_smart_subtransport
**out
, git_transport
*owner
, void *param
)
722 http_subtransport
*transport
;
728 transport
= git__calloc(sizeof(http_subtransport
), 1);
729 GIT_ERROR_CHECK_ALLOC(transport
);
731 transport
->owner
= (transport_smart
*)owner
;
732 transport
->parent
.action
= http_action
;
733 transport
->parent
.close
= http_close
;
734 transport
->parent
.free
= http_free
;
736 *out
= (git_smart_subtransport
*) transport
;
740 #endif /* !GIT_WINHTTP */