winhttp.c 41.3 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3 4 5 6 7
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

8 9
#include "common.h"

10 11 12 13 14 15 16 17
#ifdef GIT_WINHTTP

#include "git2.h"
#include "git2/transport.h"
#include "buffer.h"
#include "posix.h"
#include "netops.h"
#include "smart.h"
18 19
#include "remote.h"
#include "repository.h"
20
#include "global.h"
21
#include "http.h"
22

23
#include <wincrypt.h>
24
#include <winhttp.h>
25 26

/* For IInternetSecurityManager zone check */
27 28 29
#include <objbase.h>
#include <urlmon.h>

30 31 32 33
#define WIDEN2(s) L ## s
#define WIDEN(s) WIDEN2(s)

#define MAX_CONTENT_TYPE_LEN	100
34
#define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE	109
35 36
#define CACHED_POST_BODY_BUF_SIZE	4096
#define UUID_LENGTH_CCH	32
37 38
#define TIMEOUT_INFINITE -1
#define DEFAULT_CONNECT_TIMEOUT 60000
39 40 41 42
#ifndef WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH
#define WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH 0
#endif

43 44 45 46 47 48 49 50
#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS_1_1
# define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 0x00000200
#endif

#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS_1_2
# define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 0x00000800
#endif

51 52 53 54
static const char *prefix_https = "https://";
static const char *upload_pack_service = "upload-pack";
static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
static const char *upload_pack_service_url = "/git-upload-pack";
55 56 57
static const char *receive_pack_service = "receive-pack";
static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack";
static const char *receive_pack_service_url = "/git-receive-pack";
58 59
static const wchar_t *get_verb = L"GET";
static const wchar_t *post_verb = L"POST";
60
static const wchar_t *pragma_nocache = L"Pragma: no-cache";
61
static const wchar_t *transfer_encoding = L"Transfer-Encoding: chunked";
62 63 64 65
static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
	SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
	SECURITY_FLAG_IGNORE_UNKNOWN_CA;

66
#if defined(__MINGW32__)
67 68
static const CLSID CLSID_InternetSecurityManager_mingw =
	{ 0x7B8A2D94, 0x0AC9, 0x11D1,
69
	{ 0x89, 0x6C, 0x00, 0xC0, 0x4F, 0xB6, 0xBF, 0xC4 } };
70 71
static const IID IID_IInternetSecurityManager_mingw =
	{ 0x79EAC9EE, 0xBAF9, 0x11CE,
72
	{ 0x8C, 0x82, 0x00, 0xAA, 0x00, 0x4B, 0xA9, 0x0B } };
73 74 75

# define CLSID_InternetSecurityManager CLSID_InternetSecurityManager_mingw
# define IID_IInternetSecurityManager IID_IInternetSecurityManager_mingw
76 77
#endif

78 79
#define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport)

80 81
typedef enum {
	GIT_WINHTTP_AUTH_BASIC = 1,
82 83
	GIT_WINHTTP_AUTH_NTLM = 2,
	GIT_WINHTTP_AUTH_NEGOTIATE = 4,
84
	GIT_WINHTTP_AUTH_DIGEST = 8,
85 86
} winhttp_authmechanism_t;

87 88 89 90 91 92
typedef struct {
	git_smart_subtransport_stream parent;
	const char *service;
	const char *service_url;
	const wchar_t *verb;
	HINTERNET request;
93
	wchar_t *request_uri;
94 95 96 97
	char *chunk_buffer;
	unsigned chunk_buffer_len;
	HANDLE post_body;
	DWORD post_body_len;
98
	unsigned sent_request : 1,
99 100
		received_response : 1,
		chunked : 1;
101 102 103 104
} winhttp_stream;

typedef struct {
	git_smart_subtransport parent;
105
	transport_smart *owner;
106
	gitno_connection_data connection_data;
107
	gitno_connection_data proxy_connection_data;
108
	git_cred *cred;
109
	git_cred *url_cred;
110
	git_cred *proxy_cred;
111
	int auth_mechanisms;
112 113 114 115
	HINTERNET session;
	HINTERNET connection;
} winhttp_subtransport;

116
static int _apply_userpass_credential(HINTERNET request, DWORD target, DWORD scheme, git_cred *cred)
117 118 119
{
	git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
	wchar_t *user, *pass;
120
	int user_len = 0, pass_len = 0, error = 0;
121

122 123
	if ((error = user_len = git__utf8_to_16_alloc(&user, c->username)) < 0)
		goto done;
124

125 126
	if ((error = pass_len = git__utf8_to_16_alloc(&pass, c->password)) < 0)
		goto done;
127

128
	if (!WinHttpSetCredentials(request, target, scheme, user, pass, NULL)) {
129
		git_error_set(GIT_ERROR_OS, "failed to set credentials");
130 131 132
		error = -1;
	}

133 134 135 136 137 138 139
done:
	if (user_len > 0)
		git__memzero(user, user_len * sizeof(wchar_t));

	if (pass_len > 0)
		git__memzero(pass, pass_len * sizeof(wchar_t));

140 141 142 143 144 145
	git__free(user);
	git__free(pass);

	return error;
}

146
static int apply_userpass_credential_proxy(HINTERNET request, git_cred *cred, int mechanisms)
147
{
148 149 150 151 152
	if (GIT_WINHTTP_AUTH_DIGEST & mechanisms) {
		return _apply_userpass_credential(request, WINHTTP_AUTH_TARGET_PROXY,
			WINHTTP_AUTH_SCHEME_DIGEST, cred);
	}

153 154 155
	return _apply_userpass_credential(request, WINHTTP_AUTH_TARGET_PROXY,
		WINHTTP_AUTH_SCHEME_BASIC, cred);
}
156

157 158 159
static int apply_userpass_credential(HINTERNET request, int mechanisms, git_cred *cred)
{
	DWORD native_scheme;
160

161 162 163 164 165 166
	if ((mechanisms & GIT_WINHTTP_AUTH_NTLM) ||
		(mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE)) {
		native_scheme = WINHTTP_AUTH_SCHEME_NTLM;
	} else if (mechanisms & GIT_WINHTTP_AUTH_BASIC) {
		native_scheme = WINHTTP_AUTH_SCHEME_BASIC;
	} else {
167
		git_error_set(GIT_ERROR_NET, "invalid authentication scheme");
168
		return -1;
169 170
	}

171 172
	return _apply_userpass_credential(request, WINHTTP_AUTH_TARGET_SERVER,
		native_scheme, cred);
173 174
}

175
static int apply_default_credentials(HINTERNET request, int mechanisms)
176
{
177 178 179 180 181 182
	/* Either the caller explicitly requested that default credentials be passed,
	 * or our fallback credential callback was invoked and checked that the target
	 * URI was in the appropriate Internet Explorer security zone. By setting this
	 * flag, we guarantee that the credentials are delivered by WinHTTP. The default
	 * is "medium" which applies to the intranet and sounds like it would correspond
	 * to Internet Explorer security zones, but in fact does not. */
183
	DWORD data = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW;
184
	DWORD native_scheme = 0;
185

186
	if ((mechanisms & GIT_WINHTTP_AUTH_NTLM) != 0)
187
		native_scheme = WINHTTP_AUTH_SCHEME_NTLM;
188 189

	if ((mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE) != 0)
190
		native_scheme = WINHTTP_AUTH_SCHEME_NEGOTIATE;
191 192

	if (!native_scheme) {
193
		git_error_set(GIT_ERROR_NET, "invalid authentication scheme");
194 195 196
		return -1;
	}

197 198 199
	if (!WinHttpSetOption(request, WINHTTP_OPTION_AUTOLOGON_POLICY, &data, sizeof(DWORD)))
		return -1;

200 201 202
	if (!WinHttpSetCredentials(request, WINHTTP_AUTH_TARGET_SERVER, native_scheme, NULL, NULL, NULL))
		return -1;

203 204 205
	return 0;
}

206 207 208 209 210 211 212 213 214
static int fallback_cred_acquire_cb(
	git_cred **cred,
	const char *url,
	const char *username_from_url,
	unsigned int allowed_types,
	void *payload)
{
	int error = 1;

215 216 217
	GIT_UNUSED(username_from_url);
	GIT_UNUSED(payload);

218 219 220
	/* If the target URI supports integrated Windows authentication
	 * as an authentication mechanism */
	if (GIT_CREDTYPE_DEFAULT & allowed_types) {
221
		wchar_t *wide_url;
222
		HRESULT hCoInitResult;
223 224

		/* Convert URL to wide characters */
225
		if (git__utf8_to_16_alloc(&wide_url, url) < 0) {
226
			git_error_set(GIT_ERROR_OS, "failed to convert string to wide form");
227 228 229
			return -1;
		}

230
		hCoInitResult = CoInitializeEx(NULL, COINIT_MULTITHREADED);
231

232
		if (SUCCEEDED(hCoInitResult) || hCoInitResult == RPC_E_CHANGED_MODE) {
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
			IInternetSecurityManager* pISM;

			/* And if the target URI is in the My Computer, Intranet, or Trusted zones */
			if (SUCCEEDED(CoCreateInstance(&CLSID_InternetSecurityManager, NULL,
				CLSCTX_ALL, &IID_IInternetSecurityManager, (void **)&pISM))) {
				DWORD dwZone;

				if (SUCCEEDED(pISM->lpVtbl->MapUrlToZone(pISM, wide_url, &dwZone, 0)) &&
					(URLZONE_LOCAL_MACHINE == dwZone ||
					URLZONE_INTRANET == dwZone ||
					URLZONE_TRUSTED == dwZone)) {
					git_cred *existing = *cred;

					if (existing)
						existing->free(existing);

					/* Then use default Windows credentials to authenticate this request */
					error = git_cred_default_new(cred);
				}

				pISM->lpVtbl->Release(pISM);
			}

256 257 258
            if (SUCCEEDED(hCoInitResult))
                /* Only unitialize if the call to CoInitializeEx was successful. */
                CoUninitialize();
259 260 261 262 263 264 265 266
		}

		git__free(wide_url);
	}

	return error;
}

267 268 269 270 271 272
static int certificate_check(winhttp_stream *s, int valid)
{
	int error;
	winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
	PCERT_CONTEXT cert_ctx;
	DWORD cert_ctx_size = sizeof(cert_ctx);
273
	git_cert_x509 cert;
274

275
	/* If there is no override, we should fail if WinHTTP doesn't think it's fine */
276
	if (t->owner->certificate_check_cb == NULL && !valid) {
277 278
		if (!git_error_last())
			git_error_set(GIT_ERROR_NET, "unknown certificate check failure");
279

280
		return GIT_ECERTIFICATE;
281
	}
282

283
	if (t->owner->certificate_check_cb == NULL || !t->connection_data.use_ssl)
284 285 286
		return 0;

	if (!WinHttpQueryOption(s->request, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &cert_ctx, &cert_ctx_size)) {
287
		git_error_set(GIT_ERROR_OS, "failed to get server certificate");
288 289 290
		return -1;
	}

291
	git_error_clear();
292
	cert.parent.cert_type = GIT_CERT_X509;
293 294
	cert.data = cert_ctx->pbCertEncoded;
	cert.len = cert_ctx->cbCertEncoded;
295
	error = t->owner->certificate_check_cb((git_cert *) &cert, valid, t->connection_data.host, t->owner->message_cb_payload);
296 297
	CertFreeCertificateContext(cert_ctx);

298 299 300
	if (error == GIT_PASSTHROUGH)
		error = valid ? 0 : GIT_ECERTIFICATE;

301 302
	if (error < 0 && !git_error_last())
		git_error_set(GIT_ERROR_NET, "user cancelled certificate check");
303 304 305 306

	return error;
}

307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
static void winhttp_stream_close(winhttp_stream *s)
{
	if (s->chunk_buffer) {
		git__free(s->chunk_buffer);
		s->chunk_buffer = NULL;
	}

	if (s->post_body) {
		CloseHandle(s->post_body);
		s->post_body = NULL;
	}

	if (s->request_uri) {
		git__free(s->request_uri);
		s->request_uri = NULL;
	}

	if (s->request) {
		WinHttpCloseHandle(s->request);
		s->request = NULL;
	}

	s->sent_request = 0;
}

332 333 334
#define SCHEME_HTTP  "http://"
#define SCHEME_HTTPS "https://"

335 336 337 338
static int winhttp_stream_connect(winhttp_stream *s)
{
	winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
	git_buf buf = GIT_BUF_INIT;
339
	char *proxy_url = NULL;
340
	wchar_t ct[MAX_CONTENT_TYPE_LEN];
341
	LPCWSTR types[] = { L"*/*", NULL };
342
	BOOL peerdist = FALSE;
343
	int error = -1;
344
	unsigned long disable_redirects = WINHTTP_DISABLE_REDIRECTS;
345 346
	int default_timeout = TIMEOUT_INFINITE;
	int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT;
347
	size_t i;
348
	const git_proxy_options *proxy_opts;
349 350

	/* Prepare URL */
351
	git_buf_printf(&buf, "%s%s", t->connection_data.path, s->service_url);
352 353 354 355

	if (git_buf_oom(&buf))
		return -1;

356
	/* Convert URL to wide characters */
357
	if (git__utf8_to_16_alloc(&s->request_uri, git_buf_cstr(&buf)) < 0) {
358
		git_error_set(GIT_ERROR_OS, "failed to convert string to wide form");
359 360
		goto on_error;
	}
361 362 363 364 365

	/* Establish request */
	s->request = WinHttpOpenRequest(
			t->connection,
			s->verb,
366
			s->request_uri,
367 368 369
			NULL,
			WINHTTP_NO_REFERER,
			types,
370
			t->connection_data.use_ssl ? WINHTTP_FLAG_SECURE : 0);
371 372

	if (!s->request) {
373
		git_error_set(GIT_ERROR_OS, "failed to open request");
374 375 376
		goto on_error;
	}

377
	if (!WinHttpSetTimeouts(s->request, default_timeout, default_connect_timeout, default_timeout, default_timeout)) {
378
		git_error_set(GIT_ERROR_OS, "failed to set timeouts for WinHTTP");
379 380 381
		goto on_error;
	}

382 383 384 385 386 387
	proxy_opts = &t->owner->proxy;
	if (proxy_opts->type == GIT_PROXY_AUTO) {
		/* Set proxy if necessary */
		if (git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &proxy_url) < 0)
			goto on_error;
	}
388
	else if (proxy_opts->type == GIT_PROXY_SPECIFIED) {
389
		proxy_url = git__strdup(proxy_opts->url);
390
		GIT_ERROR_CHECK_ALLOC(proxy_url);
391
	}
392 393

	if (proxy_url) {
394
		git_buf processed_url = GIT_BUF_INIT;
395
		WINHTTP_PROXY_INFO proxy_info;
396 397
		wchar_t *proxy_wide;

398 399 400 401 402
		if (!git__prefixcmp(proxy_url, SCHEME_HTTP)) {
			t->proxy_connection_data.use_ssl = false;
		} else if (!git__prefixcmp(proxy_url, SCHEME_HTTPS)) {
			t->proxy_connection_data.use_ssl = true;
		} else {
403
			git_error_set(GIT_ERROR_NET, "invalid URL: '%s'", proxy_url);
404 405 406
			return -1;
		}

407 408
		gitno_connection_data_free_ptrs(&t->proxy_connection_data);

409 410
		if ((error = gitno_extract_url_parts(&t->proxy_connection_data.host, &t->proxy_connection_data.port, NULL,
				&t->proxy_connection_data.user, &t->proxy_connection_data.pass, proxy_url, NULL)) < 0)
411
			goto on_error;
412

413
		if (t->proxy_connection_data.user && t->proxy_connection_data.pass) {
414 415 416 417
			if (t->proxy_cred) {
				t->proxy_cred->free(t->proxy_cred);
			}

418 419 420 421 422
			if ((error = git_cred_userpass_plaintext_new(&t->proxy_cred, t->proxy_connection_data.user, t->proxy_connection_data.pass)) < 0)
				goto on_error;
		}

		if (t->proxy_connection_data.use_ssl)
423
			git_buf_PUTS(&processed_url, SCHEME_HTTPS);
424
		else
425
			git_buf_PUTS(&processed_url, SCHEME_HTTP);
426 427 428 429 430 431 432

		git_buf_puts(&processed_url, t->proxy_connection_data.host);
		if (t->proxy_connection_data.port)
			git_buf_printf(&processed_url, ":%s", t->proxy_connection_data.port);

		if (git_buf_oom(&processed_url)) {
			error = -1;
433 434
			goto on_error;
		}
435

436
		/* Convert URL to wide characters */
437
		error = git__utf8_to_16_alloc(&proxy_wide, processed_url.ptr);
438
		git_buf_dispose(&processed_url);
439
		if (error < 0)
440 441
			goto on_error;

442
		proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
443
		proxy_info.lpszProxy = proxy_wide;
444 445 446 447 448 449
		proxy_info.lpszProxyBypass = NULL;

		if (!WinHttpSetOption(s->request,
			WINHTTP_OPTION_PROXY,
			&proxy_info,
			sizeof(WINHTTP_PROXY_INFO))) {
450
			git_error_set(GIT_ERROR_OS, "failed to set proxy");
451
			git__free(proxy_wide);
452 453
			goto on_error;
		}
454 455

		git__free(proxy_wide);
456 457 458

		if (t->proxy_cred) {
			if (t->proxy_cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT) {
459
				if ((error = apply_userpass_credential_proxy(s->request, t->proxy_cred, t->auth_mechanisms)) < 0)
460 461 462 463
					goto on_error;
			}
		}

464 465
	}

466 467 468
	/* Disable WinHTTP redirects so we can handle them manually. Why, you ask?
	 * http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/b2ff8879-ab9f-4218-8f09-16d25dff87ae
	 */
469 470 471 472
	if (!WinHttpSetOption(s->request,
		WINHTTP_OPTION_DISABLE_FEATURE,
		&disable_redirects,
		sizeof(disable_redirects))) {
473
			git_error_set(GIT_ERROR_OS, "failed to disable redirects");
474 475 476
			goto on_error;
	}

477 478 479 480 481 482 483 484 485 486
	/* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP
	 * adds itself. This option may not be supported by the underlying
	 * platform, so we do not error-check it */
	WinHttpSetOption(s->request,
		WINHTTP_OPTION_PEERDIST_EXTENSION_STATE,
		&peerdist,
		sizeof(peerdist));

	/* Send Pragma: no-cache header */
	if (!WinHttpAddRequestHeaders(s->request, pragma_nocache, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) {
487
		git_error_set(GIT_ERROR_OS, "failed to add a header to the request");
488 489 490
		goto on_error;
	}

491
	if (post_verb == s->verb) {
Ben Straub committed
492
		/* Send Content-Type and Accept headers -- only necessary on a POST */
493
		git_buf_clear(&buf);
Ben Straub committed
494
		if (git_buf_printf(&buf,
495 496
			"Content-Type: application/x-git-%s-request",
			s->service) < 0)
497 498
			goto on_error;

499
		if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) {
500
			git_error_set(GIT_ERROR_OS, "failed to convert content-type to wide characters");
501 502
			goto on_error;
		}
503

504 505
		if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L,
			WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
506
			git_error_set(GIT_ERROR_OS, "failed to add a header to the request");
507 508 509 510 511 512 513 514 515
			goto on_error;
		}

		git_buf_clear(&buf);
		if (git_buf_printf(&buf,
			"Accept: application/x-git-%s-result",
			s->service) < 0)
			goto on_error;

516
		if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) {
517
			git_error_set(GIT_ERROR_OS, "failed to convert accept header to wide characters");
518 519
			goto on_error;
		}
520 521

		if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L,
Ben Straub committed
522
			WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
523
			git_error_set(GIT_ERROR_OS, "failed to add a header to the request");
524 525
			goto on_error;
		}
526 527
	}

528 529 530 531 532
	for (i = 0; i < t->owner->custom_headers.count; i++) {
		if (t->owner->custom_headers.strings[i]) {
			git_buf_clear(&buf);
			git_buf_puts(&buf, t->owner->custom_headers.strings[i]);
			if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) {
533
				git_error_set(GIT_ERROR_OS, "failed to convert custom header to wide characters");
534 535
				goto on_error;
			}
536

537 538
			if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L,
				WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
539
				git_error_set(GIT_ERROR_OS, "failed to add a header to the request");
540
				goto on_error;
541 542 543 544
			}
		}
	}

545
	/* If requested, disable certificate validation */
546
	if (t->connection_data.use_ssl) {
547 548 549 550
		int flags;

		if (t->owner->parent.read_flags(&t->owner->parent, &flags) < 0)
			goto on_error;
551 552
	}

553 554 555
	/* If we have a credential on the subtransport, apply it to the request */
	if (t->cred &&
		t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&
556
		apply_userpass_credential(s->request, t->auth_mechanisms, t->cred) < 0)
557
		goto on_error;
558 559
	else if (t->cred &&
		t->cred->credtype == GIT_CREDTYPE_DEFAULT &&
560
		apply_default_credentials(s->request, t->auth_mechanisms) < 0)
561
		goto on_error;
562

563 564
	/* If no other credentials have been applied and the URL has username and
	 * password, use those */
565
	if (!t->cred && t->connection_data.user && t->connection_data.pass) {
566
		if (!t->url_cred &&
567
			git_cred_userpass_plaintext_new(&t->url_cred, t->connection_data.user, t->connection_data.pass) < 0)
568
			goto on_error;
569
		if (apply_userpass_credential(s->request, GIT_WINHTTP_AUTH_BASIC, t->url_cred) < 0)
570 571 572
			goto on_error;
	}

573 574
	/* We've done everything up to calling WinHttpSendRequest. */

575
	error = 0;
576 577

on_error:
578 579 580
	if (error < 0)
		winhttp_stream_close(s);

581
	git__free(proxy_url);
582
	git_buf_dispose(&buf);
583
	return error;
584 585
}

586 587 588
static int parse_unauthorized_response(
	HINTERNET request,
	int *allowed_types,
589
	int *allowed_mechanisms)
590
{
591
	DWORD supported, first, target;
592 593

	*allowed_types = 0;
594
	*allowed_mechanisms = 0;
595

596 597
	/* WinHttpQueryHeaders() must be called before WinHttpQueryAuthSchemes().
	 * We can assume this was already done, since we know we are unauthorized.
598 599
	 */
	if (!WinHttpQueryAuthSchemes(request, &supported, &first, &target)) {
600
		git_error_set(GIT_ERROR_OS, "failed to parse supported auth schemes");
601 602
		return -1;
	}
603

604
	if (WINHTTP_AUTH_SCHEME_NTLM & supported) {
605
		*allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
606
		*allowed_types |= GIT_CREDTYPE_DEFAULT;
607
		*allowed_mechanisms |= GIT_WINHTTP_AUTH_NTLM;
608
	}
609

610
	if (WINHTTP_AUTH_SCHEME_NEGOTIATE & supported) {
611
		*allowed_types |= GIT_CREDTYPE_DEFAULT;
612
		*allowed_mechanisms |= GIT_WINHTTP_AUTH_NEGOTIATE;
613 614 615 616 617
	}

	if (WINHTTP_AUTH_SCHEME_BASIC & supported) {
		*allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
		*allowed_mechanisms |= GIT_WINHTTP_AUTH_BASIC;
618 619
	}

620 621 622 623 624
	if (WINHTTP_AUTH_SCHEME_DIGEST & supported) {
		*allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
		*allowed_mechanisms |= GIT_WINHTTP_AUTH_DIGEST;
	}

625 626 627 628 629 630 631 632 633
	return 0;
}

static int write_chunk(HINTERNET request, const char *buffer, size_t len)
{
	DWORD bytes_written;
	git_buf buf = GIT_BUF_INIT;

	/* Chunk header */
lhchavez committed
634
	git_buf_printf(&buf, "%"PRIXZ"\r\n", len);
635 636 637 638 639

	if (git_buf_oom(&buf))
		return -1;

	if (!WinHttpWriteData(request,
640
		git_buf_cstr(&buf),	(DWORD)git_buf_len(&buf),
641
		&bytes_written)) {
642
		git_buf_dispose(&buf);
643
		git_error_set(GIT_ERROR_OS, "failed to write chunk header");
644
		return -1;
645 646
	}

647
	git_buf_dispose(&buf);
648 649 650

	/* Chunk body */
	if (!WinHttpWriteData(request,
651
		buffer, (DWORD)len,
652
		&bytes_written)) {
653
		git_error_set(GIT_ERROR_OS, "failed to write chunk");
654 655 656 657 658 659 660
		return -1;
	}

	/* Chunk footer */
	if (!WinHttpWriteData(request,
		"\r\n", 2,
		&bytes_written)) {
661
		git_error_set(GIT_ERROR_OS, "failed to write chunk footer");
662 663 664 665
		return -1;
	}

	return 0;
666 667
}

668 669 670 671 672 673
static int winhttp_close_connection(winhttp_subtransport *t)
{
	int ret = 0;

	if (t->connection) {
		if (!WinHttpCloseHandle(t->connection)) {
674
			git_error_set(GIT_ERROR_OS, "unable to close connection");
675 676 677 678 679 680 681 682
			ret = -1;
		}

		t->connection = NULL;
	}

	if (t->session) {
		if (!WinHttpCloseHandle(t->session)) {
683
			git_error_set(GIT_ERROR_OS, "unable to close session");
684 685 686 687 688 689 690 691 692
			ret = -1;
		}

		t->session = NULL;
	}

	return ret;
}

693 694 695 696 697 698 699 700 701 702 703 704 705 706 707
static void CALLBACK winhttp_status(
	HINTERNET connection,
	DWORD_PTR ctx,
	DWORD code,
	LPVOID info,
	DWORD info_len)
{
	DWORD status;

	if (code != WINHTTP_CALLBACK_STATUS_SECURE_FAILURE)
		return;

	status = *((DWORD *)info);

	if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID))
708
		git_error_set(GIT_ERROR_NET, "SSL certificate issued for different common name");
709
	else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID))
710
		git_error_set(GIT_ERROR_NET, "SSL certificate has expired");
711
	else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA))
712
		git_error_set(GIT_ERROR_NET, "SSL certificate signed by unknown CA");
713
	else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT))
714
		git_error_set(GIT_ERROR_NET, "SSL certificate is invalid");
715
	else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED))
716
		git_error_set(GIT_ERROR_NET, "certificate revocation check failed");
717
	else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED))
718
		git_error_set(GIT_ERROR_NET, "SSL certificate was revoked");
719
	else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR))
720
		git_error_set(GIT_ERROR_NET, "security libraries could not be loaded");
721
	else
722
		git_error_set(GIT_ERROR_NET, "unknown security error %lu", status);
723 724
}

725
static int winhttp_connect(
726
	winhttp_subtransport *t)
727
{
728
	wchar_t *wide_host;
729
	int32_t port;
730 731
	wchar_t *wide_ua;
	git_buf ua = GIT_BUF_INIT;
732
	int error = -1;
733 734
	int default_timeout = TIMEOUT_INFINITE;
	int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT;
735 736 737 738
	DWORD protocols =
		WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 |
		WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 |
		WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
739

740 741 742
	t->session = NULL;
	t->connection = NULL;

743
	/* Prepare port */
744 745
	if (git__strntol32(&port, t->connection_data.port,
			   strlen(t->connection_data.port), NULL, 10) < 0)
746 747 748
		return -1;

	/* Prepare host */
749
	if (git__utf8_to_16_alloc(&wide_host, t->connection_data.host) < 0) {
750
		git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters");
751 752
		return -1;
	}
753

754 755

	if ((error = git_http__user_agent(&ua)) < 0) {
756 757 758 759 760
		git__free(wide_host);
		return error;
	}

	if (git__utf8_to_16_alloc(&wide_ua, git_buf_cstr(&ua)) < 0) {
761
		git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters");
762
		git__free(wide_host);
763
		git_buf_dispose(&ua);
764 765 766
		return -1;
	}

767
	git_buf_dispose(&ua);
768

769 770
	/* Establish session */
	t->session = WinHttpOpen(
771
		wide_ua,
772 773 774 775 776 777
		WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
		WINHTTP_NO_PROXY_NAME,
		WINHTTP_NO_PROXY_BYPASS,
		0);

	if (!t->session) {
778
		git_error_set(GIT_ERROR_OS, "failed to init WinHTTP");
779
		goto on_error;
780 781
	}

782 783 784 785 786 787 788 789 790 791
	/*
	 * Do a best-effort attempt to enable TLS 1.2 but allow this to
	 * fail; if TLS 1.2 support is not available for some reason,
	 * ignore the failure (it will keep the default protocols).
	 */
	WinHttpSetOption(t->session,
		WINHTTP_OPTION_SECURE_PROTOCOLS,
		&protocols,
		sizeof(protocols));

792
	if (!WinHttpSetTimeouts(t->session, default_timeout, default_connect_timeout, default_timeout, default_timeout)) {
793
		git_error_set(GIT_ERROR_OS, "failed to set timeouts for WinHTTP");
794 795
		goto on_error;
	}
796

797

798 799 800
	/* Establish connection */
	t->connection = WinHttpConnect(
		t->session,
801
		wide_host,
802 803 804 805
		(INTERNET_PORT) port,
		0);

	if (!t->connection) {
806
		git_error_set(GIT_ERROR_OS, "failed to connect to host");
807
		goto on_error;
808 809
	}

810
	if (WinHttpSetStatusCallback(t->connection, winhttp_status, WINHTTP_CALLBACK_FLAG_SECURE_FAILURE, 0) == WINHTTP_INVALID_STATUS_CALLBACK) {
811
		git_error_set(GIT_ERROR_OS, "failed to set status callback");
812 813 814
		goto on_error;
	}

815 816 817
	error = 0;

on_error:
818 819 820
	if (error < 0)
		winhttp_close_connection(t);

821
	git__free(wide_host);
822
	git__free(wide_ua);
823 824

	return error;
825 826
}

827
static int do_send_request(winhttp_stream *s, size_t len, int ignore_length)
828
{
829 830 831 832 833 834 835 836 837 838 839 840 841 842
	int attempts;
	bool success;

	for (attempts = 0; attempts < 5; attempts++) {
		if (ignore_length) {
			success = WinHttpSendRequest(s->request,
				WINHTTP_NO_ADDITIONAL_HEADERS, 0,
				WINHTTP_NO_REQUEST_DATA, 0,
				WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0);
		} else {
			success = WinHttpSendRequest(s->request,
				WINHTTP_NO_ADDITIONAL_HEADERS, 0,
				WINHTTP_NO_REQUEST_DATA, 0,
				len, 0);
843
		}
844

lhchavez committed
845
		if (success || GetLastError() != (DWORD)SEC_E_BUFFER_TOO_SMALL)
846
			break;
847 848
	}

849
	return success ? 0 : -1;
850 851 852 853 854 855 856
}

static int send_request(winhttp_stream *s, size_t len, int ignore_length)
{
	int request_failed = 0, cert_valid = 1, error = 0;
	DWORD ignore_flags;

857
	git_error_clear();
858
	if ((error = do_send_request(s, len, ignore_length)) < 0) {
859
		if (GetLastError() != ERROR_WINHTTP_SECURE_FAILURE) {
860
			git_error_set(GIT_ERROR_OS, "failed to send request");
861 862
			return -1;
		}
863 864 865

		request_failed = 1;
		cert_valid = 0;
866
	}
867

868
	git_error_clear();
869
	if ((error = certificate_check(s, cert_valid)) < 0) {
870 871
		if (!git_error_last())
			git_error_set(GIT_ERROR_OS, "user cancelled certificate check");
872 873 874 875 876 877 878 879

		return error;
	}

	/* if neither the request nor the certificate check returned errors, we're done */
	if (!request_failed)
		return 0;

880
	ignore_flags = no_check_cert_flags;
881

882
	if (!WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS, &ignore_flags, sizeof(ignore_flags))) {
883
		git_error_set(GIT_ERROR_OS, "failed to set security options");
884
		return -1;
885 886
	}

887
	if ((error = do_send_request(s, len, ignore_length)) < 0)
888
		git_error_set(GIT_ERROR_OS, "failed to send request with unchecked certificate");
889

890 891 892
	return error;
}

893 894 895 896 897 898 899 900
static int winhttp_stream_read(
	git_smart_subtransport_stream *stream,
	char *buffer,
	size_t buf_size,
	size_t *bytes_read)
{
	winhttp_stream *s = (winhttp_stream *)stream;
	winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
901
	DWORD dw_bytes_read;
902
	char replay_count = 0;
903
	int error;
904

905
replay:
906
	/* Enforce a reasonable cap on the number of replays */
907
	if (replay_count++ >= GIT_HTTP_REPLAY_MAX) {
908
		git_error_set(GIT_ERROR_NET, "too many redirects or authentication replays");
909 910 911
		return -1;
	}

912 913 914 915 916
	/* Connect if necessary */
	if (!s->request && winhttp_stream_connect(s) < 0)
		return -1;

	if (!s->received_response) {
917
		DWORD status_code, status_code_length, content_type_length, bytes_written;
918 919 920
		char expected_content_type_8[MAX_CONTENT_TYPE_LEN];
		wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN];

921
		if (!s->sent_request) {
922 923 924

			if ((error = send_request(s, s->post_body_len, 0)) < 0)
				return error;
925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942

			s->sent_request = 1;
		}

		if (s->chunked) {
			assert(s->verb == post_verb);

			/* Flush, if necessary */
			if (s->chunk_buffer_len > 0 &&
				write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)
				return -1;

			s->chunk_buffer_len = 0;

			/* Write the final chunk. */
			if (!WinHttpWriteData(s->request,
				"0\r\n\r\n", 5,
				&bytes_written)) {
943
				git_error_set(GIT_ERROR_OS, "failed to write final chunk");
944 945 946 947 948 949 950 951 952 953
				return -1;
			}
		}
		else if (s->post_body) {
			char *buffer;
			DWORD len = s->post_body_len, bytes_read;

			if (INVALID_SET_FILE_POINTER == SetFilePointer(s->post_body,
					0, 0, FILE_BEGIN) &&
				NO_ERROR != GetLastError()) {
954
				git_error_set(GIT_ERROR_OS, "failed to reset file pointer");
955 956 957
				return -1;
			}

958
			buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE);
959 960 961 962 963

			while (len > 0) {
				DWORD bytes_written;

				if (!ReadFile(s->post_body, buffer,
964
					min(CACHED_POST_BODY_BUF_SIZE, len),
965 966 967
					&bytes_read, NULL) ||
					!bytes_read) {
					git__free(buffer);
968
					git_error_set(GIT_ERROR_OS, "failed to read from temp file");
969 970 971 972 973 974
					return -1;
				}

				if (!WinHttpWriteData(s->request, buffer,
					bytes_read, &bytes_written)) {
					git__free(buffer);
975
					git_error_set(GIT_ERROR_OS, "failed to write data");
976 977 978 979 980 981 982 983 984 985 986 987 988 989
					return -1;
				}

				len -= bytes_read;
				assert(bytes_read == bytes_written);
			}

			git__free(buffer);

			/* Eagerly close the temp file */
			CloseHandle(s->post_body);
			s->post_body = NULL;
		}

990
		if (!WinHttpReceiveResponse(s->request, 0)) {
991
			git_error_set(GIT_ERROR_OS, "failed to receive response");
992 993 994 995 996 997 998 999 1000 1001 1002
			return -1;
		}

		/* Verify that we got a 200 back */
		status_code_length = sizeof(status_code);

		if (!WinHttpQueryHeaders(s->request,
			WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
			WINHTTP_HEADER_NAME_BY_INDEX,
			&status_code, &status_code_length,
			WINHTTP_NO_HEADER_INDEX)) {
1003
				git_error_set(GIT_ERROR_OS, "failed to retrieve status code");
1004 1005 1006
				return -1;
		}

1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
		/* The implementation of WinHTTP prior to Windows 7 will not
		 * redirect to an identical URI. Some Git hosters use self-redirects
		 * as part of their DoS mitigation strategy. Check first to see if we
		 * have a redirect status code, and that we haven't already streamed
		 * a post body. (We can't replay a streamed POST.) */
		if (!s->chunked &&
			(HTTP_STATUS_MOVED == status_code ||
			 HTTP_STATUS_REDIRECT == status_code ||
			 (HTTP_STATUS_REDIRECT_METHOD == status_code &&
			  get_verb == s->verb) ||
			 HTTP_STATUS_REDIRECT_KEEP_VERB == status_code)) {

			/* Check for Windows 7. This workaround is only necessary on
			 * Windows Vista and earlier. Windows 7 is version 6.1. */
1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
			wchar_t *location;
			DWORD location_length;
			char *location8;

			/* OK, fetch the Location header from the redirect. */
			if (WinHttpQueryHeaders(s->request,
				WINHTTP_QUERY_LOCATION,
				WINHTTP_HEADER_NAME_BY_INDEX,
				WINHTTP_NO_OUTPUT_BUFFER,
				&location_length,
				WINHTTP_NO_HEADER_INDEX) ||
				GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
1033
				git_error_set(GIT_ERROR_OS, "failed to read Location header");
1034 1035
				return -1;
			}
1036

1037
			location = git__malloc(location_length);
1038
			GIT_ERROR_CHECK_ALLOC(location);
1039 1040 1041 1042 1043 1044 1045

			if (!WinHttpQueryHeaders(s->request,
				WINHTTP_QUERY_LOCATION,
				WINHTTP_HEADER_NAME_BY_INDEX,
				location,
				&location_length,
				WINHTTP_NO_HEADER_INDEX)) {
1046
				git_error_set(GIT_ERROR_OS, "failed to read Location header");
1047
				git__free(location);
1048 1049
				return -1;
			}
1050 1051 1052

			/* Convert the Location header to UTF-8 */
			if (git__utf16_to_8_alloc(&location8, location) < 0) {
1053
				git_error_set(GIT_ERROR_OS, "failed to convert Location header to UTF-8");
1054 1055 1056 1057
				git__free(location);
				return -1;
			}

1058 1059 1060
			git__free(location);

			/* Replay the request */
1061
			winhttp_stream_close(s);
1062 1063 1064

			if (!git__prefixcmp_icase(location8, prefix_https)) {
				/* Upgrade to secure connection; disconnect and start over */
1065 1066
				if (gitno_connection_data_from_url(&t->connection_data, location8, s->service_url) < 0) {
					git__free(location8);
1067
					return -1;
1068 1069
				}

1070 1071
				winhttp_close_connection(t);

1072
				if (winhttp_connect(t) < 0)
1073
					return -1;
1074
			}
1075 1076 1077

			git__free(location8);
			goto replay;
1078 1079
		}

1080 1081 1082 1083
		/* Handle proxy authentication failures */
		if (status_code == HTTP_STATUS_PROXY_AUTH_REQ) {
			int allowed_types;

1084
			if (parse_unauthorized_response(s->request, &allowed_types, &t->auth_mechanisms) < 0)
1085 1086 1087 1088 1089
				return -1;

			/* TODO: extract the username from the url, no payload? */
			if (t->owner->proxy.credentials) {
				int cred_error = 1;
1090
				cred_error = t->owner->proxy.credentials(&t->proxy_cred, t->owner->proxy.url, NULL, allowed_types, t->owner->proxy.payload);
1091 1092 1093 1094 1095 1096 1097 1098 1099

				if (cred_error < 0)
					return cred_error;
			}

			winhttp_stream_close(s);
			goto replay;
		}

1100
		/* Handle authentication failures */
1101
		if (HTTP_STATUS_DENIED == status_code && get_verb == s->verb) {
1102 1103
			int allowed_types;

1104
			if (parse_unauthorized_response(s->request, &allowed_types, &t->auth_mechanisms) < 0)
1105 1106
				return -1;

1107
			if (allowed_types) {
1108
				int cred_error = 1;
1109

1110 1111
				git_cred_free(t->cred);
				t->cred = NULL;
1112 1113 1114 1115
				/* Start with the user-supplied credential callback, if present */
				if (t->owner->cred_acquire_cb) {
					cred_error = t->owner->cred_acquire_cb(&t->cred, t->owner->url,
						t->connection_data.user, allowed_types,	t->owner->cred_acquire_payload);
1116

1117 1118 1119 1120
					/* Treat GIT_PASSTHROUGH as though git_cred_acquire_cb isn't set */
					if (cred_error == GIT_PASSTHROUGH)
						cred_error = 1;
					else if (cred_error < 0)
1121 1122
						return cred_error;
				}
1123

1124 1125 1126 1127
				/* Invoke the fallback credentials acquisition callback if necessary */
				if (cred_error > 0) {
					cred_error = fallback_cred_acquire_cb(&t->cred, t->owner->url,
						t->connection_data.user, allowed_types, NULL);
1128

1129 1130 1131 1132 1133 1134 1135
					if (cred_error < 0)
						return cred_error;
				}

				if (!cred_error) {
					assert(t->cred);

1136
					winhttp_stream_close(s);
1137 1138 1139 1140

					/* Successfully acquired a credential */
					goto replay;
				}
1141 1142 1143
			}
		}

1144
		if (HTTP_STATUS_OK != status_code) {
1145
			git_error_set(GIT_ERROR_NET, "request failed with status code: %lu", status_code);
1146 1147 1148 1149 1150
			return -1;
		}

		/* Verify that we got the correct content-type back */
		if (post_verb == s->verb)
1151
			p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-result", s->service);
1152
		else
1153
			p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service);
1154

1155
		if (git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) {
1156
			git_error_set(GIT_ERROR_OS, "failed to convert expected content-type to wide characters");
1157 1158 1159
			return -1;
		}

1160 1161 1162 1163 1164 1165 1166
		content_type_length = sizeof(content_type);

		if (!WinHttpQueryHeaders(s->request,
			WINHTTP_QUERY_CONTENT_TYPE,
			WINHTTP_HEADER_NAME_BY_INDEX,
			&content_type, &content_type_length,
			WINHTTP_NO_HEADER_INDEX)) {
1167
				git_error_set(GIT_ERROR_OS, "failed to retrieve response content-type");
1168 1169 1170 1171
				return -1;
		}

		if (wcscmp(expected_content_type, content_type)) {
1172
			git_error_set(GIT_ERROR_NET, "received unexpected content-type");
1173 1174 1175 1176 1177 1178 1179 1180
			return -1;
		}

		s->received_response = 1;
	}

	if (!WinHttpReadData(s->request,
		(LPVOID)buffer,
1181
		(DWORD)buf_size,
1182
		&dw_bytes_read))
1183
	{
1184
		git_error_set(GIT_ERROR_OS, "failed to read data");
1185 1186 1187
		return -1;
	}

1188 1189
	*bytes_read = dw_bytes_read;

1190 1191 1192
	return 0;
}

1193
static int winhttp_stream_write_single(
1194 1195 1196 1197 1198 1199
	git_smart_subtransport_stream *stream,
	const char *buffer,
	size_t len)
{
	winhttp_stream *s = (winhttp_stream *)stream;
	DWORD bytes_written;
1200
	int error;
1201 1202 1203 1204

	if (!s->request && winhttp_stream_connect(s) < 0)
		return -1;

1205 1206
	/* This implementation of write permits only a single call. */
	if (s->sent_request) {
1207
		git_error_set(GIT_ERROR_NET, "subtransport configured for only one write");
1208 1209 1210
		return -1;
	}

1211 1212
	if ((error = send_request(s, len, 0)) < 0)
		return error;
1213 1214 1215 1216 1217 1218 1219

	s->sent_request = 1;

	if (!WinHttpWriteData(s->request,
			(LPCVOID)buffer,
			(DWORD)len,
			&bytes_written)) {
1220
		git_error_set(GIT_ERROR_OS, "failed to write data");
1221 1222 1223 1224 1225 1226 1227 1228
		return -1;
	}

	assert((DWORD)len == bytes_written);

	return 0;
}

1229
static int put_uuid_string(LPWSTR buffer, size_t buffer_len_cch)
1230 1231 1232
{
	UUID uuid;
	RPC_STATUS status = UuidCreate(&uuid);
1233
	int result;
1234 1235 1236 1237

	if (RPC_S_OK != status &&
		RPC_S_UUID_LOCAL_ONLY != status &&
		RPC_S_UUID_NO_ADDRESS != status) {
1238
		git_error_set(GIT_ERROR_NET, "unable to generate name for temp file");
1239 1240 1241
		return -1;
	}

1242
	if (buffer_len_cch < UUID_LENGTH_CCH + 1) {
1243
		git_error_set(GIT_ERROR_NET, "buffer too small for name of temp file");
1244 1245 1246
		return -1;
	}

1247 1248 1249 1250 1251
#if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API)
	result = swprintf_s(buffer, buffer_len_cch,
#else
	result = wsprintfW(buffer,
#endif
1252
		L"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x",
1253 1254 1255 1256
		uuid.Data1, uuid.Data2, uuid.Data3,
		uuid.Data4[0], uuid.Data4[1], uuid.Data4[2], uuid.Data4[3],
		uuid.Data4[4], uuid.Data4[5], uuid.Data4[6], uuid.Data4[7]);

1257
	if (result < UUID_LENGTH_CCH) {
1258
		git_error_set(GIT_ERROR_OS, "unable to generate name for temp file");
1259 1260 1261 1262 1263 1264 1265 1266
		return -1;
	}

	return 0;
}

static int get_temp_file(LPWSTR buffer, DWORD buffer_len_cch)
{
1267
	size_t len;
1268 1269

	if (!GetTempPathW(buffer_len_cch, buffer)) {
1270
		git_error_set(GIT_ERROR_OS, "failed to get temp path");
1271 1272 1273 1274 1275
		return -1;
	}

	len = wcslen(buffer);

1276
	if (buffer[len - 1] != '\\' && len < buffer_len_cch)
1277 1278
		buffer[len++] = '\\';

1279
	if (put_uuid_string(&buffer[len], (size_t)buffer_len_cch - len) < 0)
1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312
		return -1;

	return 0;
}

static int winhttp_stream_write_buffered(
	git_smart_subtransport_stream *stream,
	const char *buffer,
	size_t len)
{
	winhttp_stream *s = (winhttp_stream *)stream;
	DWORD bytes_written;

	if (!s->request && winhttp_stream_connect(s) < 0)
		return -1;

	/* Buffer the payload, using a temporary file so we delegate
	 * memory management of the data to the operating system. */
	if (!s->post_body) {
		wchar_t temp_path[MAX_PATH + 1];

		if (get_temp_file(temp_path, MAX_PATH + 1) < 0)
			return -1;

		s->post_body = CreateFileW(temp_path,
			GENERIC_READ | GENERIC_WRITE,
			FILE_SHARE_DELETE, NULL,
			CREATE_NEW,
			FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_SEQUENTIAL_SCAN,
			NULL);

		if (INVALID_HANDLE_VALUE == s->post_body) {
			s->post_body = NULL;
1313
			git_error_set(GIT_ERROR_OS, "failed to create temporary file");
1314 1315 1316 1317
			return -1;
		}
	}

1318
	if (!WriteFile(s->post_body, buffer, (DWORD)len, &bytes_written, NULL)) {
1319
		git_error_set(GIT_ERROR_OS, "failed to write to temporary file");
1320 1321 1322 1323 1324
		return -1;
	}

	assert((DWORD)len == bytes_written);

1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335
	s->post_body_len += bytes_written;

	return 0;
}

static int winhttp_stream_write_chunked(
	git_smart_subtransport_stream *stream,
	const char *buffer,
	size_t len)
{
	winhttp_stream *s = (winhttp_stream *)stream;
1336
	int error;
1337 1338 1339 1340 1341 1342 1343 1344 1345

	if (!s->request && winhttp_stream_connect(s) < 0)
		return -1;

	if (!s->sent_request) {
		/* Send Transfer-Encoding: chunked header */
		if (!WinHttpAddRequestHeaders(s->request,
			transfer_encoding, (ULONG) -1L,
			WINHTTP_ADDREQ_FLAG_ADD)) {
1346
			git_error_set(GIT_ERROR_OS, "failed to add a header to the request");
1347 1348 1349
			return -1;
		}

1350 1351
		if ((error = send_request(s, 0, 1)) < 0)
			return error;
1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370

		s->sent_request = 1;
	}

	if (len > CACHED_POST_BODY_BUF_SIZE) {
		/* Flush, if necessary */
		if (s->chunk_buffer_len > 0) {
			if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)
				return -1;

			s->chunk_buffer_len = 0;
		}

		/* Write chunk directly */
		if (write_chunk(s->request, buffer, len) < 0)
			return -1;
	}
	else {
		/* Append as much to the buffer as we can */
1371
		int count = (int)min(CACHED_POST_BODY_BUF_SIZE - s->chunk_buffer_len, len);
1372 1373

		if (!s->chunk_buffer)
1374
			s->chunk_buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE);
1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390

		memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count);
		s->chunk_buffer_len += count;
		buffer += count;
		len -= count;

		/* Is the buffer full? If so, then flush */
		if (CACHED_POST_BODY_BUF_SIZE == s->chunk_buffer_len) {
			if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)
				return -1;

			s->chunk_buffer_len = 0;

			/* Is there any remaining data from the source? */
			if (len > 0) {
				memcpy(s->chunk_buffer, buffer, len);
1391
				s->chunk_buffer_len = (unsigned int)len;
1392 1393 1394 1395
			}
		}
	}

1396 1397 1398 1399 1400 1401 1402
	return 0;
}

static void winhttp_stream_free(git_smart_subtransport_stream *stream)
{
	winhttp_stream *s = (winhttp_stream *)stream;

1403
	winhttp_stream_close(s);
1404 1405 1406
	git__free(s);
}

1407
static int winhttp_stream_alloc(winhttp_subtransport *t, winhttp_stream **stream)
1408 1409 1410 1411 1412 1413
{
	winhttp_stream *s;

	if (!stream)
		return -1;

1414
	s = git__calloc(1, sizeof(winhttp_stream));
1415
	GIT_ERROR_CHECK_ALLOC(s);
1416 1417 1418

	s->parent.subtransport = &t->parent;
	s->parent.read = winhttp_stream_read;
1419
	s->parent.write = winhttp_stream_write_single;
1420 1421
	s->parent.free = winhttp_stream_free;

1422 1423
	*stream = s;

1424 1425 1426 1427 1428
	return 0;
}

static int winhttp_uploadpack_ls(
	winhttp_subtransport *t,
1429
	winhttp_stream *s)
1430
{
1431 1432
	GIT_UNUSED(t);

1433 1434 1435 1436 1437 1438 1439 1440 1441
	s->service = upload_pack_service;
	s->service_url = upload_pack_ls_service_url;
	s->verb = get_verb;

	return 0;
}

static int winhttp_uploadpack(
	winhttp_subtransport *t,
1442
	winhttp_stream *s)
1443
{
1444 1445
	GIT_UNUSED(t);

1446 1447 1448
	s->service = upload_pack_service;
	s->service_url = upload_pack_service_url;
	s->verb = post_verb;
1449

1450 1451
	return 0;
}
1452

1453 1454 1455 1456
static int winhttp_receivepack_ls(
	winhttp_subtransport *t,
	winhttp_stream *s)
{
1457 1458
	GIT_UNUSED(t);

1459 1460 1461
	s->service = receive_pack_service;
	s->service_url = receive_pack_ls_service_url;
	s->verb = get_verb;
1462

1463 1464
	return 0;
}
1465

1466 1467 1468 1469
static int winhttp_receivepack(
	winhttp_subtransport *t,
	winhttp_stream *s)
{
1470 1471
	GIT_UNUSED(t);

1472 1473
	/* WinHTTP only supports Transfer-Encoding: chunked
	 * on Windows Vista (NT 6.0) and higher. */
1474
	s->chunked = git_has_win32_version(6, 0, 0);
1475 1476 1477 1478 1479 1480 1481 1482

	if (s->chunked)
		s->parent.write = winhttp_stream_write_chunked;
	else
		s->parent.write = winhttp_stream_write_buffered;

	s->service = receive_pack_service;
	s->service_url = receive_pack_service_url;
1483 1484 1485 1486 1487 1488 1489
	s->verb = post_verb;

	return 0;
}

static int winhttp_action(
	git_smart_subtransport_stream **stream,
1490
	git_smart_subtransport *subtransport,
1491 1492 1493
	const char *url,
	git_smart_service_t action)
{
1494 1495 1496 1497
	winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
	winhttp_stream *s;
	int ret = -1;

1498
	if (!t->connection)
1499
		if ((ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0 ||
1500
			 (ret = winhttp_connect(t)) < 0)
1501
			return ret;
1502 1503 1504

	if (winhttp_stream_alloc(t, &s) < 0)
		return -1;
1505 1506 1507 1508 1509 1510 1511

	if (!stream)
		return -1;

	switch (action)
	{
		case GIT_SERVICE_UPLOADPACK_LS:
1512 1513
			ret = winhttp_uploadpack_ls(t, s);
			break;
1514 1515

		case GIT_SERVICE_UPLOADPACK:
1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528
			ret = winhttp_uploadpack(t, s);
			break;

		case GIT_SERVICE_RECEIVEPACK_LS:
			ret = winhttp_receivepack_ls(t, s);
			break;

		case GIT_SERVICE_RECEIVEPACK:
			ret = winhttp_receivepack(t, s);
			break;

		default:
			assert(0);
1529 1530
	}

1531 1532 1533 1534
	if (!ret)
		*stream = &s->parent;

	return ret;
1535 1536
}

1537
static int winhttp_close(git_smart_subtransport *subtransport)
1538
{
1539 1540
	winhttp_subtransport *t = (winhttp_subtransport *)subtransport;

1541
	gitno_connection_data_free_ptrs(&t->connection_data);
1542
	memset(&t->connection_data, 0x0, sizeof(gitno_connection_data));
1543 1544
	gitno_connection_data_free_ptrs(&t->proxy_connection_data);
	memset(&t->proxy_connection_data, 0x0, sizeof(gitno_connection_data));
1545

1546 1547 1548 1549 1550
	if (t->cred) {
		t->cred->free(t->cred);
		t->cred = NULL;
	}

1551 1552 1553 1554 1555
	if (t->proxy_cred) {
		t->proxy_cred->free(t->proxy_cred);
		t->proxy_cred = NULL;
	}

1556 1557 1558 1559 1560
	if (t->url_cred) {
		t->url_cred->free(t->url_cred);
		t->url_cred = NULL;
	}

1561
	return winhttp_close_connection(t);
1562 1563 1564 1565 1566 1567 1568 1569
}

static void winhttp_free(git_smart_subtransport *subtransport)
{
	winhttp_subtransport *t = (winhttp_subtransport *)subtransport;

	winhttp_close(subtransport);

1570 1571 1572
	git__free(t);
}

1573
int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param)
1574 1575 1576
{
	winhttp_subtransport *t;

1577 1578
	GIT_UNUSED(param);

1579 1580 1581
	if (!out)
		return -1;

1582
	t = git__calloc(1, sizeof(winhttp_subtransport));
1583
	GIT_ERROR_CHECK_ALLOC(t);
1584

1585
	t->owner = (transport_smart *)owner;
1586
	t->parent.action = winhttp_action;
1587
	t->parent.close = winhttp_close;
1588 1589 1590 1591 1592 1593 1594
	t->parent.free = winhttp_free;

	*out = (git_smart_subtransport *) t;
	return 0;
}

#endif /* GIT_WINHTTP */