]> git.proxmox.com Git - libgit2.git/blob - src/buffer.c
Merge pull request #2508 from libgit2/rb/fix-ignore-slash-star
[libgit2.git] / src / buffer.c
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 ((d) > buf->asize && git_buf_grow(b, (d)) < 0)\
22 return -1;
23
24
25 void 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 if (initial_size)
32 git_buf_grow(buf, initial_size);
33 }
34
35 int git_buf_try_grow(
36 git_buf *buf, size_t target_size, bool mark_oom, bool preserve_external)
37 {
38 char *new_ptr;
39 size_t new_size;
40
41 if (buf->ptr == git_buf__oom)
42 return -1;
43
44 if (!target_size)
45 target_size = buf->size;
46
47 if (target_size <= buf->asize)
48 return 0;
49
50 if (buf->asize == 0) {
51 new_size = target_size;
52 new_ptr = NULL;
53 } else {
54 new_size = buf->asize;
55 new_ptr = buf->ptr;
56 }
57
58 /* grow the buffer size by 1.5, until it's big enough
59 * to fit our target size */
60 while (new_size < target_size)
61 new_size = (new_size << 1) - (new_size >> 1);
62
63 /* round allocation up to multiple of 8 */
64 new_size = (new_size + 7) & ~7;
65
66 new_ptr = git__realloc(new_ptr, new_size);
67
68 if (!new_ptr) {
69 if (mark_oom) {
70 if (buf->ptr) git__free(buf->ptr);
71 buf->ptr = git_buf__oom;
72 }
73 return -1;
74 }
75
76 if (preserve_external && !buf->asize && buf->ptr != NULL && buf->size > 0)
77 memcpy(new_ptr, buf->ptr, min(buf->size, new_size));
78
79 buf->asize = new_size;
80 buf->ptr = new_ptr;
81
82 /* truncate the existing buffer size if necessary */
83 if (buf->size >= buf->asize)
84 buf->size = buf->asize - 1;
85 buf->ptr[buf->size] = '\0';
86
87 return 0;
88 }
89
90 int git_buf_grow(git_buf *buffer, size_t target_size)
91 {
92 return git_buf_try_grow(buffer, target_size, true, true);
93 }
94
95 void git_buf_free(git_buf *buf)
96 {
97 if (!buf) return;
98
99 if (buf->asize > 0 && buf->ptr != NULL && buf->ptr != git_buf__oom)
100 git__free(buf->ptr);
101
102 git_buf_init(buf, 0);
103 }
104
105 void git_buf_sanitize(git_buf *buf)
106 {
107 if (buf->ptr == NULL) {
108 assert(buf->size == 0 && buf->asize == 0);
109 buf->ptr = git_buf__initbuf;
110 } else if (buf->asize > buf->size)
111 buf->ptr[buf->size] = '\0';
112 }
113
114 void git_buf_clear(git_buf *buf)
115 {
116 buf->size = 0;
117
118 if (!buf->ptr) {
119 buf->ptr = git_buf__initbuf;
120 buf->asize = 0;
121 }
122
123 if (buf->asize > 0)
124 buf->ptr[0] = '\0';
125 }
126
127 int git_buf_set(git_buf *buf, const void *data, size_t len)
128 {
129 if (len == 0 || data == NULL) {
130 git_buf_clear(buf);
131 } else {
132 if (data != buf->ptr) {
133 ENSURE_SIZE(buf, len + 1);
134 memmove(buf->ptr, data, len);
135 }
136
137 buf->size = len;
138 if (buf->asize > buf->size)
139 buf->ptr[buf->size] = '\0';
140
141 }
142 return 0;
143 }
144
145 int git_buf_is_binary(const git_buf *buf)
146 {
147 return git_buf_text_is_binary(buf);
148 }
149
150 int git_buf_contains_nul(const git_buf *buf)
151 {
152 return git_buf_text_contains_nul(buf);
153 }
154
155 int git_buf_sets(git_buf *buf, const char *string)
156 {
157 return git_buf_set(buf, string, string ? strlen(string) : 0);
158 }
159
160 int git_buf_putc(git_buf *buf, char c)
161 {
162 ENSURE_SIZE(buf, buf->size + 2);
163 buf->ptr[buf->size++] = c;
164 buf->ptr[buf->size] = '\0';
165 return 0;
166 }
167
168 int git_buf_putcn(git_buf *buf, char c, size_t len)
169 {
170 ENSURE_SIZE(buf, buf->size + len + 1);
171 memset(buf->ptr + buf->size, c, len);
172 buf->size += len;
173 buf->ptr[buf->size] = '\0';
174 return 0;
175 }
176
177 int git_buf_put(git_buf *buf, const char *data, size_t len)
178 {
179 ENSURE_SIZE(buf, buf->size + len + 1);
180 memmove(buf->ptr + buf->size, data, len);
181 buf->size += len;
182 buf->ptr[buf->size] = '\0';
183 return 0;
184 }
185
186 int git_buf_puts(git_buf *buf, const char *string)
187 {
188 assert(string);
189 return git_buf_put(buf, string, strlen(string));
190 }
191
192 static const char base64_encode[] =
193 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
194
195 int git_buf_encode_base64(git_buf *buf, const char *data, size_t len)
196 {
197 size_t extra = len % 3;
198 uint8_t *write, a, b, c;
199 const uint8_t *read = (const uint8_t *)data;
200
201 ENSURE_SIZE(buf, buf->size + 4 * ((len / 3) + !!extra) + 1);
202 write = (uint8_t *)&buf->ptr[buf->size];
203
204 /* convert each run of 3 bytes into 4 output bytes */
205 for (len -= extra; len > 0; len -= 3) {
206 a = *read++;
207 b = *read++;
208 c = *read++;
209
210 *write++ = base64_encode[a >> 2];
211 *write++ = base64_encode[(a & 0x03) << 4 | b >> 4];
212 *write++ = base64_encode[(b & 0x0f) << 2 | c >> 6];
213 *write++ = base64_encode[c & 0x3f];
214 }
215
216 if (extra > 0) {
217 a = *read++;
218 b = (extra > 1) ? *read++ : 0;
219
220 *write++ = base64_encode[a >> 2];
221 *write++ = base64_encode[(a & 0x03) << 4 | b >> 4];
222 *write++ = (extra > 1) ? base64_encode[(b & 0x0f) << 2] : '=';
223 *write++ = '=';
224 }
225
226 buf->size = ((char *)write) - buf->ptr;
227 buf->ptr[buf->size] = '\0';
228
229 return 0;
230 }
231
232 /* The inverse of base64_encode, offset by '+' == 43. */
233 static const int8_t base64_decode[] = {
234 62,
235 -1, -1, -1,
236 63,
237 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
238 -1, -1, -1, 0, -1, -1, -1,
239 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
240 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
241 -1, -1, -1, -1, -1, -1,
242 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
243 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
244 };
245
246 #define BASE64_DECODE_VALUE(c) (((c) < 43 || (c) > 122) ? -1 : base64_decode[c - 43])
247
248 int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len)
249 {
250 size_t i;
251 int8_t a, b, c, d;
252 size_t orig_size = buf->size;
253
254 assert(len % 4 == 0);
255 ENSURE_SIZE(buf, buf->size + (len / 4 * 3) + 1);
256
257 for (i = 0; i < len; i += 4) {
258 if ((a = BASE64_DECODE_VALUE(base64[i])) < 0 ||
259 (b = BASE64_DECODE_VALUE(base64[i+1])) < 0 ||
260 (c = BASE64_DECODE_VALUE(base64[i+2])) < 0 ||
261 (d = BASE64_DECODE_VALUE(base64[i+3])) < 0) {
262 buf->size = orig_size;
263 buf->ptr[buf->size] = '\0';
264
265 giterr_set(GITERR_INVALID, "Invalid base64 input");
266 return -1;
267 }
268
269 buf->ptr[buf->size++] = ((a << 2) | (b & 0x30) >> 4);
270 buf->ptr[buf->size++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
271 buf->ptr[buf->size++] = (c & 0x03) << 6 | (d & 0x3f);
272 }
273
274 buf->ptr[buf->size] = '\0';
275 return 0;
276 }
277
278 static const char b85str[] =
279 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
280
281 int git_buf_encode_base85(git_buf *buf, const char *data, size_t len)
282 {
283 ENSURE_SIZE(buf, buf->size + (5 * ((len / 4) + !!(len % 4))) + 1);
284
285 while (len) {
286 uint32_t acc = 0;
287 char b85[5];
288 int i;
289
290 for (i = 24; i >= 0; i -= 8) {
291 uint8_t ch = *data++;
292 acc |= ch << i;
293
294 if (--len == 0)
295 break;
296 }
297
298 for (i = 4; i >= 0; i--) {
299 int val = acc % 85;
300 acc /= 85;
301
302 b85[i] = b85str[val];
303 }
304
305 for (i = 0; i < 5; i++)
306 buf->ptr[buf->size++] = b85[i];
307 }
308
309 buf->ptr[buf->size] = '\0';
310
311 return 0;
312 }
313
314 int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
315 {
316 int len;
317 const size_t expected_size = buf->size + (strlen(format) * 2);
318
319 ENSURE_SIZE(buf, expected_size);
320
321 while (1) {
322 va_list args;
323 va_copy(args, ap);
324
325 len = p_vsnprintf(
326 buf->ptr + buf->size,
327 buf->asize - buf->size,
328 format, args
329 );
330
331 va_end(args);
332
333 if (len < 0) {
334 git__free(buf->ptr);
335 buf->ptr = git_buf__oom;
336 return -1;
337 }
338
339 if ((size_t)len + 1 <= buf->asize - buf->size) {
340 buf->size += len;
341 break;
342 }
343
344 ENSURE_SIZE(buf, buf->size + len + 1);
345 }
346
347 return 0;
348 }
349
350 int git_buf_printf(git_buf *buf, const char *format, ...)
351 {
352 int r;
353 va_list ap;
354
355 va_start(ap, format);
356 r = git_buf_vprintf(buf, format, ap);
357 va_end(ap);
358
359 return r;
360 }
361
362 void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf)
363 {
364 size_t copylen;
365
366 assert(data && datasize && buf);
367
368 data[0] = '\0';
369
370 if (buf->size == 0 || buf->asize <= 0)
371 return;
372
373 copylen = buf->size;
374 if (copylen > datasize - 1)
375 copylen = datasize - 1;
376 memmove(data, buf->ptr, copylen);
377 data[copylen] = '\0';
378 }
379
380 void git_buf_consume(git_buf *buf, const char *end)
381 {
382 if (end > buf->ptr && end <= buf->ptr + buf->size) {
383 size_t consumed = end - buf->ptr;
384 memmove(buf->ptr, end, buf->size - consumed);
385 buf->size -= consumed;
386 buf->ptr[buf->size] = '\0';
387 }
388 }
389
390 void git_buf_truncate(git_buf *buf, size_t len)
391 {
392 if (len >= buf->size)
393 return;
394
395 buf->size = len;
396 if (buf->size < buf->asize)
397 buf->ptr[buf->size] = '\0';
398 }
399
400 void git_buf_shorten(git_buf *buf, size_t amount)
401 {
402 if (buf->size > amount)
403 git_buf_truncate(buf, buf->size - amount);
404 else
405 git_buf_clear(buf);
406 }
407
408 void git_buf_rtruncate_at_char(git_buf *buf, char separator)
409 {
410 ssize_t idx = git_buf_rfind_next(buf, separator);
411 git_buf_truncate(buf, idx < 0 ? 0 : (size_t)idx);
412 }
413
414 void git_buf_swap(git_buf *buf_a, git_buf *buf_b)
415 {
416 git_buf t = *buf_a;
417 *buf_a = *buf_b;
418 *buf_b = t;
419 }
420
421 char *git_buf_detach(git_buf *buf)
422 {
423 char *data = buf->ptr;
424
425 if (buf->asize == 0 || buf->ptr == git_buf__oom)
426 return NULL;
427
428 git_buf_init(buf, 0);
429
430 return data;
431 }
432
433 void git_buf_attach(git_buf *buf, char *ptr, size_t asize)
434 {
435 git_buf_free(buf);
436
437 if (ptr) {
438 buf->ptr = ptr;
439 buf->size = strlen(ptr);
440 if (asize)
441 buf->asize = (asize < buf->size) ? buf->size + 1 : asize;
442 else /* pass 0 to fall back on strlen + 1 */
443 buf->asize = buf->size + 1;
444 } else {
445 git_buf_grow(buf, asize);
446 }
447 }
448
449 int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...)
450 {
451 va_list ap;
452 int i;
453 size_t total_size = 0, original_size = buf->size;
454 char *out, *original = buf->ptr;
455
456 if (buf->size > 0 && buf->ptr[buf->size - 1] != separator)
457 ++total_size; /* space for initial separator */
458
459 /* Make two passes to avoid multiple reallocation */
460
461 va_start(ap, nbuf);
462 for (i = 0; i < nbuf; ++i) {
463 const char* segment;
464 size_t segment_len;
465
466 segment = va_arg(ap, const char *);
467 if (!segment)
468 continue;
469
470 segment_len = strlen(segment);
471 total_size += segment_len;
472 if (segment_len == 0 || segment[segment_len - 1] != separator)
473 ++total_size; /* space for separator */
474 }
475 va_end(ap);
476
477 /* expand buffer if needed */
478 if (total_size == 0)
479 return 0;
480 if (git_buf_grow(buf, buf->size + total_size + 1) < 0)
481 return -1;
482
483 out = buf->ptr + buf->size;
484
485 /* append separator to existing buf if needed */
486 if (buf->size > 0 && out[-1] != separator)
487 *out++ = separator;
488
489 va_start(ap, nbuf);
490 for (i = 0; i < nbuf; ++i) {
491 const char* segment;
492 size_t segment_len;
493
494 segment = va_arg(ap, const char *);
495 if (!segment)
496 continue;
497
498 /* deal with join that references buffer's original content */
499 if (segment >= original && segment < original + original_size) {
500 size_t offset = (segment - original);
501 segment = buf->ptr + offset;
502 segment_len = original_size - offset;
503 } else {
504 segment_len = strlen(segment);
505 }
506
507 /* skip leading separators */
508 if (out > buf->ptr && out[-1] == separator)
509 while (segment_len > 0 && *segment == separator) {
510 segment++;
511 segment_len--;
512 }
513
514 /* copy over next buffer */
515 if (segment_len > 0) {
516 memmove(out, segment, segment_len);
517 out += segment_len;
518 }
519
520 /* append trailing separator (except for last item) */
521 if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator)
522 *out++ = separator;
523 }
524 va_end(ap);
525
526 /* set size based on num characters actually written */
527 buf->size = out - buf->ptr;
528 buf->ptr[buf->size] = '\0';
529
530 return 0;
531 }
532
533 int git_buf_join(
534 git_buf *buf,
535 char separator,
536 const char *str_a,
537 const char *str_b)
538 {
539 size_t strlen_a = str_a ? strlen(str_a) : 0;
540 size_t strlen_b = strlen(str_b);
541 int need_sep = 0;
542 ssize_t offset_a = -1;
543
544 /* not safe to have str_b point internally to the buffer */
545 assert(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
546
547 /* figure out if we need to insert a separator */
548 if (separator && strlen_a) {
549 while (*str_b == separator) { str_b++; strlen_b--; }
550 if (str_a[strlen_a - 1] != separator)
551 need_sep = 1;
552 }
553
554 /* str_a could be part of the buffer */
555 if (str_a >= buf->ptr && str_a < buf->ptr + buf->size)
556 offset_a = str_a - buf->ptr;
557
558 if (git_buf_grow(buf, strlen_a + strlen_b + need_sep + 1) < 0)
559 return -1;
560 assert(buf->ptr);
561
562 /* fix up internal pointers */
563 if (offset_a >= 0)
564 str_a = buf->ptr + offset_a;
565
566 /* do the actual copying */
567 if (offset_a != 0 && str_a)
568 memmove(buf->ptr, str_a, strlen_a);
569 if (need_sep)
570 buf->ptr[strlen_a] = separator;
571 memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b);
572
573 buf->size = strlen_a + strlen_b + need_sep;
574 buf->ptr[buf->size] = '\0';
575
576 return 0;
577 }
578
579 int git_buf_join3(
580 git_buf *buf,
581 char separator,
582 const char *str_a,
583 const char *str_b,
584 const char *str_c)
585 {
586 size_t len_a = strlen(str_a), len_b = strlen(str_b), len_c = strlen(str_c);
587 int sep_a = 0, sep_b = 0;
588 char *tgt;
589
590 /* for this function, disallow pointers into the existing buffer */
591 assert(str_a < buf->ptr || str_a >= buf->ptr + buf->size);
592 assert(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
593 assert(str_c < buf->ptr || str_c >= buf->ptr + buf->size);
594
595 if (separator) {
596 if (len_a > 0) {
597 while (*str_b == separator) { str_b++; len_b--; }
598 sep_a = (str_a[len_a - 1] != separator);
599 }
600 if (len_a > 0 || len_b > 0)
601 while (*str_c == separator) { str_c++; len_c--; }
602 if (len_b > 0)
603 sep_b = (str_b[len_b - 1] != separator);
604 }
605
606 if (git_buf_grow(buf, len_a + sep_a + len_b + sep_b + len_c + 1) < 0)
607 return -1;
608
609 tgt = buf->ptr;
610
611 if (len_a) {
612 memcpy(tgt, str_a, len_a);
613 tgt += len_a;
614 }
615 if (sep_a)
616 *tgt++ = separator;
617 if (len_b) {
618 memcpy(tgt, str_b, len_b);
619 tgt += len_b;
620 }
621 if (sep_b)
622 *tgt++ = separator;
623 if (len_c)
624 memcpy(tgt, str_c, len_c);
625
626 buf->size = len_a + sep_a + len_b + sep_b + len_c;
627 buf->ptr[buf->size] = '\0';
628
629 return 0;
630 }
631
632 void git_buf_rtrim(git_buf *buf)
633 {
634 while (buf->size > 0) {
635 if (!git__isspace(buf->ptr[buf->size - 1]))
636 break;
637
638 buf->size--;
639 }
640
641 if (buf->asize > buf->size)
642 buf->ptr[buf->size] = '\0';
643 }
644
645 int git_buf_cmp(const git_buf *a, const git_buf *b)
646 {
647 int result = memcmp(a->ptr, b->ptr, min(a->size, b->size));
648 return (result != 0) ? result :
649 (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0;
650 }
651
652 int git_buf_splice(
653 git_buf *buf,
654 size_t where,
655 size_t nb_to_remove,
656 const char *data,
657 size_t nb_to_insert)
658 {
659 assert(buf &&
660 where <= git_buf_len(buf) &&
661 where + nb_to_remove <= git_buf_len(buf));
662
663 /* Ported from git.git
664 * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176
665 */
666 ENSURE_SIZE(buf, buf->size + nb_to_insert - nb_to_insert + 1);
667
668 memmove(buf->ptr + where + nb_to_insert,
669 buf->ptr + where + nb_to_remove,
670 buf->size - where - nb_to_remove);
671
672 memcpy(buf->ptr + where, data, nb_to_insert);
673
674 buf->size = buf->size + nb_to_insert - nb_to_remove;
675 buf->ptr[buf->size] = '\0';
676 return 0;
677 }