http.c 22.5 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 "smart.h"
14 15
#include "auth.h"
#include "auth_negotiate.h"
16

17 18 19 20
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 },
};
21

22 23 24
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";
25 26 27
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";
28 29 30 31
static const char *get_verb = "GET";
static const char *post_verb = "POST";

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

33 34 35
#define PARSE_ERROR_GENERIC	-1
#define PARSE_ERROR_REPLAY	-2

36 37
#define CHUNK_SIZE	4096

38
enum last_cb {
39 40 41
	NONE,
	FIELD,
	VALUE
42
};
43

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

typedef struct {
	git_smart_subtransport parent;
60
	transport_smart *owner;
61
	gitno_socket socket;
62 63
	gitno_connection_data connection_data;
	bool connected;
64 65

	/* Parser structures */
66
	http_parser parser;
67 68
	http_parser_settings settings;
	gitno_buffer parse_buffer;
69 70
	git_buf parse_header_name;
	git_buf parse_header_value;
71
	char parse_buffer_data[2048];
72
	char *content_type;
73
	char *location;
74
	git_vector www_authenticate;
75
	enum last_cb last_cb;
76 77
	int parse_error;
	unsigned parse_finished : 1;
78

79 80 81 82
	/* Authentication */
	git_cred *cred;
	git_cred *url_cred;
	git_vector auth_contexts;
83 84 85 86 87 88 89 90 91 92 93
} 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;
94

95
static bool credtype_match(git_http_auth_scheme *scheme, void *data)
96
{
97
	unsigned int credtype = *(unsigned int *)data;
98

99
	return !!(scheme->credtypes & credtype);
100 101
}

102
static bool challenge_match(git_http_auth_scheme *scheme, void *data)
103
{
104 105 106 107 108 109 110
	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] == ' '));
111 112
}

113 114 115 116 117
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)
118
{
119 120
	git_http_auth_scheme *scheme = NULL;
	git_http_auth_context *context = NULL, *c;
121 122
	size_t i;

123
	*out = NULL;
124

125 126 127 128
	for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) {
		if (scheme_match(&auth_schemes[i], data)) {
			scheme = &auth_schemes[i];
			break;
129 130 131
		}
	}

132 133
	if (!scheme)
		return 0;
134

135 136 137 138 139 140
	/* 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;
		}
141 142
	}

143 144 145 146 147 148 149 150
	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;
	}
151

152
	*out = context;
153 154 155 156

	return 0;
}

157
static int apply_credentials(git_buf *buf, http_subtransport *t)
158
{
159 160 161 162 163 164 165 166 167
	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;
168

169
		cred = t->url_cred;
170 171
	}

172 173
	if (!cred)
		return 0;
174

175 176
	/* Get or create a context for the best scheme for this cred type */
	if (auth_context_match(&context, t, credtype_match, &cred->credtype) < 0)
177 178
		return -1;

179
	return context->next_token(buf, context, cred);
180 181
}

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

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

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

	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
204
		git_buf_puts(buf, "Accept: */*\r\n");
205 206

	/* Apply credentials to the request */
207 208
	if (apply_credentials(buf, t) < 0)
		return -1;
209

210
	git_buf_puts(buf, "\r\n");
211

212
	if (git_buf_oom(buf))
213
		return -1;
214 215

	return 0;
216 217
}

218
static int parse_authenticate_response(
219
	git_vector *www_authenticate,
220 221
	http_subtransport *t,
	int *allowed_types)
222
{
223 224 225
	git_http_auth_context *context;
	char *challenge;
	size_t i;
226

227 228 229 230 231
	git_vector_foreach(www_authenticate, i, challenge) {
		if (auth_context_match(&context, t, challenge_match, challenge) < 0)
			return -1;
		else if (!context)
			continue;
232

233 234 235
		if (context->set_challenge &&
			context->set_challenge(context, challenge) < 0)
			return -1;
236

237
		*allowed_types |= context->credtypes;
238 239 240 241 242 243 244 245 246 247
	}

	return 0;
}

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

248 249 250 251 252
	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);
		}
253 254
	}
	else if (!strcmp("WWW-Authenticate", git_buf_cstr(name))) {
255
		char *dup = git__strdup(git_buf_cstr(value));
256
		GITERR_CHECK_ALLOC(dup);
257

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

	return 0;
}

270 271
static int on_header_field(http_parser *parser, const char *str, size_t len)
{
272 273
	parser_context *ctx = (parser_context *) parser->data;
	http_subtransport *t = ctx->t;
274

275 276 277 278 279
	/* 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;
280

281 282
	if (NONE == t->last_cb || VALUE == t->last_cb)
		git_buf_clear(&t->parse_header_name);
283

284 285
	if (git_buf_put(&t->parse_header_name, str, len) < 0)
		return t->parse_error = PARSE_ERROR_GENERIC;
286

287
	t->last_cb = FIELD;
288
	return 0;
289 290 291 292
}

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

296
	assert(NONE != t->last_cb);
297

298 299
	if (FIELD == t->last_cb)
		git_buf_clear(&t->parse_header_value);
300

301 302
	if (git_buf_put(&t->parse_header_value, str, len) < 0)
		return t->parse_error = PARSE_ERROR_GENERIC;
303

304
	t->last_cb = VALUE;
305
	return 0;
306 307 308 309
}

static int on_headers_complete(http_parser *parser)
{
310 311
	parser_context *ctx = (parser_context *) parser->data;
	http_subtransport *t = ctx->t;
312 313
	http_stream *s = ctx->s;
	git_buf buf = GIT_BUF_INIT;
314
	int error = 0, no_callback = 0, allowed_auth_types = 0;
315

316 317 318 319 320 321
	/* 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;

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

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

				error = t->owner->cred_acquire_cb(&t->cred,
								  t->owner->url,
								  t->connection_data.user,
341
								  allowed_auth_types,
342 343 344 345 346 347 348 349 350 351
								  t->owner->cred_acquire_payload);

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

					/* Successfully acquired a credential. */
Edward Thomson committed
352 353
					t->parse_error = PARSE_ERROR_REPLAY;
					return 0;
354 355 356
				}
			}
		}
357

358 359
		if (no_callback) {
			giterr_set(GITERR_NET, "authentication required but no callback set");
360 361
			return t->parse_error = PARSE_ERROR_GENERIC;
		}
362 363
	}

364
	/* Check for a redirect.
365
	 * Right now we only permit a redirect to the same hostname. */
366
	if ((parser->status_code == 301 ||
367 368 369 370
	     parser->status_code == 302 ||
	     (parser->status_code == 303 && get_verb == s->verb) ||
	     parser->status_code == 307) &&
	    t->location) {
371 372 373 374 375 376

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

377
		if (gitno_connection_data_from_url(&t->connection_data, t->location, s->service_url) < 0)
378
			return t->parse_error = PARSE_ERROR_GENERIC;
379 380 381 382 383 384 385 386 387 388 389 390

		/* 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
391 392
		t->parse_error = PARSE_ERROR_REPLAY;
		return 0;
393 394
	}

395 396 397 398 399 400
	/* 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;
401 402
	}

403 404 405 406 407
	/* 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;
	}
408

409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
	/* 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;
428
	}
429

430 431
	git_buf_free(&buf);

432 433 434 435 436
	return 0;
}

static int on_message_complete(http_parser *parser)
{
437 438
	parser_context *ctx = (parser_context *) parser->data;
	http_subtransport *t = ctx->t;
439

440
	t->parse_finished = 1;
441

442 443 444
	return 0;
}

445
static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
446
{
447 448
	parser_context *ctx = (parser_context *) parser->data;
	http_subtransport *t = ctx->t;
449

Edward Thomson committed
450 451 452 453 454 455 456
	/* 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;

457
	if (ctx->buf_size < len) {
458
		giterr_set(GITERR_NET, "Can't fit data in the buffer");
459
		return t->parse_error = PARSE_ERROR_GENERIC;
460
	}
461

462 463 464 465
	memcpy(ctx->buffer, str, len);
	*(ctx->bytes_read) += len;
	ctx->buffer += len;
	ctx->buf_size -= len;
466

467 468
	return 0;
}
469

470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
static void clear_parser_state(http_subtransport *t)
{
	http_parser_init(&t->parser, HTTP_RESPONSE);
	gitno_buffer_setup(&t->socket,
		&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;

491 492 493
	git__free(t->location);
	t->location = NULL;

494
	git_vector_free_deep(&t->www_authenticate);
495 496
}

497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
static int write_chunk(gitno_socket *socket, const char *buffer, size_t len)
{
	git_buf buf = GIT_BUF_INIT;

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

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

	if (gitno_send(socket, buf.ptr, buf.size, 0) < 0) {
		git_buf_free(&buf);
		return -1;
	}

	git_buf_free(&buf);

	/* Chunk body */
	if (len > 0 && gitno_send(socket, buffer, len, 0) < 0)
		return -1;

	/* Chunk footer */
	if (gitno_send(socket, "\r\n", 2, 0) < 0)
		return -1;

	return 0;
}

525 526 527 528 529 530
static int http_connect(http_subtransport *t)
{
	int flags = 0;

	if (t->connected &&
		http_should_keep_alive(&t->parser) &&
Edward Thomson committed
531
		t->parse_finished)
532 533 534 535 536
		return 0;

	if (t->socket.socket)
		gitno_close(&t->socket);

537
	if (t->connection_data.use_ssl) {
538 539 540 541 542 543 544 545 546 547 548
		int tflags;

		if (t->owner->parent.read_flags(&t->owner->parent, &tflags) < 0)
			return -1;

		flags |= GITNO_CONNECT_SSL;

		if (GIT_TRANSPORTFLAGS_NO_CHECK_CERT & tflags)
			flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT;
	}

549
	if (gitno_connect(&t->socket, t->connection_data.host, t->connection_data.port, flags) < 0)
550 551 552 553 554 555
		return -1;

	t->connected = 1;
	return 0;
}

556 557 558 559 560
static int http_stream_read(
	git_smart_subtransport_stream *stream,
	char *buffer,
	size_t buf_size,
	size_t *bytes_read)
561
{
562
	http_stream *s = (http_stream *)stream;
563
	http_subtransport *t = OWNING_SUBTRANSPORT(s);
564
	parser_context ctx;
565
	size_t bytes_parsed;
566

567
replay:
568
	*bytes_read = 0;
569

570
	assert(t->connected);
571

572
	if (!s->sent_request) {
573 574
		git_buf request = GIT_BUF_INIT;

575 576
		clear_parser_state(t);

577
		if (gen_request(&request, s, 0) < 0)
578
			return -1;
579

580 581 582 583 584 585
		if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {
			git_buf_free(&request);
			return -1;
		}

		git_buf_free(&request);
586

587
		s->sent_request = 1;
588 589
	}

590 591 592
	if (!s->received_response) {
		if (s->chunked) {
			assert(s->verb == post_verb);
593

594 595 596 597
			/* Flush, if necessary */
			if (s->chunk_buffer_len > 0 &&
				write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0)
				return -1;
598

599
			s->chunk_buffer_len = 0;
600

601 602 603 604 605 606
			/* Write the final chunk. */
			if (gitno_send(&t->socket, "0\r\n\r\n", 5, 0) < 0)
				return -1;
		}

		s->received_response = 1;
607 608
	}

609
	while (!*bytes_read && !t->parse_finished) {
610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626
		size_t data_offset;

		/*
		 * 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;
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646

		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,
647 648
			t->parse_buffer.data + data_offset,
			t->parse_buffer.offset - data_offset);
649 650 651 652 653 654 655

		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;
656 657 658 659

			if (http_connect(t) < 0)
				return -1;

660 661 662 663 664 665
			goto replay;
		}

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

666
		if (bytes_parsed != t->parse_buffer.offset - data_offset) {
667 668 669 670 671 672
			giterr_set(GITERR_NET,
				"HTTP parser error: %s",
				http_errno_description((enum http_errno)t->parser.http_errno));
			return -1;
		}
	}
673

674
	return 0;
675 676
}

677
static int http_stream_write_chunked(
678 679 680
	git_smart_subtransport_stream *stream,
	const char *buffer,
	size_t len)
681
{
682
	http_stream *s = (http_stream *)stream;
683
	http_subtransport *t = OWNING_SUBTRANSPORT(s);
684

685
	assert(t->connected);
686

687
	/* Send the request, if necessary */
688
	if (!s->sent_request) {
689 690
		git_buf request = GIT_BUF_INIT;

691 692
		clear_parser_state(t);

693
		if (gen_request(&request, s, 0) < 0)
694
			return -1;
695

696 697 698 699
		if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {
			git_buf_free(&request);
			return -1;
		}
700

701
		git_buf_free(&request);
702

703 704
		s->sent_request = 1;
	}
705

706 707 708 709 710 711 712 713 714 715 716 717 718 719 720
	if (len > CHUNK_SIZE) {
		/* Flush, if necessary */
		if (s->chunk_buffer_len > 0) {
			if (write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0)
				return -1;

			s->chunk_buffer_len = 0;
		}

		/* Write chunk directly */
		if (write_chunk(&t->socket, buffer, len) < 0)
			return -1;
	}
	else {
		/* Append as much to the buffer as we can */
721
		int count = min(CHUNK_SIZE - s->chunk_buffer_len, len);
722 723

		if (!s->chunk_buffer)
724
			s->chunk_buffer = git__malloc(CHUNK_SIZE);
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765

		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) {
			if (write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0)
				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);

766
	if (gen_request(&request, s, len) < 0)
767 768 769 770 771 772 773 774 775 776 777
		return -1;

	if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0)
		goto on_error;

	if (len && gitno_send(&t->socket, buffer, len, 0) < 0)
		goto on_error;

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

778 779 780 781 782 783
	return 0;

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

785 786 787
static void http_stream_free(git_smart_subtransport_stream *stream)
{
	http_stream *s = (http_stream *)stream;
788

789 790 791
	if (s->chunk_buffer)
		git__free(s->chunk_buffer);

792 793 794
	if (s->redirect_url)
		git__free(s->redirect_url);

795 796
	git__free(s);
}
797

798 799
static int http_stream_alloc(http_subtransport *t,
	git_smart_subtransport_stream **stream)
800
{
801
	http_stream *s;
802

803 804
	if (!stream)
		return -1;
805

806
	s = git__calloc(sizeof(http_stream), 1);
807
	GITERR_CHECK_ALLOC(s);
808

809 810
	s->parent.subtransport = &t->parent;
	s->parent.read = http_stream_read;
811
	s->parent.write = http_stream_write_single;
812
	s->parent.free = http_stream_free;
813

814 815
	*stream = (git_smart_subtransport_stream *)s;
	return 0;
816 817
}

818 819 820
static int http_uploadpack_ls(
	http_subtransport *t,
	git_smart_subtransport_stream **stream)
821
{
822
	http_stream *s;
823

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

827
	s = (http_stream *)*stream;
828

829 830 831
	s->service = upload_pack_service;
	s->service_url = upload_pack_ls_service_url;
	s->verb = get_verb;
832

833
	return 0;
834 835
}

836 837 838
static int http_uploadpack(
	http_subtransport *t,
	git_smart_subtransport_stream **stream)
839
{
840
	http_stream *s;
841

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

845
	s = (http_stream *)*stream;
846

847 848 849
	s->service = upload_pack_service;
	s->service_url = upload_pack_service_url;
	s->verb = post_verb;
850

851
	return 0;
852 853
}

854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893
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;
}

894 895
static int http_action(
	git_smart_subtransport_stream **stream,
896
	git_smart_subtransport *subtransport,
897 898
	const char *url,
	git_smart_service_t action)
899
{
900
	http_subtransport *t = (http_subtransport *)subtransport;
901
	int ret;
902 903 904 905

	if (!stream)
		return -1;

906
	if ((!t->connection_data.host || !t->connection_data.port || !t->connection_data.path) &&
907
		 (ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0)
908
		return ret;
909

910 911
	if (http_connect(t) < 0)
		return -1;
912

913 914 915
	switch (action) {
	case GIT_SERVICE_UPLOADPACK_LS:
		return http_uploadpack_ls(t, stream);
916

917 918
	case GIT_SERVICE_UPLOADPACK:
		return http_uploadpack(t, stream);
919

920 921
	case GIT_SERVICE_RECEIVEPACK_LS:
		return http_receivepack_ls(t, stream);
922

923 924
	case GIT_SERVICE_RECEIVEPACK:
		return http_receivepack(t, stream);
925
	}
926

927 928 929 930
	*stream = NULL;
	return -1;
}

931
static int http_close(git_smart_subtransport *subtransport)
932
{
933
	http_subtransport *t = (http_subtransport *) subtransport;
934 935
	git_http_auth_context *context;
	size_t i;
936

937 938
	clear_parser_state(t);

939
	if (t->socket.socket) {
940
		gitno_close(&t->socket);
941 942
		memset(&t->socket, 0x0, sizeof(gitno_socket));
	}
943

944 945 946 947 948
	if (t->cred) {
		t->cred->free(t->cred);
		t->cred = NULL;
	}

949 950 951 952 953
	if (t->url_cred) {
		t->url_cred->free(t->url_cred);
		t->url_cred = NULL;
	}

954 955 956 957
	git_vector_foreach(&t->auth_contexts, i, context) {
		if (context->free)
			context->free(context);
	}
958

959
	git_vector_clear(&t->auth_contexts);
960

961
	gitno_connection_data_free_ptrs(&t->connection_data);
962

963 964 965 966 967 968 969 970 971
	return 0;
}

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

	http_close(subtransport);

972
	git_vector_free(&t->auth_contexts);
973
	git__free(t);
974 975
}

Vicent Marti committed
976
int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner)
977
{
978
	http_subtransport *t;
979

980 981
	if (!out)
		return -1;
982

983
	t = git__calloc(sizeof(http_subtransport), 1);
984
	GITERR_CHECK_ALLOC(t);
985

986
	t->owner = (transport_smart *)owner;
987
	t->parent.action = http_action;
988
	t->parent.close = http_close;
989
	t->parent.free = http_free;
990

991 992 993 994 995
	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;
996

997
	*out = (git_smart_subtransport *) t;
998 999
	return 0;
}
1000 1001

#endif /* !GIT_WINHTTP */