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