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.
11 #include "git2/transport.h"
17 #include "repository.h"
22 /* For IInternetSecurityManager zone check */
26 #define WIDEN2(s) L ## s
27 #define WIDEN(s) WIDEN2(s)
29 #define MAX_CONTENT_TYPE_LEN 100
30 #define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE 109
31 #define CACHED_POST_BODY_BUF_SIZE 4096
32 #define UUID_LENGTH_CCH 32
33 #define TIMEOUT_INFINITE -1
34 #define DEFAULT_CONNECT_TIMEOUT 60000
35 #ifndef WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH
36 #define WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH 0
39 static const char *prefix_https
= "https://";
40 static const char *upload_pack_service
= "upload-pack";
41 static const char *upload_pack_ls_service_url
= "/info/refs?service=git-upload-pack";
42 static const char *upload_pack_service_url
= "/git-upload-pack";
43 static const char *receive_pack_service
= "receive-pack";
44 static const char *receive_pack_ls_service_url
= "/info/refs?service=git-receive-pack";
45 static const char *receive_pack_service_url
= "/git-receive-pack";
46 static const wchar_t *get_verb
= L
"GET";
47 static const wchar_t *post_verb
= L
"POST";
48 static const wchar_t *pragma_nocache
= L
"Pragma: no-cache";
49 static const wchar_t *transfer_encoding
= L
"Transfer-Encoding: chunked";
50 static const int no_check_cert_flags
= SECURITY_FLAG_IGNORE_CERT_CN_INVALID
|
51 SECURITY_FLAG_IGNORE_CERT_DATE_INVALID
|
52 SECURITY_FLAG_IGNORE_UNKNOWN_CA
;
54 #if defined(__MINGW32__)
55 const CLSID CLSID_InternetSecurityManager
= { 0x7B8A2D94, 0x0AC9, 0x11D1,
56 { 0x89, 0x6C, 0x00, 0xC0, 0x4F, 0xB6, 0xBF, 0xC4 } };
57 const IID IID_IInternetSecurityManager
= { 0x79EAC9EE, 0xBAF9, 0x11CE,
58 { 0x8C, 0x82, 0x00, 0xAA, 0x00, 0x4B, 0xA9, 0x0B } };
61 #define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport)
64 GIT_WINHTTP_AUTH_BASIC
= 1,
65 GIT_WINHTTP_AUTH_NEGOTIATE
= 2,
66 } winhttp_authmechanism_t
;
69 git_smart_subtransport_stream parent
;
71 const char *service_url
;
76 unsigned chunk_buffer_len
;
79 unsigned sent_request
: 1,
80 received_response
: 1,
85 git_smart_subtransport parent
;
86 transport_smart
*owner
;
87 gitno_connection_data connection_data
;
93 } winhttp_subtransport
;
95 static int apply_basic_credential(HINTERNET request
, git_cred
*cred
)
97 git_cred_userpass_plaintext
*c
= (git_cred_userpass_plaintext
*)cred
;
98 git_buf buf
= GIT_BUF_INIT
, raw
= GIT_BUF_INIT
;
100 int error
= -1, wide_len
;
102 git_buf_printf(&raw
, "%s:%s", c
->username
, c
->password
);
104 if (git_buf_oom(&raw
) ||
105 git_buf_puts(&buf
, "Authorization: Basic ") < 0 ||
106 git_buf_encode_base64(&buf
, git_buf_cstr(&raw
), raw
.size
) < 0)
109 if ((wide_len
= git__utf8_to_16_alloc(&wide
, git_buf_cstr(&buf
))) < 0) {
110 giterr_set(GITERR_OS
, "Failed to convert string to wide form");
114 if (!WinHttpAddRequestHeaders(request
, wide
, (ULONG
) -1L, WINHTTP_ADDREQ_FLAG_ADD
)) {
115 giterr_set(GITERR_OS
, "Failed to add a header to the request");
122 /* We were dealing with plaintext passwords, so clean up after ourselves a bit. */
124 memset(wide
, 0x0, wide_len
* sizeof(wchar_t));
127 memset(buf
.ptr
, 0x0, buf
.size
);
130 memset(raw
.ptr
, 0x0, raw
.size
);
138 static int apply_default_credentials(HINTERNET request
)
140 /* Either the caller explicitly requested that default credentials be passed,
141 * or our fallback credential callback was invoked and checked that the target
142 * URI was in the appropriate Internet Explorer security zone. By setting this
143 * flag, we guarantee that the credentials are delivered by WinHTTP. The default
144 * is "medium" which applies to the intranet and sounds like it would correspond
145 * to Internet Explorer security zones, but in fact does not. */
146 DWORD data
= WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW
;
148 if (!WinHttpSetOption(request
, WINHTTP_OPTION_AUTOLOGON_POLICY
, &data
, sizeof(DWORD
)))
154 static int fallback_cred_acquire_cb(
157 const char *username_from_url
,
158 unsigned int allowed_types
,
163 GIT_UNUSED(username_from_url
);
166 /* If the target URI supports integrated Windows authentication
167 * as an authentication mechanism */
168 if (GIT_CREDTYPE_DEFAULT
& allowed_types
) {
171 /* Convert URL to wide characters */
172 if (git__utf8_to_16_alloc(&wide_url
, url
) < 0) {
173 giterr_set(GITERR_OS
, "Failed to convert string to wide form");
177 if (SUCCEEDED(CoInitializeEx(NULL
, COINIT_MULTITHREADED
))) {
178 IInternetSecurityManager
* pISM
;
180 /* And if the target URI is in the My Computer, Intranet, or Trusted zones */
181 if (SUCCEEDED(CoCreateInstance(&CLSID_InternetSecurityManager
, NULL
,
182 CLSCTX_ALL
, &IID_IInternetSecurityManager
, (void **)&pISM
))) {
185 if (SUCCEEDED(pISM
->lpVtbl
->MapUrlToZone(pISM
, wide_url
, &dwZone
, 0)) &&
186 (URLZONE_LOCAL_MACHINE
== dwZone
||
187 URLZONE_INTRANET
== dwZone
||
188 URLZONE_TRUSTED
== dwZone
)) {
189 git_cred
*existing
= *cred
;
192 existing
->free(existing
);
194 /* Then use default Windows credentials to authenticate this request */
195 error
= git_cred_default_new(cred
);
198 pISM
->lpVtbl
->Release(pISM
);
210 static int certificate_check(winhttp_stream
*s
, int valid
)
213 winhttp_subtransport
*t
= OWNING_SUBTRANSPORT(s
);
214 PCERT_CONTEXT cert_ctx
;
215 DWORD cert_ctx_size
= sizeof(cert_ctx
);
218 /* If there is no override, we should fail if WinHTTP doesn't think it's fine */
219 if (t
->owner
->certificate_check_cb
== NULL
&& !valid
)
220 return GIT_ECERTIFICATE
;
222 if (t
->owner
->certificate_check_cb
== NULL
|| !t
->connection_data
.use_ssl
)
225 if (!WinHttpQueryOption(s
->request
, WINHTTP_OPTION_SERVER_CERT_CONTEXT
, &cert_ctx
, &cert_ctx_size
)) {
226 giterr_set(GITERR_OS
, "failed to get server certificate");
231 cert
.parent
.cert_type
= GIT_CERT_X509
;
232 cert
.data
= cert_ctx
->pbCertEncoded
;
233 cert
.len
= cert_ctx
->cbCertEncoded
;
234 error
= t
->owner
->certificate_check_cb((git_cert
*) &cert
, valid
, t
->connection_data
.host
, t
->owner
->cred_acquire_payload
);
235 CertFreeCertificateContext(cert_ctx
);
237 if (error
< 0 && !giterr_last())
238 giterr_set(GITERR_NET
, "user cancelled certificate check");
243 static void winhttp_stream_close(winhttp_stream
*s
)
245 if (s
->chunk_buffer
) {
246 git__free(s
->chunk_buffer
);
247 s
->chunk_buffer
= NULL
;
251 CloseHandle(s
->post_body
);
255 if (s
->request_uri
) {
256 git__free(s
->request_uri
);
257 s
->request_uri
= NULL
;
261 WinHttpCloseHandle(s
->request
);
268 static int winhttp_stream_connect(winhttp_stream
*s
)
270 winhttp_subtransport
*t
= OWNING_SUBTRANSPORT(s
);
271 git_buf buf
= GIT_BUF_INIT
;
272 char *proxy_url
= NULL
;
273 wchar_t ct
[MAX_CONTENT_TYPE_LEN
];
274 LPCWSTR types
[] = { L
"*/*", NULL
};
275 BOOL peerdist
= FALSE
;
277 unsigned long disable_redirects
= WINHTTP_DISABLE_REDIRECTS
;
278 int default_timeout
= TIMEOUT_INFINITE
;
279 int default_connect_timeout
= DEFAULT_CONNECT_TIMEOUT
;
282 git_buf_printf(&buf
, "%s%s", t
->connection_data
.path
, s
->service_url
);
284 if (git_buf_oom(&buf
))
287 /* Convert URL to wide characters */
288 if (git__utf8_to_16_alloc(&s
->request_uri
, git_buf_cstr(&buf
)) < 0) {
289 giterr_set(GITERR_OS
, "Failed to convert string to wide form");
293 /* Establish request */
294 s
->request
= WinHttpOpenRequest(
301 t
->connection_data
.use_ssl
? WINHTTP_FLAG_SECURE
: 0);
304 giterr_set(GITERR_OS
, "Failed to open request");
308 if (!WinHttpSetTimeouts(s
->request
, default_timeout
, default_connect_timeout
, default_timeout
, default_timeout
)) {
309 giterr_set(GITERR_OS
, "Failed to set timeouts for WinHTTP");
313 /* Set proxy if necessary */
314 if (git_remote__get_http_proxy(t
->owner
->owner
, !!t
->connection_data
.use_ssl
, &proxy_url
) < 0)
318 WINHTTP_PROXY_INFO proxy_info
;
321 /* Convert URL to wide characters */
322 int proxy_wide_len
= git__utf8_to_16_alloc(&proxy_wide
, proxy_url
);
324 if (proxy_wide_len
< 0) {
325 giterr_set(GITERR_OS
, "Failed to convert string to wide form");
329 /* Strip any trailing forward slash on the proxy URL;
330 * WinHTTP doesn't like it if one is present */
331 if (proxy_wide_len
> 1 && L
'/' == proxy_wide
[proxy_wide_len
- 2])
332 proxy_wide
[proxy_wide_len
- 2] = L
'\0';
334 proxy_info
.dwAccessType
= WINHTTP_ACCESS_TYPE_NAMED_PROXY
;
335 proxy_info
.lpszProxy
= proxy_wide
;
336 proxy_info
.lpszProxyBypass
= NULL
;
338 if (!WinHttpSetOption(s
->request
,
339 WINHTTP_OPTION_PROXY
,
341 sizeof(WINHTTP_PROXY_INFO
))) {
342 giterr_set(GITERR_OS
, "Failed to set proxy");
343 git__free(proxy_wide
);
347 git__free(proxy_wide
);
350 /* Disable WinHTTP redirects so we can handle them manually. Why, you ask?
351 * http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/b2ff8879-ab9f-4218-8f09-16d25dff87ae
353 if (!WinHttpSetOption(s
->request
,
354 WINHTTP_OPTION_DISABLE_FEATURE
,
356 sizeof(disable_redirects
))) {
357 giterr_set(GITERR_OS
, "Failed to disable redirects");
361 /* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP
362 * adds itself. This option may not be supported by the underlying
363 * platform, so we do not error-check it */
364 WinHttpSetOption(s
->request
,
365 WINHTTP_OPTION_PEERDIST_EXTENSION_STATE
,
369 /* Send Pragma: no-cache header */
370 if (!WinHttpAddRequestHeaders(s
->request
, pragma_nocache
, (ULONG
) -1L, WINHTTP_ADDREQ_FLAG_ADD
)) {
371 giterr_set(GITERR_OS
, "Failed to add a header to the request");
375 if (post_verb
== s
->verb
) {
376 /* Send Content-Type and Accept headers -- only necessary on a POST */
378 if (git_buf_printf(&buf
,
379 "Content-Type: application/x-git-%s-request",
383 if (git__utf8_to_16(ct
, MAX_CONTENT_TYPE_LEN
, git_buf_cstr(&buf
)) < 0) {
384 giterr_set(GITERR_OS
, "Failed to convert content-type to wide characters");
388 if (!WinHttpAddRequestHeaders(s
->request
, ct
, (ULONG
)-1L,
389 WINHTTP_ADDREQ_FLAG_ADD
| WINHTTP_ADDREQ_FLAG_REPLACE
)) {
390 giterr_set(GITERR_OS
, "Failed to add a header to the request");
395 if (git_buf_printf(&buf
,
396 "Accept: application/x-git-%s-result",
400 if (git__utf8_to_16(ct
, MAX_CONTENT_TYPE_LEN
, git_buf_cstr(&buf
)) < 0) {
401 giterr_set(GITERR_OS
, "Failed to convert accept header to wide characters");
405 if (!WinHttpAddRequestHeaders(s
->request
, ct
, (ULONG
)-1L,
406 WINHTTP_ADDREQ_FLAG_ADD
| WINHTTP_ADDREQ_FLAG_REPLACE
)) {
407 giterr_set(GITERR_OS
, "Failed to add a header to the request");
412 /* If requested, disable certificate validation */
413 if (t
->connection_data
.use_ssl
) {
416 if (t
->owner
->parent
.read_flags(&t
->owner
->parent
, &flags
) < 0)
420 /* If we have a credential on the subtransport, apply it to the request */
422 t
->cred
->credtype
== GIT_CREDTYPE_USERPASS_PLAINTEXT
&&
423 t
->auth_mechanism
== GIT_WINHTTP_AUTH_BASIC
&&
424 apply_basic_credential(s
->request
, t
->cred
) < 0)
427 t
->cred
->credtype
== GIT_CREDTYPE_DEFAULT
&&
428 t
->auth_mechanism
== GIT_WINHTTP_AUTH_NEGOTIATE
&&
429 apply_default_credentials(s
->request
) < 0)
432 /* If no other credentials have been applied and the URL has username and
433 * password, use those */
434 if (!t
->cred
&& t
->connection_data
.user
&& t
->connection_data
.pass
) {
436 git_cred_userpass_plaintext_new(&t
->url_cred
, t
->connection_data
.user
, t
->connection_data
.pass
) < 0)
438 if (apply_basic_credential(s
->request
, t
->url_cred
) < 0)
442 /* We've done everything up to calling WinHttpSendRequest. */
448 winhttp_stream_close(s
);
450 git__free(proxy_url
);
455 static int parse_unauthorized_response(
460 DWORD supported
, first
, target
;
465 /* WinHttpQueryHeaders() must be called before WinHttpQueryAuthSchemes().
466 * We can assume this was already done, since we know we are unauthorized.
468 if (!WinHttpQueryAuthSchemes(request
, &supported
, &first
, &target
)) {
469 giterr_set(GITERR_OS
, "Failed to parse supported auth schemes");
473 if (WINHTTP_AUTH_SCHEME_BASIC
& supported
) {
474 *allowed_types
|= GIT_CREDTYPE_USERPASS_PLAINTEXT
;
475 *auth_mechanism
= GIT_WINHTTP_AUTH_BASIC
;
478 if ((WINHTTP_AUTH_SCHEME_NTLM
& supported
) ||
479 (WINHTTP_AUTH_SCHEME_NEGOTIATE
& supported
)) {
480 *allowed_types
|= GIT_CREDTYPE_DEFAULT
;
481 *auth_mechanism
= GIT_WINHTTP_AUTH_NEGOTIATE
;
487 static int write_chunk(HINTERNET request
, const char *buffer
, size_t len
)
490 git_buf buf
= GIT_BUF_INIT
;
493 git_buf_printf(&buf
, "%X\r\n", len
);
495 if (git_buf_oom(&buf
))
498 if (!WinHttpWriteData(request
,
499 git_buf_cstr(&buf
), (DWORD
)git_buf_len(&buf
),
502 giterr_set(GITERR_OS
, "Failed to write chunk header");
509 if (!WinHttpWriteData(request
,
512 giterr_set(GITERR_OS
, "Failed to write chunk");
517 if (!WinHttpWriteData(request
,
520 giterr_set(GITERR_OS
, "Failed to write chunk footer");
527 static int winhttp_close_connection(winhttp_subtransport
*t
)
532 if (!WinHttpCloseHandle(t
->connection
)) {
533 giterr_set(GITERR_OS
, "Unable to close connection");
537 t
->connection
= NULL
;
541 if (!WinHttpCloseHandle(t
->session
)) {
542 giterr_set(GITERR_OS
, "Unable to close session");
552 static int winhttp_connect(
553 winhttp_subtransport
*t
)
555 wchar_t *ua
= L
"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION
) L
")";
559 int default_timeout
= TIMEOUT_INFINITE
;
560 int default_connect_timeout
= DEFAULT_CONNECT_TIMEOUT
;
563 t
->connection
= NULL
;
566 if (git__strtol32(&port
, t
->connection_data
.port
, NULL
, 10) < 0)
570 if (git__utf8_to_16_alloc(&wide_host
, t
->connection_data
.host
) < 0) {
571 giterr_set(GITERR_OS
, "Unable to convert host to wide characters");
575 /* Establish session */
576 t
->session
= WinHttpOpen(
578 WINHTTP_ACCESS_TYPE_DEFAULT_PROXY
,
579 WINHTTP_NO_PROXY_NAME
,
580 WINHTTP_NO_PROXY_BYPASS
,
584 giterr_set(GITERR_OS
, "Failed to init WinHTTP");
588 if (!WinHttpSetTimeouts(t
->session
, default_timeout
, default_connect_timeout
, default_timeout
, default_timeout
)) {
589 giterr_set(GITERR_OS
, "Failed to set timeouts for WinHTTP");
594 /* Establish connection */
595 t
->connection
= WinHttpConnect(
598 (INTERNET_PORT
) port
,
601 if (!t
->connection
) {
602 giterr_set(GITERR_OS
, "Failed to connect to host");
610 winhttp_close_connection(t
);
612 git__free(wide_host
);
617 static int do_send_request(winhttp_stream
*s
, size_t len
, int ignore_length
)
620 if (!WinHttpSendRequest(s
->request
,
621 WINHTTP_NO_ADDITIONAL_HEADERS
, 0,
622 WINHTTP_NO_REQUEST_DATA
, 0,
623 WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH
, 0)) {
627 if (!WinHttpSendRequest(s
->request
,
628 WINHTTP_NO_ADDITIONAL_HEADERS
, 0,
629 WINHTTP_NO_REQUEST_DATA
, 0,
638 static int send_request(winhttp_stream
*s
, size_t len
, int ignore_length
)
640 int request_failed
= 0, cert_valid
= 1, error
= 0;
643 if ((error
= do_send_request(s
, len
, ignore_length
)) < 0)
646 if (request_failed
) {
647 if (GetLastError() != ERROR_WINHTTP_SECURE_FAILURE
) {
648 giterr_set(GITERR_OS
, "failed to send request");
656 if ((error
= certificate_check(s
, cert_valid
)) < 0) {
658 giterr_set(GITERR_OS
, "user cancelled certificate check");
663 /* if neither the request nor the certificate check returned errors, we're done */
667 ignore_flags
= no_check_cert_flags
;
669 if (!WinHttpSetOption(s
->request
, WINHTTP_OPTION_SECURITY_FLAGS
, &ignore_flags
, sizeof(ignore_flags
))) {
670 giterr_set(GITERR_OS
, "failed to set security options");
674 if ((error
= do_send_request(s
, len
, ignore_length
)) < 0)
675 giterr_set(GITERR_OS
, "failed to send request");
680 static int winhttp_stream_read(
681 git_smart_subtransport_stream
*stream
,
686 winhttp_stream
*s
= (winhttp_stream
*)stream
;
687 winhttp_subtransport
*t
= OWNING_SUBTRANSPORT(s
);
689 char replay_count
= 0;
693 /* Enforce a reasonable cap on the number of replays */
694 if (++replay_count
>= 7) {
695 giterr_set(GITERR_NET
, "Too many redirects or authentication replays");
699 /* Connect if necessary */
700 if (!s
->request
&& winhttp_stream_connect(s
) < 0)
703 if (!s
->received_response
) {
704 DWORD status_code
, status_code_length
, content_type_length
, bytes_written
;
705 char expected_content_type_8
[MAX_CONTENT_TYPE_LEN
];
706 wchar_t expected_content_type
[MAX_CONTENT_TYPE_LEN
], content_type
[MAX_CONTENT_TYPE_LEN
];
708 if (!s
->sent_request
) {
710 if ((error
= send_request(s
, s
->post_body_len
, 0)) < 0)
717 assert(s
->verb
== post_verb
);
719 /* Flush, if necessary */
720 if (s
->chunk_buffer_len
> 0 &&
721 write_chunk(s
->request
, s
->chunk_buffer
, s
->chunk_buffer_len
) < 0)
724 s
->chunk_buffer_len
= 0;
726 /* Write the final chunk. */
727 if (!WinHttpWriteData(s
->request
,
730 giterr_set(GITERR_OS
, "Failed to write final chunk");
734 else if (s
->post_body
) {
736 DWORD len
= s
->post_body_len
, bytes_read
;
738 if (INVALID_SET_FILE_POINTER
== SetFilePointer(s
->post_body
,
740 NO_ERROR
!= GetLastError()) {
741 giterr_set(GITERR_OS
, "Failed to reset file pointer");
745 buffer
= git__malloc(CACHED_POST_BODY_BUF_SIZE
);
750 if (!ReadFile(s
->post_body
, buffer
,
751 min(CACHED_POST_BODY_BUF_SIZE
, len
),
752 &bytes_read
, NULL
) ||
755 giterr_set(GITERR_OS
, "Failed to read from temp file");
759 if (!WinHttpWriteData(s
->request
, buffer
,
760 bytes_read
, &bytes_written
)) {
762 giterr_set(GITERR_OS
, "Failed to write data");
767 assert(bytes_read
== bytes_written
);
772 /* Eagerly close the temp file */
773 CloseHandle(s
->post_body
);
777 if (!WinHttpReceiveResponse(s
->request
, 0)) {
778 giterr_set(GITERR_OS
, "Failed to receive response");
782 /* Verify that we got a 200 back */
783 status_code_length
= sizeof(status_code
);
785 if (!WinHttpQueryHeaders(s
->request
,
786 WINHTTP_QUERY_STATUS_CODE
| WINHTTP_QUERY_FLAG_NUMBER
,
787 WINHTTP_HEADER_NAME_BY_INDEX
,
788 &status_code
, &status_code_length
,
789 WINHTTP_NO_HEADER_INDEX
)) {
790 giterr_set(GITERR_OS
, "Failed to retrieve status code");
794 /* The implementation of WinHTTP prior to Windows 7 will not
795 * redirect to an identical URI. Some Git hosters use self-redirects
796 * as part of their DoS mitigation strategy. Check first to see if we
797 * have a redirect status code, and that we haven't already streamed
798 * a post body. (We can't replay a streamed POST.) */
800 (HTTP_STATUS_MOVED
== status_code
||
801 HTTP_STATUS_REDIRECT
== status_code
||
802 (HTTP_STATUS_REDIRECT_METHOD
== status_code
&&
803 get_verb
== s
->verb
) ||
804 HTTP_STATUS_REDIRECT_KEEP_VERB
== status_code
)) {
806 /* Check for Windows 7. This workaround is only necessary on
807 * Windows Vista and earlier. Windows 7 is version 6.1. */
809 DWORD location_length
;
812 /* OK, fetch the Location header from the redirect. */
813 if (WinHttpQueryHeaders(s
->request
,
814 WINHTTP_QUERY_LOCATION
,
815 WINHTTP_HEADER_NAME_BY_INDEX
,
816 WINHTTP_NO_OUTPUT_BUFFER
,
818 WINHTTP_NO_HEADER_INDEX
) ||
819 GetLastError() != ERROR_INSUFFICIENT_BUFFER
) {
820 giterr_set(GITERR_OS
, "Failed to read Location header");
824 location
= git__malloc(location_length
);
825 GITERR_CHECK_ALLOC(location
);
827 if (!WinHttpQueryHeaders(s
->request
,
828 WINHTTP_QUERY_LOCATION
,
829 WINHTTP_HEADER_NAME_BY_INDEX
,
832 WINHTTP_NO_HEADER_INDEX
)) {
833 giterr_set(GITERR_OS
, "Failed to read Location header");
838 /* Convert the Location header to UTF-8 */
839 if (git__utf16_to_8_alloc(&location8
, location
) < 0) {
840 giterr_set(GITERR_OS
, "Failed to convert Location header to UTF-8");
847 /* Replay the request */
848 winhttp_stream_close(s
);
850 if (!git__prefixcmp_icase(location8
, prefix_https
)) {
851 /* Upgrade to secure connection; disconnect and start over */
852 if (gitno_connection_data_from_url(&t
->connection_data
, location8
, s
->service_url
) < 0) {
853 git__free(location8
);
857 winhttp_close_connection(t
);
859 if (winhttp_connect(t
) < 0)
863 git__free(location8
);
867 /* Handle authentication failures */
868 if (HTTP_STATUS_DENIED
== status_code
&& get_verb
== s
->verb
) {
871 if (parse_unauthorized_response(s
->request
, &allowed_types
, &t
->auth_mechanism
) < 0)
875 (!t
->cred
|| 0 == (t
->cred
->credtype
& allowed_types
))) {
878 /* Start with the user-supplied credential callback, if present */
879 if (t
->owner
->cred_acquire_cb
) {
880 cred_error
= t
->owner
->cred_acquire_cb(&t
->cred
, t
->owner
->url
,
881 t
->connection_data
.user
, allowed_types
, t
->owner
->cred_acquire_payload
);
887 /* Invoke the fallback credentials acquisition callback if necessary */
888 if (cred_error
> 0) {
889 cred_error
= fallback_cred_acquire_cb(&t
->cred
, t
->owner
->url
,
890 t
->connection_data
.user
, allowed_types
, NULL
);
899 winhttp_stream_close(s
);
901 /* Successfully acquired a credential */
907 if (HTTP_STATUS_OK
!= status_code
) {
908 giterr_set(GITERR_NET
, "Request failed with status code: %d", status_code
);
912 /* Verify that we got the correct content-type back */
913 if (post_verb
== s
->verb
)
914 p_snprintf(expected_content_type_8
, MAX_CONTENT_TYPE_LEN
, "application/x-git-%s-result", s
->service
);
916 p_snprintf(expected_content_type_8
, MAX_CONTENT_TYPE_LEN
, "application/x-git-%s-advertisement", s
->service
);
918 if (git__utf8_to_16(expected_content_type
, MAX_CONTENT_TYPE_LEN
, expected_content_type_8
) < 0) {
919 giterr_set(GITERR_OS
, "Failed to convert expected content-type to wide characters");
923 content_type_length
= sizeof(content_type
);
925 if (!WinHttpQueryHeaders(s
->request
,
926 WINHTTP_QUERY_CONTENT_TYPE
,
927 WINHTTP_HEADER_NAME_BY_INDEX
,
928 &content_type
, &content_type_length
,
929 WINHTTP_NO_HEADER_INDEX
)) {
930 giterr_set(GITERR_OS
, "Failed to retrieve response content-type");
934 if (wcscmp(expected_content_type
, content_type
)) {
935 giterr_set(GITERR_NET
, "Received unexpected content-type");
939 s
->received_response
= 1;
942 if (!WinHttpReadData(s
->request
,
947 giterr_set(GITERR_OS
, "Failed to read data");
951 *bytes_read
= dw_bytes_read
;
956 static int winhttp_stream_write_single(
957 git_smart_subtransport_stream
*stream
,
961 winhttp_stream
*s
= (winhttp_stream
*)stream
;
965 if (!s
->request
&& winhttp_stream_connect(s
) < 0)
968 /* This implementation of write permits only a single call. */
969 if (s
->sent_request
) {
970 giterr_set(GITERR_NET
, "Subtransport configured for only one write");
974 if ((error
= send_request(s
, len
, 0)) < 0)
979 if (!WinHttpWriteData(s
->request
,
983 giterr_set(GITERR_OS
, "Failed to write data");
987 assert((DWORD
)len
== bytes_written
);
992 static int put_uuid_string(LPWSTR buffer
, size_t buffer_len_cch
)
995 RPC_STATUS status
= UuidCreate(&uuid
);
998 if (RPC_S_OK
!= status
&&
999 RPC_S_UUID_LOCAL_ONLY
!= status
&&
1000 RPC_S_UUID_NO_ADDRESS
!= status
) {
1001 giterr_set(GITERR_NET
, "Unable to generate name for temp file");
1005 if (buffer_len_cch
< UUID_LENGTH_CCH
+ 1) {
1006 giterr_set(GITERR_NET
, "Buffer too small for name of temp file");
1010 #if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API)
1011 result
= swprintf_s(buffer
, buffer_len_cch
,
1013 result
= wsprintfW(buffer
,
1015 L
"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x",
1016 uuid
.Data1
, uuid
.Data2
, uuid
.Data3
,
1017 uuid
.Data4
[0], uuid
.Data4
[1], uuid
.Data4
[2], uuid
.Data4
[3],
1018 uuid
.Data4
[4], uuid
.Data4
[5], uuid
.Data4
[6], uuid
.Data4
[7]);
1020 if (result
< UUID_LENGTH_CCH
) {
1021 giterr_set(GITERR_OS
, "Unable to generate name for temp file");
1028 static int get_temp_file(LPWSTR buffer
, DWORD buffer_len_cch
)
1032 if (!GetTempPathW(buffer_len_cch
, buffer
)) {
1033 giterr_set(GITERR_OS
, "Failed to get temp path");
1037 len
= wcslen(buffer
);
1039 if (buffer
[len
- 1] != '\\' && len
< buffer_len_cch
)
1040 buffer
[len
++] = '\\';
1042 if (put_uuid_string(&buffer
[len
], (size_t)buffer_len_cch
- len
) < 0)
1048 static int winhttp_stream_write_buffered(
1049 git_smart_subtransport_stream
*stream
,
1053 winhttp_stream
*s
= (winhttp_stream
*)stream
;
1054 DWORD bytes_written
;
1056 if (!s
->request
&& winhttp_stream_connect(s
) < 0)
1059 /* Buffer the payload, using a temporary file so we delegate
1060 * memory management of the data to the operating system. */
1061 if (!s
->post_body
) {
1062 wchar_t temp_path
[MAX_PATH
+ 1];
1064 if (get_temp_file(temp_path
, MAX_PATH
+ 1) < 0)
1067 s
->post_body
= CreateFileW(temp_path
,
1068 GENERIC_READ
| GENERIC_WRITE
,
1069 FILE_SHARE_DELETE
, NULL
,
1071 FILE_ATTRIBUTE_TEMPORARY
| FILE_FLAG_DELETE_ON_CLOSE
| FILE_FLAG_SEQUENTIAL_SCAN
,
1074 if (INVALID_HANDLE_VALUE
== s
->post_body
) {
1075 s
->post_body
= NULL
;
1076 giterr_set(GITERR_OS
, "Failed to create temporary file");
1081 if (!WriteFile(s
->post_body
, buffer
, (DWORD
)len
, &bytes_written
, NULL
)) {
1082 giterr_set(GITERR_OS
, "Failed to write to temporary file");
1086 assert((DWORD
)len
== bytes_written
);
1088 s
->post_body_len
+= bytes_written
;
1093 static int winhttp_stream_write_chunked(
1094 git_smart_subtransport_stream
*stream
,
1098 winhttp_stream
*s
= (winhttp_stream
*)stream
;
1101 if (!s
->request
&& winhttp_stream_connect(s
) < 0)
1104 if (!s
->sent_request
) {
1105 /* Send Transfer-Encoding: chunked header */
1106 if (!WinHttpAddRequestHeaders(s
->request
,
1107 transfer_encoding
, (ULONG
) -1L,
1108 WINHTTP_ADDREQ_FLAG_ADD
)) {
1109 giterr_set(GITERR_OS
, "Failed to add a header to the request");
1113 if ((error
= send_request(s
, 0, 1)) < 0)
1116 s
->sent_request
= 1;
1119 if (len
> CACHED_POST_BODY_BUF_SIZE
) {
1120 /* Flush, if necessary */
1121 if (s
->chunk_buffer_len
> 0) {
1122 if (write_chunk(s
->request
, s
->chunk_buffer
, s
->chunk_buffer_len
) < 0)
1125 s
->chunk_buffer_len
= 0;
1128 /* Write chunk directly */
1129 if (write_chunk(s
->request
, buffer
, len
) < 0)
1133 /* Append as much to the buffer as we can */
1134 int count
= (int)min(CACHED_POST_BODY_BUF_SIZE
- s
->chunk_buffer_len
, len
);
1136 if (!s
->chunk_buffer
)
1137 s
->chunk_buffer
= git__malloc(CACHED_POST_BODY_BUF_SIZE
);
1139 memcpy(s
->chunk_buffer
+ s
->chunk_buffer_len
, buffer
, count
);
1140 s
->chunk_buffer_len
+= count
;
1144 /* Is the buffer full? If so, then flush */
1145 if (CACHED_POST_BODY_BUF_SIZE
== s
->chunk_buffer_len
) {
1146 if (write_chunk(s
->request
, s
->chunk_buffer
, s
->chunk_buffer_len
) < 0)
1149 s
->chunk_buffer_len
= 0;
1151 /* Is there any remaining data from the source? */
1153 memcpy(s
->chunk_buffer
, buffer
, len
);
1154 s
->chunk_buffer_len
= (unsigned int)len
;
1162 static void winhttp_stream_free(git_smart_subtransport_stream
*stream
)
1164 winhttp_stream
*s
= (winhttp_stream
*)stream
;
1166 winhttp_stream_close(s
);
1170 static int winhttp_stream_alloc(winhttp_subtransport
*t
, winhttp_stream
**stream
)
1177 s
= git__calloc(1, sizeof(winhttp_stream
));
1178 GITERR_CHECK_ALLOC(s
);
1180 s
->parent
.subtransport
= &t
->parent
;
1181 s
->parent
.read
= winhttp_stream_read
;
1182 s
->parent
.write
= winhttp_stream_write_single
;
1183 s
->parent
.free
= winhttp_stream_free
;
1190 static int winhttp_uploadpack_ls(
1191 winhttp_subtransport
*t
,
1196 s
->service
= upload_pack_service
;
1197 s
->service_url
= upload_pack_ls_service_url
;
1203 static int winhttp_uploadpack(
1204 winhttp_subtransport
*t
,
1209 s
->service
= upload_pack_service
;
1210 s
->service_url
= upload_pack_service_url
;
1211 s
->verb
= post_verb
;
1216 static int winhttp_receivepack_ls(
1217 winhttp_subtransport
*t
,
1222 s
->service
= receive_pack_service
;
1223 s
->service_url
= receive_pack_ls_service_url
;
1229 static int winhttp_receivepack(
1230 winhttp_subtransport
*t
,
1235 /* WinHTTP only supports Transfer-Encoding: chunked
1236 * on Windows Vista (NT 6.0) and higher. */
1237 s
->chunked
= git_has_win32_version(6, 0, 0);
1240 s
->parent
.write
= winhttp_stream_write_chunked
;
1242 s
->parent
.write
= winhttp_stream_write_buffered
;
1244 s
->service
= receive_pack_service
;
1245 s
->service_url
= receive_pack_service_url
;
1246 s
->verb
= post_verb
;
1251 static int winhttp_action(
1252 git_smart_subtransport_stream
**stream
,
1253 git_smart_subtransport
*subtransport
,
1255 git_smart_service_t action
)
1257 winhttp_subtransport
*t
= (winhttp_subtransport
*)subtransport
;
1262 if ((ret
= gitno_connection_data_from_url(&t
->connection_data
, url
, NULL
)) < 0 ||
1263 (ret
= winhttp_connect(t
)) < 0)
1266 if (winhttp_stream_alloc(t
, &s
) < 0)
1274 case GIT_SERVICE_UPLOADPACK_LS
:
1275 ret
= winhttp_uploadpack_ls(t
, s
);
1278 case GIT_SERVICE_UPLOADPACK
:
1279 ret
= winhttp_uploadpack(t
, s
);
1282 case GIT_SERVICE_RECEIVEPACK_LS
:
1283 ret
= winhttp_receivepack_ls(t
, s
);
1286 case GIT_SERVICE_RECEIVEPACK
:
1287 ret
= winhttp_receivepack(t
, s
);
1295 *stream
= &s
->parent
;
1300 static int winhttp_close(git_smart_subtransport
*subtransport
)
1302 winhttp_subtransport
*t
= (winhttp_subtransport
*)subtransport
;
1304 gitno_connection_data_free_ptrs(&t
->connection_data
);
1305 memset(&t
->connection_data
, 0x0, sizeof(gitno_connection_data
));
1308 t
->cred
->free(t
->cred
);
1313 t
->url_cred
->free(t
->url_cred
);
1317 return winhttp_close_connection(t
);
1320 static void winhttp_free(git_smart_subtransport
*subtransport
)
1322 winhttp_subtransport
*t
= (winhttp_subtransport
*)subtransport
;
1324 winhttp_close(subtransport
);
1329 int git_smart_subtransport_http(git_smart_subtransport
**out
, git_transport
*owner
, void *param
)
1331 winhttp_subtransport
*t
;
1338 t
= git__calloc(1, sizeof(winhttp_subtransport
));
1339 GITERR_CHECK_ALLOC(t
);
1341 t
->owner
= (transport_smart
*)owner
;
1342 t
->parent
.action
= winhttp_action
;
1343 t
->parent
.close
= winhttp_close
;
1344 t
->parent
.free
= winhttp_free
;
1346 *out
= (git_smart_subtransport
*) t
;
1350 #endif /* GIT_WINHTTP */