smart_pkt.c 13.4 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3
 *
Vicent Marti committed
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
 */

Vicent Marti committed
8 9
#include "common.h"

10
#include "smart.h"
11
#include "util.h"
12
#include "netops.h"
13
#include "posix.h"
14
#include "str.h"
15
#include "oid.h"
16 17 18 19 20

#include "git2/types.h"
#include "git2/errors.h"
#include "git2/refs.h"
#include "git2/revwalk.h"
21

22 23 24
#include <ctype.h>

#define PKT_LEN_SIZE 4
25 26 27 28
static const char pkt_done_str[] = "0009done\n";
static const char pkt_flush_str[] = "0000";
static const char pkt_have_prefix[] = "0032have ";
static const char pkt_want_prefix[] = "0032want ";
29

30 31 32 33
static int flush_pkt(git_pkt **out)
{
	git_pkt *pkt;

34
	pkt = git__malloc(sizeof(git_pkt));
35
	GIT_ERROR_CHECK_ALLOC(pkt);
36 37 38 39

	pkt->type = GIT_PKT_FLUSH;
	*out = pkt;

40
	return 0;
41 42
}

43
/* the rest of the line will be useful for multi_ack and multi_ack_detailed */
44
static int ack_pkt(git_pkt **out, const char *line, size_t len)
Carlos Martín Nieto committed
45
{
46
	git_pkt_ack *pkt;
Carlos Martín Nieto committed
47

48
	pkt = git__calloc(1, sizeof(git_pkt_ack));
49
	GIT_ERROR_CHECK_ALLOC(pkt);
Carlos Martín Nieto committed
50
	pkt->type = GIT_PKT_ACK;
51

52 53 54 55 56
	if (git__prefixncmp(line, len, "ACK "))
		goto out_err;
	line += 4;
	len -= 4;

Edward Thomson committed
57
	if (len < GIT_OID_SHA1_HEXSIZE ||
58
	    git_oid__fromstr(&pkt->oid, line, GIT_OID_SHA1) < 0)
59
		goto out_err;
60 61
	line += GIT_OID_SHA1_HEXSIZE;
	len -= GIT_OID_SHA1_HEXSIZE;
62

63 64 65 66 67
	if (len && line[0] == ' ') {
		line++;
		len--;

		if (!git__prefixncmp(line, len, "continue"))
68
			pkt->status = GIT_ACK_CONTINUE;
69
		else if (!git__prefixncmp(line, len, "common"))
70
			pkt->status = GIT_ACK_COMMON;
71
		else if (!git__prefixncmp(line, len, "ready"))
72
			pkt->status = GIT_ACK_READY;
73 74
		else
			goto out_err;
75 76 77
	}

	*out = (git_pkt *) pkt;
Carlos Martín Nieto committed
78

79
	return 0;
80 81

out_err:
82
	git_error_set(GIT_ERROR_NET, "error parsing ACK pkt-line");
83 84
	git__free(pkt);
	return -1;
Carlos Martín Nieto committed
85 86
}

Carlos Martín Nieto committed
87
static int nak_pkt(git_pkt **out)
Carlos Martín Nieto committed
88 89 90
{
	git_pkt *pkt;

91
	pkt = git__malloc(sizeof(git_pkt));
92
	GIT_ERROR_CHECK_ALLOC(pkt);
Carlos Martín Nieto committed
93

Carlos Martín Nieto committed
94 95 96
	pkt->type = GIT_PKT_NAK;
	*out = pkt;

97
	return 0;
Carlos Martín Nieto committed
98 99
}

100 101 102
static int comment_pkt(git_pkt **out, const char *line, size_t len)
{
	git_pkt_comment *pkt;
103
	size_t alloclen;
104

105 106
	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_comment), len);
	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
107
	pkt = git__malloc(alloclen);
108
	GIT_ERROR_CHECK_ALLOC(pkt);
109 110 111 112 113 114 115

	pkt->type = GIT_PKT_COMMENT;
	memcpy(pkt->comment, line, len);
	pkt->comment[len] = '\0';

	*out = (git_pkt *) pkt;

116
	return 0;
117 118
}

119 120
static int err_pkt(git_pkt **out, const char *line, size_t len)
{
121
	git_pkt_err *pkt = NULL;
122
	size_t alloclen;
123 124

	/* Remove "ERR " from the line */
125 126
	if (git__prefixncmp(line, len, "ERR "))
		goto out_err;
127 128
	line += 4;
	len -= 4;
129

130 131
	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len);
	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
132
	pkt = git__malloc(alloclen);
133
	GIT_ERROR_CHECK_ALLOC(pkt);
134
	pkt->type = GIT_PKT_ERR;
135 136
	pkt->len = len;

137 138 139 140 141 142
	memcpy(pkt->error, line, len);
	pkt->error[len] = '\0';

	*out = (git_pkt *) pkt;

	return 0;
143 144

out_err:
145
	git_error_set(GIT_ERROR_NET, "error parsing ERR pkt-line");
146 147
	git__free(pkt);
	return -1;
148 149
}

150 151 152
static int data_pkt(git_pkt **out, const char *line, size_t len)
{
	git_pkt_data *pkt;
153
	size_t alloclen;
154 155 156

	line++;
	len--;
157

158
	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len);
159
	pkt = git__malloc(alloclen);
160
	GIT_ERROR_CHECK_ALLOC(pkt);
161 162

	pkt->type = GIT_PKT_DATA;
163
	pkt->len = len;
164 165 166 167 168 169 170
	memcpy(pkt->data, line, len);

	*out = (git_pkt *) pkt;

	return 0;
}

171
static int sideband_progress_pkt(git_pkt **out, const char *line, size_t len)
172 173
{
	git_pkt_progress *pkt;
174
	size_t alloclen;
175 176 177

	line++;
	len--;
178

179
	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len);
180
	pkt = git__malloc(alloclen);
181
	GIT_ERROR_CHECK_ALLOC(pkt);
182 183

	pkt->type = GIT_PKT_PROGRESS;
184
	pkt->len = len;
185 186 187 188 189 190 191
	memcpy(pkt->data, line, len);

	*out = (git_pkt *) pkt;

	return 0;
}

192 193 194
static int sideband_error_pkt(git_pkt **out, const char *line, size_t len)
{
	git_pkt_err *pkt;
195
	size_t alloc_len;
196 197 198

	line++;
	len--;
199

200 201
	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(git_pkt_err), len);
	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1);
202
	pkt = git__malloc(alloc_len);
203
	GIT_ERROR_CHECK_ALLOC(pkt);
204 205 206 207 208 209 210 211 212 213 214

	pkt->type = GIT_PKT_ERR;
	pkt->len = (int)len;
	memcpy(pkt->error, line, len);
	pkt->error[len] = '\0';

	*out = (git_pkt *)pkt;

	return 0;
}

215
/*
216 217
 * Parse an other-ref line.
 */
Carlos Martín Nieto committed
218
static int ref_pkt(git_pkt **out, const char *line, size_t len)
219 220
{
	git_pkt_ref *pkt;
221
	size_t alloclen;
222

223
	pkt = git__calloc(1, sizeof(git_pkt_ref));
224
	GIT_ERROR_CHECK_ALLOC(pkt);
225 226
	pkt->type = GIT_PKT_REF;

Edward Thomson committed
227
	if (len < GIT_OID_SHA1_HEXSIZE ||
228
	    git_oid__fromstr(&pkt->head.oid, line, GIT_OID_SHA1) < 0)
229
		goto out_err;
230 231
	line += GIT_OID_SHA1_HEXSIZE;
	len -= GIT_OID_SHA1_HEXSIZE;
232 233 234 235 236 237 238 239

	if (git__prefixncmp(line, len, " "))
		goto out_err;
	line++;
	len--;

	if (!len)
		goto out_err;
240

241 242
	if (line[len - 1] == '\n')
		--len;
243

244
	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1);
245
	pkt->head.name = git__malloc(alloclen);
246
	GIT_ERROR_CHECK_ALLOC(pkt->head.name);
247

248 249
	memcpy(pkt->head.name, line, len);
	pkt->head.name[len] = '\0';
250

251
	if (strlen(pkt->head.name) < len)
252
		pkt->capabilities = strchr(pkt->head.name, '\0') + 1;
253

254 255 256
	*out = (git_pkt *)pkt;
	return 0;

257
out_err:
258
	git_error_set(GIT_ERROR_NET, "error parsing REF pkt-line");
259 260
	if (pkt)
		git__free(pkt->head.name);
261
	git__free(pkt);
262
	return -1;
263 264
}

265 266 267
static int ok_pkt(git_pkt **out, const char *line, size_t len)
{
	git_pkt_ok *pkt;
268
	size_t alloc_len;
269

270
	pkt = git__malloc(sizeof(*pkt));
271
	GIT_ERROR_CHECK_ALLOC(pkt);
272 273
	pkt->type = GIT_PKT_OK;

274 275 276 277 278
	if (git__prefixncmp(line, len, "ok "))
		goto out_err;
	line += 3;
	len -= 3;

279
	if (len && line[len - 1] == '\n')
280
		--len;
281

282
	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1);
283
	pkt->ref = git__malloc(alloc_len);
284
	GIT_ERROR_CHECK_ALLOC(pkt->ref);
285 286 287 288 289 290

	memcpy(pkt->ref, line, len);
	pkt->ref[len] = '\0';

	*out = (git_pkt *)pkt;
	return 0;
291 292

out_err:
293
	git_error_set(GIT_ERROR_NET, "error parsing OK pkt-line");
294 295
	git__free(pkt);
	return -1;
296 297 298 299 300
}

static int ng_pkt(git_pkt **out, const char *line, size_t len)
{
	git_pkt_ng *pkt;
301
	const char *ptr, *eol;
302
	size_t alloclen;
303

304
	pkt = git__malloc(sizeof(*pkt));
305
	GIT_ERROR_CHECK_ALLOC(pkt);
306

307
	pkt->ref = NULL;
308 309
	pkt->type = GIT_PKT_NG;

310 311
	eol = line + len;

312
	if (git__prefixncmp(line, len, "ng "))
313
		goto out_err;
314
	line += 3;
315 316

	if (!(ptr = memchr(line, ' ', eol - line)))
317
		goto out_err;
318 319
	len = ptr - line;

320
	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1);
321
	pkt->ref = git__malloc(alloclen);
322
	GIT_ERROR_CHECK_ALLOC(pkt->ref);
323 324 325 326 327

	memcpy(pkt->ref, line, len);
	pkt->ref[len] = '\0';

	line = ptr + 1;
328 329 330 331
	if (line >= eol)
		goto out_err;

	if (!(ptr = memchr(line, '\n', eol - line)))
332
		goto out_err;
333 334
	len = ptr - line;

335
	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1);
336
	pkt->msg = git__malloc(alloclen);
337
	GIT_ERROR_CHECK_ALLOC(pkt->msg);
338 339 340 341 342 343

	memcpy(pkt->msg, line, len);
	pkt->msg[len] = '\0';

	*out = (git_pkt *)pkt;
	return 0;
344 345

out_err:
346
	git_error_set(GIT_ERROR_NET, "invalid packet line");
347 348 349
	git__free(pkt->ref);
	git__free(pkt);
	return -1;
350 351 352 353 354 355
}

static int unpack_pkt(git_pkt **out, const char *line, size_t len)
{
	git_pkt_unpack *pkt;

356
	pkt = git__malloc(sizeof(*pkt));
357
	GIT_ERROR_CHECK_ALLOC(pkt);
358
	pkt->type = GIT_PKT_UNPACK;
359 360

	if (!git__prefixncmp(line, len, "unpack ok"))
361 362 363 364 365 366 367 368
		pkt->unpack_ok = 1;
	else
		pkt->unpack_ok = 0;

	*out = (git_pkt *)pkt;
	return 0;
}

369
static int parse_len(size_t *out, const char *line, size_t linelen)
370 371
{
	char num[PKT_LEN_SIZE + 1];
372
	int i, k, error;
373
	int32_t len;
374 375
	const char *num_end;

376 377 378 379
	/* Not even enough for the length */
	if (linelen < PKT_LEN_SIZE)
		return GIT_EBUFS;

380 381 382 383
	memcpy(num, line, PKT_LEN_SIZE);
	num[PKT_LEN_SIZE] = '\0';

	for (i = 0; i < PKT_LEN_SIZE; ++i) {
384
		if (!isxdigit(num[i])) {
385 386 387 388 389 390
			/* Make sure there are no special characters before passing to error message */
			for (k = 0; k < PKT_LEN_SIZE; ++k) {
				if(!isprint(num[k])) {
					num[k] = '.';
				}
			}
391

392
			git_error_set(GIT_ERROR_NET, "invalid hex digit in length: '%s'", num);
393 394
			return -1;
		}
395 396
	}

397
	if ((error = git__strntol32(&len, num, PKT_LEN_SIZE, &num_end, 16)) < 0)
398
		return error;
399

400 401 402 403 404
	if (len < 0)
		return -1;

	*out = (size_t) len;
	return 0;
405 406
}

407
/*
408 409
 * As per the documentation, the syntax is:
 *
Vicent Marti committed
410 411 412
 * pkt-line	= data-pkt / flush-pkt
 * data-pkt	= pkt-len pkt-payload
 * pkt-len		= 4*(HEXDIG)
413
 * pkt-payload = (pkt-len -4)*(OCTET)
Vicent Marti committed
414
 * flush-pkt	= "0000"
415 416 417 418 419
 *
 * Which means that the first four bytes are the length of the line,
 * in ASCII hexadecimal (including itself)
 */

420
int git_pkt_parse_line(
421
	git_pkt **pkt, const char **endptr, const char *line, size_t linelen)
422
{
423 424
	int error;
	size_t len;
425

426
	if ((error = parse_len(&len, line, linelen)) < 0) {
427
		/*
428 429 430 431
		 * If we fail to parse the length, it might be
		 * because the server is trying to send us the
		 * packfile already or because we do not yet have
		 * enough data.
432
		 */
433 434 435
		if (error == GIT_EBUFS)
			;
		else if (!git__prefixncmp(line, linelen, "PACK"))
436
			git_error_set(GIT_ERROR_NET, "unexpected pack file");
437
		else
438
			git_error_set(GIT_ERROR_NET, "bad packet length");
439
		return error;
440
	}
441

442
	/*
443 444
	 * Make sure there is enough in the buffer to satisfy
	 * this line.
445
	 */
446
	if (linelen < len)
447
		return GIT_EBUFS;
448

449 450 451 452 453 454 455 456
	/*
	 * The length has to be exactly 0 in case of a flush
	 * packet or greater than PKT_LEN_SIZE, as the decoded
	 * length includes its own encoded length of four bytes.
	 */
	if (len != 0 && len < PKT_LEN_SIZE)
		return GIT_ERROR;

457
	line += PKT_LEN_SIZE;
458
	/*
459 460 461
	 * The Git protocol does not specify empty lines as part
	 * of the protocol. Not knowing what to do with an empty
	 * line, we should return an error upon hitting one.
462
	 */
463
	if (len == PKT_LEN_SIZE) {
464
		git_error_set_str(GIT_ERROR_NET, "Invalid empty packet");
465
		return GIT_ERROR;
466 467
	}

468
	if (len == 0) { /* Flush pkt */
469 470
		*endptr = line;
		return flush_pkt(pkt);
471 472
	}

473
	len -= PKT_LEN_SIZE; /* the encoded length includes its own size */
474

475
	if (*line == GIT_SIDE_BAND_DATA)
476
		error = data_pkt(pkt, line, len);
477
	else if (*line == GIT_SIDE_BAND_PROGRESS)
478
		error = sideband_progress_pkt(pkt, line, len);
479
	else if (*line == GIT_SIDE_BAND_ERROR)
480
		error = sideband_error_pkt(pkt, line, len);
481
	else if (!git__prefixncmp(line, len, "ACK"))
482
		error = ack_pkt(pkt, line, len);
483
	else if (!git__prefixncmp(line, len, "NAK"))
484
		error = nak_pkt(pkt);
485
	else if (!git__prefixncmp(line, len, "ERR"))
486
		error = err_pkt(pkt, line, len);
487
	else if (*line == '#')
488
		error = comment_pkt(pkt, line, len);
489
	else if (!git__prefixncmp(line, len, "ok"))
490
		error = ok_pkt(pkt, line, len);
491
	else if (!git__prefixncmp(line, len, "ng"))
492
		error = ng_pkt(pkt, line, len);
493
	else if (!git__prefixncmp(line, len, "unpack"))
494
		error = unpack_pkt(pkt, line, len);
Carlos Martín Nieto committed
495
	else
496
		error = ref_pkt(pkt, line, len);
497

498
	*endptr = line + len;
499

500
	return error;
501
}
502

503 504
void git_pkt_free(git_pkt *pkt)
{
505 506 507
	if (pkt == NULL) {
		return;
	}
508
	if (pkt->type == GIT_PKT_REF) {
509
		git_pkt_ref *p = (git_pkt_ref *) pkt;
510
		git__free(p->head.name);
511
		git__free(p->head.symref_target);
512 513
	}

514 515 516 517 518 519 520 521 522 523 524
	if (pkt->type == GIT_PKT_OK) {
		git_pkt_ok *p = (git_pkt_ok *) pkt;
		git__free(p->ref);
	}

	if (pkt->type == GIT_PKT_NG) {
		git_pkt_ng *p = (git_pkt_ng *) pkt;
		git__free(p->ref);
		git__free(p->msg);
	}

525
	git__free(pkt);
526 527
}

528
int git_pkt_buffer_flush(git_str *buf)
529
{
530
	return git_str_put(buf, pkt_flush_str, strlen(pkt_flush_str));
531 532
}

533
static int buffer_want_with_caps(const git_remote_head *head, transport_smart_caps *caps, git_str *buf)
534
{
535
	git_str str = GIT_STR_INIT;
536
	char oid[GIT_OID_SHA1_HEXSIZE +1] = {0};
537
	size_t len;
538

539 540
	/* Prefer multi_ack_detailed */
	if (caps->multi_ack_detailed)
541
		git_str_puts(&str, GIT_CAP_MULTI_ACK_DETAILED " ");
542
	else if (caps->multi_ack)
543
		git_str_puts(&str, GIT_CAP_MULTI_ACK " ");
544

545 546
	/* Prefer side-band-64k if the server supports both */
	if (caps->side_band_64k)
547
		git_str_printf(&str, "%s ", GIT_CAP_SIDE_BAND_64K);
548
	else if (caps->side_band)
549
		git_str_printf(&str, "%s ", GIT_CAP_SIDE_BAND);
550

551
	if (caps->include_tag)
552
		git_str_puts(&str, GIT_CAP_INCLUDE_TAG " ");
553

554
	if (caps->thin_pack)
555
		git_str_puts(&str, GIT_CAP_THIN_PACK " ");
556

557
	if (caps->ofs_delta)
558
		git_str_puts(&str, GIT_CAP_OFS_DELTA " ");
559

560
	if (git_str_oom(&str))
561
		return -1;
562

563
	len = strlen("XXXXwant ") + GIT_OID_SHA1_HEXSIZE + 1 /* NUL */ +
564
		 git_str_len(&str) + 1 /* LF */;
565 566

	if (len > 0xffff) {
567
		git_error_set(GIT_ERROR_NET,
568
			"tried to produce packet with invalid length %" PRIuZ, len);
569 570 571
		return -1;
	}

572
	git_str_grow_by(buf, len);
573
	git_oid_fmt(oid, &head->oid);
574 575 576
	git_str_printf(buf,
		"%04xwant %s %s\n", (unsigned int)len, oid, git_str_cstr(&str));
	git_str_dispose(&str);
577

578
	GIT_ERROR_CHECK_ALLOC_STR(buf);
579 580

	return 0;
581 582
}

583 584 585 586 587
/*
 * All "want" packets have the same length and format, so what we do
 * is overwrite the OID each time.
 */

588 589 590 591
int git_pkt_buffer_wants(
	const git_remote_head * const *refs,
	size_t count,
	transport_smart_caps *caps,
592
	git_str *buf)
593
{
594 595
	size_t i = 0;
	const git_remote_head *head;
596 597

	if (caps->common) {
598 599
		for (; i < count; ++i) {
			head = refs[i];
600 601 602 603
			if (!head->local)
				break;
		}

604
		if (buffer_want_with_caps(refs[i], caps, buf) < 0)
605
			return -1;
606 607 608 609

		i++;
	}

610
	for (; i < count; ++i) {
611
		char oid[GIT_OID_SHA1_HEXSIZE];
612

613
		head = refs[i];
614 615 616 617
		if (head->local)
			continue;

		git_oid_fmt(oid, &head->oid);
618
		git_str_put(buf, pkt_want_prefix, strlen(pkt_want_prefix));
619
		git_str_put(buf, oid, GIT_OID_SHA1_HEXSIZE);
620 621
		git_str_putc(buf, '\n');
		if (git_str_oom(buf))
622
			return -1;
623 624 625 626 627
	}

	return git_pkt_buffer_flush(buf);
}

628
int git_pkt_buffer_have(git_oid *oid, git_str *buf)
629
{
630
	char oidhex[GIT_OID_SHA1_HEXSIZE + 1];
631 632 633

	memset(oidhex, 0x0, sizeof(oidhex));
	git_oid_fmt(oidhex, oid);
634
	return git_str_printf(buf, "%s%s\n", pkt_have_prefix, oidhex);
635 636
}

637
int git_pkt_buffer_done(git_str *buf)
638
{
639
	return git_str_puts(buf, pkt_done_str);
640
}