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

8
#include "smart.h"
9 10

#include "git2.h"
11
#include "refs.h"
12
#include "refspec.h"
13
#include "proxy.h"
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

static int git_smart__recv_cb(gitno_buffer *buf)
{
	transport_smart *t = (transport_smart *) buf->cb_data;
	size_t old_len, bytes_read;
	int error;

	assert(t->current_stream);

	old_len = buf->offset;

	if ((error = t->current_stream->read(t->current_stream, buf->data + buf->offset, buf->len - buf->offset, &bytes_read)) < 0)
		return error;

	buf->offset += bytes_read;

30 31 32
	if (t->packetsize_cb && !t->cancelled.val) {
		error = t->packetsize_cb(bytes_read, t->packetsize_payload);
		if (error) {
33
			git_atomic_set(&t->cancelled, 1);
34
			return GIT_EUSER;
35
		}
36
	}
37 38 39 40

	return (int)(buf->offset - old_len);
}

41
GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport)
42 43 44 45 46
{
	if (t->current_stream) {
		t->current_stream->free(t->current_stream);
		t->current_stream = NULL;
	}
47

48
	if (close_subtransport) {
49 50 51
		git__free(t->url);
		t->url = NULL;

52 53 54
		if (t->wrapped->close(t->wrapped) < 0)
			return -1;
	}
55 56

	return 0;
57 58 59 60 61 62
}

static int git_smart__set_callbacks(
	git_transport *transport,
	git_transport_message_cb progress_cb,
	git_transport_message_cb error_cb,
63
	git_transport_certificate_check_cb certificate_check_cb,
64 65
	void *message_cb_payload)
{
66
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
67 68 69

	t->progress_cb = progress_cb;
	t->error_cb = error_cb;
70
	t->certificate_check_cb = certificate_check_cb;
71 72 73 74 75
	t->message_cb_payload = message_cb_payload;

	return 0;
}

76
static size_t http_header_name_length(const char *http_header)
77 78 79 80 81 82
{
	const char *colon = strchr(http_header, ':');
	if (!colon)
		return 0;
	return colon - http_header;
}
83

Matt Burke committed
84
static bool is_malformed_http_header(const char *http_header)
85 86
{
	const char *c;
87
	size_t name_len;
88

89
	/* Disallow \r and \n */
90 91 92 93 94 95
	c = strchr(http_header, '\r');
	if (c)
		return true;
	c = strchr(http_header, '\n');
	if (c)
		return true;
96

97
	/* Require a header name followed by : */
98
	name_len = http_header_name_length(http_header);
99
	if (name_len < 1)
100
		return true;
101

102
	return false;
103 104
}

105 106 107 108 109 110 111 112
static char *forbidden_custom_headers[] = {
	"User-Agent",
	"Host",
	"Accept",
	"Content-Type",
	"Transfer-Encoding",
	"Content-Length",
};
113

Matt Burke committed
114
static bool is_forbidden_custom_header(const char *custom_header)
115 116
{
	unsigned long i;
117
	size_t name_len = http_header_name_length(custom_header);
118

119
	/* Disallow headers that we set */
120 121 122
	for (i = 0; i < ARRAY_SIZE(forbidden_custom_headers); i++)
		if (strncmp(forbidden_custom_headers[i], custom_header, name_len) == 0)
			return true;
123

124
	return false;
125 126
}

127 128 129 130
static int git_smart__set_custom_headers(
	git_transport *transport,
	const git_strarray *custom_headers)
{
131
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
132
	size_t i;
133

134 135 136 137 138 139
	if (t->custom_headers.count)
		git_strarray_free(&t->custom_headers);

	if (!custom_headers)
		return 0;

140 141
	for (i = 0; i < custom_headers->count; i++) {
		if (is_malformed_http_header(custom_headers->strings[i])) {
142
			git_error_set(GIT_ERROR_INVALID, "custom HTTP header '%s' is malformed", custom_headers->strings[i]);
143 144 145
			return -1;
		}
		if (is_forbidden_custom_header(custom_headers->strings[i])) {
146
			git_error_set(GIT_ERROR_INVALID, "custom HTTP header '%s' is already set by libgit2", custom_headers->strings[i]);
147 148
			return -1;
		}
149
	}
150

151
	return git_strarray_copy(&t->custom_headers, custom_headers);
152 153
}

154
int git_smart__update_heads(transport_smart *t, git_vector *symrefs)
155 156 157 158 159 160 161 162 163 164
{
	size_t i;
	git_pkt *pkt;

	git_vector_clear(&t->heads);
	git_vector_foreach(&t->refs, i, pkt) {
		git_pkt_ref *ref = (git_pkt_ref *) pkt;
		if (pkt->type != GIT_PKT_REF)
			continue;

165 166 167 168
		if (symrefs) {
			git_refspec *spec;
			git_buf buf = GIT_BUF_INIT;
			size_t j;
169
			int error = 0;
170 171 172 173

			git_vector_foreach(symrefs, j, spec) {
				git_buf_clear(&buf);
				if (git_refspec_src_matches(spec, ref->head.name) &&
174 175
				    !(error = git_refspec_transform(&buf, spec, ref->head.name))) {
					git__free(ref->head.symref_target);
176
					ref->head.symref_target = git_buf_detach(&buf);
177
				}
178 179
			}

180
			git_buf_dispose(&buf);
181 182 183 184 185

			if (error < 0)
				return error;
		}

186 187 188 189 190 191 192
		if (git_vector_insert(&t->heads, &ref->head) < 0)
			return -1;
	}

	return 0;
}

193 194 195 196 197 198
static void free_symrefs(git_vector *symrefs)
{
	git_refspec *spec;
	size_t i;

	git_vector_foreach(symrefs, i, spec) {
199
		git_refspec__dispose(spec);
200 201 202 203 204 205
		git__free(spec);
	}

	git_vector_free(symrefs);
}

206 207 208
static int git_smart__connect(
	git_transport *transport,
	const char *url,
209
	git_credential_acquire_cb cred_acquire_cb,
210
	void *cred_acquire_payload,
211
	const git_proxy_options *proxy,
212 213
	int direction,
	int flags)
214
{
215
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
216 217 218
	git_smart_subtransport_stream *stream;
	int error;
	git_pkt *pkt;
219
	git_pkt_ref *first;
220
	git_vector symrefs;
221
	git_smart_service_t service;
222

223 224
	if (git_smart__reset_stream(t, true) < 0)
		return -1;
225 226

	t->url = git__strdup(url);
227
	GIT_ERROR_CHECK_ALLOC(t->url);
228

229 230 231
	if (git_proxy_options_dup(&t->proxy, proxy) < 0)
		return -1;

232 233
	t->direction = direction;
	t->flags = flags;
234
	t->cred_acquire_cb = cred_acquire_cb;
235
	t->cred_acquire_payload = cred_acquire_payload;
236

237 238 239 240 241
	if (GIT_DIRECTION_FETCH == t->direction)
		service = GIT_SERVICE_UPLOADPACK_LS;
	else if (GIT_DIRECTION_PUSH == t->direction)
		service = GIT_SERVICE_RECEIVEPACK_LS;
	else {
242
		git_error_set(GIT_ERROR_NET, "invalid direction");
243 244
		return -1;
	}
245

246 247
	if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0)
		return error;
248

249 250
	/* Save off the current stream (i.e. socket) that we are working with */
	t->current_stream = stream;
251

252
	gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
253

254 255 256 257 258 259 260
	/* 2 flushes for RPC; 1 for stateful */
	if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0)
		return error;

	/* Strip the comment packet for RPC */
	if (t->rpc) {
		pkt = (git_pkt *)git_vector_get(&t->refs, 0);
261

262
		if (!pkt || GIT_PKT_COMMENT != pkt->type) {
263
			git_error_set(GIT_ERROR_NET, "invalid response");
264 265 266 267 268 269
			return -1;
		} else {
			/* Remove the comment pkt from the list */
			git_vector_remove(&t->refs, 0);
			git__free(pkt);
		}
270
	}
271 272 273 274

	/* We now have loaded the refs. */
	t->have_refs = 1;

275 276
	pkt = (git_pkt *)git_vector_get(&t->refs, 0);
	if (pkt && GIT_PKT_REF != pkt->type) {
277
		git_error_set(GIT_ERROR_NET, "invalid response");
278 279 280
		return -1;
	}
	first = (git_pkt_ref *)pkt;
281

282 283 284
	if ((error = git_vector_init(&symrefs, 1, NULL)) < 0)
		return error;

285
	/* Detect capabilities */
286
	if ((error = git_smart__detect_caps(first, &t->caps, &symrefs)) == 0) {
287 288
		/* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */
		if (1 == t->refs.length && !strcmp(first->head.name, "capabilities^{}") &&
289
			git_oid_is_zero(&first->head.oid)) {
290 291 292
			git_vector_clear(&t->refs);
			git_pkt_free((git_pkt *)first);
		}
293

294 295 296 297 298 299
		/* Keep a list of heads for _ls */
		git_smart__update_heads(t, &symrefs);
	} else if (error == GIT_ENOTFOUND) {
		/* There was no ref packet received, or the cap list was empty */
		error = 0;
	} else {
300
		git_error_set(GIT_ERROR_NET, "invalid response");
301
		goto cleanup;
302 303
	}

304 305
	if (t->rpc && (error = git_smart__reset_stream(t, false)) < 0)
		goto cleanup;
306 307 308 309

	/* We're now logically connected. */
	t->connected = 1;

310 311 312 313
cleanup:
	free_symrefs(&symrefs);

	return error;
314 315
}

316
static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transport *transport)
317
{
318
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
319 320

	if (!t->have_refs) {
321
		git_error_set(GIT_ERROR_NET, "the transport has not yet loaded the refs");
322 323 324
		return -1;
	}

325 326
	*out = (const git_remote_head **) t->heads.contents;
	*size = t->heads.length;
327 328 329 330 331 332

	return 0;
}

int git_smart__negotiation_step(git_transport *transport, void *data, size_t len)
{
333
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
334 335 336
	git_smart_subtransport_stream *stream;
	int error;

337 338 339 340
	if (t->rpc && git_smart__reset_stream(t, false) < 0)
		return -1;

	if (GIT_DIRECTION_FETCH != t->direction) {
341
		git_error_set(GIT_ERROR_NET, "this operation is only valid for fetch");
342 343 344 345 346 347 348 349
		return -1;
	}

	if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)
		return error;

	/* If this is a stateful implementation, the stream we get back should be the same */
	assert(t->rpc || t->current_stream == stream);
350

351 352
	/* Save off the current stream (i.e. socket) that we are working with */
	t->current_stream = stream;
353

354 355
	if ((error = stream->write(stream, (const char *)data, len)) < 0)
		return error;
356

357
	gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
358

359 360
	return 0;
}
361

362 363 364 365 366 367
int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **stream)
{
	int error;

	if (t->rpc && git_smart__reset_stream(t, false) < 0)
		return -1;
368

369
	if (GIT_DIRECTION_PUSH != t->direction) {
370
		git_error_set(GIT_ERROR_NET, "this operation is only valid for push");
371
		return -1;
372 373
	}

374 375 376 377 378 379 380 381 382
	if ((error = t->wrapped->action(stream, t->wrapped, t->url, GIT_SERVICE_RECEIVEPACK)) < 0)
		return error;

	/* If this is a stateful implementation, the stream we get back should be the same */
	assert(t->rpc || t->current_stream == *stream);

	/* Save off the current stream (i.e. socket) that we are working with */
	t->current_stream = *stream;

383
	gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
384 385

	return 0;
386 387 388 389
}

static void git_smart__cancel(git_transport *transport)
{
390
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
391 392 393 394

	git_atomic_set(&t->cancelled, 1);
}

395
static int git_smart__is_connected(git_transport *transport)
396
{
397
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
398

399
	return t->connected;
400 401 402 403
}

static int git_smart__read_flags(git_transport *transport, int *flags)
{
404
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
405 406 407 408 409 410 411 412

	*flags = t->flags;

	return 0;
}

static int git_smart__close(git_transport *transport)
{
413
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
414 415 416 417
	git_vector *common = &t->common;
	unsigned int i;
	git_pkt *p;
	int ret;
418 419 420 421 422 423 424 425 426 427 428 429
	git_smart_subtransport_stream *stream;
	const char flush[] = "0000";

	/*
	 * If we're still connected at this point and not using RPC,
	 * we should say goodbye by sending a flush, or git-daemon
	 * will complain that we disconnected unexpectedly.
	 */
	if (t->connected && !t->rpc &&
	    !t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) {
		t->current_stream->write(t->current_stream, flush, 4);
	}
430 431 432 433 434 435 436 437 438 439 440 441

	ret = git_smart__reset_stream(t, true);

	git_vector_foreach(common, i, p)
		git_pkt_free(p);

	git_vector_free(common);

	if (t->url) {
		git__free(t->url);
		t->url = NULL;
	}
442 443 444

	t->connected = 0;

445
	return ret;
446 447 448 449
}

static void git_smart__free(git_transport *transport)
{
450
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
451 452 453
	git_vector *refs = &t->refs;
	unsigned int i;
	git_pkt *p;
454 455 456 457 458 459 460

	/* Make sure that the current stream is closed, if we have one. */
	git_smart__close(transport);

	/* Free the subtransport */
	t->wrapped->free(t->wrapped);

461
	git_vector_free(&t->heads);
462 463 464 465
	git_vector_foreach(refs, i, p)
		git_pkt_free(p);

	git_vector_free(refs);
466
	git__free((char *)t->proxy.url);
467

468 469
	git_strarray_free(&t->custom_headers);

470 471 472
	git__free(t);
}

473 474 475 476 477 478 479
static int ref_name_cmp(const void *a, const void *b)
{
	const git_pkt_ref *ref_a = a, *ref_b = b;

	return strcmp(ref_a->head.name, ref_b->head.name);
}

480 481
int git_transport_smart_certificate_check(git_transport *transport, git_cert *cert, int valid, const char *hostname)
{
482
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
483

484 485 486 487 488
	assert(transport && cert && hostname);

	if (!t->certificate_check_cb)
		return GIT_PASSTHROUGH;

489 490 491
	return t->certificate_check_cb(cert, valid, hostname, t->message_cb_payload);
}

492
int git_transport_smart_credentials(git_credential **out, git_transport *transport, const char *user, int methods)
493
{
494
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
495

496 497 498 499 500
	assert(out && transport);

	if (!t->cred_acquire_cb)
		return GIT_PASSTHROUGH;

501 502 503
	return t->cred_acquire_cb(out, t->url, user, methods, t->cred_acquire_payload);
}

504 505
int git_transport_smart_proxy_options(git_proxy_options *out, git_transport *transport)
{
506
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
507 508 509
	return git_proxy_options_dup(out, &t->proxy);
}

510
int git_transport_smart(git_transport **out, git_remote *owner, void *param)
511 512 513 514 515 516 517
{
	transport_smart *t;
	git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param;

	if (!param)
		return -1;

518
	t = git__calloc(1, sizeof(transport_smart));
519
	GIT_ERROR_CHECK_ALLOC(t);
520

521
	t->parent.version = GIT_TRANSPORT_VERSION;
522
	t->parent.set_callbacks = git_smart__set_callbacks;
523
	t->parent.set_custom_headers = git_smart__set_custom_headers;
524 525 526 527 528
	t->parent.connect = git_smart__connect;
	t->parent.close = git_smart__close;
	t->parent.free = git_smart__free;
	t->parent.negotiate_fetch = git_smart__negotiate_fetch;
	t->parent.download_pack = git_smart__download_pack;
529
	t->parent.push = git_smart__push;
530 531 532 533
	t->parent.ls = git_smart__ls;
	t->parent.is_connected = git_smart__is_connected;
	t->parent.read_flags = git_smart__read_flags;
	t->parent.cancel = git_smart__cancel;
534

535
	t->owner = owner;
536 537
	t->rpc = definition->rpc;

538
	if (git_vector_init(&t->refs, 16, ref_name_cmp) < 0) {
539 540 541 542
		git__free(t);
		return -1;
	}

543 544 545 546 547
	if (git_vector_init(&t->heads, 16, ref_name_cmp) < 0) {
		git__free(t);
		return -1;
	}

548
	if (definition->callback(&t->wrapped, &t->parent, definition->param) < 0) {
549 550
		git__free(t);
		return -1;
551
	}
552 553 554 555

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