buffer.c 22.5 KB
Newer Older
Vicent Marti committed
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
Vicent Marti committed
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 "buffer.h"
#include "posix.h"
9
#include "git2/buffer.h"
joshaber committed
10
#include "buf_text.h"
11
#include <ctype.h>
12

13 14 15
/* Used as default value for git_buf->ptr so that people can always
 * assume ptr is non-NULL and zero terminated even for new git_bufs.
 */
16
char git_buf__initbuf[1];
17

18
char git_buf__oom[1];
19

20
#define ENSURE_SIZE(b, d) \
21 22
	if ((d) > buf->asize && git_buf_grow(b, (d)) < 0)\
		return -1;
23

24

25 26 27 28
void git_buf_init(git_buf *buf, size_t initial_size)
{
	buf->asize = 0;
	buf->size = 0;
29
	buf->ptr = git_buf__initbuf;
30 31 32 33 34

	if (initial_size)
		git_buf_grow(buf, initial_size);
}

35
int git_buf_try_grow(
36
	git_buf *buf, size_t target_size, bool mark_oom)
37
{
38
	char *new_ptr;
39
	size_t new_size;
40

41
	if (buf->ptr == git_buf__oom)
42
		return -1;
43

44 45 46 47
	if (buf->asize == 0 && buf->size != 0) {
		giterr_set(GITERR_INVALID, "cannot grow a borrowed buffer");
		return GIT_EINVALID;
	}
48

49 50 51
	if (!target_size)
		target_size = buf->size;

52
	if (target_size <= buf->asize)
53
		return 0;
54

55 56 57 58
	if (buf->asize == 0) {
		new_size = target_size;
		new_ptr = NULL;
	} else {
59
		new_size = buf->asize;
60 61
		new_ptr = buf->ptr;
	}
62 63 64

	/* grow the buffer size by 1.5, until it's big enough
	 * to fit our target size */
65 66
	while (new_size < target_size)
		new_size = (new_size << 1) - (new_size >> 1);
67

68
	/* round allocation up to multiple of 8 */
69
	new_size = (new_size + 7) & ~7;
70

71 72 73 74 75 76 77 78
	if (new_size < buf->size) {
		if (mark_oom)
			buf->ptr = git_buf__oom;

		giterr_set_oom();
		return -1;
	}

79
	new_ptr = git__realloc(new_ptr, new_size);
80 81

	if (!new_ptr) {
82
		if (mark_oom) {
83 84
			if (buf->ptr && (buf->ptr != git_buf__initbuf))
				git__free(buf->ptr);
85
			buf->ptr = git_buf__oom;
86
		}
87
		return -1;
88
	}
89

90 91 92 93 94 95 96 97
	buf->asize = new_size;
	buf->ptr   = new_ptr;

	/* truncate the existing buffer size if necessary */
	if (buf->size >= buf->asize)
		buf->size = buf->asize - 1;
	buf->ptr[buf->size] = '\0';

98
	return 0;
99 100
}

101 102
int git_buf_grow(git_buf *buffer, size_t target_size)
{
103
	return git_buf_try_grow(buffer, target_size, true);
104 105
}

106 107
int git_buf_grow_by(git_buf *buffer, size_t additional_size)
{
108 109 110
	size_t newsize;

	if (GIT_ADD_SIZET_OVERFLOW(&newsize, buffer->size, additional_size)) {
111 112 113 114
		buffer->ptr = git_buf__oom;
		return -1;
	}

115
	return git_buf_try_grow(buffer, newsize, true);
116 117
}

118 119 120 121
void git_buf_free(git_buf *buf)
{
	if (!buf) return;

122
	if (buf->asize > 0 && buf->ptr != NULL && buf->ptr != git_buf__oom)
123 124 125 126 127
		git__free(buf->ptr);

	git_buf_init(buf, 0);
}

128 129 130
void git_buf_sanitize(git_buf *buf)
{
	if (buf->ptr == NULL) {
131
		assert(buf->size == 0 && buf->asize == 0);
132
		buf->ptr = git_buf__initbuf;
133 134
	} else if (buf->asize > buf->size)
		buf->ptr[buf->size] = '\0';
135 136
}

137 138 139
void git_buf_clear(git_buf *buf)
{
	buf->size = 0;
140

141
	if (!buf->ptr) {
142
		buf->ptr = git_buf__initbuf;
143 144
		buf->asize = 0;
	}
145

146 147 148 149
	if (buf->asize > 0)
		buf->ptr[0] = '\0';
}

150
int git_buf_set(git_buf *buf, const void *data, size_t len)
151
{
152 153
	size_t alloclen;

154 155 156
	if (len == 0 || data == NULL) {
		git_buf_clear(buf);
	} else {
157
		if (data != buf->ptr) {
158 159
			GITERR_CHECK_ALLOC_ADD(&alloclen, len, 1);
			ENSURE_SIZE(buf, alloclen);
160 161
			memmove(buf->ptr, data, len);
		}
162

163
		buf->size = len;
164 165 166
		if (buf->asize > buf->size)
			buf->ptr[buf->size] = '\0';

167
	}
168
	return 0;
169 170
}

joshaber committed
171 172 173 174 175 176 177 178 179 180
int git_buf_is_binary(const git_buf *buf)
{
	return git_buf_text_is_binary(buf);
}

int git_buf_contains_nul(const git_buf *buf)
{
	return git_buf_text_contains_nul(buf);
}

181
int git_buf_sets(git_buf *buf, const char *string)
182
{
183
	return git_buf_set(buf, string, string ? strlen(string) : 0);
184 185
}

186
int git_buf_putc(git_buf *buf, char c)
187
{
188 189 190
	size_t new_size;
	GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, 2);
	ENSURE_SIZE(buf, new_size);
191
	buf->ptr[buf->size++] = c;
192
	buf->ptr[buf->size] = '\0';
193
	return 0;
194 195
}

196 197
int git_buf_putcn(git_buf *buf, char c, size_t len)
{
198 199 200 201
	size_t new_size;
	GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
	GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
	ENSURE_SIZE(buf, new_size);
202 203 204 205 206 207
	memset(buf->ptr + buf->size, c, len);
	buf->size += len;
	buf->ptr[buf->size] = '\0';
	return 0;
}

208
int git_buf_put(git_buf *buf, const char *data, size_t len)
209
{
210
	if (len) {
211 212
		size_t new_size;

213
		assert(data);
214 215 216 217
		
		GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
		GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
		ENSURE_SIZE(buf, new_size);
218 219 220 221
		memmove(buf->ptr + buf->size, data, len);
		buf->size += len;
		buf->ptr[buf->size] = '\0';
	}
222
	return 0;
223 224
}

225
int git_buf_puts(git_buf *buf, const char *string)
226
{
227
	assert(string);
228
	return git_buf_put(buf, string, strlen(string));
229 230
}

231
static const char base64_encode[] =
232 233
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

234
int git_buf_encode_base64(git_buf *buf, const char *data, size_t len)
235 236 237 238
{
	size_t extra = len % 3;
	uint8_t *write, a, b, c;
	const uint8_t *read = (const uint8_t *)data;
239 240 241 242 243
	size_t blocks = (len / 3) + !!extra, alloclen;

	GITERR_CHECK_ALLOC_ADD(&blocks, blocks, 1);
	GITERR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 4);
	GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size);
244

245
	ENSURE_SIZE(buf, alloclen);
246 247 248 249 250 251 252 253
	write = (uint8_t *)&buf->ptr[buf->size];

	/* convert each run of 3 bytes into 4 output bytes */
	for (len -= extra; len > 0; len -= 3) {
		a = *read++;
		b = *read++;
		c = *read++;

254 255 256 257
		*write++ = base64_encode[a >> 2];
		*write++ = base64_encode[(a & 0x03) << 4 | b >> 4];
		*write++ = base64_encode[(b & 0x0f) << 2 | c >> 6];
		*write++ = base64_encode[c & 0x3f];
258 259 260 261 262 263
	}

	if (extra > 0) {
		a = *read++;
		b = (extra > 1) ? *read++ : 0;

264 265 266
		*write++ = base64_encode[a >> 2];
		*write++ = base64_encode[(a & 0x03) << 4 | b >> 4];
		*write++ = (extra > 1) ? base64_encode[(b & 0x0f) << 2] : '=';
267 268 269 270 271 272 273 274 275
		*write++ = '=';
	}

	buf->size = ((char *)write) - buf->ptr;
	buf->ptr[buf->size] = '\0';

	return 0;
}

276
/* The inverse of base64_encode */
277
static const int8_t base64_decode[] = {
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1,  0, -1, -1,
	-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
	-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
294 295 296 297 298 299
};

int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len)
{
	size_t i;
	int8_t a, b, c, d;
300
	size_t orig_size = buf->size, new_size;
301

302 303 304 305 306
	if (len % 4) {
		giterr_set(GITERR_INVALID, "invalid base64 input");
		return -1;
	}

307
	assert(len % 4 == 0);
308 309 310
	GITERR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size);
	GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
	ENSURE_SIZE(buf, new_size);
311 312

	for (i = 0; i < len; i += 4) {
313 314 315 316
		if ((a = base64_decode[(unsigned char)base64[i]]) < 0 ||
			(b = base64_decode[(unsigned char)base64[i+1]]) < 0 ||
			(c = base64_decode[(unsigned char)base64[i+2]]) < 0 ||
			(d = base64_decode[(unsigned char)base64[i+3]]) < 0) {
317 318 319
			buf->size = orig_size;
			buf->ptr[buf->size] = '\0';

320
			giterr_set(GITERR_INVALID, "invalid base64 input");
321 322 323 324 325 326 327 328 329 330 331 332
			return -1;
		}

		buf->ptr[buf->size++] = ((a << 2) | (b & 0x30) >> 4);
		buf->ptr[buf->size++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
		buf->ptr[buf->size++] = (c & 0x03) << 6 | (d & 0x3f);
	}

	buf->ptr[buf->size] = '\0';
	return 0;
}

333
static const char base85_encode[] =
334 335
	"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";

336
int git_buf_encode_base85(git_buf *buf, const char *data, size_t len)
337
{
338
	size_t blocks = (len / 4) + !!(len % 4), alloclen;
339

340 341 342 343 344
	GITERR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 5);
	GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size);
	GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);

	ENSURE_SIZE(buf, alloclen);
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362

	while (len) {
		uint32_t acc = 0;
		char b85[5];
		int i;

		for (i = 24; i >= 0; i -= 8) {
			uint8_t ch = *data++;
			acc |= ch << i;

			if (--len == 0)
				break;
		}

		for (i = 4; i >= 0; i--) {
			int val = acc % 85;
			acc /= 85;

363
			b85[i] = base85_encode[val];
364 365 366 367 368 369 370 371 372 373 374
		}

		for (i = 0; i < 5; i++)
			buf->ptr[buf->size++] = b85[i];
	}

	buf->ptr[buf->size] = '\0';

	return 0;
}

375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
/* The inverse of base85_encode */
static const int8_t base85_decode[] = {
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, 63, -1, 64, 65, 66, 67, -1, 68, 69, 70, 71, -1, 72, -1, -1,
	 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, -1, 73, 74, 75, 76, 77,
	78, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
	26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, 79, 80,
	81, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 82, 83, 84, 85, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};

int git_buf_decode_base85(
	git_buf *buf,
	const char *base85,
	size_t base85_len,
	size_t output_len)
{
	size_t orig_size = buf->size, new_size;

	if (base85_len % 5 ||
		output_len > base85_len * 4 / 5) {
		giterr_set(GITERR_INVALID, "invalid base85 input");
		return -1;
	}

	GITERR_CHECK_ALLOC_ADD(&new_size, output_len, buf->size);
	GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
	ENSURE_SIZE(buf, new_size);

	while (output_len) {
		unsigned acc = 0;
		int de, cnt = 4;
		unsigned char ch;
		do {
			ch = *base85++;
			de = base85_decode[ch];
			if (--de < 0)
				goto on_error;

			acc = acc * 85 + de;
		} while (--cnt);
		ch = *base85++;
		de = base85_decode[ch];
		if (--de < 0)
			goto on_error;

		/* Detect overflow. */
		if (0xffffffff / 85 < acc ||
			0xffffffff - de < (acc *= 85))
			goto on_error;

		acc += de;

		cnt = (output_len < 4) ? output_len : 4;
		output_len -= cnt;
		do {
			acc = (acc << 8) | (acc >> 24);
			buf->ptr[buf->size++] = acc;
		} while (--cnt);
	}

	buf->ptr[buf->size] = 0;

	return 0;

on_error:
	buf->size = orig_size;
	buf->ptr[buf->size] = '\0';

	giterr_set(GITERR_INVALID, "invalid base85 input");
	return -1;
}

457
int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
458
{
459
	size_t expected_size, new_size;
460
	int len;
461

462 463
	GITERR_CHECK_ALLOC_MULTIPLY(&expected_size, strlen(format), 2);
	GITERR_CHECK_ALLOC_ADD(&expected_size, expected_size, buf->size);
464
	ENSURE_SIZE(buf, expected_size);
465 466

	while (1) {
467 468 469 470 471 472 473 474
		va_list args;
		va_copy(args, ap);

		len = p_vsnprintf(
			buf->ptr + buf->size,
			buf->asize - buf->size,
			format, args
		);
475

476 477
		va_end(args);

478
		if (len < 0) {
479
			git__free(buf->ptr);
480
			buf->ptr = git_buf__oom;
481
			return -1;
482 483
		}

484
		if ((size_t)len + 1 <= buf->asize - buf->size) {
485
			buf->size += len;
486
			break;
487 488
		}

489 490 491
		GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
		GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
		ENSURE_SIZE(buf, new_size);
492
	}
493

494
	return 0;
495 496
}

497 498 499 500 501 502 503 504 505 506 507 508
int git_buf_printf(git_buf *buf, const char *format, ...)
{
	int r;
	va_list ap;

	va_start(ap, format);
	r = git_buf_vprintf(buf, format, ap);
	va_end(ap);

	return r;
}

509
void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf)
510 511 512
{
	size_t copylen;

513
	assert(data && datasize && buf);
514 515 516 517 518 519 520 521 522 523 524

	data[0] = '\0';

	if (buf->size == 0 || buf->asize <= 0)
		return;

	copylen = buf->size;
	if (copylen > datasize - 1)
		copylen = datasize - 1;
	memmove(data, buf->ptr, copylen);
	data[copylen] = '\0';
525 526
}

527 528
void git_buf_consume(git_buf *buf, const char *end)
{
529 530 531 532
	if (end > buf->ptr && end <= buf->ptr + buf->size) {
		size_t consumed = end - buf->ptr;
		memmove(buf->ptr, end, buf->size - consumed);
		buf->size -= consumed;
533
		buf->ptr[buf->size] = '\0';
534 535 536
	}
}

537
void git_buf_truncate(git_buf *buf, size_t len)
538
{
539 540 541 542 543
	if (len >= buf->size)
		return;

	buf->size = len;
	if (buf->size < buf->asize)
544 545 546
		buf->ptr[buf->size] = '\0';
}

547 548
void git_buf_shorten(git_buf *buf, size_t amount)
{
549 550 551 552
	if (buf->size > amount)
		git_buf_truncate(buf, buf->size - amount);
	else
		git_buf_clear(buf);
553 554
}

555 556
void git_buf_rtruncate_at_char(git_buf *buf, char separator)
{
557 558
	ssize_t idx = git_buf_rfind_next(buf, separator);
	git_buf_truncate(buf, idx < 0 ? 0 : (size_t)idx);
559 560
}

561 562 563 564 565
void git_buf_swap(git_buf *buf_a, git_buf *buf_b)
{
	git_buf t = *buf_a;
	*buf_a = *buf_b;
	*buf_b = t;
566
}
567

568
char *git_buf_detach(git_buf *buf)
569
{
570
	char *data = buf->ptr;
571

572
	if (buf->asize == 0 || buf->ptr == git_buf__oom)
573 574
		return NULL;

575
	git_buf_init(buf, 0);
576 577 578 579

	return data;
}

580
void git_buf_attach(git_buf *buf, char *ptr, size_t asize)
581
{
582 583 584 585 586 587 588 589 590 591 592 593 594
	git_buf_free(buf);

	if (ptr) {
		buf->ptr = ptr;
		buf->size = strlen(ptr);
		if (asize)
			buf->asize = (asize < buf->size) ? buf->size + 1 : asize;
		else /* pass 0 to fall back on strlen + 1 */
			buf->asize = buf->size + 1;
	} else {
		git_buf_grow(buf, asize);
	}
}
595

596 597 598 599 600 601 602 603 604 605 606 607 608 609
void git_buf_attach_notowned(git_buf *buf, const char *ptr, size_t size)
{
	if (git_buf_is_allocated(buf))
		git_buf_free(buf);

	if (!size) {
		git_buf_init(buf, 0);
	} else {
		buf->ptr = (char *)ptr;
		buf->asize = 0;
		buf->size = size;
	}
}

610 611
int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...)
{
612
	va_list ap;
613
	int i;
614 615
	size_t total_size = 0, original_size = buf->size;
	char *out, *original = buf->ptr;
616 617 618 619

	if (buf->size > 0 && buf->ptr[buf->size - 1] != separator)
		++total_size; /* space for initial separator */

620 621
	/* Make two passes to avoid multiple reallocation */

622 623 624
	va_start(ap, nbuf);
	for (i = 0; i < nbuf; ++i) {
		const char* segment;
625
		size_t segment_len;
626 627 628 629 630 631

		segment = va_arg(ap, const char *);
		if (!segment)
			continue;

		segment_len = strlen(segment);
632 633 634

		GITERR_CHECK_ALLOC_ADD(&total_size, total_size, segment_len);

635
		if (segment_len == 0 || segment[segment_len - 1] != separator)
636
			GITERR_CHECK_ALLOC_ADD(&total_size, total_size, 1);
637 638 639
	}
	va_end(ap);

640
	/* expand buffer if needed */
641 642
	if (total_size == 0)
		return 0;
643

644 645
	GITERR_CHECK_ALLOC_ADD(&total_size, total_size, 1);
	if (git_buf_grow_by(buf, total_size) < 0)
646
		return -1;
647 648 649 650 651 652 653 654 655 656

	out = buf->ptr + buf->size;

	/* append separator to existing buf if needed */
	if (buf->size > 0 && out[-1] != separator)
		*out++ = separator;

	va_start(ap, nbuf);
	for (i = 0; i < nbuf; ++i) {
		const char* segment;
657
		size_t segment_len;
658 659 660 661 662

		segment = va_arg(ap, const char *);
		if (!segment)
			continue;

663 664 665 666 667 668 669 670 671
		/* deal with join that references buffer's original content */
		if (segment >= original && segment < original + original_size) {
			size_t offset = (segment - original);
			segment = buf->ptr + offset;
			segment_len = original_size - offset;
		} else {
			segment_len = strlen(segment);
		}

672 673
		/* skip leading separators */
		if (out > buf->ptr && out[-1] == separator)
674 675 676 677
			while (segment_len > 0 && *segment == separator) {
				segment++;
				segment_len--;
			}
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692

		/* copy over next buffer */
		if (segment_len > 0) {
			memmove(out, segment, segment_len);
			out += segment_len;
		}

		/* append trailing separator (except for last item) */
		if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator)
			*out++ = separator;
	}
	va_end(ap);

	/* set size based on num characters actually written */
	buf->size = out - buf->ptr;
693
	buf->ptr[buf->size] = '\0';
694

695
	return 0;
696 697
}

698
int git_buf_join(
699 700 701 702 703
	git_buf *buf,
	char separator,
	const char *str_a,
	const char *str_b)
{
704
	size_t strlen_a = str_a ? strlen(str_a) : 0;
705
	size_t strlen_b = strlen(str_b);
706
	size_t alloc_len;
707
	int need_sep = 0;
708 709 710
	ssize_t offset_a = -1;

	/* not safe to have str_b point internally to the buffer */
711
	assert(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
712 713 714 715 716 717

	/* figure out if we need to insert a separator */
	if (separator && strlen_a) {
		while (*str_b == separator) { str_b++; strlen_b--; }
		if (str_a[strlen_a - 1] != separator)
			need_sep = 1;
718 719
	}

720 721 722 723
	/* str_a could be part of the buffer */
	if (str_a >= buf->ptr && str_a < buf->ptr + buf->size)
		offset_a = str_a - buf->ptr;

724 725 726 727
	GITERR_CHECK_ALLOC_ADD(&alloc_len, strlen_a, strlen_b);
	GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, need_sep);
	GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1);
	if (git_buf_grow(buf, alloc_len) < 0)
728
		return -1;
729
	assert(buf->ptr);
730

731 732 733 734 735
	/* fix up internal pointers */
	if (offset_a >= 0)
		str_a = buf->ptr + offset_a;

	/* do the actual copying */
736
	if (offset_a != 0 && str_a)
737
		memmove(buf->ptr, str_a, strlen_a);
738 739
	if (need_sep)
		buf->ptr[strlen_a] = separator;
740
	memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b);
741

742
	buf->size = strlen_a + strlen_b + need_sep;
743
	buf->ptr[buf->size] = '\0';
744

745
	return 0;
746
}
747

748 749 750 751 752 753 754
int git_buf_join3(
	git_buf *buf,
	char separator,
	const char *str_a,
	const char *str_b,
	const char *str_c)
{
755 756 757 758
	size_t len_a = strlen(str_a),
		len_b = strlen(str_b),
		len_c = strlen(str_c),
		len_total;
759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777
	int sep_a = 0, sep_b = 0;
	char *tgt;

	/* for this function, disallow pointers into the existing buffer */
	assert(str_a < buf->ptr || str_a >= buf->ptr + buf->size);
	assert(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
	assert(str_c < buf->ptr || str_c >= buf->ptr + buf->size);

	if (separator) {
		if (len_a > 0) {
			while (*str_b == separator) { str_b++; len_b--; }
			sep_a = (str_a[len_a - 1] != separator);
		}
		if (len_a > 0 || len_b > 0)
			while (*str_c == separator) { str_c++; len_c--; }
		if (len_b > 0)
			sep_b = (str_b[len_b - 1] != separator);
	}

778 779 780 781 782 783
	GITERR_CHECK_ALLOC_ADD(&len_total, len_a, sep_a);
	GITERR_CHECK_ALLOC_ADD(&len_total, len_total, len_b);
	GITERR_CHECK_ALLOC_ADD(&len_total, len_total, sep_b);
	GITERR_CHECK_ALLOC_ADD(&len_total, len_total, len_c);
	GITERR_CHECK_ALLOC_ADD(&len_total, len_total, 1);
	if (git_buf_grow(buf, len_total) < 0)
784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808
		return -1;

	tgt = buf->ptr;

	if (len_a) {
		memcpy(tgt, str_a, len_a);
		tgt += len_a;
	}
	if (sep_a)
		*tgt++ = separator;
	if (len_b) {
		memcpy(tgt, str_b, len_b);
		tgt += len_b;
	}
	if (sep_b)
		*tgt++ = separator;
	if (len_c)
		memcpy(tgt, str_c, len_c);

	buf->size = len_a + sep_a + len_b + sep_b + len_c;
	buf->ptr[buf->size] = '\0';

	return 0;
}

809 810 811
void git_buf_rtrim(git_buf *buf)
{
	while (buf->size > 0) {
812
		if (!git__isspace(buf->ptr[buf->size - 1]))
813 814 815 816
			break;

		buf->size--;
	}
817

818 819
	if (buf->asize > buf->size)
		buf->ptr[buf->size] = '\0';
820
}
821 822 823 824 825 826 827

int git_buf_cmp(const git_buf *a, const git_buf *b)
{
	int result = memcmp(a->ptr, b->ptr, min(a->size, b->size));
	return (result != 0) ? result :
		(a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0;
}
828

829 830 831 832 833 834 835
int git_buf_splice(
	git_buf *buf,
	size_t where,
	size_t nb_to_remove,
	const char *data,
	size_t nb_to_insert)
{
836 837
	char *splice_loc;
	size_t new_size, alloc_size;
838

839 840 841
	assert(buf && where <= buf->size && nb_to_remove <= buf->size - where);

	splice_loc = buf->ptr + where;
842 843 844 845

	/* Ported from git.git
	 * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176
	 */
846 847 848
	GITERR_CHECK_ALLOC_ADD(&new_size, (buf->size - nb_to_remove), nb_to_insert);
	GITERR_CHECK_ALLOC_ADD(&alloc_size, new_size, 1);
	ENSURE_SIZE(buf, alloc_size);
849

850 851 852
	memmove(splice_loc + nb_to_insert,
		splice_loc + nb_to_remove,
		buf->size - where - nb_to_remove);
853

854
	memcpy(splice_loc, data, nb_to_insert);
855

856
	buf->size = new_size;
857 858 859
	buf->ptr[buf->size] = '\0';
	return 0;
}
860

861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903
/* Quote per http://marc.info/?l=git&m=112927316408690&w=2 */
int git_buf_quote(git_buf *buf)
{
	const char whitespace[] = { 'a', 'b', 't', 'n', 'v', 'f', 'r' };
	git_buf quoted = GIT_BUF_INIT;
	size_t i = 0;
	bool quote = false;
	int error = 0;

	/* walk to the first char that needs quoting */
	if (buf->size && buf->ptr[0] == '!')
		quote = true;

	for (i = 0; !quote && i < buf->size; i++) {
		if (buf->ptr[i] == '"' || buf->ptr[i] == '\\' ||
			buf->ptr[i] < ' ' || buf->ptr[i] > '~') {
			quote = true;
			break;
		}
	}

	if (!quote)
		goto done;

	git_buf_putc(&quoted, '"');
	git_buf_put(&quoted, buf->ptr, i);

	for (; i < buf->size; i++) {
		/* whitespace - use the map above, which is ordered by ascii value */
		if (buf->ptr[i] >= '\a' && buf->ptr[i] <= '\r') {
			git_buf_putc(&quoted, '\\');
			git_buf_putc(&quoted, whitespace[buf->ptr[i] - '\a']);
		}

		/* double quote and backslash must be escaped */
		else if (buf->ptr[i] == '"' || buf->ptr[i] == '\\') {
			git_buf_putc(&quoted, '\\');
			git_buf_putc(&quoted, buf->ptr[i]);
		}

		/* escape anything unprintable as octal */
		else if (buf->ptr[i] != ' ' &&
				(buf->ptr[i] < '!' || buf->ptr[i] > '~')) {
904
			git_buf_printf(&quoted, "\\%03o", (unsigned char)buf->ptr[i]);
905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926
		}

		/* yay, printable! */
		else {
			git_buf_putc(&quoted, buf->ptr[i]);
		}
	}

	git_buf_putc(&quoted, '"');

	if (git_buf_oom(&quoted)) {
		error = -1;
		goto done;
	}

	git_buf_swap(&quoted, buf);

done:
	git_buf_free(&quoted);
	return error;
}

927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961
/* Unquote per http://marc.info/?l=git&m=112927316408690&w=2 */
int git_buf_unquote(git_buf *buf)
{
	size_t i, j;
	char ch;

	git_buf_rtrim(buf);

	if (buf->size < 2 || buf->ptr[0] != '"' || buf->ptr[buf->size-1] != '"')
		goto invalid;

	for (i = 0, j = 1; j < buf->size-1; i++, j++) {
		ch = buf->ptr[j];

		if (ch == '\\') {
			if (j == buf->size-2)
				goto invalid;

			ch = buf->ptr[++j];

			switch (ch) {
			/* \" or \\ simply copy the char in */
			case '"': case '\\':
				break;

			/* add the appropriate escaped char */
			case 'a': ch = '\a'; break;
			case 'b': ch = '\b'; break;
			case 'f': ch = '\f'; break;
			case 'n': ch = '\n'; break;
			case 'r': ch = '\r'; break;
			case 't': ch = '\t'; break;
			case 'v': ch = '\v'; break;

			/* \xyz digits convert to the char*/
962
			case '0': case '1': case '2': case '3':
963 964
				if (j == buf->size-3) {
					giterr_set(GITERR_INVALID,
965
						"truncated quoted character \\%c", ch);
966 967 968 969 970 971
					return -1;
				}

				if (buf->ptr[j+1] < '0' || buf->ptr[j+1] > '7' ||
					buf->ptr[j+2] < '0' || buf->ptr[j+2] > '7') {
					giterr_set(GITERR_INVALID,
972
						"truncated quoted character \\%c%c%c",
973 974 975 976 977 978 979 980 981 982 983
						buf->ptr[j], buf->ptr[j+1], buf->ptr[j+2]);
					return -1;
				}

				ch = ((buf->ptr[j] - '0') << 6) |
					((buf->ptr[j+1] - '0') << 3) |
					(buf->ptr[j+2] - '0');
				j += 2;
				break;

			default:
984
				giterr_set(GITERR_INVALID, "invalid quoted character \\%c", ch);
985 986 987 988 989 990 991 992 993 994 995 996 997
				return -1;
			}
		}

		buf->ptr[i] = ch;
	}

	buf->ptr[i] = '\0';
	buf->size = i;

	return 0;

invalid:
998
	giterr_set(GITERR_INVALID, "invalid quoted line");
999 1000
	return -1;
}