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

9 10 11
#include "git2.h"
#include "http_parser.h"
#include "buffer.h"
12
#include "netops.h"
13
#include "remote.h"
14
#include "smart.h"
15 16
#include "auth.h"
#include "auth_negotiate.h"
17
#include "tls_stream.h"
18
#include "socket_stream.h"
19
#include "curl_stream.h"
20

21 22 23 24
git_http_auth_scheme auth_schemes[] = {
	{ GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate },
	{ GIT_AUTHTYPE_BASIC, "Basic", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_basic },
};
25

26 27 28
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";
29 30 31
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";
32 33 34 35
static const char *get_verb = "GET";
static const char *post_verb = "POST";

#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
36

37 38 39
#define PARSE_ERROR_GENERIC	-1
#define PARSE_ERROR_REPLAY	-2

40 41
#define CHUNK_SIZE	4096

42
enum last_cb {
43 44 45
	NONE,
	FIELD,
	VALUE
46
};
47

48
typedef struct {
49 50 51
	git_smart_subtransport_stream parent;
	const char *service;
	const char *service_url;
52
	char *redirect_url;
53
	const char *verb;
54 55 56 57
	char *chunk_buffer;
	unsigned chunk_buffer_len;
	unsigned sent_request : 1,
		received_response : 1,
58 59
		chunked : 1,
		redirect_count : 3;
60 61 62 63
} http_stream;

typedef struct {
	git_smart_subtransport parent;
64
	transport_smart *owner;
65
	git_stream *io;
66 67
	gitno_connection_data connection_data;
	bool connected;
68 69

	/* Parser structures */
70
	http_parser parser;
71 72
	http_parser_settings settings;
	gitno_buffer parse_buffer;
73 74
	git_buf parse_header_name;
	git_buf parse_header_value;
75
	char parse_buffer_data[NETIO_BUFSIZE];
76
	char *content_type;
77
	char *location;
78
	git_vector www_authenticate;
79
	enum last_cb last_cb;
80 81
	int parse_error;
	unsigned parse_finished : 1;
82

83 84 85 86
	/* Authentication */
	git_cred *cred;
	git_cred *url_cred;
	git_vector auth_contexts;
87 88 89 90 91 92 93 94 95 96 97
} http_subtransport;

typedef struct {
	http_stream *s;
	http_subtransport *t;

	/* Target buffer details from read() */
	char *buffer;
	size_t buf_size;
	size_t *bytes_read;
} parser_context;
98

99
static bool credtype_match(git_http_auth_scheme *scheme, void *data)
100
{
101
	unsigned int credtype = *(unsigned int *)data;
102

103
	return !!(scheme->credtypes & credtype);
104 105
}

106
static bool challenge_match(git_http_auth_scheme *scheme, void *data)
107
{
108 109 110 111 112 113 114
	const char *scheme_name = scheme->name;
	const char *challenge = (const char *)data;
	size_t scheme_len;

	scheme_len = strlen(scheme_name);
	return (strncmp(challenge, scheme_name, scheme_len) == 0 &&
		(challenge[scheme_len] == '\0' || challenge[scheme_len] == ' '));
115 116
}

117 118 119 120 121
static int auth_context_match(
	git_http_auth_context **out,
	http_subtransport *t,
	bool (*scheme_match)(git_http_auth_scheme *scheme, void *data),
	void *data)
122
{
123 124
	git_http_auth_scheme *scheme = NULL;
	git_http_auth_context *context = NULL, *c;
125 126
	size_t i;

127
	*out = NULL;
128

129 130 131 132
	for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) {
		if (scheme_match(&auth_schemes[i], data)) {
			scheme = &auth_schemes[i];
			break;
133 134 135
		}
	}

136 137
	if (!scheme)
		return 0;
138

139 140 141 142 143 144
	/* See if authentication has already started for this scheme */
	git_vector_foreach(&t->auth_contexts, i, c) {
		if (c->type == scheme->type) {
			context = c;
			break;
		}
145 146
	}

147 148 149 150 151 152 153 154
	if (!context) {
		if (scheme->init_context(&context, &t->connection_data) < 0)
			return -1;
		else if (!context)
			return 0;
		else if (git_vector_insert(&t->auth_contexts, context) < 0)
			return -1;
	}
155

156
	*out = context;
157 158 159 160

	return 0;
}

161
static int apply_credentials(git_buf *buf, http_subtransport *t)
162
{
163 164 165 166 167 168 169 170 171
	git_cred *cred = t->cred;
	git_http_auth_context *context;

	/* Apply the credentials given to us in the URL */
	if (!cred && t->connection_data.user && t->connection_data.pass) {
		if (!t->url_cred &&
			git_cred_userpass_plaintext_new(&t->url_cred,
				t->connection_data.user, t->connection_data.pass) < 0)
			return -1;
172

173
		cred = t->url_cred;
174 175
	}

176 177
	if (!cred)
		return 0;
178

179 180
	/* Get or create a context for the best scheme for this cred type */
	if (auth_context_match(&context, t, credtype_match, &cred->credtype) < 0)
181 182
		return -1;

183
	return context->next_token(buf, context, cred);
184 185
}

186 187
static int gen_request(
	git_buf *buf,
188 189
	http_stream *s,
	size_t content_length)
190
{
191
	http_subtransport *t = OWNING_SUBTRANSPORT(s);
192
	const char *path = t->connection_data.path ? t->connection_data.path : "/";
193

194
	git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, path, s->service_url);
195

196
	git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");
197
	git_buf_printf(buf, "Host: %s\r\n", t->connection_data.host);
198 199 200 201 202 203 204 205 206 207

	if (s->chunked || content_length > 0) {
		git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", s->service);
		git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", s->service);

		if (s->chunked)
			git_buf_puts(buf, "Transfer-Encoding: chunked\r\n");
		else
			git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length);
	} else
208
		git_buf_puts(buf, "Accept: */*\r\n");
209 210

	/* Apply credentials to the request */
211 212
	if (apply_credentials(buf, t) < 0)
		return -1;
213

214
	git_buf_puts(buf, "\r\n");
215

216
	if (git_buf_oom(buf))
217
		return -1;
218 219

	return 0;
220 221
}

222
static int parse_authenticate_response(
223
	git_vector *www_authenticate,
224 225
	http_subtransport *t,
	int *allowed_types)
226
{
227 228 229
	git_http_auth_context *context;
	char *challenge;
	size_t i;
230

231 232 233 234 235
	git_vector_foreach(www_authenticate, i, challenge) {
		if (auth_context_match(&context, t, challenge_match, challenge) < 0)
			return -1;
		else if (!context)
			continue;
236

237 238 239
		if (context->set_challenge &&
			context->set_challenge(context, challenge) < 0)
			return -1;
240

241
		*allowed_types |= context->credtypes;
242 243 244 245 246 247 248 249 250 251
	}

	return 0;
}

static int on_header_ready(http_subtransport *t)
{
	git_buf *name = &t->parse_header_name;
	git_buf *value = &t->parse_header_value;

252 253 254 255 256
	if (!strcasecmp("Content-Type", git_buf_cstr(name))) {
		if (!t->content_type) {
			t->content_type = git__strdup(git_buf_cstr(value));
			GITERR_CHECK_ALLOC(t->content_type);
		}
257 258
	}
	else if (!strcmp("WWW-Authenticate", git_buf_cstr(name))) {
259
		char *dup = git__strdup(git_buf_cstr(value));
260
		GITERR_CHECK_ALLOC(dup);
261

262 263
		git_vector_insert(&t->www_authenticate, dup);
	}
264 265
	else if (!strcasecmp("Location", git_buf_cstr(name))) {
		if (!t->location) {
266
			t->location = git__strdup(git_buf_cstr(value));
267 268 269
			GITERR_CHECK_ALLOC(t->location);
		}
	}
270 271 272 273

	return 0;
}

274 275
static int on_header_field(http_parser *parser, const char *str, size_t len)
{
276 277
	parser_context *ctx = (parser_context *) parser->data;
	http_subtransport *t = ctx->t;
278

279 280 281 282 283
	/* Both parse_header_name and parse_header_value are populated
	 * and ready for consumption */
	if (VALUE == t->last_cb)
		if (on_header_ready(t) < 0)
			return t->parse_error = PARSE_ERROR_GENERIC;
284

285 286
	if (NONE == t->last_cb || VALUE == t->last_cb)
		git_buf_clear(&t->parse_header_name);
287

288 289
	if (git_buf_put(&t->parse_header_name, str, len) < 0)
		return t->parse_error = PARSE_ERROR_GENERIC;
290

291
	t->last_cb = FIELD;
292
	return 0;
293 294 295 296
}

static int on_header_value(http_parser *parser, const char *str, size_t len)
{
297 298
	parser_context *ctx = (parser_context *) parser->data;
	http_subtransport *t = ctx->t;
299

300
	assert(NONE != t->last_cb);
301

302 303
	if (FIELD == t->last_cb)
		git_buf_clear(&t->parse_header_value);
304

305 306
	if (git_buf_put(&t->parse_header_value, str, len) < 0)
		return t->parse_error = PARSE_ERROR_GENERIC;
307

308
	t->last_cb = VALUE;
309
	return 0;
310 311 312 313
}

static int on_headers_complete(http_parser *parser)
{
314 315
	parser_context *ctx = (parser_context *) parser->data;
	http_subtransport *t = ctx->t;
316 317
	http_stream *s = ctx->s;
	git_buf buf = GIT_BUF_INIT;
318
	int error = 0, no_callback = 0, allowed_auth_types = 0;
319

320 321 322 323 324 325
	/* Both parse_header_name and parse_header_value are populated
	 * and ready for consumption. */
	if (VALUE == t->last_cb)
		if (on_header_ready(t) < 0)
			return t->parse_error = PARSE_ERROR_GENERIC;

326 327 328 329
	/* Capture authentication headers which may be a 401 (authentication
	 * is not complete) or a 200 (simply informing us that auth *is*
	 * complete.)
	 */
330 331
	if (parse_authenticate_response(&t->www_authenticate, t,
			&allowed_auth_types) < 0)
332 333 334 335
		return t->parse_error = PARSE_ERROR_GENERIC;

	/* Check for an authentication failure. */
	if (parser->status_code == 401 && get_verb == s->verb) {
336
		if (!t->owner->cred_acquire_cb) {
337 338
			no_callback = 1;
		} else {
339 340
			if (allowed_auth_types &&
			    (!t->cred || 0 == (t->cred->credtype & allowed_auth_types))) {
341 342 343 344

				error = t->owner->cred_acquire_cb(&t->cred,
								  t->owner->url,
								  t->connection_data.user,
345
								  allowed_auth_types,
346 347 348 349 350 351 352 353 354
								  t->owner->cred_acquire_payload);

				if (error == GIT_PASSTHROUGH) {
					no_callback = 1;
				} else if (error < 0) {
					return PARSE_ERROR_GENERIC;
				} else {
					assert(t->cred);

355 356 357 358 359
					if (!(t->cred->credtype & allowed_auth_types)) {
						giterr_set(GITERR_NET, "credentials callback returned an invalid cred type");
						return t->parse_error = PARSE_ERROR_GENERIC;
					}

360
					/* Successfully acquired a credential. */
Edward Thomson committed
361 362
					t->parse_error = PARSE_ERROR_REPLAY;
					return 0;
363 364 365
				}
			}
		}
366

367 368
		if (no_callback) {
			giterr_set(GITERR_NET, "authentication required but no callback set");
369 370
			return t->parse_error = PARSE_ERROR_GENERIC;
		}
371 372
	}

373
	/* Check for a redirect.
374
	 * Right now we only permit a redirect to the same hostname. */
375
	if ((parser->status_code == 301 ||
376 377 378 379
	     parser->status_code == 302 ||
	     (parser->status_code == 303 && get_verb == s->verb) ||
	     parser->status_code == 307) &&
	    t->location) {
380 381 382 383 384 385

		if (s->redirect_count >= 7) {
			giterr_set(GITERR_NET, "Too many redirects");
			return t->parse_error = PARSE_ERROR_GENERIC;
		}

386
		if (gitno_connection_data_from_url(&t->connection_data, t->location, s->service_url) < 0)
387
			return t->parse_error = PARSE_ERROR_GENERIC;
388 389 390 391 392 393 394 395 396 397 398 399

		/* Set the redirect URL on the stream. This is a transfer of
		 * ownership of the memory. */
		if (s->redirect_url)
			git__free(s->redirect_url);

		s->redirect_url = t->location;
		t->location = NULL;

		t->connected = 0;
		s->redirect_count++;

Edward Thomson committed
400 401
		t->parse_error = PARSE_ERROR_REPLAY;
		return 0;
402 403
	}

404 405 406 407 408 409
	/* Check for a 200 HTTP status code. */
	if (parser->status_code != 200) {
		giterr_set(GITERR_NET,
			"Unexpected HTTP status code: %d",
			parser->status_code);
		return t->parse_error = PARSE_ERROR_GENERIC;
410 411
	}

412 413 414 415 416
	/* The response must contain a Content-Type header. */
	if (!t->content_type) {
		giterr_set(GITERR_NET, "No Content-Type header in response");
		return t->parse_error = PARSE_ERROR_GENERIC;
	}
417

418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
	/* The Content-Type header must match our expectation. */
	if (get_verb == s->verb)
		git_buf_printf(&buf,
			"application/x-git-%s-advertisement",
			ctx->s->service);
	else
		git_buf_printf(&buf,
			"application/x-git-%s-result",
			ctx->s->service);

	if (git_buf_oom(&buf))
		return t->parse_error = PARSE_ERROR_GENERIC;

	if (strcmp(t->content_type, git_buf_cstr(&buf))) {
		git_buf_free(&buf);
		giterr_set(GITERR_NET,
			"Invalid Content-Type: %s",
			t->content_type);
		return t->parse_error = PARSE_ERROR_GENERIC;
437
	}
438

439 440
	git_buf_free(&buf);

441 442 443 444 445
	return 0;
}

static int on_message_complete(http_parser *parser)
{
446 447
	parser_context *ctx = (parser_context *) parser->data;
	http_subtransport *t = ctx->t;
448

449
	t->parse_finished = 1;
450

451 452 453
	return 0;
}

454
static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
455
{
456 457
	parser_context *ctx = (parser_context *) parser->data;
	http_subtransport *t = ctx->t;
458

Edward Thomson committed
459 460 461 462 463 464 465
	/* If our goal is to replay the request (either an auth failure or
	 * a redirect) then don't bother buffering since we're ignoring the
	 * content anyway.
	 */
	if (t->parse_error == PARSE_ERROR_REPLAY)
		return 0;

466
	if (ctx->buf_size < len) {
467
		giterr_set(GITERR_NET, "Can't fit data in the buffer");
468
		return t->parse_error = PARSE_ERROR_GENERIC;
469
	}
470

471 472 473 474
	memcpy(ctx->buffer, str, len);
	*(ctx->bytes_read) += len;
	ctx->buffer += len;
	ctx->buf_size -= len;
475

476 477
	return 0;
}
478

479 480 481
static void clear_parser_state(http_subtransport *t)
{
	http_parser_init(&t->parser, HTTP_RESPONSE);
482
	gitno_buffer_setup_fromstream(t->io,
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
		&t->parse_buffer,
		t->parse_buffer_data,
		sizeof(t->parse_buffer_data));

	t->last_cb = NONE;
	t->parse_error = 0;
	t->parse_finished = 0;

	git_buf_free(&t->parse_header_name);
	git_buf_init(&t->parse_header_name, 0);

	git_buf_free(&t->parse_header_value);
	git_buf_init(&t->parse_header_value, 0);

	git__free(t->content_type);
	t->content_type = NULL;

500 501 502
	git__free(t->location);
	t->location = NULL;

503
	git_vector_free_deep(&t->www_authenticate);
504 505
}

506
static int write_chunk(git_stream *io, const char *buffer, size_t len)
507 508 509 510 511 512 513 514 515
{
	git_buf buf = GIT_BUF_INIT;

	/* Chunk header */
	git_buf_printf(&buf, "%X\r\n", (unsigned)len);

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

516
	if (git_stream_write(io, buf.ptr, buf.size, 0) < 0) {
517 518 519 520 521 522 523
		git_buf_free(&buf);
		return -1;
	}

	git_buf_free(&buf);

	/* Chunk body */
524
	if (len > 0 && git_stream_write(io, buffer, len, 0) < 0)
525 526 527
		return -1;

	/* Chunk footer */
528
	if (git_stream_write(io, "\r\n", 2, 0) < 0)
529 530 531 532 533
		return -1;

	return 0;
}

534 535
static int http_connect(http_subtransport *t)
{
536
	int error;
537
	char *proxy_url;
538 539 540

	if (t->connected &&
		http_should_keep_alive(&t->parser) &&
Edward Thomson committed
541
		t->parse_finished)
542 543
		return 0;

544 545 546 547 548
	if (t->io) {
		git_stream_close(t->io);
		git_stream_free(t->io);
		t->io = NULL;
	}
549

550 551 552
#ifdef GIT_CURL
	error = git_curl_stream_new(&t->io, t->connection_data.host, t->connection_data.port, t->connection_data.use_ssl);
#else
553
	if (t->connection_data.use_ssl) {
554
		error = git_tls_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
555 556 557
	} else {
		error = git_socket_stream_new(&t->io,  t->connection_data.host, t->connection_data.port);
	}
558
#endif
559

560 561
	if (error < 0)
		return error;
562

563
	GITERR_CHECK_VERSION(t->io, GIT_STREAM_VERSION, "git_stream");
564

565 566 567 568 569 570 571 572 573
	if (git_stream_supports_proxy(t->io) &&
	    !git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &proxy_url)) {
		error = git_stream_set_proxy(t->io, proxy_url);
		git__free(proxy_url);

		if (error < 0)
			return error;
	}

574
	error = git_stream_connect(t->io);
575

576
#if defined(GIT_OPENSSL) || defined(GIT_SECURE_TRANSPORT) || defined(GIT_CURL)
577 578
	if ((!error || error == GIT_ECERTIFICATE) && t->owner->certificate_check_cb != NULL &&
	    git_stream_is_encrypted(t->io)) {
579 580
		git_cert *cert;
		int is_valid;
581

582 583
		if ((error = git_stream_certificate(&cert, t->io)) < 0)
			return error;
584

585
		giterr_clear();
586
		is_valid = error != GIT_ECERTIFICATE;
587
		error = t->owner->certificate_check_cb(cert, is_valid, t->connection_data.host, t->owner->message_cb_payload);
588

589 590 591 592 593 594
		if (error < 0) {
			if (!giterr_last())
				giterr_set(GITERR_NET, "user cancelled certificate check");

			return error;
		}
595
	}
596
#endif
597 598
	if (error < 0)
		return error;
599 600 601 602 603

	t->connected = 1;
	return 0;
}

604 605 606 607 608
static int http_stream_read(
	git_smart_subtransport_stream *stream,
	char *buffer,
	size_t buf_size,
	size_t *bytes_read)
609
{
610
	http_stream *s = (http_stream *)stream;
611
	http_subtransport *t = OWNING_SUBTRANSPORT(s);
612
	parser_context ctx;
613
	size_t bytes_parsed;
614

615
replay:
616
	*bytes_read = 0;
617

618
	assert(t->connected);
619

620
	if (!s->sent_request) {
621 622
		git_buf request = GIT_BUF_INIT;

623 624
		clear_parser_state(t);

625
		if (gen_request(&request, s, 0) < 0)
626
			return -1;
627

628
		if (git_stream_write(t->io, request.ptr, request.size, 0) < 0) {
629 630 631 632 633
			git_buf_free(&request);
			return -1;
		}

		git_buf_free(&request);
634

635
		s->sent_request = 1;
636 637
	}

638 639 640
	if (!s->received_response) {
		if (s->chunked) {
			assert(s->verb == post_verb);
641

642 643
			/* Flush, if necessary */
			if (s->chunk_buffer_len > 0 &&
644
				write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
645
				return -1;
646

647
			s->chunk_buffer_len = 0;
648

649
			/* Write the final chunk. */
650
			if (git_stream_write(t->io, "0\r\n\r\n", 5, 0) < 0)
651 652 653 654
				return -1;
		}

		s->received_response = 1;
655 656
	}

657
	while (!*bytes_read && !t->parse_finished) {
658
		size_t data_offset;
659
		int error;
660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675

		/*
		 * Make the parse_buffer think it's as full of data as
		 * the buffer, so it won't try to recv more data than
		 * we can put into it.
		 *
		 * data_offset is the actual data offset from which we
		 * should tell the parser to start reading.
		 */
		if (buf_size >= t->parse_buffer.len) {
			t->parse_buffer.offset = 0;
		} else {
			t->parse_buffer.offset = t->parse_buffer.len - buf_size;
		}

		data_offset = t->parse_buffer.offset;
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695

		if (gitno_recv(&t->parse_buffer) < 0)
			return -1;

		/* This call to http_parser_execute will result in invocations of the
		 * on_* family of callbacks. The most interesting of these is
		 * on_body_fill_buffer, which is called when data is ready to be copied
		 * into the target buffer. We need to marshal the buffer, buf_size, and
		 * bytes_read parameters to this callback. */
		ctx.t = t;
		ctx.s = s;
		ctx.buffer = buffer;
		ctx.buf_size = buf_size;
		ctx.bytes_read = bytes_read;

		/* Set the context, call the parser, then unset the context. */
		t->parser.data = &ctx;

		bytes_parsed = http_parser_execute(&t->parser,
			&t->settings,
696 697
			t->parse_buffer.data + data_offset,
			t->parse_buffer.offset - data_offset);
698 699 700 701 702 703 704

		t->parser.data = NULL;

		/* If there was a handled authentication failure, then parse_error
		 * will have signaled us that we should replay the request. */
		if (PARSE_ERROR_REPLAY == t->parse_error) {
			s->sent_request = 0;
705

706 707
			if ((error = http_connect(t)) < 0)
				return error;
708

709 710 711 712 713 714
			goto replay;
		}

		if (t->parse_error < 0)
			return -1;

715
		if (bytes_parsed != t->parse_buffer.offset - data_offset) {
716 717 718 719 720 721
			giterr_set(GITERR_NET,
				"HTTP parser error: %s",
				http_errno_description((enum http_errno)t->parser.http_errno));
			return -1;
		}
	}
722

723
	return 0;
724 725
}

726
static int http_stream_write_chunked(
727 728 729
	git_smart_subtransport_stream *stream,
	const char *buffer,
	size_t len)
730
{
731
	http_stream *s = (http_stream *)stream;
732
	http_subtransport *t = OWNING_SUBTRANSPORT(s);
733

734
	assert(t->connected);
735

736
	/* Send the request, if necessary */
737
	if (!s->sent_request) {
738 739
		git_buf request = GIT_BUF_INIT;

740 741
		clear_parser_state(t);

742
		if (gen_request(&request, s, 0) < 0)
743
			return -1;
744

745
		if (git_stream_write(t->io, request.ptr, request.size, 0) < 0) {
746 747 748
			git_buf_free(&request);
			return -1;
		}
749

750
		git_buf_free(&request);
751

752 753
		s->sent_request = 1;
	}
754

755 756 757
	if (len > CHUNK_SIZE) {
		/* Flush, if necessary */
		if (s->chunk_buffer_len > 0) {
758
			if (write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
759 760 761 762 763 764
				return -1;

			s->chunk_buffer_len = 0;
		}

		/* Write chunk directly */
765
		if (write_chunk(t->io, buffer, len) < 0)
766 767 768 769
			return -1;
	}
	else {
		/* Append as much to the buffer as we can */
770
		int count = min(CHUNK_SIZE - s->chunk_buffer_len, len);
771 772

		if (!s->chunk_buffer)
773
			s->chunk_buffer = git__malloc(CHUNK_SIZE);
774 775 776 777 778 779 780 781

		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 (CHUNK_SIZE == s->chunk_buffer_len) {
782
			if (write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814
				return -1;

			s->chunk_buffer_len = 0;

			if (len > 0) {
				memcpy(s->chunk_buffer, buffer, len);
				s->chunk_buffer_len = len;
			}
		}
	}

	return 0;
}

static int http_stream_write_single(
	git_smart_subtransport_stream *stream,
	const char *buffer,
	size_t len)
{
	http_stream *s = (http_stream *)stream;
	http_subtransport *t = OWNING_SUBTRANSPORT(s);
	git_buf request = GIT_BUF_INIT;

	assert(t->connected);

	if (s->sent_request) {
		giterr_set(GITERR_NET, "Subtransport configured for only one write");
		return -1;
	}

	clear_parser_state(t);

815
	if (gen_request(&request, s, len) < 0)
816 817
		return -1;

818
	if (git_stream_write(t->io, request.ptr, request.size, 0) < 0)
819 820
		goto on_error;

821
	if (len && git_stream_write(t->io, buffer, len, 0) < 0)
822 823 824 825 826
		goto on_error;

	git_buf_free(&request);
	s->sent_request = 1;

827 828 829 830 831 832
	return 0;

on_error:
	git_buf_free(&request);
	return -1;
}
833

834 835 836
static void http_stream_free(git_smart_subtransport_stream *stream)
{
	http_stream *s = (http_stream *)stream;
837

838 839 840
	if (s->chunk_buffer)
		git__free(s->chunk_buffer);

841 842 843
	if (s->redirect_url)
		git__free(s->redirect_url);

844 845
	git__free(s);
}
846

847 848
static int http_stream_alloc(http_subtransport *t,
	git_smart_subtransport_stream **stream)
849
{
850
	http_stream *s;
851

852 853
	if (!stream)
		return -1;
854

855
	s = git__calloc(sizeof(http_stream), 1);
856
	GITERR_CHECK_ALLOC(s);
857

858 859
	s->parent.subtransport = &t->parent;
	s->parent.read = http_stream_read;
860
	s->parent.write = http_stream_write_single;
861
	s->parent.free = http_stream_free;
862

863 864
	*stream = (git_smart_subtransport_stream *)s;
	return 0;
865 866
}

867 868 869
static int http_uploadpack_ls(
	http_subtransport *t,
	git_smart_subtransport_stream **stream)
870
{
871
	http_stream *s;
872

873
	if (http_stream_alloc(t, stream) < 0)
874 875
		return -1;

876
	s = (http_stream *)*stream;
877

878 879 880
	s->service = upload_pack_service;
	s->service_url = upload_pack_ls_service_url;
	s->verb = get_verb;
881

882
	return 0;
883 884
}

885 886 887
static int http_uploadpack(
	http_subtransport *t,
	git_smart_subtransport_stream **stream)
888
{
889
	http_stream *s;
890

891
	if (http_stream_alloc(t, stream) < 0)
892
		return -1;
893

894
	s = (http_stream *)*stream;
895

896 897 898
	s->service = upload_pack_service;
	s->service_url = upload_pack_service_url;
	s->verb = post_verb;
899

900
	return 0;
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
static int http_receivepack_ls(
	http_subtransport *t,
	git_smart_subtransport_stream **stream)
{
	http_stream *s;

	if (http_stream_alloc(t, stream) < 0)
		return -1;

	s = (http_stream *)*stream;

	s->service = receive_pack_service;
	s->service_url = receive_pack_ls_service_url;
	s->verb = get_verb;

	return 0;
}

static int http_receivepack(
	http_subtransport *t,
	git_smart_subtransport_stream **stream)
{
	http_stream *s;

	if (http_stream_alloc(t, stream) < 0)
		return -1;

	s = (http_stream *)*stream;

	/* Use Transfer-Encoding: chunked for this request */
	s->chunked = 1;
	s->parent.write = http_stream_write_chunked;

	s->service = receive_pack_service;
	s->service_url = receive_pack_service_url;
	s->verb = post_verb;

	return 0;
}

943 944
static int http_action(
	git_smart_subtransport_stream **stream,
945
	git_smart_subtransport *subtransport,
946 947
	const char *url,
	git_smart_service_t action)
948
{
949
	http_subtransport *t = (http_subtransport *)subtransport;
950
	int ret;
951 952 953 954

	if (!stream)
		return -1;

955
	if ((!t->connection_data.host || !t->connection_data.port || !t->connection_data.path) &&
956
		 (ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0)
957
		return ret;
958

959 960
	if ((ret = http_connect(t)) < 0)
		return ret;
961

962 963 964
	switch (action) {
	case GIT_SERVICE_UPLOADPACK_LS:
		return http_uploadpack_ls(t, stream);
965

966 967
	case GIT_SERVICE_UPLOADPACK:
		return http_uploadpack(t, stream);
968

969 970
	case GIT_SERVICE_RECEIVEPACK_LS:
		return http_receivepack_ls(t, stream);
971

972 973
	case GIT_SERVICE_RECEIVEPACK:
		return http_receivepack(t, stream);
974
	}
975

976 977 978 979
	*stream = NULL;
	return -1;
}

980
static int http_close(git_smart_subtransport *subtransport)
981
{
982
	http_subtransport *t = (http_subtransport *) subtransport;
983 984
	git_http_auth_context *context;
	size_t i;
985

986 987
	clear_parser_state(t);

988 989 990 991
	if (t->io) {
		git_stream_close(t->io);
		git_stream_free(t->io);
		t->io = NULL;
992
	}
993

994 995 996 997 998
	if (t->cred) {
		t->cred->free(t->cred);
		t->cred = NULL;
	}

999 1000 1001 1002 1003
	if (t->url_cred) {
		t->url_cred->free(t->url_cred);
		t->url_cred = NULL;
	}

1004 1005 1006 1007
	git_vector_foreach(&t->auth_contexts, i, context) {
		if (context->free)
			context->free(context);
	}
1008

1009
	git_vector_clear(&t->auth_contexts);
1010

1011
	gitno_connection_data_free_ptrs(&t->connection_data);
1012
	memset(&t->connection_data, 0x0, sizeof(gitno_connection_data));
1013

1014 1015 1016 1017 1018 1019 1020 1021 1022
	return 0;
}

static void http_free(git_smart_subtransport *subtransport)
{
	http_subtransport *t = (http_subtransport *) subtransport;

	http_close(subtransport);

1023
	git_vector_free(&t->auth_contexts);
1024
	git__free(t);
1025 1026
}

1027
int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param)
1028
{
1029
	http_subtransport *t;
1030

1031 1032
	GIT_UNUSED(param);

1033 1034
	if (!out)
		return -1;
1035

1036
	t = git__calloc(sizeof(http_subtransport), 1);
1037
	GITERR_CHECK_ALLOC(t);
1038

1039
	t->owner = (transport_smart *)owner;
1040
	t->parent.action = http_action;
1041
	t->parent.close = http_close;
1042
	t->parent.free = http_free;
1043

1044 1045 1046 1047 1048
	t->settings.on_header_field = on_header_field;
	t->settings.on_header_value = on_header_value;
	t->settings.on_headers_complete = on_headers_complete;
	t->settings.on_body = on_body_fill_buffer;
	t->settings.on_message_complete = on_message_complete;
1049

1050
	*out = (git_smart_subtransport *) t;
1051 1052
	return 0;
}
1053 1054

#endif /* !GIT_WINHTTP */