smart_pkt.c 13.2 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 11
#include "git2/types.h"
#include "git2/errors.h"
12 13
#include "git2/refs.h"
#include "git2/revwalk.h"
14

15
#include "smart.h"
16
#include "util.h"
17
#include "netops.h"
18
#include "posix.h"
19
#include "buffer.h"
20

21 22 23
#include <ctype.h>

#define PKT_LEN_SIZE 4
24 25 26 27
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 ";
28

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

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

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

39
	return 0;
40 41
}

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

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

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

	if (len < GIT_OID_HEXSZ || git_oid_fromstr(&pkt->oid, line) < 0)
		goto out_err;
	line += GIT_OID_HEXSZ;
	len -= GIT_OID_HEXSZ;
60

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

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

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

77
	return 0;
78 79

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

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

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

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

95
	return 0;
Carlos Martín Nieto committed
96 97
}

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

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

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

	*out = (git_pkt *) pkt;

114
	return 0;
115 116
}

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

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

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

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

	*out = (git_pkt *) pkt;

	return 0;
141 142

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

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

	line++;
	len--;
155

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

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

	*out = (git_pkt *) pkt;

	return 0;
}

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

	line++;
	len--;
176

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

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

	*out = (git_pkt *) pkt;

	return 0;
}

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

	line++;
	len--;
197

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

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

	*out = (git_pkt *)pkt;

	return 0;
}

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

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

225 226 227 228 229 230 231 232 233 234 235 236
	if (len < GIT_OID_HEXSZ || git_oid_fromstr(&pkt->head.oid, line) < 0)
		goto out_err;
	line += GIT_OID_HEXSZ;
	len -= GIT_OID_HEXSZ;

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

	if (!len)
		goto out_err;
237

238 239
	if (line[len - 1] == '\n')
		--len;
240

241
	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1);
242
	pkt->head.name = git__malloc(alloclen);
243
	GIT_ERROR_CHECK_ALLOC(pkt->head.name);
244

245 246
	memcpy(pkt->head.name, line, len);
	pkt->head.name[len] = '\0';
247

248
	if (strlen(pkt->head.name) < len)
249
		pkt->capabilities = strchr(pkt->head.name, '\0') + 1;
250

251 252 253
	*out = (git_pkt *)pkt;
	return 0;

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

262 263 264
static int ok_pkt(git_pkt **out, const char *line, size_t len)
{
	git_pkt_ok *pkt;
265
	size_t alloc_len;
266

267
	pkt = git__malloc(sizeof(*pkt));
268
	GIT_ERROR_CHECK_ALLOC(pkt);
269 270
	pkt->type = GIT_PKT_OK;

271 272 273 274 275
	if (git__prefixncmp(line, len, "ok "))
		goto out_err;
	line += 3;
	len -= 3;

276
	if (len && line[len - 1] == '\n')
277
		--len;
278

279
	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1);
280
	pkt->ref = git__malloc(alloc_len);
281
	GIT_ERROR_CHECK_ALLOC(pkt->ref);
282 283 284 285 286 287

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

	*out = (git_pkt *)pkt;
	return 0;
288 289

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

static int ng_pkt(git_pkt **out, const char *line, size_t len)
{
	git_pkt_ng *pkt;
298
	const char *ptr, *eol;
299
	size_t alloclen;
300

301
	pkt = git__malloc(sizeof(*pkt));
302
	GIT_ERROR_CHECK_ALLOC(pkt);
303

304
	pkt->ref = NULL;
305 306
	pkt->type = GIT_PKT_NG;

307 308
	eol = line + len;

309
	if (git__prefixncmp(line, len, "ng "))
310
		goto out_err;
311
	line += 3;
312 313

	if (!(ptr = memchr(line, ' ', eol - line)))
314
		goto out_err;
315 316
	len = ptr - line;

317
	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1);
318
	pkt->ref = git__malloc(alloclen);
319
	GIT_ERROR_CHECK_ALLOC(pkt->ref);
320 321 322 323 324

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

	line = ptr + 1;
325 326 327 328
	if (line >= eol)
		goto out_err;

	if (!(ptr = memchr(line, '\n', eol - line)))
329
		goto out_err;
330 331
	len = ptr - line;

332
	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1);
333
	pkt->msg = git__malloc(alloclen);
334
	GIT_ERROR_CHECK_ALLOC(pkt->msg);
335 336 337 338 339 340

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

	*out = (git_pkt *)pkt;
	return 0;
341 342

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

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

353
	pkt = git__malloc(sizeof(*pkt));
354
	GIT_ERROR_CHECK_ALLOC(pkt);
355
	pkt->type = GIT_PKT_UNPACK;
356 357

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

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

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

373 374 375 376
	/* Not even enough for the length */
	if (linelen < PKT_LEN_SIZE)
		return GIT_EBUFS;

377 378 379 380
	memcpy(num, line, PKT_LEN_SIZE);
	num[PKT_LEN_SIZE] = '\0';

	for (i = 0; i < PKT_LEN_SIZE; ++i) {
381
		if (!isxdigit(num[i])) {
382 383 384 385 386 387
			/* 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] = '.';
				}
			}
388

389
			git_error_set(GIT_ERROR_NET, "invalid hex digit in length: '%s'", num);
390 391
			return -1;
		}
392 393
	}

394
	if ((error = git__strntol32(&len, num, PKT_LEN_SIZE, &num_end, 16)) < 0)
395
		return error;
396

397 398 399 400 401
	if (len < 0)
		return -1;

	*out = (size_t) len;
	return 0;
402 403
}

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

417
int git_pkt_parse_line(
418
	git_pkt **pkt, const char **endptr, const char *line, size_t linelen)
419
{
420 421
	int error;
	size_t len;
422

423
	if ((error = parse_len(&len, line, linelen)) < 0) {
424
		/*
425 426 427 428
		 * 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.
429
		 */
430 431 432
		if (error == GIT_EBUFS)
			;
		else if (!git__prefixncmp(line, linelen, "PACK"))
433
			git_error_set(GIT_ERROR_NET, "unexpected pack file");
434
		else
435
			git_error_set(GIT_ERROR_NET, "bad packet length");
436
		return error;
437
	}
438

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

446 447 448 449 450 451 452 453
	/*
	 * 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;

454
	line += PKT_LEN_SIZE;
455
	/*
456 457 458
	 * 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.
459
	 */
460
	if (len == PKT_LEN_SIZE) {
461
		git_error_set_str(GIT_ERROR_NET, "Invalid empty packet");
462
		return GIT_ERROR;
463 464
	}

465
	if (len == 0) { /* Flush pkt */
466 467
		*endptr = line;
		return flush_pkt(pkt);
468 469
	}

470
	len -= PKT_LEN_SIZE; /* the encoded length includes its own size */
471

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

495
	*endptr = line + len;
496

497
	return error;
498
}
499

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

511 512 513 514 515 516 517 518 519 520 521
	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);
	}

522
	git__free(pkt);
523 524
}

525 526
int git_pkt_buffer_flush(git_buf *buf)
{
527
	return git_buf_put(buf, pkt_flush_str, strlen(pkt_flush_str));
528 529
}

530
static int buffer_want_with_caps(const git_remote_head *head, transport_smart_caps *caps, git_buf *buf)
531
{
532
	git_buf str = GIT_BUF_INIT;
533
	char oid[GIT_OID_HEXSZ +1] = {0};
534
	size_t len;
535

536 537 538 539
	/* Prefer multi_ack_detailed */
	if (caps->multi_ack_detailed)
		git_buf_puts(&str, GIT_CAP_MULTI_ACK_DETAILED " ");
	else if (caps->multi_ack)
540 541
		git_buf_puts(&str, GIT_CAP_MULTI_ACK " ");

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

548 549 550
	if (caps->include_tag)
		git_buf_puts(&str, GIT_CAP_INCLUDE_TAG " ");

551 552 553
	if (caps->thin_pack)
		git_buf_puts(&str, GIT_CAP_THIN_PACK " ");

554 555 556
	if (caps->ofs_delta)
		git_buf_puts(&str, GIT_CAP_OFS_DELTA " ");

557 558
	if (git_buf_oom(&str))
		return -1;
559

560 561 562 563
	len = strlen("XXXXwant ") + GIT_OID_HEXSZ + 1 /* NUL */ +
		 git_buf_len(&str) + 1 /* LF */;

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

569
	git_buf_grow_by(buf, len);
570
	git_oid_fmt(oid, &head->oid);
571 572
	git_buf_printf(buf,
		"%04xwant %s %s\n", (unsigned int)len, oid, git_buf_cstr(&str));
573
	git_buf_dispose(&str);
574

575
	GIT_ERROR_CHECK_ALLOC_BUF(buf);
576 577

	return 0;
578 579
}

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

585 586 587 588 589
int git_pkt_buffer_wants(
	const git_remote_head * const *refs,
	size_t count,
	transport_smart_caps *caps,
	git_buf *buf)
590
{
591 592
	size_t i = 0;
	const git_remote_head *head;
593 594

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

601
		if (buffer_want_with_caps(refs[i], caps, buf) < 0)
602
			return -1;
603 604 605 606

		i++;
	}

607
	for (; i < count; ++i) {
608 609
		char oid[GIT_OID_HEXSZ];

610
		head = refs[i];
611 612 613 614
		if (head->local)
			continue;

		git_oid_fmt(oid, &head->oid);
615
		git_buf_put(buf, pkt_want_prefix, strlen(pkt_want_prefix));
616
		git_buf_put(buf, oid, GIT_OID_HEXSZ);
617
		git_buf_putc(buf, '\n');
618 619
		if (git_buf_oom(buf))
			return -1;
620 621 622 623 624 625 626 627 628 629 630
	}

	return git_pkt_buffer_flush(buf);
}

int git_pkt_buffer_have(git_oid *oid, git_buf *buf)
{
	char oidhex[GIT_OID_HEXSZ + 1];

	memset(oidhex, 0x0, sizeof(oidhex));
	git_oid_fmt(oidhex, oid);
631
	return git_buf_printf(buf, "%s%s\n", pkt_have_prefix, oidhex);
632 633 634 635
}

int git_pkt_buffer_done(git_buf *buf)
{
636
	return git_buf_puts(buf, pkt_done_str);
637
}