http.c 14.5 KB
Newer Older
1
/*
schu committed
2
 * Copyright (C) 2009-2012 the libgit2 contributors
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
 */
#include <stdlib.h>
8 9
#include "git2.h"
#include "http_parser.h"
10 11 12

#include "transport.h"
#include "common.h"
13 14 15
#include "netops.h"
#include "buffer.h"
#include "pkt.h"
16
#include "refs.h"
17
#include "pack.h"
18 19 20
#include "fetch.h"
#include "filebuf.h"
#include "repository.h"
21
#include "protocol.h"
22 23 24 25 26 27 28
#if GIT_WINHTTP
# include <winhttp.h>
# pragma comment(lib, "winhttp.lib")
#endif

#define WIDEN2(s) L ## s
#define WIDEN(s) WIDEN2(s)
29

30
enum last_cb {
31 32 33
	NONE,
	FIELD,
	VALUE
34
};
35

36 37
typedef struct {
	git_transport parent;
38
	http_parser_settings settings;
39 40
	git_buf buf;
	int error;
41 42
	int transfer_finished :1,
		ct_found :1,
43 44
		ct_finished :1,
		pack_ready :1;
45
	enum last_cb last_cb;
46
	http_parser parser;
47
	char *content_type;
48
	char *path;
49 50
	char *host;
	char *port;
51
	char *service;
52
	char buffer[65536];
53 54 55
#ifdef GIT_WIN32
	WSADATA wsd;
#endif
56 57 58 59 60
#ifdef GIT_WINHTTP
	HINTERNET session;
	HINTERNET connection;
	HINTERNET request;
#endif
61 62
} transport_http;

63
static int gen_request(git_buf *buf, const char *path, const char *host, const char *op,
64
                       const char *service, ssize_t content_length, int ls)
65 66 67 68
{
	if (path == NULL) /* Is 'git fetch http://host.com/' valid? */
		path = "/";

69 70 71 72 73
	if (ls) {
		git_buf_printf(buf, "%s %s/info/refs?service=git-%s HTTP/1.1\r\n", op, path, service);
	} else {
		git_buf_printf(buf, "%s %s/git-%s HTTP/1.1\r\n", op, path, service);
	}
74 75
	git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");
	git_buf_printf(buf, "Host: %s\r\n", host);
76 77 78
	if (content_length > 0) {
		git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", service);
		git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", service);
79
		git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length);
80 81 82
	} else {
		git_buf_puts(buf, "Accept: */*\r\n");
	}
83
	git_buf_puts(buf, "\r\n");
84

85
	if (git_buf_oom(buf))
86
		return -1;
87 88

	return 0;
89 90
}

91
static int send_request(transport_http *t, const char *service, void *data, ssize_t content_length, int ls)
92
{
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
#ifndef GIT_WINHTTP
	git_buf request = GIT_BUF_INIT;
	const char *verb;

	verb = ls ? "GET" : "POST";
	/* Generate and send the HTTP request */
	if (gen_request(&request, t->path, t->host, verb, service, content_length, ls) < 0) {
		giterr_set(GITERR_NET, "Failed to generate request");
		return -1;
	}


	if (gitno_send((git_transport *) t, request.ptr, request.size, 0) < 0) {
		git_buf_free(&request);
		return -1;
	}

	if (content_length) {
		if (gitno_send((git_transport *) t, data, content_length, 0) < 0)
			return -1;
	}

	return 0;
#else
117 118
	wchar_t *verb;
	wchar_t url[GIT_WIN_PATH], ct[GIT_WIN_PATH];
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
	git_buf buf = GIT_BUF_INIT;
	BOOL ret;
	DWORD flags;
	void *buffer;
	wchar_t *types[] = {
		L"*/*",
		NULL,
	};

	verb = ls ? L"GET" : L"POST";
	buffer = data ? data : WINHTTP_NO_REQUEST_DATA;
	flags = t->parent.use_ssl ? WINHTTP_FLAG_SECURE : 0;

	if (ls)
		git_buf_printf(&buf, "%s/info/refs?service=git-%s", t->path, service);
	else
		git_buf_printf(&buf, "%s/git-%s", t->path, service);

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

140
	git__utf8_to_16(url, GIT_WIN_PATH, git_buf_cstr(&buf));
141 142 143 144 145 146 147 148 149 150 151

	t->request = WinHttpOpenRequest(t->connection, verb, url, NULL, WINHTTP_NO_REFERER, types, flags);
	if (t->request == NULL) {
		git_buf_free(&buf);
		giterr_set(GITERR_OS, "Failed to open request");
		return -1;
	}

	git_buf_clear(&buf);
	if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", service) < 0)
		goto on_error;
152 153

	git__utf8_to_16(ct, GIT_WIN_PATH, git_buf_cstr(&buf));
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193

	if (WinHttpAddRequestHeaders(t->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD) == FALSE) {
		giterr_set(GITERR_OS, "Failed to add a header to the request");
		goto on_error;
	}

	if (!t->parent.check_cert) {
		int flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA;
		if (WinHttpSetOption(t->request, WINHTTP_OPTION_SECURITY_FLAGS, &flags, sizeof(flags)) == FALSE) {
			giterr_set(GITERR_OS, "Failed to set options to ignore cert errors");
			goto on_error;
		}
	}

	if (WinHttpSendRequest(t->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
		data, content_length, content_length, 0) == FALSE) {
		giterr_set(GITERR_OS, "Failed to send request");
		goto on_error;
	}

	ret = WinHttpReceiveResponse(t->request, NULL);
	if (ret == FALSE) {
		giterr_set(GITERR_OS, "Failed to receive response");
		goto on_error;
	}

	return 0;

on_error:
	git_buf_free(&buf);
	if (t->request)
		WinHttpCloseHandle(t->request);
	t->request = NULL;
	return -1;
#endif
}

static int do_connect(transport_http *t)
{
#ifndef GIT_WINHTTP
194
	if (t->parent.connected && http_should_keep_alive(&t->parser))
195 196
		return 0;

197
	if (gitno_connect((git_transport *) t, t->host, t->port) < 0)
198
		return -1;
199

200
	t->parent.connected = 1;
201

202
	return 0;
203 204
#else
	wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")";
205
	wchar_t host[GIT_WIN_PATH];
206 207 208 209 210 211 212 213 214 215
	int32_t port;

	t->session = WinHttpOpen(ua, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
		WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);

	if (t->session == NULL) {
		giterr_set(GITERR_OS, "Failed to init WinHTTP");
		goto on_error;
	}

216
	git__utf8_to_16(host, GIT_WIN_PATH, t->host);
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236

	if (git__strtol32(&port, t->port, NULL, 10) < 0)
		goto on_error;

	t->connection = WinHttpConnect(t->session, host, port, 0);
	if (t->connection == NULL) {
		giterr_set(GITERR_OS, "Failed to connect to host");
		goto on_error;
	}

	t->parent.connected = 1;
	return 0;

on_error:
	if (t->session) {
		WinHttpCloseHandle(t->session);
		t->session = NULL;
	}
	return -1;
#endif
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
}

/*
 * The HTTP parser is streaming, so we need to wait until we're in the
 * field handler before we can be sure that we can store the previous
 * value. Right now, we only care about the
 * Content-Type. on_header_{field,value} should be kept generic enough
 * to work for any request.
 */

static const char *typestr = "Content-Type";

static int on_header_field(http_parser *parser, const char *str, size_t len)
{
	transport_http *t = (transport_http *) parser->data;
	git_buf *buf = &t->buf;

254 255 256
	if (t->last_cb == VALUE && t->ct_found) {
		t->ct_finished = 1;
		t->ct_found = 0;
257
		t->content_type = git__strdup(git_buf_cstr(buf));
258
		GITERR_CHECK_ALLOC(t->content_type);
259 260 261
		git_buf_clear(buf);
	}

262 263
	if (t->ct_found) {
		t->last_cb = FIELD;
264 265 266
		return 0;
	}

267
	if (t->last_cb != FIELD)
268 269 270
		git_buf_clear(buf);

	git_buf_put(buf, str, len);
271
	t->last_cb = FIELD;
272 273 274 275 276 277 278 279 280

	return git_buf_oom(buf);
}

static int on_header_value(http_parser *parser, const char *str, size_t len)
{
	transport_http *t = (transport_http *) parser->data;
	git_buf *buf = &t->buf;

281 282
	if (t->ct_finished) {
		t->last_cb = VALUE;
283 284 285
		return 0;
	}

286
	if (t->last_cb == VALUE)
287 288
		git_buf_put(buf, str, len);

289 290
	if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), typestr)) {
		t->ct_found = 1;
291 292 293 294
		git_buf_clear(buf);
		git_buf_put(buf, str, len);
	}

295
	t->last_cb = VALUE;
296 297 298 299 300 301 302 303 304

	return git_buf_oom(buf);
}

static int on_headers_complete(http_parser *parser)
{
	transport_http *t = (transport_http *) parser->data;
	git_buf *buf = &t->buf;

305 306 307 308 309 310
	/* The content-type is text/plain for 404, so don't validate */
	if (parser->status_code == 404) {
		git_buf_clear(buf);
		return 0;
	}

311 312 313
	if (t->content_type == NULL) {
		t->content_type = git__strdup(git_buf_cstr(buf));
		if (t->content_type == NULL)
314
			return t->error = -1;
315 316
	}

317 318 319
	git_buf_clear(buf);
	git_buf_printf(buf, "application/x-git-%s-advertisement", t->service);
	if (git_buf_oom(buf))
320
		return t->error = -1;
321 322

	if (strcmp(t->content_type, git_buf_cstr(buf)))
323
		return t->error = -1;
324 325 326 327 328 329 330 331 332 333

	git_buf_clear(buf);
	return 0;
}

static int on_message_complete(http_parser *parser)
{
	transport_http *t = (transport_http *) parser->data;

	t->transfer_finished = 1;
334 335 336 337 338 339

	if (parser->status_code == 404) {
		giterr_set(GITERR_NET, "Remote error: %s", git_buf_cstr(&t->buf));
		t->error = -1;
	}

340 341 342
	return 0;
}

343
static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
344
{
345 346 347
	git_transport *transport = (git_transport *) parser->data;
	transport_http *t = (transport_http *) parser->data;
	gitno_buffer *buf = &transport->buffer;
348

349 350 351 352
	if (buf->len - buf->offset < len) {
		giterr_set(GITERR_NET, "Can't fit data in the buffer");
		return t->error = -1;
	}
353

354 355
	memcpy(buf->data + buf->offset, str, len);
	buf->offset += len;
356

357 358
	return 0;
}
359

360 361 362 363 364 365
static int http_recv_cb(gitno_buffer *buf)
{
	git_transport *transport = (git_transport *) buf->cb_data;
	transport_http *t = (transport_http *) transport;
	size_t old_len;
	char buffer[2048];
366 367 368 369
#ifdef GIT_WINHTTP
	DWORD recvd;
#else
	gitno_buffer inner;
370
	int error;
371
#endif
372

373 374
	if (t->transfer_finished)
		return 0;
375

376
#ifndef GIT_WINHTTP
377
	gitno_buffer_setup(transport, &inner, buffer, sizeof(buffer));
378

379 380
	if ((error = gitno_recv(&inner)) < 0)
		return -1;
381

382 383 384 385
	old_len = buf->offset;
	http_parser_execute(&t->parser, &t->settings, inner.data, inner.offset);
	if (t->error < 0)
		return t->error;
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
#else
	old_len = buf->offset;
	if (WinHttpReadData(t->request, buffer, sizeof(buffer), &recvd) == FALSE) {
		giterr_set(GITERR_OS, "Failed to read data from the network");
		return t->error = -1;
	}

	if (buf->len - buf->offset < recvd) {
		giterr_set(GITERR_NET, "Can't fit data in the buffer");
		return t->error = -1;
	}

	memcpy(buf->data + buf->offset, buffer, recvd);
	buf->offset += recvd;
#endif
401

402
	return (int)(buf->offset - old_len);
403
}
404

405 406 407 408 409
/* Set up the gitno_buffer so calling gitno_recv() grabs data from the HTTP response */
static void setup_gitno_buffer(git_transport *transport)
{
	transport_http *t = (transport_http *) transport;

410 411
	/* WinHTTP takes care of this for us */
#ifndef GIT_WINHTTP
412 413 414 415 416 417 418 419 420
	http_parser_init(&t->parser, HTTP_RESPONSE);
	t->parser.data = t;
	t->transfer_finished = 0;
	memset(&t->settings, 0x0, sizeof(http_parser_settings));
	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;
421
#endif
422 423

	gitno_buffer_setup_callback(transport, &transport->buffer, t->buffer, sizeof(t->buffer), http_recv_cb, t);
424 425 426 427 428
}

static int http_connect(git_transport *transport, int direction)
{
	transport_http *t = (transport_http *) transport;
429
	int ret;
430 431
	git_buf request = GIT_BUF_INIT;
	const char *service = "upload-pack";
432 433
	const char *url = t->parent.url, *prefix_http = "http://", *prefix_https = "https://";
	const char *default_port;
434
	git_pkt *pkt;
435

436 437 438 439
	if (direction == GIT_DIR_PUSH) {
		giterr_set(GITERR_NET, "Pushing over HTTP is not implemented");
		return -1;
	}
440 441 442

	t->parent.direction = direction;

443 444 445 446 447 448 449 450 451 452 453
	if (!git__prefixcmp(url, prefix_http)) {
		url = t->parent.url + strlen(prefix_http);
		default_port = "80";
	}

	if (!git__prefixcmp(url, prefix_https)) {
		url += strlen(prefix_https);
		default_port = "443";
	}

	t->path = strchr(url, '/');
454

455
	if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0)
456 457 458
		goto cleanup;

	t->service = git__strdup(service);
459
	GITERR_CHECK_ALLOC(t->service);
460

461
	if ((ret = do_connect(t)) < 0)
462 463
		goto cleanup;

464
	if ((ret = send_request(t, "upload-pack", NULL, 0, 1)) < 0)
465
		goto cleanup;
466

467 468 469
	setup_gitno_buffer(transport);
	if ((ret = git_protocol_store_refs(transport, 2)) < 0)
		goto cleanup;
470

471 472 473
	pkt = git_vector_get(&transport->refs, 0);
	if (pkt == NULL || pkt->type != GIT_PKT_COMMENT) {
		giterr_set(GITERR_NET, "Invalid HTTP response");
474
		return t->error = -1;
475 476 477 478 479 480 481
	} else {
		/* Remove the comment and flush pkts */
		git_vector_remove(&transport->refs, 0);
		git__free(pkt);
		pkt = git_vector_get(&transport->refs, 0);
		git_vector_remove(&transport->refs, 0);
		git__free(pkt);
482 483
	}

484 485
	if (git_protocol_detect_caps(git_vector_get(&transport->refs, 0), &transport->caps) < 0)
		return t->error = -1;
486

487 488 489
cleanup:
	git_buf_free(&request);
	git_buf_clear(&t->buf);
490

491
	return ret;
492 493
}

494
static int http_negotiation_step(struct git_transport *transport, void *data, size_t len)
495 496
{
	transport_http *t = (transport_http *) transport;
497
	int ret;
498

499
	/* First, send the data as a HTTP POST request */
500
	if ((ret = do_connect(t)) < 0)
501 502
		return -1;

503 504
	if (send_request(t, "upload-pack", data, len, 0) < 0)
		return -1;
505

506
	/* Then we need to set up the buffer to grab data from the HTTP response */
507
	setup_gitno_buffer(transport);
508

509
	return 0;
510 511
}

512 513
static int http_close(git_transport *transport)
{
514
#ifndef GIT_WINHTTP
515 516
	if (gitno_ssl_teardown(transport) < 0)
		return -1;
517

518
	if (gitno_close(transport->socket) < 0) {
519 520 521
		giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno));
		return -1;
	}
522 523 524 525 526 527 528 529 530 531
#else
	transport_http *t = (transport_http *) transport;

	if (t->request)
		WinHttpCloseHandle(t->request);
	if (t->connection)
		WinHttpCloseHandle(t->connection);
	if (t->session)
		WinHttpCloseHandle(t->session);
#endif
532

533
	transport->connected = 0;
534

535
	return 0;
536 537 538
}


539 540 541
static void http_free(git_transport *transport)
{
	transport_http *t = (transport_http *) transport;
542
	git_vector *refs = &transport->refs;
543
	git_vector *common = &transport->common;
544 545
	unsigned int i;
	git_pkt *p;
546

547 548 549 550 551 552 553 554
#ifdef GIT_WIN32
	/* cleanup the WSA context. note that this context
	 * can be initialized more than once with WSAStartup(),
	 * and needs to be cleaned one time for each init call
	 */
	WSACleanup();
#endif

555 556 557 558
	git_vector_foreach(refs, i, p) {
		git_pkt_free(p);
	}
	git_vector_free(refs);
559 560 561 562
	git_vector_foreach(common, i, p) {
		git_pkt_free(p);
	}
	git_vector_free(common);
563
	git_buf_free(&t->buf);
564 565 566 567 568 569
	git__free(t->content_type);
	git__free(t->host);
	git__free(t->port);
	git__free(t->service);
	git__free(t->parent.url);
	git__free(t);
570 571 572 573 574 575 576
}

int git_transport_http(git_transport **out)
{
	transport_http *t;

	t = git__malloc(sizeof(transport_http));
577
	GITERR_CHECK_ALLOC(t);
578 579 580

	memset(t, 0x0, sizeof(transport_http));

581
	t->parent.connect = http_connect;
582
	t->parent.negotiation_step = http_negotiation_step;
583
	t->parent.close = http_close;
584
	t->parent.free = http_free;
585
	t->parent.rpc = 1;
586 587 588 589 590

	if (git_vector_init(&t->parent.refs, 16, NULL) < 0) {
		git__free(t);
		return -1;
	}
591

592
#ifdef GIT_WIN32
593 594 595
	/* on win32, the WSA context needs to be initialized
	 * before any socket calls can be performed */
	if (WSAStartup(MAKEWORD(2,2), &t->wsd) != 0) {
596
		http_free((git_transport *) t);
597 598
		giterr_set(GITERR_OS, "Winsock init failed");
		return -1;
599 600 601
	}
#endif

602
	*out = (git_transport *) t;
603
	return 0;
604
}
605 606 607

int git_transport_https(git_transport **out)
{
608
#if defined(GIT_SSL) || defined(GIT_WINHTTP)
609 610 611 612
	transport_http *t;
	if (git_transport_http((git_transport **)&t) < 0)
		return -1;

613
	t->parent.use_ssl = 1;
614
	t->parent.check_cert = 1;
615 616 617 618 619 620 621 622 623 624
	*out = (git_transport *) t;

	return 0;
#else
	GIT_UNUSED(out);

	giterr_set(GITERR_NET, "HTTPS support not available");
	return -1;
#endif
}