http.c 10.7 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 8
 */

#include <stdlib.h>
9 10
#include "git2.h"
#include "http_parser.h"
11 12 13

#include "transport.h"
#include "common.h"
14 15 16
#include "netops.h"
#include "buffer.h"
#include "pkt.h"
17
#include "refs.h"
18
#include "pack.h"
19 20 21
#include "fetch.h"
#include "filebuf.h"
#include "repository.h"
22
#include "protocol.h"
23

24
enum last_cb {
25 26 27
	NONE,
	FIELD,
	VALUE
28
};
29

30 31
typedef struct {
	git_transport parent;
32
	http_parser_settings settings;
33 34
	git_buf buf;
	int error;
35 36
	int transfer_finished :1,
		ct_found :1,
37 38
		ct_finished :1,
		pack_ready :1;
39
	enum last_cb last_cb;
40
	http_parser parser;
41
	char *content_type;
42
	char *path;
43 44
	char *host;
	char *port;
45
	char *service;
46
	char buffer[4096];
47 48 49
#ifdef GIT_WIN32
	WSADATA wsd;
#endif
50 51
} transport_http;

52
static int gen_request(git_buf *buf, const char *path, const char *host, const char *op,
53
                       const char *service, ssize_t content_length, int ls)
54 55 56 57
{
	if (path == NULL) /* Is 'git fetch http://host.com/' valid? */
		path = "/";

58 59 60 61 62
	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);
	}
63 64
	git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");
	git_buf_printf(buf, "Host: %s\r\n", host);
65 66 67
	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);
68
		git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length);
69 70 71
	} else {
		git_buf_puts(buf, "Accept: */*\r\n");
	}
72
	git_buf_puts(buf, "\r\n");
73

74
	if (git_buf_oom(buf))
75
		return -1;
76 77

	return 0;
78 79
}

80
static int do_connect(transport_http *t, const char *host, const char *port)
81
{
82
	if (t->parent.connected && http_should_keep_alive(&t->parser))
83 84
		return 0;

85
	if (gitno_connect((git_transport *) t, host, port) < 0)
86
		return -1;
87

88
	t->parent.connected = 1;
89

90
	return 0;
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
}

/*
 * 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;

108 109 110
	if (t->last_cb == VALUE && t->ct_found) {
		t->ct_finished = 1;
		t->ct_found = 0;
111
		t->content_type = git__strdup(git_buf_cstr(buf));
112
		GITERR_CHECK_ALLOC(t->content_type);
113 114 115
		git_buf_clear(buf);
	}

116 117
	if (t->ct_found) {
		t->last_cb = FIELD;
118 119 120
		return 0;
	}

121
	if (t->last_cb != FIELD)
122 123 124
		git_buf_clear(buf);

	git_buf_put(buf, str, len);
125
	t->last_cb = FIELD;
126 127 128 129 130 131 132 133 134

	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;

135 136
	if (t->ct_finished) {
		t->last_cb = VALUE;
137 138 139
		return 0;
	}

140
	if (t->last_cb == VALUE)
141 142
		git_buf_put(buf, str, len);

143 144
	if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), typestr)) {
		t->ct_found = 1;
145 146 147 148
		git_buf_clear(buf);
		git_buf_put(buf, str, len);
	}

149
	t->last_cb = VALUE;
150 151 152 153 154 155 156 157 158

	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;

159 160 161 162 163 164
	/* The content-type is text/plain for 404, so don't validate */
	if (parser->status_code == 404) {
		git_buf_clear(buf);
		return 0;
	}

165 166 167
	if (t->content_type == NULL) {
		t->content_type = git__strdup(git_buf_cstr(buf));
		if (t->content_type == NULL)
168
			return t->error = -1;
169 170
	}

171 172 173
	git_buf_clear(buf);
	git_buf_printf(buf, "application/x-git-%s-advertisement", t->service);
	if (git_buf_oom(buf))
174
		return t->error = -1;
175 176

	if (strcmp(t->content_type, git_buf_cstr(buf)))
177
		return t->error = -1;
178 179 180 181 182 183 184 185 186 187

	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;
188 189 190 191 192 193

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

194 195 196
	return 0;
}

197
static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
198
{
199 200 201
	git_transport *transport = (git_transport *) parser->data;
	transport_http *t = (transport_http *) parser->data;
	gitno_buffer *buf = &transport->buffer;
202

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

208 209
	memcpy(buf->data + buf->offset, str, len);
	buf->offset += len;
210

211 212
	return 0;
}
213

214 215 216 217 218 219 220 221
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;
	gitno_buffer inner;
	char buffer[2048];
	int error;
222

223 224
	if (t->transfer_finished)
		return 0;
225

226
	gitno_buffer_setup(transport, &inner, buffer, sizeof(buffer));
227

228 229
	if ((error = gitno_recv(&inner)) < 0)
		return -1;
230

231 232 233 234
	old_len = buf->offset;
	http_parser_execute(&t->parser, &t->settings, inner.data, inner.offset);
	if (t->error < 0)
		return t->error;
235

236 237
	return buf->offset - old_len;
}
238

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
/* 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;

	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;

	gitno_buffer_setup_callback(transport, &transport->buffer, t->buffer, sizeof(t->buffer), http_recv_cb, t);
255 256 257 258 259
}

static int http_connect(git_transport *transport, int direction)
{
	transport_http *t = (transport_http *) transport;
260
	int ret;
261 262
	git_buf request = GIT_BUF_INIT;
	const char *service = "upload-pack";
263 264
	const char *url = t->parent.url, *prefix_http = "http://", *prefix_https = "https://";
	const char *default_port;
265
	git_pkt *pkt;
266

267 268 269 270
	if (direction == GIT_DIR_PUSH) {
		giterr_set(GITERR_NET, "Pushing over HTTP is not implemented");
		return -1;
	}
271 272 273

	t->parent.direction = direction;

274 275 276 277 278 279 280 281 282 283 284
	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, '/');
285

286
	if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0)
287 288 289
		goto cleanup;

	t->service = git__strdup(service);
290
	GITERR_CHECK_ALLOC(t->service);
291

292
	if ((ret = do_connect(t, t->host, t->port)) < 0)
293 294
		goto cleanup;

295
	/* Generate and send the HTTP request */
296
	if ((ret = gen_request(&request, t->path, t->host, "GET", service, 0, 1)) < 0) {
297
		giterr_set(GITERR_NET, "Failed to generate request");
298 299 300
		goto cleanup;
	}

301 302

	if (gitno_send(transport, request.ptr, request.size, 0) < 0)
303
		goto cleanup;
304

305 306 307
	setup_gitno_buffer(transport);
	if ((ret = git_protocol_store_refs(transport, 2)) < 0)
		goto cleanup;
308

309 310 311
	pkt = git_vector_get(&transport->refs, 0);
	if (pkt == NULL || pkt->type != GIT_PKT_COMMENT) {
		giterr_set(GITERR_NET, "Invalid HTTP response");
312
		return t->error = -1;
313 314 315 316 317 318 319
	} 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);
320 321
	}

322 323
	if (git_protocol_detect_caps(git_vector_get(&transport->refs, 0), &transport->caps) < 0)
		return t->error = -1;
324

325 326 327
cleanup:
	git_buf_free(&request);
	git_buf_clear(&t->buf);
328

329
	return ret;
330 331
}

332
static int http_negotiation_step(struct git_transport *transport, void *data, size_t len)
333 334
{
	transport_http *t = (transport_http *) transport;
335 336
	git_buf request = GIT_BUF_INIT;
	int ret;
337

338 339
	/* First, send the data as a HTTP POST request */
	if ((ret = do_connect(t, t->host, t->port)) < 0)
340 341
		return -1;

342
	if ((ret = gen_request(&request, t->path, t->host, "POST", "upload-pack", len, 0)) < 0)
343
		goto on_error;
344

345 346
	if ((ret = gitno_send(transport, request.ptr, request.size, 0)) < 0)
		goto on_error;
347

348 349
	if ((ret = gitno_send(transport, data, len, 0)) < 0)
		goto on_error;
350

351
	git_buf_free(&request);
352

353
	/* Then we need to set up the buffer to grab data from the HTTP response */
354
	setup_gitno_buffer(transport);
355

356 357 358
	return 0;

on_error:
359
	git_buf_free(&request);
360
	return -1;
361 362
}

363 364
static int http_close(git_transport *transport)
{
365 366
	if (gitno_ssl_teardown(transport) < 0)
		return -1;
367

368
	if (gitno_close(transport->socket) < 0) {
369 370 371
		giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno));
		return -1;
	}
372

373
	transport->connected = 0;
374

375
	return 0;
376 377 378
}


379 380 381
static void http_free(git_transport *transport)
{
	transport_http *t = (transport_http *) transport;
382
	git_vector *refs = &transport->refs;
383
	git_vector *common = &transport->common;
384 385
	unsigned int i;
	git_pkt *p;
386

387 388 389 390 391 392 393 394
#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

395 396 397 398
	git_vector_foreach(refs, i, p) {
		git_pkt_free(p);
	}
	git_vector_free(refs);
399 400 401 402
	git_vector_foreach(common, i, p) {
		git_pkt_free(p);
	}
	git_vector_free(common);
403
	git_buf_free(&t->buf);
404 405 406 407 408 409
	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);
410 411 412 413 414 415 416
}

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

	t = git__malloc(sizeof(transport_http));
417
	GITERR_CHECK_ALLOC(t);
418 419 420

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

421
	t->parent.connect = http_connect;
422
	t->parent.negotiation_step = http_negotiation_step;
423
	t->parent.close = http_close;
424
	t->parent.free = http_free;
425
	t->parent.rpc = 1;
426 427 428 429 430

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

432
#ifdef GIT_WIN32
433 434 435
	/* on win32, the WSA context needs to be initialized
	 * before any socket calls can be performed */
	if (WSAStartup(MAKEWORD(2,2), &t->wsd) != 0) {
436
		http_free((git_transport *) t);
437 438
		giterr_set(GITERR_OS, "Winsock init failed");
		return -1;
439 440 441
	}
#endif

442
	*out = (git_transport *) t;
443
	return 0;
444
}
445 446 447

int git_transport_https(git_transport **out)
{
448
#ifdef GIT_SSL
449 450 451 452
	transport_http *t;
	if (git_transport_http((git_transport **)&t) < 0)
		return -1;

453
	t->parent.use_ssl = 1;
454
	t->parent.check_cert = 1;
455 456 457 458 459 460 461 462 463 464
	*out = (git_transport *) t;

	return 0;
#else
	GIT_UNUSED(out);

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