winhttp.c 40.7 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
#include "git2/sys/cred.h"
23

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

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

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

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

44 45 46 47 48 49 50 51
#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

52 53 54 55
#ifndef DWORD_MAX
# define DWORD_MAX 0xffffffff
#endif

56 57 58 59
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";
60 61 62
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";
63 64
static const wchar_t *get_verb = L"GET";
static const wchar_t *post_verb = L"POST";
65
static const wchar_t *pragma_nocache = L"Pragma: no-cache";
66
static const wchar_t *transfer_encoding = L"Transfer-Encoding: chunked";
67 68 69 70
static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
	SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
	SECURITY_FLAG_IGNORE_UNKNOWN_CA;

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

# define CLSID_InternetSecurityManager CLSID_InternetSecurityManager_mingw
# define IID_IInternetSecurityManager IID_IInternetSecurityManager_mingw
81 82
#endif

83 84
#define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport)

85 86
typedef enum {
	GIT_WINHTTP_AUTH_BASIC = 1,
87 88
	GIT_WINHTTP_AUTH_NTLM = 2,
	GIT_WINHTTP_AUTH_NEGOTIATE = 4,
89
	GIT_WINHTTP_AUTH_DIGEST = 8,
90 91
} winhttp_authmechanism_t;

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

typedef struct {
109
	git_net_url url;
110
	git_cred *cred;
111
	int auth_mechanisms;
112 113 114 115 116 117 118 119 120 121
	bool url_cred_presented;
} winhttp_server;

typedef struct {
	git_smart_subtransport parent;
	transport_smart *owner;

	winhttp_server server;
	winhttp_server proxy;

122 123 124 125
	HINTERNET session;
	HINTERNET connection;
} winhttp_subtransport;

126
static int apply_userpass_credentials(HINTERNET request, DWORD target, int mechanisms, git_cred *cred)
127 128
{
	git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
129
	wchar_t *user = NULL, *pass = NULL;
130
	int user_len = 0, pass_len = 0, error = 0;
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
	DWORD native_scheme;

	if (mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE) {
		native_scheme = WINHTTP_AUTH_SCHEME_NEGOTIATE;
	} else if (mechanisms & GIT_WINHTTP_AUTH_NTLM) {
		native_scheme = WINHTTP_AUTH_SCHEME_NTLM;
	} else if (mechanisms & GIT_WINHTTP_AUTH_DIGEST) {
		native_scheme = WINHTTP_AUTH_SCHEME_DIGEST;
	} else if (mechanisms & GIT_WINHTTP_AUTH_BASIC) {
		native_scheme = WINHTTP_AUTH_SCHEME_BASIC;
	} else {
		git_error_set(GIT_ERROR_NET, "invalid authentication scheme");
		error = -1;
		goto done;
	}
146

147 148
	if ((error = user_len = git__utf8_to_16_alloc(&user, c->username)) < 0)
		goto done;
149

150 151
	if ((error = pass_len = git__utf8_to_16_alloc(&pass, c->password)) < 0)
		goto done;
152

153
	if (!WinHttpSetCredentials(request, target, native_scheme, user, pass, NULL)) {
154
		git_error_set(GIT_ERROR_OS, "failed to set credentials");
155 156 157
		error = -1;
	}

158 159 160 161 162 163 164
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));

165 166 167 168 169 170
	git__free(user);
	git__free(pass);

	return error;
}

171
static int apply_default_credentials(HINTERNET request, DWORD target, int mechanisms)
172
{
173 174
	DWORD autologon_level = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW;
	DWORD native_scheme = 0;
175

176 177 178
	if ((mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE) != 0) {
		native_scheme = WINHTTP_AUTH_SCHEME_NEGOTIATE;
	} else if ((mechanisms & GIT_WINHTTP_AUTH_NTLM) != 0) {
179 180
		native_scheme = WINHTTP_AUTH_SCHEME_NTLM;
	} else {
181
		git_error_set(GIT_ERROR_NET, "invalid authentication scheme");
182
		return -1;
183 184
	}

185 186 187 188 189 190
	/*
	 * Autologon policy must be "low" to use default creds.
	 * This is safe as the user has explicitly requested it.
	 */
	if (!WinHttpSetOption(request, WINHTTP_OPTION_AUTOLOGON_POLICY, &autologon_level, sizeof(DWORD))) {
		git_error_set(GIT_ERROR_OS, "could not configure logon policy");
191 192 193
		return -1;
	}

194 195
	if (!WinHttpSetCredentials(request, target, native_scheme, NULL, NULL, NULL)) {
		git_error_set(GIT_ERROR_OS, "could not configure credentials");
196
		return -1;
197
	}
198

199 200 201
	return 0;
}

202
static int acquire_url_cred(
203 204
	git_cred **cred,
	unsigned int allowed_types,
205 206
	const char *username,
	const char *password)
207
{
208 209 210 211 212 213 214 215
	if (allowed_types & GIT_CREDTYPE_USERPASS_PLAINTEXT)
		return git_cred_userpass_plaintext_new(cred, username, password);

	if ((allowed_types & GIT_CREDTYPE_DEFAULT) && *username == '\0' && *password == '\0')
		return git_cred_default_new(cred);

	return 1;
}
216

217 218 219 220 221 222
static int acquire_fallback_cred(
	git_cred **cred,
	const char *url,
	unsigned int allowed_types)
{
	int error = 1;
223

224 225 226
	/* If the target URI supports integrated Windows authentication
	 * as an authentication mechanism */
	if (GIT_CREDTYPE_DEFAULT & allowed_types) {
227
		wchar_t *wide_url;
228
		HRESULT hCoInitResult;
229 230

		/* Convert URL to wide characters */
231
		if (git__utf8_to_16_alloc(&wide_url, url) < 0) {
232
			git_error_set(GIT_ERROR_OS, "failed to convert string to wide form");
233 234 235
			return -1;
		}

236
		hCoInitResult = CoInitializeEx(NULL, COINIT_MULTITHREADED);
237

238
		if (SUCCEEDED(hCoInitResult) || hCoInitResult == RPC_E_CHANGED_MODE) {
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
			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);
			}

262 263 264
			/* Only unitialize if the call to CoInitializeEx was successful. */
			if (SUCCEEDED(hCoInitResult))
				CoUninitialize();
265 266 267 268 269 270 271 272
		}

		git__free(wide_url);
	}

	return error;
}

273 274 275 276 277 278
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);
279
	git_cert_x509 cert;
280

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

286
		return GIT_ECERTIFICATE;
287
	}
288

289
	if (t->owner->certificate_check_cb == NULL || git__strcmp(t->server.url.scheme, "https") != 0)
290 291 292
		return 0;

	if (!WinHttpQueryOption(s->request, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &cert_ctx, &cert_ctx_size)) {
293
		git_error_set(GIT_ERROR_OS, "failed to get server certificate");
294 295 296
		return -1;
	}

297
	git_error_clear();
298
	cert.parent.cert_type = GIT_CERT_X509;
299 300
	cert.data = cert_ctx->pbCertEncoded;
	cert.len = cert_ctx->cbCertEncoded;
301
	error = t->owner->certificate_check_cb((git_cert *) &cert, valid, t->server.url.host, t->owner->message_cb_payload);
302 303
	CertFreeCertificateContext(cert_ctx);

304 305 306
	if (error == GIT_PASSTHROUGH)
		error = valid ? 0 : GIT_ECERTIFICATE;

307 308
	if (error < 0 && !git_error_last())
		git_error_set(GIT_ERROR_NET, "user cancelled certificate check");
309 310 311 312

	return error;
}

313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
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;
}

338 339 340 341 342 343 344 345 346
static int apply_credentials(
	HINTERNET request,
	git_net_url *url,
	int target,
	git_cred *creds,
	int mechanisms)
{
	int error = 0;

347 348
	GIT_UNUSED(url);

349 350 351 352 353 354 355 356 357
	/* If we have creds, just apply them */
	if (creds && creds->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT)
		error = apply_userpass_credentials(request, target, mechanisms, creds);
	else if (creds && creds->credtype == GIT_CREDTYPE_DEFAULT)
		error = apply_default_credentials(request, target, mechanisms);

	return error;
}

358 359 360 361
static int winhttp_stream_connect(winhttp_stream *s)
{
	winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
	git_buf buf = GIT_BUF_INIT;
362
	char *proxy_url = NULL;
363
	wchar_t ct[MAX_CONTENT_TYPE_LEN];
364
	LPCWSTR types[] = { L"*/*", NULL };
365
	BOOL peerdist = FALSE;
366
	int error = -1;
367
	unsigned long disable_redirects = WINHTTP_DISABLE_REDIRECTS;
368 369
	int default_timeout = TIMEOUT_INFINITE;
	int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT;
370 371
	DWORD autologon_policy = WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH;

372
	size_t i;
373
	const git_proxy_options *proxy_opts;
374 375

	/* Prepare URL */
376
	git_buf_printf(&buf, "%s%s", t->server.url.path, s->service_url);
377 378 379 380

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

381
	/* Convert URL to wide characters */
382
	if (git__utf8_to_16_alloc(&s->request_uri, git_buf_cstr(&buf)) < 0) {
383
		git_error_set(GIT_ERROR_OS, "failed to convert string to wide form");
384 385
		goto on_error;
	}
386 387 388 389 390

	/* Establish request */
	s->request = WinHttpOpenRequest(
			t->connection,
			s->verb,
391
			s->request_uri,
392 393 394
			NULL,
			WINHTTP_NO_REFERER,
			types,
395
			git__strcmp(t->server.url.scheme, "https") == 0 ? WINHTTP_FLAG_SECURE : 0);
396 397

	if (!s->request) {
398
		git_error_set(GIT_ERROR_OS, "failed to open request");
399 400 401
		goto on_error;
	}

402 403 404 405
	/* Never attempt default credentials; we'll provide them explicitly. */
	if (!WinHttpSetOption(s->request, WINHTTP_OPTION_AUTOLOGON_POLICY, &autologon_policy, sizeof(DWORD)))
		return -1;

406
	if (!WinHttpSetTimeouts(s->request, default_timeout, default_connect_timeout, default_timeout, default_timeout)) {
407
		git_error_set(GIT_ERROR_OS, "failed to set timeouts for WinHTTP");
408 409 410
		goto on_error;
	}

411 412 413
	proxy_opts = &t->owner->proxy;
	if (proxy_opts->type == GIT_PROXY_AUTO) {
		/* Set proxy if necessary */
414
		if (git_remote__get_http_proxy(t->owner->owner, (strcmp(t->server.url.scheme, "https") == 0), &proxy_url) < 0)
415 416
			goto on_error;
	}
417
	else if (proxy_opts->type == GIT_PROXY_SPECIFIED) {
418
		proxy_url = git__strdup(proxy_opts->url);
419
		GIT_ERROR_CHECK_ALLOC(proxy_url);
420
	}
421 422

	if (proxy_url) {
423
		git_buf processed_url = GIT_BUF_INIT;
424
		WINHTTP_PROXY_INFO proxy_info;
425 426
		wchar_t *proxy_wide;

427
		git_net_url_dispose(&t->proxy.url);
428

429
		if ((error = git_net_url_parse(&t->proxy.url, proxy_url)) < 0)
430
			goto on_error;
431

432
		if (strcmp(t->proxy.url.scheme, "http") != 0 && strcmp(t->proxy.url.scheme, "https") != 0) {
433 434
			git_error_set(GIT_ERROR_NET, "invalid URL: '%s'", proxy_url);
			error = -1;
435
			goto on_error;
436
		}
437

438
		git_buf_puts(&processed_url, t->proxy.url.scheme);
439 440
		git_buf_PUTS(&processed_url, "://");

441
		git_buf_puts(&processed_url, t->proxy.url.host);
442

443 444
		if (!git_net_url_is_default_port(&t->proxy.url))
			git_buf_printf(&processed_url, ":%s", t->proxy.url.port);
445 446 447

		if (git_buf_oom(&processed_url)) {
			error = -1;
448 449
			goto on_error;
		}
450

451
		/* Convert URL to wide characters */
452
		error = git__utf8_to_16_alloc(&proxy_wide, processed_url.ptr);
453
		git_buf_dispose(&processed_url);
454
		if (error < 0)
455 456
			goto on_error;

457
		proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
458
		proxy_info.lpszProxy = proxy_wide;
459 460 461 462 463 464
		proxy_info.lpszProxyBypass = NULL;

		if (!WinHttpSetOption(s->request,
			WINHTTP_OPTION_PROXY,
			&proxy_info,
			sizeof(WINHTTP_PROXY_INFO))) {
465
			git_error_set(GIT_ERROR_OS, "failed to set proxy");
466
			git__free(proxy_wide);
467 468
			goto on_error;
		}
469 470

		git__free(proxy_wide);
471

472 473
		if ((error = apply_credentials(s->request, &t->proxy.url, WINHTTP_AUTH_TARGET_PROXY, t->proxy.cred, t->proxy.auth_mechanisms)) < 0)
			goto on_error;
474 475
	}

476 477 478
	/* 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
	 */
479 480 481 482
	if (!WinHttpSetOption(s->request,
		WINHTTP_OPTION_DISABLE_FEATURE,
		&disable_redirects,
		sizeof(disable_redirects))) {
483
			git_error_set(GIT_ERROR_OS, "failed to disable redirects");
484
			error = -1;
485 486 487
			goto on_error;
	}

488 489 490 491 492 493 494 495 496 497
	/* 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)) {
498
		git_error_set(GIT_ERROR_OS, "failed to add a header to the request");
499 500 501
		goto on_error;
	}

502
	if (post_verb == s->verb) {
Ben Straub committed
503
		/* Send Content-Type and Accept headers -- only necessary on a POST */
504
		git_buf_clear(&buf);
Ben Straub committed
505
		if (git_buf_printf(&buf,
506 507
			"Content-Type: application/x-git-%s-request",
			s->service) < 0)
508 509
			goto on_error;

510
		if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) {
511
			git_error_set(GIT_ERROR_OS, "failed to convert content-type to wide characters");
512 513
			goto on_error;
		}
514

515 516
		if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L,
			WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
517
			git_error_set(GIT_ERROR_OS, "failed to add a header to the request");
518 519 520 521 522 523 524 525 526
			goto on_error;
		}

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

527
		if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) {
528
			git_error_set(GIT_ERROR_OS, "failed to convert accept header to wide characters");
529 530
			goto on_error;
		}
531 532

		if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L,
Ben Straub committed
533
			WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
534
			git_error_set(GIT_ERROR_OS, "failed to add a header to the request");
535 536
			goto on_error;
		}
537 538
	}

539 540 541 542 543
	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) {
544
				git_error_set(GIT_ERROR_OS, "failed to convert custom header to wide characters");
545 546
				goto on_error;
			}
547

548 549
			if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L,
				WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
550
				git_error_set(GIT_ERROR_OS, "failed to add a header to the request");
551
				goto on_error;
552 553 554 555
			}
		}
	}

556
	/* If requested, disable certificate validation */
557
	if (strcmp(t->server.url.scheme, "https") == 0) {
558 559 560 561
		int flags;

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

564
	if ((error = apply_credentials(s->request, &t->server.url, WINHTTP_AUTH_TARGET_SERVER, t->server.cred, t->server.auth_mechanisms)) < 0)
565
		goto on_error;
566

567 568
	/* We've done everything up to calling WinHttpSendRequest. */

569
	error = 0;
570 571

on_error:
572 573 574
	if (error < 0)
		winhttp_stream_close(s);

575
	git__free(proxy_url);
576
	git_buf_dispose(&buf);
577
	return error;
578 579
}

580 581
static int parse_unauthorized_response(
	int *allowed_types,
582 583
	int *allowed_mechanisms,
	HINTERNET request)
584
{
585
	DWORD supported, first, target;
586 587

	*allowed_types = 0;
588
	*allowed_mechanisms = 0;
589

590 591
	/* WinHttpQueryHeaders() must be called before WinHttpQueryAuthSchemes().
	 * We can assume this was already done, since we know we are unauthorized.
592 593
	 */
	if (!WinHttpQueryAuthSchemes(request, &supported, &first, &target)) {
594
		git_error_set(GIT_ERROR_OS, "failed to parse supported auth schemes");
595 596
		return -1;
	}
597

598
	if (WINHTTP_AUTH_SCHEME_NTLM & supported) {
599
		*allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
600
		*allowed_types |= GIT_CREDTYPE_DEFAULT;
601
		*allowed_mechanisms |= GIT_WINHTTP_AUTH_NTLM;
602
	}
603

604
	if (WINHTTP_AUTH_SCHEME_NEGOTIATE & supported) {
605
		*allowed_types |= GIT_CREDTYPE_DEFAULT;
606
		*allowed_mechanisms |= GIT_WINHTTP_AUTH_NEGOTIATE;
607 608 609 610 611
	}

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

614 615 616 617 618
	if (WINHTTP_AUTH_SCHEME_DIGEST & supported) {
		*allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
		*allowed_mechanisms |= GIT_WINHTTP_AUTH_DIGEST;
	}

619 620 621 622 623 624 625 626 627
	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
628
	git_buf_printf(&buf, "%"PRIXZ"\r\n", len);
629 630 631 632 633

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

	if (!WinHttpWriteData(request,
634
		git_buf_cstr(&buf),	(DWORD)git_buf_len(&buf),
635
		&bytes_written)) {
636
		git_buf_dispose(&buf);
637
		git_error_set(GIT_ERROR_OS, "failed to write chunk header");
638
		return -1;
639 640
	}

641
	git_buf_dispose(&buf);
642 643 644

	/* Chunk body */
	if (!WinHttpWriteData(request,
645
		buffer, (DWORD)len,
646
		&bytes_written)) {
647
		git_error_set(GIT_ERROR_OS, "failed to write chunk");
648 649 650 651 652 653 654
		return -1;
	}

	/* Chunk footer */
	if (!WinHttpWriteData(request,
		"\r\n", 2,
		&bytes_written)) {
655
		git_error_set(GIT_ERROR_OS, "failed to write chunk footer");
656 657 658 659
		return -1;
	}

	return 0;
660 661
}

662 663 664 665 666 667
static int winhttp_close_connection(winhttp_subtransport *t)
{
	int ret = 0;

	if (t->connection) {
		if (!WinHttpCloseHandle(t->connection)) {
668
			git_error_set(GIT_ERROR_OS, "unable to close connection");
669 670 671 672 673 674 675 676
			ret = -1;
		}

		t->connection = NULL;
	}

	if (t->session) {
		if (!WinHttpCloseHandle(t->session)) {
677
			git_error_set(GIT_ERROR_OS, "unable to close session");
678 679 680 681 682 683 684 685 686
			ret = -1;
		}

		t->session = NULL;
	}

	return ret;
}

687 688 689 690 691 692 693 694 695
static void CALLBACK winhttp_status(
	HINTERNET connection,
	DWORD_PTR ctx,
	DWORD code,
	LPVOID info,
	DWORD info_len)
{
	DWORD status;

696 697 698 699
	GIT_UNUSED(connection);
	GIT_UNUSED(ctx);
	GIT_UNUSED(info_len);

700 701 702 703 704 705
	if (code != WINHTTP_CALLBACK_STATUS_SECURE_FAILURE)
		return;

	status = *((DWORD *)info);

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

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

738 739 740
	t->session = NULL;
	t->connection = NULL;

741
	/* Prepare port */
742 743
	if (git__strntol32(&port, t->server.url.port,
			   strlen(t->server.url.port), NULL, 10) < 0)
744 745 746
		return -1;

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

752 753

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

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

765
	git_buf_dispose(&ua);
766

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

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

780 781 782 783 784 785 786 787 788 789
	/*
	 * 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));

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

795

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

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

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

813 814 815
	error = 0;

on_error:
816 817 818
	if (error < 0)
		winhttp_close_connection(t);

819
	git__free(wide_host);
820
	git__free(wide_ua);
821 822

	return error;
823 824
}

825
static int do_send_request(winhttp_stream *s, size_t len, int ignore_length)
826
{
827 828 829
	int attempts;
	bool success;

830 831 832 833 834
	if (len > DWORD_MAX) {
		SetLastError(ERROR_NOT_ENOUGH_MEMORY);
		return -1;
	}

835 836 837 838 839 840 841 842 843 844
	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,
845
				(DWORD)len, 0);
846
		}
847

lhchavez committed
848
		if (success || GetLastError() != (DWORD)SEC_E_BUFFER_TOO_SMALL)
849
			break;
850 851
	}

852
	return success ? 0 : -1;
853 854 855 856 857 858 859
}

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;

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

		request_failed = 1;
		cert_valid = 0;
869
	}
870

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

		return error;
	}

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

883
	ignore_flags = no_check_cert_flags;
884

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

890
	if ((error = do_send_request(s, len, ignore_length)) < 0)
891
		git_error_set(GIT_ERROR_OS, "failed to send request with unchecked certificate");
892

893 894 895
	return error;
}

896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948
static int acquire_credentials(
	HINTERNET request,
	winhttp_server *server,
	const char *url_str,
	git_cred_acquire_cb cred_cb,
	void *cred_cb_payload)
{
	int allowed_types;
	int error = 1;

	if (parse_unauthorized_response(&allowed_types, &server->auth_mechanisms, request) < 0)
		return -1;

	if (allowed_types) {
		git_cred_free(server->cred);
		server->cred = NULL;

		/* Start with URL-specified credentials, if there were any. */
		if (!server->url_cred_presented && server->url.username && server->url.password) {
			error = acquire_url_cred(&server->cred, allowed_types, server->url.username, server->url.password);
			server->url_cred_presented = 1;

			if (error < 0)
				return error;
		}

		/* Next use the user-defined callback, if there is one. */
		if (error > 0 && cred_cb) {
			error = cred_cb(&server->cred, url_str, server->url.username, allowed_types, cred_cb_payload);

			/* Treat GIT_PASSTHROUGH as though git_cred_acquire_cb isn't set */
			if (error == GIT_PASSTHROUGH)
				error = 1;
			else if (error < 0)
				return error;
		}

		/* Finally, invoke the fallback default credential lookup. */
		if (error > 0) {
			error = acquire_fallback_cred(&server->cred, url_str, allowed_types);

			if (error < 0)
				return error;
		}
	}

	/*
	 * No error occurred but we could not find appropriate credentials.
	 * This behaves like a pass-through.
	 */
	return error;
}

949 950 951 952 953 954 955 956
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);
957
	DWORD dw_bytes_read;
958
	char replay_count = 0;
959
	int error;
960

961
replay:
962
	/* Enforce a reasonable cap on the number of replays */
963
	if (replay_count++ >= GIT_HTTP_REPLAY_MAX) {
964
		git_error_set(GIT_ERROR_NET, "too many redirects or authentication replays");
965 966 967
		return -1;
	}

968 969 970 971 972
	/* Connect if necessary */
	if (!s->request && winhttp_stream_connect(s) < 0)
		return -1;

	if (!s->received_response) {
973
		DWORD status_code, status_code_length, content_type_length, bytes_written;
974 975 976
		char expected_content_type_8[MAX_CONTENT_TYPE_LEN];
		wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN];

977
		if (!s->sent_request) {
978 979 980

			if ((error = send_request(s, s->post_body_len, 0)) < 0)
				return error;
981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998

			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)) {
999
				git_error_set(GIT_ERROR_OS, "failed to write final chunk");
1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
				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()) {
1010
				git_error_set(GIT_ERROR_OS, "failed to reset file pointer");
1011 1012 1013
				return -1;
			}

1014
			buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE);
1015
			GIT_ERROR_CHECK_ALLOC(buffer);
1016 1017 1018 1019 1020

			while (len > 0) {
				DWORD bytes_written;

				if (!ReadFile(s->post_body, buffer,
1021
					min(CACHED_POST_BODY_BUF_SIZE, len),
1022 1023 1024
					&bytes_read, NULL) ||
					!bytes_read) {
					git__free(buffer);
1025
					git_error_set(GIT_ERROR_OS, "failed to read from temp file");
1026 1027 1028 1029 1030 1031
					return -1;
				}

				if (!WinHttpWriteData(s->request, buffer,
					bytes_read, &bytes_written)) {
					git__free(buffer);
1032
					git_error_set(GIT_ERROR_OS, "failed to write data");
1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046
					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;
		}

1047
		if (!WinHttpReceiveResponse(s->request, 0)) {
1048
			git_error_set(GIT_ERROR_OS, "failed to receive response");
1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059
			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)) {
1060
				git_error_set(GIT_ERROR_OS, "failed to retrieve status code");
1061 1062 1063
				return -1;
		}

1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077
		/* 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. */
1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089
			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) {
1090
				git_error_set(GIT_ERROR_OS, "failed to read Location header");
1091 1092
				return -1;
			}
1093

1094
			location = git__malloc(location_length);
1095
			GIT_ERROR_CHECK_ALLOC(location);
1096 1097 1098 1099 1100 1101 1102

			if (!WinHttpQueryHeaders(s->request,
				WINHTTP_QUERY_LOCATION,
				WINHTTP_HEADER_NAME_BY_INDEX,
				location,
				&location_length,
				WINHTTP_NO_HEADER_INDEX)) {
1103
				git_error_set(GIT_ERROR_OS, "failed to read Location header");
1104
				git__free(location);
1105 1106
				return -1;
			}
1107 1108 1109

			/* Convert the Location header to UTF-8 */
			if (git__utf16_to_8_alloc(&location8, location) < 0) {
1110
				git_error_set(GIT_ERROR_OS, "failed to convert Location header to UTF-8");
1111 1112 1113 1114
				git__free(location);
				return -1;
			}

1115 1116 1117
			git__free(location);

			/* Replay the request */
1118
			winhttp_stream_close(s);
1119 1120 1121

			if (!git__prefixcmp_icase(location8, prefix_https)) {
				/* Upgrade to secure connection; disconnect and start over */
1122
				if (gitno_connection_data_handle_redirect(&t->server.url, location8, s->service_url) < 0) {
1123
					git__free(location8);
1124
					return -1;
1125 1126
				}

1127 1128
				winhttp_close_connection(t);

1129
				if (winhttp_connect(t) < 0)
1130
					return -1;
1131
			}
1132 1133 1134

			git__free(location8);
			goto replay;
1135 1136
		}

1137
		/* Handle authentication failures */
1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164
		if (status_code == HTTP_STATUS_DENIED) {
			int error = acquire_credentials(s->request,
				&t->server,
				t->owner->url,
				t->owner->cred_acquire_cb,
				t->owner->cred_acquire_payload);

			if (error < 0) {
				return error;
			} else if (!error) {
				assert(t->server.cred);
				winhttp_stream_close(s);
				goto replay;
			}
		} else if (status_code == HTTP_STATUS_PROXY_AUTH_REQ) {
			int error = acquire_credentials(s->request,
				&t->proxy,
				t->owner->proxy.url,
				t->owner->proxy.credentials,
				t->owner->proxy.payload);

			if (error < 0) {
				return error;
			} else if (!error) {
				assert(t->proxy.cred);
				winhttp_stream_close(s);
				goto replay;
1165 1166 1167
			}
		}

1168
		if (HTTP_STATUS_OK != status_code) {
1169
			git_error_set(GIT_ERROR_NET, "request failed with status code: %lu", status_code);
1170 1171 1172 1173 1174
			return -1;
		}

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

1179
		if (git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) {
1180
			git_error_set(GIT_ERROR_OS, "failed to convert expected content-type to wide characters");
1181 1182 1183
			return -1;
		}

1184 1185 1186 1187 1188 1189 1190
		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)) {
1191
				git_error_set(GIT_ERROR_OS, "failed to retrieve response content-type");
1192 1193 1194 1195
				return -1;
		}

		if (wcscmp(expected_content_type, content_type)) {
1196
			git_error_set(GIT_ERROR_NET, "received unexpected content-type");
1197 1198 1199 1200 1201 1202 1203 1204
			return -1;
		}

		s->received_response = 1;
	}

	if (!WinHttpReadData(s->request,
		(LPVOID)buffer,
1205
		(DWORD)buf_size,
1206
		&dw_bytes_read))
1207
	{
1208
		git_error_set(GIT_ERROR_OS, "failed to read data");
1209 1210 1211
		return -1;
	}

1212 1213
	*bytes_read = dw_bytes_read;

1214 1215 1216
	return 0;
}

1217
static int winhttp_stream_write_single(
1218 1219 1220 1221 1222 1223
	git_smart_subtransport_stream *stream,
	const char *buffer,
	size_t len)
{
	winhttp_stream *s = (winhttp_stream *)stream;
	DWORD bytes_written;
1224
	int error;
1225 1226 1227 1228

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

1229 1230
	/* This implementation of write permits only a single call. */
	if (s->sent_request) {
1231
		git_error_set(GIT_ERROR_NET, "subtransport configured for only one write");
1232 1233 1234
		return -1;
	}

1235 1236
	if ((error = send_request(s, len, 0)) < 0)
		return error;
1237 1238 1239 1240 1241 1242 1243

	s->sent_request = 1;

	if (!WinHttpWriteData(s->request,
			(LPCVOID)buffer,
			(DWORD)len,
			&bytes_written)) {
1244
		git_error_set(GIT_ERROR_OS, "failed to write data");
1245 1246 1247 1248 1249 1250 1251 1252
		return -1;
	}

	assert((DWORD)len == bytes_written);

	return 0;
}

1253
static int put_uuid_string(LPWSTR buffer, size_t buffer_len_cch)
1254 1255 1256
{
	UUID uuid;
	RPC_STATUS status = UuidCreate(&uuid);
1257
	int result;
1258 1259 1260 1261

	if (RPC_S_OK != status &&
		RPC_S_UUID_LOCAL_ONLY != status &&
		RPC_S_UUID_NO_ADDRESS != status) {
1262
		git_error_set(GIT_ERROR_NET, "unable to generate name for temp file");
1263 1264 1265
		return -1;
	}

1266
	if (buffer_len_cch < UUID_LENGTH_CCH + 1) {
1267
		git_error_set(GIT_ERROR_NET, "buffer too small for name of temp file");
1268 1269 1270
		return -1;
	}

1271 1272 1273 1274 1275
#if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API)
	result = swprintf_s(buffer, buffer_len_cch,
#else
	result = wsprintfW(buffer,
#endif
1276
		L"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x",
1277 1278 1279 1280
		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]);

1281
	if (result < UUID_LENGTH_CCH) {
1282
		git_error_set(GIT_ERROR_OS, "unable to generate name for temp file");
1283 1284 1285 1286 1287 1288 1289 1290
		return -1;
	}

	return 0;
}

static int get_temp_file(LPWSTR buffer, DWORD buffer_len_cch)
{
1291
	size_t len;
1292 1293

	if (!GetTempPathW(buffer_len_cch, buffer)) {
1294
		git_error_set(GIT_ERROR_OS, "failed to get temp path");
1295 1296 1297 1298 1299
		return -1;
	}

	len = wcslen(buffer);

1300
	if (buffer[len - 1] != '\\' && len < buffer_len_cch)
1301 1302
		buffer[len++] = '\\';

1303
	if (put_uuid_string(&buffer[len], (size_t)buffer_len_cch - len) < 0)
1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336
		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;
1337
			git_error_set(GIT_ERROR_OS, "failed to create temporary file");
1338 1339 1340 1341
			return -1;
		}
	}

1342
	if (!WriteFile(s->post_body, buffer, (DWORD)len, &bytes_written, NULL)) {
1343
		git_error_set(GIT_ERROR_OS, "failed to write to temporary file");
1344 1345 1346 1347 1348
		return -1;
	}

	assert((DWORD)len == bytes_written);

1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359
	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;
1360
	int error;
1361 1362 1363 1364 1365 1366 1367 1368 1369

	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)) {
1370
			git_error_set(GIT_ERROR_OS, "failed to add a header to the request");
1371 1372 1373
			return -1;
		}

1374 1375
		if ((error = send_request(s, 0, 1)) < 0)
			return error;
1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394

		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 */
1395
		int count = (int)min(CACHED_POST_BODY_BUF_SIZE - s->chunk_buffer_len, len);
1396

1397
		if (!s->chunk_buffer) {
1398
			s->chunk_buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE);
1399 1400
			GIT_ERROR_CHECK_ALLOC(s->chunk_buffer);
		}
1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416

		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);
1417
				s->chunk_buffer_len = (unsigned int)len;
1418 1419 1420 1421
			}
		}
	}

1422 1423 1424 1425 1426 1427 1428
	return 0;
}

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

1429
	winhttp_stream_close(s);
1430 1431 1432
	git__free(s);
}

1433
static int winhttp_stream_alloc(winhttp_subtransport *t, winhttp_stream **stream)
1434 1435 1436 1437 1438 1439
{
	winhttp_stream *s;

	if (!stream)
		return -1;

1440
	s = git__calloc(1, sizeof(winhttp_stream));
1441
	GIT_ERROR_CHECK_ALLOC(s);
1442 1443 1444

	s->parent.subtransport = &t->parent;
	s->parent.read = winhttp_stream_read;
1445
	s->parent.write = winhttp_stream_write_single;
1446 1447
	s->parent.free = winhttp_stream_free;

1448 1449
	*stream = s;

1450 1451 1452 1453 1454
	return 0;
}

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

1459 1460 1461 1462 1463 1464 1465 1466 1467
	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,
1468
	winhttp_stream *s)
1469
{
1470 1471
	GIT_UNUSED(t);

1472 1473 1474
	s->service = upload_pack_service;
	s->service_url = upload_pack_service_url;
	s->verb = post_verb;
1475

1476 1477
	return 0;
}
1478

1479 1480 1481 1482
static int winhttp_receivepack_ls(
	winhttp_subtransport *t,
	winhttp_stream *s)
{
1483 1484
	GIT_UNUSED(t);

1485 1486 1487
	s->service = receive_pack_service;
	s->service_url = receive_pack_ls_service_url;
	s->verb = get_verb;
1488

1489 1490
	return 0;
}
1491

1492 1493 1494 1495
static int winhttp_receivepack(
	winhttp_subtransport *t,
	winhttp_stream *s)
{
1496 1497
	GIT_UNUSED(t);

1498 1499
	/* WinHTTP only supports Transfer-Encoding: chunked
	 * on Windows Vista (NT 6.0) and higher. */
1500
	s->chunked = git_has_win32_version(6, 0, 0);
1501 1502 1503 1504 1505 1506 1507 1508

	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;
1509 1510 1511 1512 1513 1514 1515
	s->verb = post_verb;

	return 0;
}

static int winhttp_action(
	git_smart_subtransport_stream **stream,
1516
	git_smart_subtransport *subtransport,
1517 1518 1519
	const char *url,
	git_smart_service_t action)
{
1520 1521 1522 1523
	winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
	winhttp_stream *s;
	int ret = -1;

1524
	if (!t->connection)
1525
		if ((ret = git_net_url_parse(&t->server.url, url)) < 0 ||
1526
			 (ret = winhttp_connect(t)) < 0)
1527
			return ret;
1528 1529 1530

	if (winhttp_stream_alloc(t, &s) < 0)
		return -1;
1531 1532 1533 1534 1535 1536 1537

	if (!stream)
		return -1;

	switch (action)
	{
		case GIT_SERVICE_UPLOADPACK_LS:
1538 1539
			ret = winhttp_uploadpack_ls(t, s);
			break;
1540 1541

		case GIT_SERVICE_UPLOADPACK:
1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554
			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);
1555 1556
	}

1557 1558 1559 1560
	if (!ret)
		*stream = &s->parent;

	return ret;
1561 1562
}

1563
static int winhttp_close(git_smart_subtransport *subtransport)
1564
{
1565 1566
	winhttp_subtransport *t = (winhttp_subtransport *)subtransport;

1567 1568
	git_net_url_dispose(&t->server.url);
	git_net_url_dispose(&t->proxy.url);
1569

1570 1571 1572
	if (t->server.cred) {
		t->server.cred->free(t->server.cred);
		t->server.cred = NULL;
1573 1574
	}

1575 1576 1577
	if (t->proxy.cred) {
		t->proxy.cred->free(t->proxy.cred);
		t->proxy.cred = NULL;
1578 1579
	}

1580
	return winhttp_close_connection(t);
1581 1582 1583 1584 1585 1586 1587 1588
}

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

	winhttp_close(subtransport);

1589 1590 1591
	git__free(t);
}

1592
int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param)
1593 1594 1595
{
	winhttp_subtransport *t;

1596 1597
	GIT_UNUSED(param);

1598 1599 1600
	if (!out)
		return -1;

1601
	t = git__calloc(1, sizeof(winhttp_subtransport));
1602
	GIT_ERROR_CHECK_ALLOC(t);
1603

1604
	t->owner = (transport_smart *)owner;
1605
	t->parent.action = winhttp_action;
1606
	t->parent.close = winhttp_close;
1607 1608 1609 1610 1611 1612 1613
	t->parent.free = winhttp_free;

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

#endif /* GIT_WINHTTP */