1 /*
2  * Copyright (C) the libgit2 contributors. All rights reserved.
3  *
4  * This file is part of libgit2, distributed under the GNU GPL v2 with
5  * a Linking Exception. For full terms see the included COPYING file.
6  */
7 #include "buffer.h"
8 #include "posix.h"
9 #include "git2/buffer.h"
10 #include "buf_text.h"
11 #include <ctype.h>
12 
13 /* Used as default value for git_buf->ptr so that people can always
14  * assume ptr is non-NULL and zero terminated even for new git_bufs.
15  */
16 char git_buf__initbuf[1];
17 
18 char git_buf__oom[1];
19 
20 #define ENSURE_SIZE(b, d) \
21 	if ((b)->ptr == git_buf__oom || \
22 	    ((d) > (b)->asize && git_buf_grow((b), (d)) < 0))\
23 		return -1;
24 
25 
git_buf_init(git_buf * buf,size_t initial_size)26 int git_buf_init(git_buf *buf, size_t initial_size)
27 {
28 	buf->asize = 0;
29 	buf->size = 0;
30 	buf->ptr = git_buf__initbuf;
31 
32 	ENSURE_SIZE(buf, initial_size);
33 
34 	return 0;
35 }
36 
git_buf_try_grow(git_buf * buf,size_t target_size,bool mark_oom)37 int git_buf_try_grow(
38 	git_buf *buf, size_t target_size, bool mark_oom)
39 {
40 	char *new_ptr;
41 	size_t new_size;
42 
43 	if (buf->ptr == git_buf__oom)
44 		return -1;
45 
46 	if (buf->asize == 0 && buf->size != 0) {
47 		git_error_set(GIT_ERROR_INVALID, "cannot grow a borrowed buffer");
48 		return GIT_EINVALID;
49 	}
50 
51 	if (!target_size)
52 		target_size = buf->size;
53 
54 	if (target_size <= buf->asize)
55 		return 0;
56 
57 	if (buf->asize == 0) {
58 		new_size = target_size;
59 		new_ptr = NULL;
60 	} else {
61 		new_size = buf->asize;
62 		/*
63 		 * Grow the allocated buffer by 1.5 to allow
64 		 * re-use of memory holes resulting from the
65 		 * realloc. If this is still too small, then just
66 		 * use the target size.
67 		 */
68 		if ((new_size = (new_size << 1) - (new_size >> 1)) < target_size)
69 			new_size = target_size;
70 		new_ptr = buf->ptr;
71 	}
72 
73 	/* round allocation up to multiple of 8 */
74 	new_size = (new_size + 7) & ~7;
75 
76 	if (new_size < buf->size) {
77 		if (mark_oom) {
78 			if (buf->ptr && buf->ptr != git_buf__initbuf)
79 				git__free(buf->ptr);
80 			buf->ptr = git_buf__oom;
81 		}
82 
83 		git_error_set_oom();
84 		return -1;
85 	}
86 
87 	new_ptr = git__realloc(new_ptr, new_size);
88 
89 	if (!new_ptr) {
90 		if (mark_oom) {
91 			if (buf->ptr && (buf->ptr != git_buf__initbuf))
92 				git__free(buf->ptr);
93 			buf->ptr = git_buf__oom;
94 		}
95 		return -1;
96 	}
97 
98 	buf->asize = new_size;
99 	buf->ptr   = new_ptr;
100 
101 	/* truncate the existing buffer size if necessary */
102 	if (buf->size >= buf->asize)
103 		buf->size = buf->asize - 1;
104 	buf->ptr[buf->size] = '\0';
105 
106 	return 0;
107 }
108 
git_buf_grow(git_buf * buffer,size_t target_size)109 int git_buf_grow(git_buf *buffer, size_t target_size)
110 {
111 	return git_buf_try_grow(buffer, target_size, true);
112 }
113 
git_buf_grow_by(git_buf * buffer,size_t additional_size)114 int git_buf_grow_by(git_buf *buffer, size_t additional_size)
115 {
116 	size_t newsize;
117 
118 	if (GIT_ADD_SIZET_OVERFLOW(&newsize, buffer->size, additional_size)) {
119 		buffer->ptr = git_buf__oom;
120 		return -1;
121 	}
122 
123 	return git_buf_try_grow(buffer, newsize, true);
124 }
125 
git_buf_dispose(git_buf * buf)126 void git_buf_dispose(git_buf *buf)
127 {
128 	if (!buf) return;
129 
130 	if (buf->asize > 0 && buf->ptr != NULL && buf->ptr != git_buf__oom)
131 		git__free(buf->ptr);
132 
133 	git_buf_init(buf, 0);
134 }
135 
git_buf_free(git_buf * buf)136 void git_buf_free(git_buf *buf)
137 {
138 	git_buf_dispose(buf);
139 }
140 
git_buf_sanitize(git_buf * buf)141 void git_buf_sanitize(git_buf *buf)
142 {
143 	if (buf->ptr == NULL) {
144 		assert(buf->size == 0 && buf->asize == 0);
145 		buf->ptr = git_buf__initbuf;
146 	} else if (buf->asize > buf->size)
147 		buf->ptr[buf->size] = '\0';
148 }
149 
git_buf_clear(git_buf * buf)150 void git_buf_clear(git_buf *buf)
151 {
152 	buf->size = 0;
153 
154 	if (!buf->ptr) {
155 		buf->ptr = git_buf__initbuf;
156 		buf->asize = 0;
157 	}
158 
159 	if (buf->asize > 0)
160 		buf->ptr[0] = '\0';
161 }
162 
git_buf_set(git_buf * buf,const void * data,size_t len)163 int git_buf_set(git_buf *buf, const void *data, size_t len)
164 {
165 	size_t alloclen;
166 
167 	if (len == 0 || data == NULL) {
168 		git_buf_clear(buf);
169 	} else {
170 		if (data != buf->ptr) {
171 			GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1);
172 			ENSURE_SIZE(buf, alloclen);
173 			memmove(buf->ptr, data, len);
174 		}
175 
176 		buf->size = len;
177 		if (buf->asize > buf->size)
178 			buf->ptr[buf->size] = '\0';
179 
180 	}
181 	return 0;
182 }
183 
git_buf_is_binary(const git_buf * buf)184 int git_buf_is_binary(const git_buf *buf)
185 {
186 	return git_buf_text_is_binary(buf);
187 }
188 
git_buf_contains_nul(const git_buf * buf)189 int git_buf_contains_nul(const git_buf *buf)
190 {
191 	return git_buf_text_contains_nul(buf);
192 }
193 
git_buf_sets(git_buf * buf,const char * string)194 int git_buf_sets(git_buf *buf, const char *string)
195 {
196 	return git_buf_set(buf, string, string ? strlen(string) : 0);
197 }
198 
git_buf_putc(git_buf * buf,char c)199 int git_buf_putc(git_buf *buf, char c)
200 {
201 	size_t new_size;
202 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, 2);
203 	ENSURE_SIZE(buf, new_size);
204 	buf->ptr[buf->size++] = c;
205 	buf->ptr[buf->size] = '\0';
206 	return 0;
207 }
208 
git_buf_putcn(git_buf * buf,char c,size_t len)209 int git_buf_putcn(git_buf *buf, char c, size_t len)
210 {
211 	size_t new_size;
212 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
213 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
214 	ENSURE_SIZE(buf, new_size);
215 	memset(buf->ptr + buf->size, c, len);
216 	buf->size += len;
217 	buf->ptr[buf->size] = '\0';
218 	return 0;
219 }
220 
git_buf_put(git_buf * buf,const char * data,size_t len)221 int git_buf_put(git_buf *buf, const char *data, size_t len)
222 {
223 	if (len) {
224 		size_t new_size;
225 
226 		assert(data);
227 
228 		GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
229 		GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
230 		ENSURE_SIZE(buf, new_size);
231 		memmove(buf->ptr + buf->size, data, len);
232 		buf->size += len;
233 		buf->ptr[buf->size] = '\0';
234 	}
235 	return 0;
236 }
237 
git_buf_puts(git_buf * buf,const char * string)238 int git_buf_puts(git_buf *buf, const char *string)
239 {
240 	assert(string);
241 	return git_buf_put(buf, string, strlen(string));
242 }
243 
244 static const char base64_encode[] =
245 	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
246 
git_buf_encode_base64(git_buf * buf,const char * data,size_t len)247 int git_buf_encode_base64(git_buf *buf, const char *data, size_t len)
248 {
249 	size_t extra = len % 3;
250 	uint8_t *write, a, b, c;
251 	const uint8_t *read = (const uint8_t *)data;
252 	size_t blocks = (len / 3) + !!extra, alloclen;
253 
254 	GIT_ERROR_CHECK_ALLOC_ADD(&blocks, blocks, 1);
255 	GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 4);
256 	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size);
257 
258 	ENSURE_SIZE(buf, alloclen);
259 	write = (uint8_t *)&buf->ptr[buf->size];
260 
261 	/* convert each run of 3 bytes into 4 output bytes */
262 	for (len -= extra; len > 0; len -= 3) {
263 		a = *read++;
264 		b = *read++;
265 		c = *read++;
266 
267 		*write++ = base64_encode[a >> 2];
268 		*write++ = base64_encode[(a & 0x03) << 4 | b >> 4];
269 		*write++ = base64_encode[(b & 0x0f) << 2 | c >> 6];
270 		*write++ = base64_encode[c & 0x3f];
271 	}
272 
273 	if (extra > 0) {
274 		a = *read++;
275 		b = (extra > 1) ? *read++ : 0;
276 
277 		*write++ = base64_encode[a >> 2];
278 		*write++ = base64_encode[(a & 0x03) << 4 | b >> 4];
279 		*write++ = (extra > 1) ? base64_encode[(b & 0x0f) << 2] : '=';
280 		*write++ = '=';
281 	}
282 
283 	buf->size = ((char *)write) - buf->ptr;
284 	buf->ptr[buf->size] = '\0';
285 
286 	return 0;
287 }
288 
289 /* The inverse of base64_encode */
290 static const int8_t base64_decode[] = {
291 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
292 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
293 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
294 	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1,  0, -1, -1,
295 	-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
296 	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
297 	-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
298 	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
299 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
300 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
301 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
302 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
303 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
304 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
305 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
306 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
307 };
308 
git_buf_decode_base64(git_buf * buf,const char * base64,size_t len)309 int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len)
310 {
311 	size_t i;
312 	int8_t a, b, c, d;
313 	size_t orig_size = buf->size, new_size;
314 
315 	if (len % 4) {
316 		git_error_set(GIT_ERROR_INVALID, "invalid base64 input");
317 		return -1;
318 	}
319 
320 	assert(len % 4 == 0);
321 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size);
322 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
323 	ENSURE_SIZE(buf, new_size);
324 
325 	for (i = 0; i < len; i += 4) {
326 		if ((a = base64_decode[(unsigned char)base64[i]]) < 0 ||
327 			(b = base64_decode[(unsigned char)base64[i+1]]) < 0 ||
328 			(c = base64_decode[(unsigned char)base64[i+2]]) < 0 ||
329 			(d = base64_decode[(unsigned char)base64[i+3]]) < 0) {
330 			buf->size = orig_size;
331 			buf->ptr[buf->size] = '\0';
332 
333 			git_error_set(GIT_ERROR_INVALID, "invalid base64 input");
334 			return -1;
335 		}
336 
337 		buf->ptr[buf->size++] = ((a << 2) | (b & 0x30) >> 4);
338 		buf->ptr[buf->size++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
339 		buf->ptr[buf->size++] = (c & 0x03) << 6 | (d & 0x3f);
340 	}
341 
342 	buf->ptr[buf->size] = '\0';
343 	return 0;
344 }
345 
346 static const char base85_encode[] =
347 	"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
348 
git_buf_encode_base85(git_buf * buf,const char * data,size_t len)349 int git_buf_encode_base85(git_buf *buf, const char *data, size_t len)
350 {
351 	size_t blocks = (len / 4) + !!(len % 4), alloclen;
352 
353 	GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 5);
354 	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size);
355 	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
356 
357 	ENSURE_SIZE(buf, alloclen);
358 
359 	while (len) {
360 		uint32_t acc = 0;
361 		char b85[5];
362 		int i;
363 
364 		for (i = 24; i >= 0; i -= 8) {
365 			uint8_t ch = *data++;
366 			acc |= ch << i;
367 
368 			if (--len == 0)
369 				break;
370 		}
371 
372 		for (i = 4; i >= 0; i--) {
373 			int val = acc % 85;
374 			acc /= 85;
375 
376 			b85[i] = base85_encode[val];
377 		}
378 
379 		for (i = 0; i < 5; i++)
380 			buf->ptr[buf->size++] = b85[i];
381 	}
382 
383 	buf->ptr[buf->size] = '\0';
384 
385 	return 0;
386 }
387 
388 /* The inverse of base85_encode */
389 static const int8_t base85_decode[] = {
390 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
391 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
392 	-1, 63, -1, 64, 65, 66, 67, -1, 68, 69, 70, 71, -1, 72, -1, -1,
393 	 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, -1, 73, 74, 75, 76, 77,
394 	78, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
395 	26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, 79, 80,
396 	81, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
397 	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 82, 83, 84, 85, -1,
398 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
399 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
400 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
401 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
402 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
403 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
404 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
405 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
406 };
407 
git_buf_decode_base85(git_buf * buf,const char * base85,size_t base85_len,size_t output_len)408 int git_buf_decode_base85(
409 	git_buf *buf,
410 	const char *base85,
411 	size_t base85_len,
412 	size_t output_len)
413 {
414 	size_t orig_size = buf->size, new_size;
415 
416 	if (base85_len % 5 ||
417 		output_len > base85_len * 4 / 5) {
418 		git_error_set(GIT_ERROR_INVALID, "invalid base85 input");
419 		return -1;
420 	}
421 
422 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, output_len, buf->size);
423 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
424 	ENSURE_SIZE(buf, new_size);
425 
426 	while (output_len) {
427 		unsigned acc = 0;
428 		int de, cnt = 4;
429 		unsigned char ch;
430 		do {
431 			ch = *base85++;
432 			de = base85_decode[ch];
433 			if (--de < 0)
434 				goto on_error;
435 
436 			acc = acc * 85 + de;
437 		} while (--cnt);
438 		ch = *base85++;
439 		de = base85_decode[ch];
440 		if (--de < 0)
441 			goto on_error;
442 
443 		/* Detect overflow. */
444 		if (0xffffffff / 85 < acc ||
445 			0xffffffff - de < (acc *= 85))
446 			goto on_error;
447 
448 		acc += de;
449 
450 		cnt = (output_len < 4) ? (int)output_len : 4;
451 		output_len -= cnt;
452 		do {
453 			acc = (acc << 8) | (acc >> 24);
454 			buf->ptr[buf->size++] = acc;
455 		} while (--cnt);
456 	}
457 
458 	buf->ptr[buf->size] = 0;
459 
460 	return 0;
461 
462 on_error:
463 	buf->size = orig_size;
464 	buf->ptr[buf->size] = '\0';
465 
466 	git_error_set(GIT_ERROR_INVALID, "invalid base85 input");
467 	return -1;
468 }
469 
470 #define HEX_DECODE(c) ((c | 32) % 39 - 9)
471 
git_buf_decode_percent(git_buf * buf,const char * str,size_t str_len)472 int git_buf_decode_percent(
473 	git_buf *buf,
474 	const char *str,
475 	size_t str_len)
476 {
477 	size_t str_pos, new_size;
478 
479 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, str_len);
480 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
481 	ENSURE_SIZE(buf, new_size);
482 
483 	for (str_pos = 0; str_pos < str_len; buf->size++, str_pos++) {
484 		if (str[str_pos] == '%' &&
485 			str_len > str_pos + 2 &&
486 			isxdigit(str[str_pos + 1]) &&
487 			isxdigit(str[str_pos + 2])) {
488 			buf->ptr[buf->size] = (HEX_DECODE(str[str_pos + 1]) << 4) +
489 				HEX_DECODE(str[str_pos + 2]);
490 			str_pos += 2;
491 		} else {
492 			buf->ptr[buf->size] = str[str_pos];
493 		}
494 	}
495 
496 	buf->ptr[buf->size] = '\0';
497 	return 0;
498 }
499 
git_buf_vprintf(git_buf * buf,const char * format,va_list ap)500 int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
501 {
502 	size_t expected_size, new_size;
503 	int len;
504 
505 	GIT_ERROR_CHECK_ALLOC_MULTIPLY(&expected_size, strlen(format), 2);
506 	GIT_ERROR_CHECK_ALLOC_ADD(&expected_size, expected_size, buf->size);
507 	ENSURE_SIZE(buf, expected_size);
508 
509 	while (1) {
510 		va_list args;
511 		va_copy(args, ap);
512 
513 		len = p_vsnprintf(
514 			buf->ptr + buf->size,
515 			buf->asize - buf->size,
516 			format, args
517 		);
518 
519 		va_end(args);
520 
521 		if (len < 0) {
522 			git__free(buf->ptr);
523 			buf->ptr = git_buf__oom;
524 			return -1;
525 		}
526 
527 		if ((size_t)len + 1 <= buf->asize - buf->size) {
528 			buf->size += len;
529 			break;
530 		}
531 
532 		GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
533 		GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
534 		ENSURE_SIZE(buf, new_size);
535 	}
536 
537 	return 0;
538 }
539 
git_buf_printf(git_buf * buf,const char * format,...)540 int git_buf_printf(git_buf *buf, const char *format, ...)
541 {
542 	int r;
543 	va_list ap;
544 
545 	va_start(ap, format);
546 	r = git_buf_vprintf(buf, format, ap);
547 	va_end(ap);
548 
549 	return r;
550 }
551 
git_buf_copy_cstr(char * data,size_t datasize,const git_buf * buf)552 void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf)
553 {
554 	size_t copylen;
555 
556 	assert(data && datasize && buf);
557 
558 	data[0] = '\0';
559 
560 	if (buf->size == 0 || buf->asize <= 0)
561 		return;
562 
563 	copylen = buf->size;
564 	if (copylen > datasize - 1)
565 		copylen = datasize - 1;
566 	memmove(data, buf->ptr, copylen);
567 	data[copylen] = '\0';
568 }
569 
git_buf_consume_bytes(git_buf * buf,size_t len)570 void git_buf_consume_bytes(git_buf *buf, size_t len)
571 {
572 	git_buf_consume(buf, buf->ptr + len);
573 }
574 
git_buf_consume(git_buf * buf,const char * end)575 void git_buf_consume(git_buf *buf, const char *end)
576 {
577 	if (end > buf->ptr && end <= buf->ptr + buf->size) {
578 		size_t consumed = end - buf->ptr;
579 		memmove(buf->ptr, end, buf->size - consumed);
580 		buf->size -= consumed;
581 		buf->ptr[buf->size] = '\0';
582 	}
583 }
584 
git_buf_truncate(git_buf * buf,size_t len)585 void git_buf_truncate(git_buf *buf, size_t len)
586 {
587 	if (len >= buf->size)
588 		return;
589 
590 	buf->size = len;
591 	if (buf->size < buf->asize)
592 		buf->ptr[buf->size] = '\0';
593 }
594 
git_buf_shorten(git_buf * buf,size_t amount)595 void git_buf_shorten(git_buf *buf, size_t amount)
596 {
597 	if (buf->size > amount)
598 		git_buf_truncate(buf, buf->size - amount);
599 	else
600 		git_buf_clear(buf);
601 }
602 
git_buf_rtruncate_at_char(git_buf * buf,char separator)603 void git_buf_rtruncate_at_char(git_buf *buf, char separator)
604 {
605 	ssize_t idx = git_buf_rfind_next(buf, separator);
606 	git_buf_truncate(buf, idx < 0 ? 0 : (size_t)idx);
607 }
608 
git_buf_swap(git_buf * buf_a,git_buf * buf_b)609 void git_buf_swap(git_buf *buf_a, git_buf *buf_b)
610 {
611 	git_buf t = *buf_a;
612 	*buf_a = *buf_b;
613 	*buf_b = t;
614 }
615 
git_buf_detach(git_buf * buf)616 char *git_buf_detach(git_buf *buf)
617 {
618 	char *data = buf->ptr;
619 
620 	if (buf->asize == 0 || buf->ptr == git_buf__oom)
621 		return NULL;
622 
623 	git_buf_init(buf, 0);
624 
625 	return data;
626 }
627 
git_buf_attach(git_buf * buf,char * ptr,size_t asize)628 int git_buf_attach(git_buf *buf, char *ptr, size_t asize)
629 {
630 	git_buf_dispose(buf);
631 
632 	if (ptr) {
633 		buf->ptr = ptr;
634 		buf->size = strlen(ptr);
635 		if (asize)
636 			buf->asize = (asize < buf->size) ? buf->size + 1 : asize;
637 		else /* pass 0 to fall back on strlen + 1 */
638 			buf->asize = buf->size + 1;
639 	}
640 
641 	ENSURE_SIZE(buf, asize);
642 	return 0;
643 }
644 
git_buf_attach_notowned(git_buf * buf,const char * ptr,size_t size)645 void git_buf_attach_notowned(git_buf *buf, const char *ptr, size_t size)
646 {
647 	if (git_buf_is_allocated(buf))
648 		git_buf_dispose(buf);
649 
650 	if (!size) {
651 		git_buf_init(buf, 0);
652 	} else {
653 		buf->ptr = (char *)ptr;
654 		buf->asize = 0;
655 		buf->size = size;
656 	}
657 }
658 
git_buf_join_n(git_buf * buf,char separator,int nbuf,...)659 int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...)
660 {
661 	va_list ap;
662 	int i;
663 	size_t total_size = 0, original_size = buf->size;
664 	char *out, *original = buf->ptr;
665 
666 	if (buf->size > 0 && buf->ptr[buf->size - 1] != separator)
667 		++total_size; /* space for initial separator */
668 
669 	/* Make two passes to avoid multiple reallocation */
670 
671 	va_start(ap, nbuf);
672 	for (i = 0; i < nbuf; ++i) {
673 		const char* segment;
674 		size_t segment_len;
675 
676 		segment = va_arg(ap, const char *);
677 		if (!segment)
678 			continue;
679 
680 		segment_len = strlen(segment);
681 
682 		GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, segment_len);
683 
684 		if (segment_len == 0 || segment[segment_len - 1] != separator)
685 			GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1);
686 	}
687 	va_end(ap);
688 
689 	/* expand buffer if needed */
690 	if (total_size == 0)
691 		return 0;
692 
693 	GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1);
694 	if (git_buf_grow_by(buf, total_size) < 0)
695 		return -1;
696 
697 	out = buf->ptr + buf->size;
698 
699 	/* append separator to existing buf if needed */
700 	if (buf->size > 0 && out[-1] != separator)
701 		*out++ = separator;
702 
703 	va_start(ap, nbuf);
704 	for (i = 0; i < nbuf; ++i) {
705 		const char* segment;
706 		size_t segment_len;
707 
708 		segment = va_arg(ap, const char *);
709 		if (!segment)
710 			continue;
711 
712 		/* deal with join that references buffer's original content */
713 		if (segment >= original && segment < original + original_size) {
714 			size_t offset = (segment - original);
715 			segment = buf->ptr + offset;
716 			segment_len = original_size - offset;
717 		} else {
718 			segment_len = strlen(segment);
719 		}
720 
721 		/* skip leading separators */
722 		if (out > buf->ptr && out[-1] == separator)
723 			while (segment_len > 0 && *segment == separator) {
724 				segment++;
725 				segment_len--;
726 			}
727 
728 		/* copy over next buffer */
729 		if (segment_len > 0) {
730 			memmove(out, segment, segment_len);
731 			out += segment_len;
732 		}
733 
734 		/* append trailing separator (except for last item) */
735 		if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator)
736 			*out++ = separator;
737 	}
738 	va_end(ap);
739 
740 	/* set size based on num characters actually written */
741 	buf->size = out - buf->ptr;
742 	buf->ptr[buf->size] = '\0';
743 
744 	return 0;
745 }
746 
git_buf_join(git_buf * buf,char separator,const char * str_a,const char * str_b)747 int git_buf_join(
748 	git_buf *buf,
749 	char separator,
750 	const char *str_a,
751 	const char *str_b)
752 {
753 	size_t strlen_a = str_a ? strlen(str_a) : 0;
754 	size_t strlen_b = strlen(str_b);
755 	size_t alloc_len;
756 	int need_sep = 0;
757 	ssize_t offset_a = -1;
758 
759 	/* not safe to have str_b point internally to the buffer */
760 	assert(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
761 
762 	/* figure out if we need to insert a separator */
763 	if (separator && strlen_a) {
764 		while (*str_b == separator) { str_b++; strlen_b--; }
765 		if (str_a[strlen_a - 1] != separator)
766 			need_sep = 1;
767 	}
768 
769 	/* str_a could be part of the buffer */
770 	if (str_a >= buf->ptr && str_a < buf->ptr + buf->size)
771 		offset_a = str_a - buf->ptr;
772 
773 	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, strlen_a, strlen_b);
774 	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, need_sep);
775 	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1);
776 	ENSURE_SIZE(buf, alloc_len);
777 
778 	/* fix up internal pointers */
779 	if (offset_a >= 0)
780 		str_a = buf->ptr + offset_a;
781 
782 	/* do the actual copying */
783 	if (offset_a != 0 && str_a)
784 		memmove(buf->ptr, str_a, strlen_a);
785 	if (need_sep)
786 		buf->ptr[strlen_a] = separator;
787 	memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b);
788 
789 	buf->size = strlen_a + strlen_b + need_sep;
790 	buf->ptr[buf->size] = '\0';
791 
792 	return 0;
793 }
794 
git_buf_join3(git_buf * buf,char separator,const char * str_a,const char * str_b,const char * str_c)795 int git_buf_join3(
796 	git_buf *buf,
797 	char separator,
798 	const char *str_a,
799 	const char *str_b,
800 	const char *str_c)
801 {
802 	size_t len_a = strlen(str_a),
803 		len_b = strlen(str_b),
804 		len_c = strlen(str_c),
805 		len_total;
806 	int sep_a = 0, sep_b = 0;
807 	char *tgt;
808 
809 	/* for this function, disallow pointers into the existing buffer */
810 	assert(str_a < buf->ptr || str_a >= buf->ptr + buf->size);
811 	assert(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
812 	assert(str_c < buf->ptr || str_c >= buf->ptr + buf->size);
813 
814 	if (separator) {
815 		if (len_a > 0) {
816 			while (*str_b == separator) { str_b++; len_b--; }
817 			sep_a = (str_a[len_a - 1] != separator);
818 		}
819 		if (len_a > 0 || len_b > 0)
820 			while (*str_c == separator) { str_c++; len_c--; }
821 		if (len_b > 0)
822 			sep_b = (str_b[len_b - 1] != separator);
823 	}
824 
825 	GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_a, sep_a);
826 	GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_b);
827 	GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, sep_b);
828 	GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_c);
829 	GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, 1);
830 	ENSURE_SIZE(buf, len_total);
831 
832 	tgt = buf->ptr;
833 
834 	if (len_a) {
835 		memcpy(tgt, str_a, len_a);
836 		tgt += len_a;
837 	}
838 	if (sep_a)
839 		*tgt++ = separator;
840 	if (len_b) {
841 		memcpy(tgt, str_b, len_b);
842 		tgt += len_b;
843 	}
844 	if (sep_b)
845 		*tgt++ = separator;
846 	if (len_c)
847 		memcpy(tgt, str_c, len_c);
848 
849 	buf->size = len_a + sep_a + len_b + sep_b + len_c;
850 	buf->ptr[buf->size] = '\0';
851 
852 	return 0;
853 }
854 
git_buf_rtrim(git_buf * buf)855 void git_buf_rtrim(git_buf *buf)
856 {
857 	while (buf->size > 0) {
858 		if (!git__isspace(buf->ptr[buf->size - 1]))
859 			break;
860 
861 		buf->size--;
862 	}
863 
864 	if (buf->asize > buf->size)
865 		buf->ptr[buf->size] = '\0';
866 }
867 
git_buf_cmp(const git_buf * a,const git_buf * b)868 int git_buf_cmp(const git_buf *a, const git_buf *b)
869 {
870 	int result = memcmp(a->ptr, b->ptr, min(a->size, b->size));
871 	return (result != 0) ? result :
872 		(a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0;
873 }
874 
git_buf_splice(git_buf * buf,size_t where,size_t nb_to_remove,const char * data,size_t nb_to_insert)875 int git_buf_splice(
876 	git_buf *buf,
877 	size_t where,
878 	size_t nb_to_remove,
879 	const char *data,
880 	size_t nb_to_insert)
881 {
882 	char *splice_loc;
883 	size_t new_size, alloc_size;
884 
885 	assert(buf && where <= buf->size && nb_to_remove <= buf->size - where);
886 
887 	splice_loc = buf->ptr + where;
888 
889 	/* Ported from git.git
890 	 * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176
891 	 */
892 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (buf->size - nb_to_remove), nb_to_insert);
893 	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, new_size, 1);
894 	ENSURE_SIZE(buf, alloc_size);
895 
896 	memmove(splice_loc + nb_to_insert,
897 		splice_loc + nb_to_remove,
898 		buf->size - where - nb_to_remove);
899 
900 	memcpy(splice_loc, data, nb_to_insert);
901 
902 	buf->size = new_size;
903 	buf->ptr[buf->size] = '\0';
904 	return 0;
905 }
906 
907 /* Quote per http://marc.info/?l=git&m=112927316408690&w=2 */
git_buf_quote(git_buf * buf)908 int git_buf_quote(git_buf *buf)
909 {
910 	const char whitespace[] = { 'a', 'b', 't', 'n', 'v', 'f', 'r' };
911 	git_buf quoted = GIT_BUF_INIT;
912 	size_t i = 0;
913 	bool quote = false;
914 	int error = 0;
915 
916 	/* walk to the first char that needs quoting */
917 	if (buf->size && buf->ptr[0] == '!')
918 		quote = true;
919 
920 	for (i = 0; !quote && i < buf->size; i++) {
921 		if (buf->ptr[i] == '"' || buf->ptr[i] == '\\' ||
922 			buf->ptr[i] < ' ' || buf->ptr[i] > '~') {
923 			quote = true;
924 			break;
925 		}
926 	}
927 
928 	if (!quote)
929 		goto done;
930 
931 	git_buf_putc(&quoted, '"');
932 	git_buf_put(&quoted, buf->ptr, i);
933 
934 	for (; i < buf->size; i++) {
935 		/* whitespace - use the map above, which is ordered by ascii value */
936 		if (buf->ptr[i] >= '\a' && buf->ptr[i] <= '\r') {
937 			git_buf_putc(&quoted, '\\');
938 			git_buf_putc(&quoted, whitespace[buf->ptr[i] - '\a']);
939 		}
940 
941 		/* double quote and backslash must be escaped */
942 		else if (buf->ptr[i] == '"' || buf->ptr[i] == '\\') {
943 			git_buf_putc(&quoted, '\\');
944 			git_buf_putc(&quoted, buf->ptr[i]);
945 		}
946 
947 		/* escape anything unprintable as octal */
948 		else if (buf->ptr[i] != ' ' &&
949 				(buf->ptr[i] < '!' || buf->ptr[i] > '~')) {
950 			git_buf_printf(&quoted, "\\%03o", (unsigned char)buf->ptr[i]);
951 		}
952 
953 		/* yay, printable! */
954 		else {
955 			git_buf_putc(&quoted, buf->ptr[i]);
956 		}
957 	}
958 
959 	git_buf_putc(&quoted, '"');
960 
961 	if (git_buf_oom(&quoted)) {
962 		error = -1;
963 		goto done;
964 	}
965 
966 	git_buf_swap(&quoted, buf);
967 
968 done:
969 	git_buf_dispose(&quoted);
970 	return error;
971 }
972 
973 /* Unquote per http://marc.info/?l=git&m=112927316408690&w=2 */
git_buf_unquote(git_buf * buf)974 int git_buf_unquote(git_buf *buf)
975 {
976 	size_t i, j;
977 	char ch;
978 
979 	git_buf_rtrim(buf);
980 
981 	if (buf->size < 2 || buf->ptr[0] != '"' || buf->ptr[buf->size-1] != '"')
982 		goto invalid;
983 
984 	for (i = 0, j = 1; j < buf->size-1; i++, j++) {
985 		ch = buf->ptr[j];
986 
987 		if (ch == '\\') {
988 			if (j == buf->size-2)
989 				goto invalid;
990 
991 			ch = buf->ptr[++j];
992 
993 			switch (ch) {
994 			/* \" or \\ simply copy the char in */
995 			case '"': case '\\':
996 				break;
997 
998 			/* add the appropriate escaped char */
999 			case 'a': ch = '\a'; break;
1000 			case 'b': ch = '\b'; break;
1001 			case 'f': ch = '\f'; break;
1002 			case 'n': ch = '\n'; break;
1003 			case 'r': ch = '\r'; break;
1004 			case 't': ch = '\t'; break;
1005 			case 'v': ch = '\v'; break;
1006 
1007 			/* \xyz digits convert to the char*/
1008 			case '0': case '1': case '2': case '3':
1009 				if (j == buf->size-3) {
1010 					git_error_set(GIT_ERROR_INVALID,
1011 						"truncated quoted character \\%c", ch);
1012 					return -1;
1013 				}
1014 
1015 				if (buf->ptr[j+1] < '0' || buf->ptr[j+1] > '7' ||
1016 					buf->ptr[j+2] < '0' || buf->ptr[j+2] > '7') {
1017 					git_error_set(GIT_ERROR_INVALID,
1018 						"truncated quoted character \\%c%c%c",
1019 						buf->ptr[j], buf->ptr[j+1], buf->ptr[j+2]);
1020 					return -1;
1021 				}
1022 
1023 				ch = ((buf->ptr[j] - '0') << 6) |
1024 					((buf->ptr[j+1] - '0') << 3) |
1025 					(buf->ptr[j+2] - '0');
1026 				j += 2;
1027 				break;
1028 
1029 			default:
1030 				git_error_set(GIT_ERROR_INVALID, "invalid quoted character \\%c", ch);
1031 				return -1;
1032 			}
1033 		}
1034 
1035 		buf->ptr[i] = ch;
1036 	}
1037 
1038 	buf->ptr[i] = '\0';
1039 	buf->size = i;
1040 
1041 	return 0;
1042 
1043 invalid:
1044 	git_error_set(GIT_ERROR_INVALID, "invalid quoted line");
1045 	return -1;
1046 }
1047