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 <ctype.h>
11 
12 /* Used as default value for git_buf->ptr so that people can always
13  * assume ptr is non-NULL and zero terminated even for new git_bufs.
14  */
15 char git_buf__initbuf[1];
16 
17 char git_buf__oom[1];
18 
19 #define ENSURE_SIZE(b, d) \
20 	if ((b)->ptr == git_buf__oom || \
21 	    ((d) > (b)->asize && git_buf_grow((b), (d)) < 0))\
22 		return -1;
23 
24 
git_buf_init(git_buf * buf,size_t initial_size)25 int git_buf_init(git_buf *buf, size_t initial_size)
26 {
27 	buf->asize = 0;
28 	buf->size = 0;
29 	buf->ptr = git_buf__initbuf;
30 
31 	ENSURE_SIZE(buf, initial_size);
32 
33 	return 0;
34 }
35 
git_buf_try_grow(git_buf * buf,size_t target_size,bool mark_oom)36 int git_buf_try_grow(
37 	git_buf *buf, size_t target_size, bool mark_oom)
38 {
39 	char *new_ptr;
40 	size_t new_size;
41 
42 	if (buf->ptr == git_buf__oom)
43 		return -1;
44 
45 	if (buf->asize == 0 && buf->size != 0) {
46 		git_error_set(GIT_ERROR_INVALID, "cannot grow a borrowed buffer");
47 		return GIT_EINVALID;
48 	}
49 
50 	if (!target_size)
51 		target_size = buf->size;
52 
53 	if (target_size <= buf->asize)
54 		return 0;
55 
56 	if (buf->asize == 0) {
57 		new_size = target_size;
58 		new_ptr = NULL;
59 	} else {
60 		new_size = buf->asize;
61 		/*
62 		 * Grow the allocated buffer by 1.5 to allow
63 		 * re-use of memory holes resulting from the
64 		 * realloc. If this is still too small, then just
65 		 * use the target size.
66 		 */
67 		if ((new_size = (new_size << 1) - (new_size >> 1)) < target_size)
68 			new_size = target_size;
69 		new_ptr = buf->ptr;
70 	}
71 
72 	/* round allocation up to multiple of 8 */
73 	new_size = (new_size + 7) & ~7;
74 
75 	if (new_size < buf->size) {
76 		if (mark_oom) {
77 			if (buf->ptr && buf->ptr != git_buf__initbuf)
78 				git__free(buf->ptr);
79 			buf->ptr = git_buf__oom;
80 		}
81 
82 		git_error_set_oom();
83 		return -1;
84 	}
85 
86 	new_ptr = git__realloc(new_ptr, new_size);
87 
88 	if (!new_ptr) {
89 		if (mark_oom) {
90 			if (buf->ptr && (buf->ptr != git_buf__initbuf))
91 				git__free(buf->ptr);
92 			buf->ptr = git_buf__oom;
93 		}
94 		return -1;
95 	}
96 
97 	buf->asize = new_size;
98 	buf->ptr   = new_ptr;
99 
100 	/* truncate the existing buffer size if necessary */
101 	if (buf->size >= buf->asize)
102 		buf->size = buf->asize - 1;
103 	buf->ptr[buf->size] = '\0';
104 
105 	return 0;
106 }
107 
git_buf_grow(git_buf * buffer,size_t target_size)108 int git_buf_grow(git_buf *buffer, size_t target_size)
109 {
110 	return git_buf_try_grow(buffer, target_size, true);
111 }
112 
git_buf_grow_by(git_buf * buffer,size_t additional_size)113 int git_buf_grow_by(git_buf *buffer, size_t additional_size)
114 {
115 	size_t newsize;
116 
117 	if (GIT_ADD_SIZET_OVERFLOW(&newsize, buffer->size, additional_size)) {
118 		buffer->ptr = git_buf__oom;
119 		return -1;
120 	}
121 
122 	return git_buf_try_grow(buffer, newsize, true);
123 }
124 
git_buf_dispose(git_buf * buf)125 void git_buf_dispose(git_buf *buf)
126 {
127 	if (!buf) return;
128 
129 	if (buf->asize > 0 && buf->ptr != NULL && buf->ptr != git_buf__oom)
130 		git__free(buf->ptr);
131 
132 	git_buf_init(buf, 0);
133 }
134 
135 #ifndef GIT_DEPRECATE_HARD
git_buf_free(git_buf * buf)136 void git_buf_free(git_buf *buf)
137 {
138 	git_buf_dispose(buf);
139 }
140 #endif
141 
git_buf_sanitize(git_buf * buf)142 int git_buf_sanitize(git_buf *buf)
143 {
144 	if (buf->ptr == NULL) {
145 		GIT_ASSERT_ARG(buf->size == 0 && buf->asize == 0);
146 
147 		buf->ptr = git_buf__initbuf;
148 	} else if (buf->asize > buf->size) {
149 		buf->ptr[buf->size] = '\0';
150 	}
151 
152 	return 0;
153 }
154 
git_buf_clear(git_buf * buf)155 void git_buf_clear(git_buf *buf)
156 {
157 	buf->size = 0;
158 
159 	if (!buf->ptr) {
160 		buf->ptr = git_buf__initbuf;
161 		buf->asize = 0;
162 	}
163 
164 	if (buf->asize > 0)
165 		buf->ptr[0] = '\0';
166 }
167 
git_buf_set(git_buf * buf,const void * data,size_t len)168 int git_buf_set(git_buf *buf, const void *data, size_t len)
169 {
170 	size_t alloclen;
171 
172 	if (len == 0 || data == NULL) {
173 		git_buf_clear(buf);
174 	} else {
175 		if (data != buf->ptr) {
176 			GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1);
177 			ENSURE_SIZE(buf, alloclen);
178 			memmove(buf->ptr, data, len);
179 		}
180 
181 		buf->size = len;
182 		if (buf->asize > buf->size)
183 			buf->ptr[buf->size] = '\0';
184 
185 	}
186 	return 0;
187 }
188 
git_buf_sets(git_buf * buf,const char * string)189 int git_buf_sets(git_buf *buf, const char *string)
190 {
191 	return git_buf_set(buf, string, string ? strlen(string) : 0);
192 }
193 
git_buf_putc(git_buf * buf,char c)194 int git_buf_putc(git_buf *buf, char c)
195 {
196 	size_t new_size;
197 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, 2);
198 	ENSURE_SIZE(buf, new_size);
199 	buf->ptr[buf->size++] = c;
200 	buf->ptr[buf->size] = '\0';
201 	return 0;
202 }
203 
git_buf_putcn(git_buf * buf,char c,size_t len)204 int git_buf_putcn(git_buf *buf, char c, size_t len)
205 {
206 	size_t new_size;
207 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
208 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
209 	ENSURE_SIZE(buf, new_size);
210 	memset(buf->ptr + buf->size, c, len);
211 	buf->size += len;
212 	buf->ptr[buf->size] = '\0';
213 	return 0;
214 }
215 
git_buf_put(git_buf * buf,const char * data,size_t len)216 int git_buf_put(git_buf *buf, const char *data, size_t len)
217 {
218 	if (len) {
219 		size_t new_size;
220 
221 		GIT_ASSERT_ARG(data);
222 
223 		GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
224 		GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
225 		ENSURE_SIZE(buf, new_size);
226 		memmove(buf->ptr + buf->size, data, len);
227 		buf->size += len;
228 		buf->ptr[buf->size] = '\0';
229 	}
230 	return 0;
231 }
232 
git_buf_puts(git_buf * buf,const char * string)233 int git_buf_puts(git_buf *buf, const char *string)
234 {
235 	GIT_ASSERT_ARG(string);
236 
237 	return git_buf_put(buf, string, strlen(string));
238 }
239 
240 static const char base64_encode[] =
241 	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
242 
git_buf_encode_base64(git_buf * buf,const char * data,size_t len)243 int git_buf_encode_base64(git_buf *buf, const char *data, size_t len)
244 {
245 	size_t extra = len % 3;
246 	uint8_t *write, a, b, c;
247 	const uint8_t *read = (const uint8_t *)data;
248 	size_t blocks = (len / 3) + !!extra, alloclen;
249 
250 	GIT_ERROR_CHECK_ALLOC_ADD(&blocks, blocks, 1);
251 	GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 4);
252 	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size);
253 
254 	ENSURE_SIZE(buf, alloclen);
255 	write = (uint8_t *)&buf->ptr[buf->size];
256 
257 	/* convert each run of 3 bytes into 4 output bytes */
258 	for (len -= extra; len > 0; len -= 3) {
259 		a = *read++;
260 		b = *read++;
261 		c = *read++;
262 
263 		*write++ = base64_encode[a >> 2];
264 		*write++ = base64_encode[(a & 0x03) << 4 | b >> 4];
265 		*write++ = base64_encode[(b & 0x0f) << 2 | c >> 6];
266 		*write++ = base64_encode[c & 0x3f];
267 	}
268 
269 	if (extra > 0) {
270 		a = *read++;
271 		b = (extra > 1) ? *read++ : 0;
272 
273 		*write++ = base64_encode[a >> 2];
274 		*write++ = base64_encode[(a & 0x03) << 4 | b >> 4];
275 		*write++ = (extra > 1) ? base64_encode[(b & 0x0f) << 2] : '=';
276 		*write++ = '=';
277 	}
278 
279 	buf->size = ((char *)write) - buf->ptr;
280 	buf->ptr[buf->size] = '\0';
281 
282 	return 0;
283 }
284 
285 /* The inverse of base64_encode */
286 static const int8_t base64_decode[] = {
287 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
288 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
289 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
290 	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1,  0, -1, -1,
291 	-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
292 	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
293 	-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
294 	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
295 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
296 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
297 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
298 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -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 };
304 
git_buf_decode_base64(git_buf * buf,const char * base64,size_t len)305 int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len)
306 {
307 	size_t i;
308 	int8_t a, b, c, d;
309 	size_t orig_size = buf->size, new_size;
310 
311 	if (len % 4) {
312 		git_error_set(GIT_ERROR_INVALID, "invalid base64 input");
313 		return -1;
314 	}
315 
316 	GIT_ASSERT_ARG(len % 4 == 0);
317 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size);
318 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
319 	ENSURE_SIZE(buf, new_size);
320 
321 	for (i = 0; i < len; i += 4) {
322 		if ((a = base64_decode[(unsigned char)base64[i]]) < 0 ||
323 			(b = base64_decode[(unsigned char)base64[i+1]]) < 0 ||
324 			(c = base64_decode[(unsigned char)base64[i+2]]) < 0 ||
325 			(d = base64_decode[(unsigned char)base64[i+3]]) < 0) {
326 			buf->size = orig_size;
327 			buf->ptr[buf->size] = '\0';
328 
329 			git_error_set(GIT_ERROR_INVALID, "invalid base64 input");
330 			return -1;
331 		}
332 
333 		buf->ptr[buf->size++] = ((a << 2) | (b & 0x30) >> 4);
334 		buf->ptr[buf->size++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
335 		buf->ptr[buf->size++] = (c & 0x03) << 6 | (d & 0x3f);
336 	}
337 
338 	buf->ptr[buf->size] = '\0';
339 	return 0;
340 }
341 
342 static const char base85_encode[] =
343 	"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
344 
git_buf_encode_base85(git_buf * buf,const char * data,size_t len)345 int git_buf_encode_base85(git_buf *buf, const char *data, size_t len)
346 {
347 	size_t blocks = (len / 4) + !!(len % 4), alloclen;
348 
349 	GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 5);
350 	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size);
351 	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
352 
353 	ENSURE_SIZE(buf, alloclen);
354 
355 	while (len) {
356 		uint32_t acc = 0;
357 		char b85[5];
358 		int i;
359 
360 		for (i = 24; i >= 0; i -= 8) {
361 			uint8_t ch = *data++;
362 			acc |= (uint32_t)ch << i;
363 
364 			if (--len == 0)
365 				break;
366 		}
367 
368 		for (i = 4; i >= 0; i--) {
369 			int val = acc % 85;
370 			acc /= 85;
371 
372 			b85[i] = base85_encode[val];
373 		}
374 
375 		for (i = 0; i < 5; i++)
376 			buf->ptr[buf->size++] = b85[i];
377 	}
378 
379 	buf->ptr[buf->size] = '\0';
380 
381 	return 0;
382 }
383 
384 /* The inverse of base85_encode */
385 static const int8_t base85_decode[] = {
386 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
387 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
388 	-1, 63, -1, 64, 65, 66, 67, -1, 68, 69, 70, 71, -1, 72, -1, -1,
389 	 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, -1, 73, 74, 75, 76, 77,
390 	78, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
391 	26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, 79, 80,
392 	81, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
393 	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 82, 83, 84, 85, -1,
394 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
395 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
396 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
397 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -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 };
403 
git_buf_decode_base85(git_buf * buf,const char * base85,size_t base85_len,size_t output_len)404 int git_buf_decode_base85(
405 	git_buf *buf,
406 	const char *base85,
407 	size_t base85_len,
408 	size_t output_len)
409 {
410 	size_t orig_size = buf->size, new_size;
411 
412 	if (base85_len % 5 ||
413 		output_len > base85_len * 4 / 5) {
414 		git_error_set(GIT_ERROR_INVALID, "invalid base85 input");
415 		return -1;
416 	}
417 
418 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, output_len, buf->size);
419 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
420 	ENSURE_SIZE(buf, new_size);
421 
422 	while (output_len) {
423 		unsigned acc = 0;
424 		int de, cnt = 4;
425 		unsigned char ch;
426 		do {
427 			ch = *base85++;
428 			de = base85_decode[ch];
429 			if (--de < 0)
430 				goto on_error;
431 
432 			acc = acc * 85 + de;
433 		} while (--cnt);
434 		ch = *base85++;
435 		de = base85_decode[ch];
436 		if (--de < 0)
437 			goto on_error;
438 
439 		/* Detect overflow. */
440 		if (0xffffffff / 85 < acc ||
441 			0xffffffff - de < (acc *= 85))
442 			goto on_error;
443 
444 		acc += de;
445 
446 		cnt = (output_len < 4) ? (int)output_len : 4;
447 		output_len -= cnt;
448 		do {
449 			acc = (acc << 8) | (acc >> 24);
450 			buf->ptr[buf->size++] = acc;
451 		} while (--cnt);
452 	}
453 
454 	buf->ptr[buf->size] = 0;
455 
456 	return 0;
457 
458 on_error:
459 	buf->size = orig_size;
460 	buf->ptr[buf->size] = '\0';
461 
462 	git_error_set(GIT_ERROR_INVALID, "invalid base85 input");
463 	return -1;
464 }
465 
466 #define HEX_DECODE(c) ((c | 32) % 39 - 9)
467 
git_buf_decode_percent(git_buf * buf,const char * str,size_t str_len)468 int git_buf_decode_percent(
469 	git_buf *buf,
470 	const char *str,
471 	size_t str_len)
472 {
473 	size_t str_pos, new_size;
474 
475 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, str_len);
476 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
477 	ENSURE_SIZE(buf, new_size);
478 
479 	for (str_pos = 0; str_pos < str_len; buf->size++, str_pos++) {
480 		if (str[str_pos] == '%' &&
481 			str_len > str_pos + 2 &&
482 			isxdigit(str[str_pos + 1]) &&
483 			isxdigit(str[str_pos + 2])) {
484 			buf->ptr[buf->size] = (HEX_DECODE(str[str_pos + 1]) << 4) +
485 				HEX_DECODE(str[str_pos + 2]);
486 			str_pos += 2;
487 		} else {
488 			buf->ptr[buf->size] = str[str_pos];
489 		}
490 	}
491 
492 	buf->ptr[buf->size] = '\0';
493 	return 0;
494 }
495 
git_buf_vprintf(git_buf * buf,const char * format,va_list ap)496 int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
497 {
498 	size_t expected_size, new_size;
499 	int len;
500 
501 	GIT_ERROR_CHECK_ALLOC_MULTIPLY(&expected_size, strlen(format), 2);
502 	GIT_ERROR_CHECK_ALLOC_ADD(&expected_size, expected_size, buf->size);
503 	ENSURE_SIZE(buf, expected_size);
504 
505 	while (1) {
506 		va_list args;
507 		va_copy(args, ap);
508 
509 		len = p_vsnprintf(
510 			buf->ptr + buf->size,
511 			buf->asize - buf->size,
512 			format, args
513 		);
514 
515 		va_end(args);
516 
517 		if (len < 0) {
518 			git__free(buf->ptr);
519 			buf->ptr = git_buf__oom;
520 			return -1;
521 		}
522 
523 		if ((size_t)len + 1 <= buf->asize - buf->size) {
524 			buf->size += len;
525 			break;
526 		}
527 
528 		GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
529 		GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
530 		ENSURE_SIZE(buf, new_size);
531 	}
532 
533 	return 0;
534 }
535 
git_buf_printf(git_buf * buf,const char * format,...)536 int git_buf_printf(git_buf *buf, const char *format, ...)
537 {
538 	int r;
539 	va_list ap;
540 
541 	va_start(ap, format);
542 	r = git_buf_vprintf(buf, format, ap);
543 	va_end(ap);
544 
545 	return r;
546 }
547 
git_buf_copy_cstr(char * data,size_t datasize,const git_buf * buf)548 int git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf)
549 {
550 	size_t copylen;
551 
552 	GIT_ASSERT_ARG(data);
553 	GIT_ASSERT_ARG(datasize);
554 	GIT_ASSERT_ARG(buf);
555 
556 	data[0] = '\0';
557 
558 	if (buf->size == 0 || buf->asize <= 0)
559 		return 0;
560 
561 	copylen = buf->size;
562 	if (copylen > datasize - 1)
563 		copylen = datasize - 1;
564 	memmove(data, buf->ptr, copylen);
565 	data[copylen] = '\0';
566 
567 	return 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_truncate_at_char(git_buf * buf,char separator)603 void git_buf_truncate_at_char(git_buf *buf, char separator)
604 {
605 	ssize_t idx = git_buf_find(buf, separator);
606 	if (idx >= 0)
607 		git_buf_truncate(buf, (size_t)idx);
608 }
609 
git_buf_rtruncate_at_char(git_buf * buf,char separator)610 void git_buf_rtruncate_at_char(git_buf *buf, char separator)
611 {
612 	ssize_t idx = git_buf_rfind_next(buf, separator);
613 	git_buf_truncate(buf, idx < 0 ? 0 : (size_t)idx);
614 }
615 
git_buf_swap(git_buf * buf_a,git_buf * buf_b)616 void git_buf_swap(git_buf *buf_a, git_buf *buf_b)
617 {
618 	git_buf t = *buf_a;
619 	*buf_a = *buf_b;
620 	*buf_b = t;
621 }
622 
git_buf_detach(git_buf * buf)623 char *git_buf_detach(git_buf *buf)
624 {
625 	char *data = buf->ptr;
626 
627 	if (buf->asize == 0 || buf->ptr == git_buf__oom)
628 		return NULL;
629 
630 	git_buf_init(buf, 0);
631 
632 	return data;
633 }
634 
git_buf_attach(git_buf * buf,char * ptr,size_t asize)635 int git_buf_attach(git_buf *buf, char *ptr, size_t asize)
636 {
637 	git_buf_dispose(buf);
638 
639 	if (ptr) {
640 		buf->ptr = ptr;
641 		buf->size = strlen(ptr);
642 		if (asize)
643 			buf->asize = (asize < buf->size) ? buf->size + 1 : asize;
644 		else /* pass 0 to fall back on strlen + 1 */
645 			buf->asize = buf->size + 1;
646 	}
647 
648 	ENSURE_SIZE(buf, asize);
649 	return 0;
650 }
651 
git_buf_attach_notowned(git_buf * buf,const char * ptr,size_t size)652 void git_buf_attach_notowned(git_buf *buf, const char *ptr, size_t size)
653 {
654 	if (git_buf_is_allocated(buf))
655 		git_buf_dispose(buf);
656 
657 	if (!size) {
658 		git_buf_init(buf, 0);
659 	} else {
660 		buf->ptr = (char *)ptr;
661 		buf->asize = 0;
662 		buf->size = size;
663 	}
664 }
665 
git_buf_join_n(git_buf * buf,char separator,int nbuf,...)666 int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...)
667 {
668 	va_list ap;
669 	int i;
670 	size_t total_size = 0, original_size = buf->size;
671 	char *out, *original = buf->ptr;
672 
673 	if (buf->size > 0 && buf->ptr[buf->size - 1] != separator)
674 		++total_size; /* space for initial separator */
675 
676 	/* Make two passes to avoid multiple reallocation */
677 
678 	va_start(ap, nbuf);
679 	for (i = 0; i < nbuf; ++i) {
680 		const char *segment;
681 		size_t segment_len;
682 
683 		segment = va_arg(ap, const char *);
684 		if (!segment)
685 			continue;
686 
687 		segment_len = strlen(segment);
688 
689 		GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, segment_len);
690 
691 		if (segment_len == 0 || segment[segment_len - 1] != separator)
692 			GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1);
693 	}
694 	va_end(ap);
695 
696 	/* expand buffer if needed */
697 	if (total_size == 0)
698 		return 0;
699 
700 	GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1);
701 	if (git_buf_grow_by(buf, total_size) < 0)
702 		return -1;
703 
704 	out = buf->ptr + buf->size;
705 
706 	/* append separator to existing buf if needed */
707 	if (buf->size > 0 && out[-1] != separator)
708 		*out++ = separator;
709 
710 	va_start(ap, nbuf);
711 	for (i = 0; i < nbuf; ++i) {
712 		const char *segment;
713 		size_t segment_len;
714 
715 		segment = va_arg(ap, const char *);
716 		if (!segment)
717 			continue;
718 
719 		/* deal with join that references buffer's original content */
720 		if (segment >= original && segment < original + original_size) {
721 			size_t offset = (segment - original);
722 			segment = buf->ptr + offset;
723 			segment_len = original_size - offset;
724 		} else {
725 			segment_len = strlen(segment);
726 		}
727 
728 		/* skip leading separators */
729 		if (out > buf->ptr && out[-1] == separator)
730 			while (segment_len > 0 && *segment == separator) {
731 				segment++;
732 				segment_len--;
733 			}
734 
735 		/* copy over next buffer */
736 		if (segment_len > 0) {
737 			memmove(out, segment, segment_len);
738 			out += segment_len;
739 		}
740 
741 		/* append trailing separator (except for last item) */
742 		if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator)
743 			*out++ = separator;
744 	}
745 	va_end(ap);
746 
747 	/* set size based on num characters actually written */
748 	buf->size = out - buf->ptr;
749 	buf->ptr[buf->size] = '\0';
750 
751 	return 0;
752 }
753 
git_buf_join(git_buf * buf,char separator,const char * str_a,const char * str_b)754 int git_buf_join(
755 	git_buf *buf,
756 	char separator,
757 	const char *str_a,
758 	const char *str_b)
759 {
760 	size_t strlen_a = str_a ? strlen(str_a) : 0;
761 	size_t strlen_b = strlen(str_b);
762 	size_t alloc_len;
763 	int need_sep = 0;
764 	ssize_t offset_a = -1;
765 
766 	/* not safe to have str_b point internally to the buffer */
767 	if (buf->size)
768 		GIT_ASSERT_ARG(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
769 
770 	/* figure out if we need to insert a separator */
771 	if (separator && strlen_a) {
772 		while (*str_b == separator) { str_b++; strlen_b--; }
773 		if (str_a[strlen_a - 1] != separator)
774 			need_sep = 1;
775 	}
776 
777 	/* str_a could be part of the buffer */
778 	if (buf->size && str_a >= buf->ptr && str_a < buf->ptr + buf->size)
779 		offset_a = str_a - buf->ptr;
780 
781 	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, strlen_a, strlen_b);
782 	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, need_sep);
783 	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1);
784 	ENSURE_SIZE(buf, alloc_len);
785 
786 	/* fix up internal pointers */
787 	if (offset_a >= 0)
788 		str_a = buf->ptr + offset_a;
789 
790 	/* do the actual copying */
791 	if (offset_a != 0 && str_a)
792 		memmove(buf->ptr, str_a, strlen_a);
793 	if (need_sep)
794 		buf->ptr[strlen_a] = separator;
795 	memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b);
796 
797 	buf->size = strlen_a + strlen_b + need_sep;
798 	buf->ptr[buf->size] = '\0';
799 
800 	return 0;
801 }
802 
git_buf_join3(git_buf * buf,char separator,const char * str_a,const char * str_b,const char * str_c)803 int git_buf_join3(
804 	git_buf *buf,
805 	char separator,
806 	const char *str_a,
807 	const char *str_b,
808 	const char *str_c)
809 {
810 	size_t len_a = strlen(str_a),
811 		len_b = strlen(str_b),
812 		len_c = strlen(str_c),
813 		len_total;
814 	int sep_a = 0, sep_b = 0;
815 	char *tgt;
816 
817 	/* for this function, disallow pointers into the existing buffer */
818 	GIT_ASSERT(str_a < buf->ptr || str_a >= buf->ptr + buf->size);
819 	GIT_ASSERT(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
820 	GIT_ASSERT(str_c < buf->ptr || str_c >= buf->ptr + buf->size);
821 
822 	if (separator) {
823 		if (len_a > 0) {
824 			while (*str_b == separator) { str_b++; len_b--; }
825 			sep_a = (str_a[len_a - 1] != separator);
826 		}
827 		if (len_a > 0 || len_b > 0)
828 			while (*str_c == separator) { str_c++; len_c--; }
829 		if (len_b > 0)
830 			sep_b = (str_b[len_b - 1] != separator);
831 	}
832 
833 	GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_a, sep_a);
834 	GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_b);
835 	GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, sep_b);
836 	GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_c);
837 	GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, 1);
838 	ENSURE_SIZE(buf, len_total);
839 
840 	tgt = buf->ptr;
841 
842 	if (len_a) {
843 		memcpy(tgt, str_a, len_a);
844 		tgt += len_a;
845 	}
846 	if (sep_a)
847 		*tgt++ = separator;
848 	if (len_b) {
849 		memcpy(tgt, str_b, len_b);
850 		tgt += len_b;
851 	}
852 	if (sep_b)
853 		*tgt++ = separator;
854 	if (len_c)
855 		memcpy(tgt, str_c, len_c);
856 
857 	buf->size = len_a + sep_a + len_b + sep_b + len_c;
858 	buf->ptr[buf->size] = '\0';
859 
860 	return 0;
861 }
862 
git_buf_rtrim(git_buf * buf)863 void git_buf_rtrim(git_buf *buf)
864 {
865 	while (buf->size > 0) {
866 		if (!git__isspace(buf->ptr[buf->size - 1]))
867 			break;
868 
869 		buf->size--;
870 	}
871 
872 	if (buf->asize > buf->size)
873 		buf->ptr[buf->size] = '\0';
874 }
875 
git_buf_cmp(const git_buf * a,const git_buf * b)876 int git_buf_cmp(const git_buf *a, const git_buf *b)
877 {
878 	int result = memcmp(a->ptr, b->ptr, min(a->size, b->size));
879 	return (result != 0) ? result :
880 		(a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0;
881 }
882 
git_buf_splice(git_buf * buf,size_t where,size_t nb_to_remove,const char * data,size_t nb_to_insert)883 int git_buf_splice(
884 	git_buf *buf,
885 	size_t where,
886 	size_t nb_to_remove,
887 	const char *data,
888 	size_t nb_to_insert)
889 {
890 	char *splice_loc;
891 	size_t new_size, alloc_size;
892 
893 	GIT_ASSERT(buf);
894 	GIT_ASSERT(where <= buf->size);
895 	GIT_ASSERT(nb_to_remove <= buf->size - where);
896 
897 	splice_loc = buf->ptr + where;
898 
899 	/* Ported from git.git
900 	 * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176
901 	 */
902 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (buf->size - nb_to_remove), nb_to_insert);
903 	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, new_size, 1);
904 	ENSURE_SIZE(buf, alloc_size);
905 
906 	memmove(splice_loc + nb_to_insert,
907 		splice_loc + nb_to_remove,
908 		buf->size - where - nb_to_remove);
909 
910 	memcpy(splice_loc, data, nb_to_insert);
911 
912 	buf->size = new_size;
913 	buf->ptr[buf->size] = '\0';
914 	return 0;
915 }
916 
917 /* Quote per http://marc.info/?l=git&m=112927316408690&w=2 */
git_buf_quote(git_buf * buf)918 int git_buf_quote(git_buf *buf)
919 {
920 	const char whitespace[] = { 'a', 'b', 't', 'n', 'v', 'f', 'r' };
921 	git_buf quoted = GIT_BUF_INIT;
922 	size_t i = 0;
923 	bool quote = false;
924 	int error = 0;
925 
926 	/* walk to the first char that needs quoting */
927 	if (buf->size && buf->ptr[0] == '!')
928 		quote = true;
929 
930 	for (i = 0; !quote && i < buf->size; i++) {
931 		if (buf->ptr[i] == '"' || buf->ptr[i] == '\\' ||
932 			buf->ptr[i] < ' ' || buf->ptr[i] > '~') {
933 			quote = true;
934 			break;
935 		}
936 	}
937 
938 	if (!quote)
939 		goto done;
940 
941 	git_buf_putc(&quoted, '"');
942 	git_buf_put(&quoted, buf->ptr, i);
943 
944 	for (; i < buf->size; i++) {
945 		/* whitespace - use the map above, which is ordered by ascii value */
946 		if (buf->ptr[i] >= '\a' && buf->ptr[i] <= '\r') {
947 			git_buf_putc(&quoted, '\\');
948 			git_buf_putc(&quoted, whitespace[buf->ptr[i] - '\a']);
949 		}
950 
951 		/* double quote and backslash must be escaped */
952 		else if (buf->ptr[i] == '"' || buf->ptr[i] == '\\') {
953 			git_buf_putc(&quoted, '\\');
954 			git_buf_putc(&quoted, buf->ptr[i]);
955 		}
956 
957 		/* escape anything unprintable as octal */
958 		else if (buf->ptr[i] != ' ' &&
959 				(buf->ptr[i] < '!' || buf->ptr[i] > '~')) {
960 			git_buf_printf(&quoted, "\\%03o", (unsigned char)buf->ptr[i]);
961 		}
962 
963 		/* yay, printable! */
964 		else {
965 			git_buf_putc(&quoted, buf->ptr[i]);
966 		}
967 	}
968 
969 	git_buf_putc(&quoted, '"');
970 
971 	if (git_buf_oom(&quoted)) {
972 		error = -1;
973 		goto done;
974 	}
975 
976 	git_buf_swap(&quoted, buf);
977 
978 done:
979 	git_buf_dispose(&quoted);
980 	return error;
981 }
982 
983 /* Unquote per http://marc.info/?l=git&m=112927316408690&w=2 */
git_buf_unquote(git_buf * buf)984 int git_buf_unquote(git_buf *buf)
985 {
986 	size_t i, j;
987 	char ch;
988 
989 	git_buf_rtrim(buf);
990 
991 	if (buf->size < 2 || buf->ptr[0] != '"' || buf->ptr[buf->size-1] != '"')
992 		goto invalid;
993 
994 	for (i = 0, j = 1; j < buf->size-1; i++, j++) {
995 		ch = buf->ptr[j];
996 
997 		if (ch == '\\') {
998 			if (j == buf->size-2)
999 				goto invalid;
1000 
1001 			ch = buf->ptr[++j];
1002 
1003 			switch (ch) {
1004 			/* \" or \\ simply copy the char in */
1005 			case '"': case '\\':
1006 				break;
1007 
1008 			/* add the appropriate escaped char */
1009 			case 'a': ch = '\a'; break;
1010 			case 'b': ch = '\b'; break;
1011 			case 'f': ch = '\f'; break;
1012 			case 'n': ch = '\n'; break;
1013 			case 'r': ch = '\r'; break;
1014 			case 't': ch = '\t'; break;
1015 			case 'v': ch = '\v'; break;
1016 
1017 			/* \xyz digits convert to the char*/
1018 			case '0': case '1': case '2': case '3':
1019 				if (j == buf->size-3) {
1020 					git_error_set(GIT_ERROR_INVALID,
1021 						"truncated quoted character \\%c", ch);
1022 					return -1;
1023 				}
1024 
1025 				if (buf->ptr[j+1] < '0' || buf->ptr[j+1] > '7' ||
1026 					buf->ptr[j+2] < '0' || buf->ptr[j+2] > '7') {
1027 					git_error_set(GIT_ERROR_INVALID,
1028 						"truncated quoted character \\%c%c%c",
1029 						buf->ptr[j], buf->ptr[j+1], buf->ptr[j+2]);
1030 					return -1;
1031 				}
1032 
1033 				ch = ((buf->ptr[j] - '0') << 6) |
1034 					((buf->ptr[j+1] - '0') << 3) |
1035 					(buf->ptr[j+2] - '0');
1036 				j += 2;
1037 				break;
1038 
1039 			default:
1040 				git_error_set(GIT_ERROR_INVALID, "invalid quoted character \\%c", ch);
1041 				return -1;
1042 			}
1043 		}
1044 
1045 		buf->ptr[i] = ch;
1046 	}
1047 
1048 	buf->ptr[i] = '\0';
1049 	buf->size = i;
1050 
1051 	return 0;
1052 
1053 invalid:
1054 	git_error_set(GIT_ERROR_INVALID, "invalid quoted line");
1055 	return -1;
1056 }
1057 
git_buf_puts_escaped(git_buf * buf,const char * string,const char * esc_chars,const char * esc_with)1058 int git_buf_puts_escaped(
1059 	git_buf *buf,
1060 	const char *string,
1061 	const char *esc_chars,
1062 	const char *esc_with)
1063 {
1064 	const char *scan;
1065 	size_t total = 0, esc_len = strlen(esc_with), count, alloclen;
1066 
1067 	if (!string)
1068 		return 0;
1069 
1070 	for (scan = string; *scan; ) {
1071 		/* count run of non-escaped characters */
1072 		count = strcspn(scan, esc_chars);
1073 		total += count;
1074 		scan += count;
1075 		/* count run of escaped characters */
1076 		count = strspn(scan, esc_chars);
1077 		total += count * (esc_len + 1);
1078 		scan += count;
1079 	}
1080 
1081 	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, total, 1);
1082 	if (git_buf_grow_by(buf, alloclen) < 0)
1083 		return -1;
1084 
1085 	for (scan = string; *scan; ) {
1086 		count = strcspn(scan, esc_chars);
1087 
1088 		memmove(buf->ptr + buf->size, scan, count);
1089 		scan += count;
1090 		buf->size += count;
1091 
1092 		for (count = strspn(scan, esc_chars); count > 0; --count) {
1093 			/* copy escape sequence */
1094 			memmove(buf->ptr + buf->size, esc_with, esc_len);
1095 			buf->size += esc_len;
1096 			/* copy character to be escaped */
1097 			buf->ptr[buf->size] = *scan;
1098 			buf->size++;
1099 			scan++;
1100 		}
1101 	}
1102 
1103 	buf->ptr[buf->size] = '\0';
1104 
1105 	return 0;
1106 }
1107 
git_buf_unescape(git_buf * buf)1108 void git_buf_unescape(git_buf *buf)
1109 {
1110 	buf->size = git__unescape(buf->ptr);
1111 }
1112 
git_buf_crlf_to_lf(git_buf * tgt,const git_buf * src)1113 int git_buf_crlf_to_lf(git_buf *tgt, const git_buf *src)
1114 {
1115 	const char *scan = src->ptr;
1116 	const char *scan_end = src->ptr + src->size;
1117 	const char *next = memchr(scan, '\r', src->size);
1118 	size_t new_size;
1119 	char *out;
1120 
1121 	GIT_ASSERT(tgt != src);
1122 
1123 	if (!next)
1124 		return git_buf_set(tgt, src->ptr, src->size);
1125 
1126 	/* reduce reallocs while in the loop */
1127 	GIT_ERROR_CHECK_ALLOC_ADD(&new_size, src->size, 1);
1128 	if (git_buf_grow(tgt, new_size) < 0)
1129 		return -1;
1130 
1131 	out = tgt->ptr;
1132 	tgt->size = 0;
1133 
1134 	/* Find the next \r and copy whole chunk up to there to tgt */
1135 	for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) {
1136 		if (next > scan) {
1137 			size_t copylen = (size_t)(next - scan);
1138 			memcpy(out, scan, copylen);
1139 			out += copylen;
1140 		}
1141 
1142 		/* Do not drop \r unless it is followed by \n */
1143 		if (next + 1 == scan_end || next[1] != '\n')
1144 			*out++ = '\r';
1145 	}
1146 
1147 	/* Copy remaining input into dest */
1148 	if (scan < scan_end) {
1149 		size_t remaining = (size_t)(scan_end - scan);
1150 		memcpy(out, scan, remaining);
1151 		out += remaining;
1152 	}
1153 
1154 	tgt->size = (size_t)(out - tgt->ptr);
1155 	tgt->ptr[tgt->size] = '\0';
1156 
1157 	return 0;
1158 }
1159 
git_buf_lf_to_crlf(git_buf * tgt,const git_buf * src)1160 int git_buf_lf_to_crlf(git_buf *tgt, const git_buf *src)
1161 {
1162 	const char *start = src->ptr;
1163 	const char *end = start + src->size;
1164 	const char *scan = start;
1165 	const char *next = memchr(scan, '\n', src->size);
1166 	size_t alloclen;
1167 
1168 	GIT_ASSERT(tgt != src);
1169 
1170 	if (!next)
1171 		return git_buf_set(tgt, src->ptr, src->size);
1172 
1173 	/* attempt to reduce reallocs while in the loop */
1174 	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, src->size, src->size >> 4);
1175 	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
1176 	if (git_buf_grow(tgt, alloclen) < 0)
1177 		return -1;
1178 	tgt->size = 0;
1179 
1180 	for (; next; scan = next + 1, next = memchr(scan, '\n', end - scan)) {
1181 		size_t copylen = next - scan;
1182 
1183 		/* if we find mixed line endings, carry on */
1184 		if (copylen && next[-1] == '\r')
1185 			copylen--;
1186 
1187 		GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, copylen, 3);
1188 		if (git_buf_grow_by(tgt, alloclen) < 0)
1189 			return -1;
1190 
1191 		if (copylen) {
1192 			memcpy(tgt->ptr + tgt->size, scan, copylen);
1193 			tgt->size += copylen;
1194 		}
1195 
1196 		tgt->ptr[tgt->size++] = '\r';
1197 		tgt->ptr[tgt->size++] = '\n';
1198 	}
1199 
1200 	tgt->ptr[tgt->size] = '\0';
1201 	return git_buf_put(tgt, scan, end - scan);
1202 }
1203 
git_buf_common_prefix(git_buf * buf,char * const * const strings,size_t count)1204 int git_buf_common_prefix(git_buf *buf, char *const *const strings, size_t count)
1205 {
1206 	size_t i;
1207 	const char *str, *pfx;
1208 
1209 	git_buf_clear(buf);
1210 
1211 	if (!strings || !count)
1212 		return 0;
1213 
1214 	/* initialize common prefix to first string */
1215 	if (git_buf_sets(buf, strings[0]) < 0)
1216 		return -1;
1217 
1218 	/* go through the rest of the strings, truncating to shared prefix */
1219 	for (i = 1; i < count; ++i) {
1220 
1221 		for (str = strings[i], pfx = buf->ptr;
1222 		     *str && *str == *pfx;
1223 		     str++, pfx++)
1224 			/* scanning */;
1225 
1226 		git_buf_truncate(buf, pfx - buf->ptr);
1227 
1228 		if (!buf->size)
1229 			break;
1230 	}
1231 
1232 	return 0;
1233 }
1234 
git_buf_is_binary(const git_buf * buf)1235 int git_buf_is_binary(const git_buf *buf)
1236 {
1237 	const char *scan = buf->ptr, *end = buf->ptr + buf->size;
1238 	git_buf_bom_t bom;
1239 	int printable = 0, nonprintable = 0;
1240 
1241 	scan += git_buf_detect_bom(&bom, buf);
1242 
1243 	if (bom > GIT_BUF_BOM_UTF8)
1244 		return 1;
1245 
1246 	while (scan < end) {
1247 		unsigned char c = *scan++;
1248 
1249 		/* Printable characters are those above SPACE (0x1F) excluding DEL,
1250 		 * and including BS, ESC and FF.
1251 		 */
1252 		if ((c > 0x1F && c != 127) || c == '\b' || c == '\033' || c == '\014')
1253 			printable++;
1254 		else if (c == '\0')
1255 			return true;
1256 		else if (!git__isspace(c))
1257 			nonprintable++;
1258 	}
1259 
1260 	return ((printable >> 7) < nonprintable);
1261 }
1262 
git_buf_contains_nul(const git_buf * buf)1263 int git_buf_contains_nul(const git_buf *buf)
1264 {
1265 	return (memchr(buf->ptr, '\0', buf->size) != NULL);
1266 }
1267 
git_buf_detect_bom(git_buf_bom_t * bom,const git_buf * buf)1268 int git_buf_detect_bom(git_buf_bom_t *bom, const git_buf *buf)
1269 {
1270 	const char *ptr;
1271 	size_t len;
1272 
1273 	*bom = GIT_BUF_BOM_NONE;
1274 	/* need at least 2 bytes to look for any BOM */
1275 	if (buf->size < 2)
1276 		return 0;
1277 
1278 	ptr = buf->ptr;
1279 	len = buf->size;
1280 
1281 	switch (*ptr++) {
1282 	case 0:
1283 		if (len >= 4 && ptr[0] == 0 && ptr[1] == '\xFE' && ptr[2] == '\xFF') {
1284 			*bom = GIT_BUF_BOM_UTF32_BE;
1285 			return 4;
1286 		}
1287 		break;
1288 	case '\xEF':
1289 		if (len >= 3 && ptr[0] == '\xBB' && ptr[1] == '\xBF') {
1290 			*bom = GIT_BUF_BOM_UTF8;
1291 			return 3;
1292 		}
1293 		break;
1294 	case '\xFE':
1295 		if (*ptr == '\xFF') {
1296 			*bom = GIT_BUF_BOM_UTF16_BE;
1297 			return 2;
1298 		}
1299 		break;
1300 	case '\xFF':
1301 		if (*ptr != '\xFE')
1302 			break;
1303 		if (len >= 4 && ptr[1] == 0 && ptr[2] == 0) {
1304 			*bom = GIT_BUF_BOM_UTF32_LE;
1305 			return 4;
1306 		} else {
1307 			*bom = GIT_BUF_BOM_UTF16_LE;
1308 			return 2;
1309 		}
1310 		break;
1311 	default:
1312 		break;
1313 	}
1314 
1315 	return 0;
1316 }
1317 
git_buf_gather_text_stats(git_buf_text_stats * stats,const git_buf * buf,bool skip_bom)1318 bool git_buf_gather_text_stats(
1319 	git_buf_text_stats *stats, const git_buf *buf, bool skip_bom)
1320 {
1321 	const char *scan = buf->ptr, *end = buf->ptr + buf->size;
1322 	int skip;
1323 
1324 	memset(stats, 0, sizeof(*stats));
1325 
1326 	/* BOM detection */
1327 	skip = git_buf_detect_bom(&stats->bom, buf);
1328 	if (skip_bom)
1329 		scan += skip;
1330 
1331 	/* Ignore EOF character */
1332 	if (buf->size > 0 && end[-1] == '\032')
1333 		end--;
1334 
1335 	/* Counting loop */
1336 	while (scan < end) {
1337 		unsigned char c = *scan++;
1338 
1339 		if (c > 0x1F && c != 0x7F)
1340 			stats->printable++;
1341 		else switch (c) {
1342 			case '\0':
1343 				stats->nul++;
1344 				stats->nonprintable++;
1345 				break;
1346 			case '\n':
1347 				stats->lf++;
1348 				break;
1349 			case '\r':
1350 				stats->cr++;
1351 				if (scan < end && *scan == '\n')
1352 					stats->crlf++;
1353 				break;
1354 			case '\t': case '\f': case '\v': case '\b': case 0x1b: /*ESC*/
1355 				stats->printable++;
1356 				break;
1357 			default:
1358 				stats->nonprintable++;
1359 				break;
1360 			}
1361 	}
1362 
1363 	/* Treat files with a bare CR as binary */
1364 	return (stats->cr != stats->crlf || stats->nul > 0 ||
1365 		((stats->printable >> 7) < stats->nonprintable));
1366 }
1367