smart.c 12.8 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 "git2/sys/remote.h"
12
#include "refs.h"
13
#include "refspec.h"
14
#include "proxy.h"
15

16
int git_smart__recv(transport_smart *t)
17
{
18 19
	size_t bytes_read;
	int ret;
20

21
	GIT_ASSERT_ARG(t);
22
	GIT_ASSERT(t->current_stream);
23

24 25 26 27
	if (git_staticstr_remain(&t->buffer) == 0) {
		git_error_set(GIT_ERROR_NET, "out of buffer space");
		return -1;
	}
28

29 30 31 32
	ret = t->current_stream->read(t->current_stream,
		git_staticstr_offset(&t->buffer),
		git_staticstr_remain(&t->buffer),
		&bytes_read);
33

34 35 36 37 38 39 40
	if (ret < 0)
		return ret;

	GIT_ASSERT(bytes_read <= INT_MAX);
	GIT_ASSERT(bytes_read <= git_staticstr_remain(&t->buffer));

	git_staticstr_increase(&t->buffer, bytes_read);
41

42
	if (t->packetsize_cb && !t->cancelled.val) {
43 44 45
		ret = t->packetsize_cb(bytes_read, t->packetsize_payload);

		if (ret) {
46
			git_atomic32_set(&t->cancelled, 1);
47
			return GIT_EUSER;
48
		}
49
	}
50

51
	return (int)bytes_read;
52 53
}

54
GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport)
55 56 57 58 59
{
	if (t->current_stream) {
		t->current_stream->free(t->current_stream);
		t->current_stream = NULL;
	}
60

61
	if (close_subtransport) {
62 63 64
		git__free(t->url);
		t->url = NULL;

65 66 67
		if (t->wrapped->close(t->wrapped) < 0)
			return -1;
	}
68

69 70 71 72 73 74
	git__free(t->caps.object_format);
	t->caps.object_format = NULL;

	git__free(t->caps.agent);
	t->caps.agent = NULL;

75
	return 0;
76 77
}

78
int git_smart__update_heads(transport_smart *t, git_vector *symrefs)
79 80 81 82 83 84 85 86 87 88
{
	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;

89 90
		if (symrefs) {
			git_refspec *spec;
91
			git_str buf = GIT_STR_INIT;
92
			size_t j;
93
			int error = 0;
94 95

			git_vector_foreach(symrefs, j, spec) {
96
				git_str_clear(&buf);
97
				if (git_refspec_src_matches(spec, ref->head.name) &&
98
				    !(error = git_refspec__transform(&buf, spec, ref->head.name))) {
99
					git__free(ref->head.symref_target);
100
					ref->head.symref_target = git_str_detach(&buf);
101
				}
102 103
			}

104
			git_str_dispose(&buf);
105 106 107 108 109

			if (error < 0)
				return error;
		}

110 111 112 113 114 115 116
		if (git_vector_insert(&t->heads, &ref->head) < 0)
			return -1;
	}

	return 0;
}

117 118 119 120 121 122
static void free_symrefs(git_vector *symrefs)
{
	git_refspec *spec;
	size_t i;

	git_vector_foreach(symrefs, i, spec) {
123
		git_refspec__dispose(spec);
124 125 126 127 128 129
		git__free(spec);
	}

	git_vector_free(symrefs);
}

130 131 132 133
static int git_smart__connect(
	git_transport *transport,
	const char *url,
	int direction,
134
	const git_remote_connect_options *connect_opts)
135
{
136
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
137 138 139
	git_smart_subtransport_stream *stream;
	int error;
	git_pkt *pkt;
140
	git_pkt_ref *first;
141
	git_vector symrefs;
142
	git_smart_service_t service;
143

144 145
	if (git_smart__reset_stream(t, true) < 0)
		return -1;
146

147
	if (git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts) < 0)
148 149
		return -1;

150
	t->url = git__strdup(url);
151
	GIT_ERROR_CHECK_ALLOC(t->url);
152 153 154

	t->direction = direction;

155
	if (GIT_DIRECTION_FETCH == t->direction) {
156
		service = GIT_SERVICE_UPLOADPACK_LS;
157
	} else if (GIT_DIRECTION_PUSH == t->direction) {
158
		service = GIT_SERVICE_RECEIVEPACK_LS;
159
	} else {
160
		git_error_set(GIT_ERROR_NET, "invalid direction");
161 162
		return -1;
	}
163

164 165
	if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0)
		return error;
166

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

170 171 172 173 174 175 176
	/* 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);
177

178
		if (!pkt || GIT_PKT_COMMENT != pkt->type) {
179
			git_error_set(GIT_ERROR_NET, "invalid response");
180 181 182 183 184 185
			return -1;
		} else {
			/* Remove the comment pkt from the list */
			git_vector_remove(&t->refs, 0);
			git__free(pkt);
		}
186
	}
187 188 189 190

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

191 192
	pkt = (git_pkt *)git_vector_get(&t->refs, 0);
	if (pkt && GIT_PKT_REF != pkt->type) {
193
		git_error_set(GIT_ERROR_NET, "invalid response");
194 195 196
		return -1;
	}
	first = (git_pkt_ref *)pkt;
197

198 199 200
	if ((error = git_vector_init(&symrefs, 1, NULL)) < 0)
		return error;

201
	/* Detect capabilities */
202
	if ((error = git_smart__detect_caps(first, &t->caps, &symrefs)) == 0) {
203 204
		/* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */
		if (1 == t->refs.length && !strcmp(first->head.name, "capabilities^{}") &&
205
			git_oid_is_zero(&first->head.oid)) {
206 207 208
			git_vector_clear(&t->refs);
			git_pkt_free((git_pkt *)first);
		}
209

210 211 212 213 214 215
		/* 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 {
216
		git_error_set(GIT_ERROR_NET, "invalid response");
217
		goto cleanup;
218 219
	}

220 221
	if (t->rpc && (error = git_smart__reset_stream(t, false)) < 0)
		goto cleanup;
222 223 224 225

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

226 227 228 229
cleanup:
	free_symrefs(&symrefs);

	return error;
230 231
}

232 233 234 235 236 237 238 239 240 241 242
static int git_smart__set_connect_opts(
	git_transport *transport,
	const git_remote_connect_options *opts)
{
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);

	if (!t->connected) {
		git_error_set(GIT_ERROR_NET, "cannot reconfigure a transport that is not connected");
		return -1;
	}

243
	return git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, opts);
244 245
}

246 247
static int git_smart__capabilities(unsigned int *capabilities, git_transport *transport)
{
248
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
249 250

	*capabilities = 0;
251 252 253 254 255 256 257

	if (t->caps.want_tip_sha1)
		*capabilities |= GIT_REMOTE_CAPABILITY_TIP_OID;

	if (t->caps.want_reachable_sha1)
		*capabilities |= GIT_REMOTE_CAPABILITY_REACHABLE_OID;

258 259 260
	return 0;
}

261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
#ifdef GIT_EXPERIMENTAL_SHA256
static int git_smart__oid_type(git_oid_t *out, git_transport *transport)
{
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);

	*out = 0;

	if (t->caps.object_format == NULL) {
		*out = GIT_OID_DEFAULT;
	} else {
		*out = git_oid_type_fromstr(t->caps.object_format);

		if (!*out) {
			git_error_set(GIT_ERROR_INVALID,
				"unknown object format '%s'",
				t->caps.object_format);
			return -1;
		}
	}

	return 0;
}
#endif

285
static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transport *transport)
286
{
287
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
288 289

	if (!t->have_refs) {
290
		git_error_set(GIT_ERROR_NET, "the transport has not yet loaded the refs");
291 292 293
		return -1;
	}

294 295
	*out = (const git_remote_head **) t->heads.contents;
	*size = t->heads.length;
296 297 298 299 300 301

	return 0;
}

int git_smart__negotiation_step(git_transport *transport, void *data, size_t len)
{
302
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
303 304 305
	git_smart_subtransport_stream *stream;
	int error;

306 307 308 309
	if (t->rpc && git_smart__reset_stream(t, false) < 0)
		return -1;

	if (GIT_DIRECTION_FETCH != t->direction) {
310
		git_error_set(GIT_ERROR_NET, "this operation is only valid for fetch");
311 312 313 314 315 316 317
		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 */
318
	GIT_ASSERT(t->rpc || t->current_stream == stream);
319

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

323 324
	if ((error = stream->write(stream, (const char *)data, len)) < 0)
		return error;
325

326 327
	return 0;
}
328

329 330 331 332 333 334
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;
335

336
	if (GIT_DIRECTION_PUSH != t->direction) {
337
		git_error_set(GIT_ERROR_NET, "this operation is only valid for push");
338
		return -1;
339 340
	}

341 342 343 344
	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 */
345
	GIT_ASSERT(t->rpc || t->current_stream == *stream);
346 347 348 349 350

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

	return 0;
351 352 353 354
}

static void git_smart__cancel(git_transport *transport)
{
355
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
356

357
	git_atomic32_set(&t->cancelled, 1);
358 359
}

360
static int git_smart__is_connected(git_transport *transport)
361
{
362
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
363

364
	return t->connected;
365 366 367 368
}

static int git_smart__close(git_transport *transport)
{
369
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
370 371 372 373
	git_vector *common = &t->common;
	unsigned int i;
	git_pkt *p;
	int ret;
374 375 376 377 378 379 380 381 382 383 384 385
	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);
	}
386 387 388 389 390 391 392 393 394 395 396 397

	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;
	}
398 399 400

	t->connected = 0;

401
	return ret;
402 403 404 405
}

static void git_smart__free(git_transport *transport)
{
406
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
407 408 409
	git_vector *refs = &t->refs;
	unsigned int i;
	git_pkt *p;
410 411 412 413 414 415 416

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

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

417
	git_vector_free(&t->heads);
418 419 420 421 422
	git_vector_foreach(refs, i, p)
		git_pkt_free(p);

	git_vector_free(refs);

423
	git_remote_connect_options_dispose(&t->connect_opts);
424

425 426
	git_array_dispose(t->shallow_roots);

427 428
	git__free(t->caps.object_format);
	git__free(t->caps.agent);
429 430 431
	git__free(t);
}

432 433 434 435 436 437 438
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);
}

439 440
int git_transport_smart_certificate_check(git_transport *transport, git_cert *cert, int valid, const char *hostname)
{
441
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
442
	git_remote_connect_options *connect_opts = &t->connect_opts;
443

444 445 446
	GIT_ASSERT_ARG(transport);
	GIT_ASSERT_ARG(cert);
	GIT_ASSERT_ARG(hostname);
447

448
	if (!connect_opts->callbacks.certificate_check)
449 450
		return GIT_PASSTHROUGH;

451
	return connect_opts->callbacks.certificate_check(cert, valid, hostname, connect_opts->callbacks.payload);
452 453
}

454
int git_transport_smart_credentials(git_credential **out, git_transport *transport, const char *user, int methods)
455
{
456
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
457
	git_remote_connect_options *connect_opts = &t->connect_opts;
458

459 460
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(transport);
461

462
	if (!connect_opts->callbacks.credentials)
463 464
		return GIT_PASSTHROUGH;

465
	return connect_opts->callbacks.credentials(out, t->url, user, methods, connect_opts->callbacks.payload);
466 467
}

468
int git_transport_remote_connect_options(
469 470 471 472 473 474 475 476 477 478 479
		git_remote_connect_options *out,
		git_transport *transport)
{
	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);

	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(transport);

	return git_remote_connect_options_dup(out, &t->connect_opts);
}

480
int git_transport_smart(git_transport **out, git_remote *owner, void *param)
481 482 483 484 485 486 487
{
	transport_smart *t;
	git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param;

	if (!param)
		return -1;

488
	t = git__calloc(1, sizeof(transport_smart));
489
	GIT_ERROR_CHECK_ALLOC(t);
490

491
	t->parent.version = GIT_TRANSPORT_VERSION;
492
	t->parent.connect = git_smart__connect;
493
	t->parent.set_connect_opts = git_smart__set_connect_opts;
494
	t->parent.capabilities = git_smart__capabilities;
495 496 497
#ifdef GIT_EXPERIMENTAL_SHA256
	t->parent.oid_type = git_smart__oid_type;
#endif
498 499 500
	t->parent.close = git_smart__close;
	t->parent.free = git_smart__free;
	t->parent.negotiate_fetch = git_smart__negotiate_fetch;
501
	t->parent.shallow_roots = git_smart__shallow_roots;
502
	t->parent.download_pack = git_smart__download_pack;
503
	t->parent.push = git_smart__push;
504 505 506
	t->parent.ls = git_smart__ls;
	t->parent.is_connected = git_smart__is_connected;
	t->parent.cancel = git_smart__cancel;
507

508
	t->owner = owner;
509 510
	t->rpc = definition->rpc;

511 512 513 514 515 516
	if (git_vector_init(&t->refs, 16, ref_name_cmp) < 0 ||
	    git_vector_init(&t->heads, 16, ref_name_cmp) < 0 ||
	    definition->callback(&t->wrapped, &t->parent, definition->param) < 0) {
		git_vector_free(&t->refs);
		git_vector_free(&t->heads);
		t->wrapped->free(t->wrapped);
517 518 519 520
		git__free(t);
		return -1;
	}

521
	git_staticstr_init(&t->buffer, GIT_SMART_BUFFER_SIZE);
522 523 524 525

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