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("ed, '"');
942 git_buf_put("ed, 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("ed, '\\');
948 git_buf_putc("ed, 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("ed, '\\');
954 git_buf_putc("ed, 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("ed, "\\%03o", (unsigned char)buf->ptr[i]);
961 }
962
963 /* yay, printable! */
964 else {
965 git_buf_putc("ed, buf->ptr[i]);
966 }
967 }
968
969 git_buf_putc("ed, '"');
970
971 if (git_buf_oom("ed)) {
972 error = -1;
973 goto done;
974 }
975
976 git_buf_swap("ed, buf);
977
978 done:
979 git_buf_dispose("ed);
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