]> git.proxmox.com Git - libgit2.git/blob - src/buffer.c
Merge pull request #2250 from jacquesg/vector-leak
[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 }
110 }
111
112 void git_buf_clear(git_buf *buf)
113 {
114 buf->size = 0;
115
116 if (!buf->ptr)
117 buf->ptr = git_buf__initbuf;
118
119 if (buf->asize > 0)
120 buf->ptr[0] = '\0';
121 }
122
123 int git_buf_set(git_buf *buf, const void *data, size_t len)
124 {
125 if (len == 0 || data == NULL) {
126 git_buf_clear(buf);
127 } else {
128 if (data != buf->ptr) {
129 ENSURE_SIZE(buf, len + 1);
130 memmove(buf->ptr, data, len);
131 }
132 buf->size = len;
133 buf->ptr[buf->size] = '\0';
134 }
135 return 0;
136 }
137
138 int git_buf_sets(git_buf *buf, const char *string)
139 {
140 return git_buf_set(buf, string, string ? strlen(string) : 0);
141 }
142
143 int git_buf_putc(git_buf *buf, char c)
144 {
145 ENSURE_SIZE(buf, buf->size + 2);
146 buf->ptr[buf->size++] = c;
147 buf->ptr[buf->size] = '\0';
148 return 0;
149 }
150
151 int git_buf_put(git_buf *buf, const char *data, size_t len)
152 {
153 ENSURE_SIZE(buf, buf->size + len + 1);
154 memmove(buf->ptr + buf->size, data, len);
155 buf->size += len;
156 buf->ptr[buf->size] = '\0';
157 return 0;
158 }
159
160 int git_buf_puts(git_buf *buf, const char *string)
161 {
162 assert(string);
163 return git_buf_put(buf, string, strlen(string));
164 }
165
166 static const char b64str[] =
167 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
168
169 int git_buf_put_base64(git_buf *buf, const char *data, size_t len)
170 {
171 size_t extra = len % 3;
172 uint8_t *write, a, b, c;
173 const uint8_t *read = (const uint8_t *)data;
174
175 ENSURE_SIZE(buf, buf->size + 4 * ((len / 3) + !!extra) + 1);
176 write = (uint8_t *)&buf->ptr[buf->size];
177
178 /* convert each run of 3 bytes into 4 output bytes */
179 for (len -= extra; len > 0; len -= 3) {
180 a = *read++;
181 b = *read++;
182 c = *read++;
183
184 *write++ = b64str[a >> 2];
185 *write++ = b64str[(a & 0x03) << 4 | b >> 4];
186 *write++ = b64str[(b & 0x0f) << 2 | c >> 6];
187 *write++ = b64str[c & 0x3f];
188 }
189
190 if (extra > 0) {
191 a = *read++;
192 b = (extra > 1) ? *read++ : 0;
193
194 *write++ = b64str[a >> 2];
195 *write++ = b64str[(a & 0x03) << 4 | b >> 4];
196 *write++ = (extra > 1) ? b64str[(b & 0x0f) << 2] : '=';
197 *write++ = '=';
198 }
199
200 buf->size = ((char *)write) - buf->ptr;
201 buf->ptr[buf->size] = '\0';
202
203 return 0;
204 }
205
206 int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
207 {
208 int len;
209 const size_t expected_size = buf->size + (strlen(format) * 2);
210
211 ENSURE_SIZE(buf, expected_size);
212
213 while (1) {
214 va_list args;
215 va_copy(args, ap);
216
217 len = p_vsnprintf(
218 buf->ptr + buf->size,
219 buf->asize - buf->size,
220 format, args
221 );
222
223 va_end(args);
224
225 if (len < 0) {
226 git__free(buf->ptr);
227 buf->ptr = git_buf__oom;
228 return -1;
229 }
230
231 if ((size_t)len + 1 <= buf->asize - buf->size) {
232 buf->size += len;
233 break;
234 }
235
236 ENSURE_SIZE(buf, buf->size + len + 1);
237 }
238
239 return 0;
240 }
241
242 int git_buf_printf(git_buf *buf, const char *format, ...)
243 {
244 int r;
245 va_list ap;
246
247 va_start(ap, format);
248 r = git_buf_vprintf(buf, format, ap);
249 va_end(ap);
250
251 return r;
252 }
253
254 void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf)
255 {
256 size_t copylen;
257
258 assert(data && datasize && buf);
259
260 data[0] = '\0';
261
262 if (buf->size == 0 || buf->asize <= 0)
263 return;
264
265 copylen = buf->size;
266 if (copylen > datasize - 1)
267 copylen = datasize - 1;
268 memmove(data, buf->ptr, copylen);
269 data[copylen] = '\0';
270 }
271
272 void git_buf_consume(git_buf *buf, const char *end)
273 {
274 if (end > buf->ptr && end <= buf->ptr + buf->size) {
275 size_t consumed = end - buf->ptr;
276 memmove(buf->ptr, end, buf->size - consumed);
277 buf->size -= consumed;
278 buf->ptr[buf->size] = '\0';
279 }
280 }
281
282 void git_buf_truncate(git_buf *buf, size_t len)
283 {
284 if (len < buf->size) {
285 buf->size = len;
286 buf->ptr[buf->size] = '\0';
287 }
288 }
289
290 void git_buf_shorten(git_buf *buf, size_t amount)
291 {
292 if (amount > buf->size)
293 amount = buf->size;
294
295 buf->size = buf->size - amount;
296 buf->ptr[buf->size] = '\0';
297 }
298
299 void git_buf_rtruncate_at_char(git_buf *buf, char separator)
300 {
301 ssize_t idx = git_buf_rfind_next(buf, separator);
302 git_buf_truncate(buf, idx < 0 ? 0 : (size_t)idx);
303 }
304
305 void git_buf_swap(git_buf *buf_a, git_buf *buf_b)
306 {
307 git_buf t = *buf_a;
308 *buf_a = *buf_b;
309 *buf_b = t;
310 }
311
312 char *git_buf_detach(git_buf *buf)
313 {
314 char *data = buf->ptr;
315
316 if (buf->asize == 0 || buf->ptr == git_buf__oom)
317 return NULL;
318
319 git_buf_init(buf, 0);
320
321 return data;
322 }
323
324 void git_buf_attach(git_buf *buf, char *ptr, size_t asize)
325 {
326 git_buf_free(buf);
327
328 if (ptr) {
329 buf->ptr = ptr;
330 buf->size = strlen(ptr);
331 if (asize)
332 buf->asize = (asize < buf->size) ? buf->size + 1 : asize;
333 else /* pass 0 to fall back on strlen + 1 */
334 buf->asize = buf->size + 1;
335 } else {
336 git_buf_grow(buf, asize);
337 }
338 }
339
340 int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...)
341 {
342 va_list ap;
343 int i;
344 size_t total_size = 0, original_size = buf->size;
345 char *out, *original = buf->ptr;
346
347 if (buf->size > 0 && buf->ptr[buf->size - 1] != separator)
348 ++total_size; /* space for initial separator */
349
350 /* Make two passes to avoid multiple reallocation */
351
352 va_start(ap, nbuf);
353 for (i = 0; i < nbuf; ++i) {
354 const char* segment;
355 size_t segment_len;
356
357 segment = va_arg(ap, const char *);
358 if (!segment)
359 continue;
360
361 segment_len = strlen(segment);
362 total_size += segment_len;
363 if (segment_len == 0 || segment[segment_len - 1] != separator)
364 ++total_size; /* space for separator */
365 }
366 va_end(ap);
367
368 /* expand buffer if needed */
369 if (total_size == 0)
370 return 0;
371 if (git_buf_grow(buf, buf->size + total_size + 1) < 0)
372 return -1;
373
374 out = buf->ptr + buf->size;
375
376 /* append separator to existing buf if needed */
377 if (buf->size > 0 && out[-1] != separator)
378 *out++ = separator;
379
380 va_start(ap, nbuf);
381 for (i = 0; i < nbuf; ++i) {
382 const char* segment;
383 size_t segment_len;
384
385 segment = va_arg(ap, const char *);
386 if (!segment)
387 continue;
388
389 /* deal with join that references buffer's original content */
390 if (segment >= original && segment < original + original_size) {
391 size_t offset = (segment - original);
392 segment = buf->ptr + offset;
393 segment_len = original_size - offset;
394 } else {
395 segment_len = strlen(segment);
396 }
397
398 /* skip leading separators */
399 if (out > buf->ptr && out[-1] == separator)
400 while (segment_len > 0 && *segment == separator) {
401 segment++;
402 segment_len--;
403 }
404
405 /* copy over next buffer */
406 if (segment_len > 0) {
407 memmove(out, segment, segment_len);
408 out += segment_len;
409 }
410
411 /* append trailing separator (except for last item) */
412 if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator)
413 *out++ = separator;
414 }
415 va_end(ap);
416
417 /* set size based on num characters actually written */
418 buf->size = out - buf->ptr;
419 buf->ptr[buf->size] = '\0';
420
421 return 0;
422 }
423
424 int git_buf_join(
425 git_buf *buf,
426 char separator,
427 const char *str_a,
428 const char *str_b)
429 {
430 size_t strlen_a = str_a ? strlen(str_a) : 0;
431 size_t strlen_b = strlen(str_b);
432 int need_sep = 0;
433 ssize_t offset_a = -1;
434
435 /* not safe to have str_b point internally to the buffer */
436 assert(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
437
438 /* figure out if we need to insert a separator */
439 if (separator && strlen_a) {
440 while (*str_b == separator) { str_b++; strlen_b--; }
441 if (str_a[strlen_a - 1] != separator)
442 need_sep = 1;
443 }
444
445 /* str_a could be part of the buffer */
446 if (str_a >= buf->ptr && str_a < buf->ptr + buf->size)
447 offset_a = str_a - buf->ptr;
448
449 if (git_buf_grow(buf, strlen_a + strlen_b + need_sep + 1) < 0)
450 return -1;
451 assert(buf->ptr);
452
453 /* fix up internal pointers */
454 if (offset_a >= 0)
455 str_a = buf->ptr + offset_a;
456
457 /* do the actual copying */
458 if (offset_a != 0 && str_a)
459 memmove(buf->ptr, str_a, strlen_a);
460 if (need_sep)
461 buf->ptr[strlen_a] = separator;
462 memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b);
463
464 buf->size = strlen_a + strlen_b + need_sep;
465 buf->ptr[buf->size] = '\0';
466
467 return 0;
468 }
469
470 int git_buf_join3(
471 git_buf *buf,
472 char separator,
473 const char *str_a,
474 const char *str_b,
475 const char *str_c)
476 {
477 size_t len_a = strlen(str_a), len_b = strlen(str_b), len_c = strlen(str_c);
478 int sep_a = 0, sep_b = 0;
479 char *tgt;
480
481 /* for this function, disallow pointers into the existing buffer */
482 assert(str_a < buf->ptr || str_a >= buf->ptr + buf->size);
483 assert(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
484 assert(str_c < buf->ptr || str_c >= buf->ptr + buf->size);
485
486 if (separator) {
487 if (len_a > 0) {
488 while (*str_b == separator) { str_b++; len_b--; }
489 sep_a = (str_a[len_a - 1] != separator);
490 }
491 if (len_a > 0 || len_b > 0)
492 while (*str_c == separator) { str_c++; len_c--; }
493 if (len_b > 0)
494 sep_b = (str_b[len_b - 1] != separator);
495 }
496
497 if (git_buf_grow(buf, len_a + sep_a + len_b + sep_b + len_c + 1) < 0)
498 return -1;
499
500 tgt = buf->ptr;
501
502 if (len_a) {
503 memcpy(tgt, str_a, len_a);
504 tgt += len_a;
505 }
506 if (sep_a)
507 *tgt++ = separator;
508 if (len_b) {
509 memcpy(tgt, str_b, len_b);
510 tgt += len_b;
511 }
512 if (sep_b)
513 *tgt++ = separator;
514 if (len_c)
515 memcpy(tgt, str_c, len_c);
516
517 buf->size = len_a + sep_a + len_b + sep_b + len_c;
518 buf->ptr[buf->size] = '\0';
519
520 return 0;
521 }
522
523 void git_buf_rtrim(git_buf *buf)
524 {
525 while (buf->size > 0) {
526 if (!git__isspace(buf->ptr[buf->size - 1]))
527 break;
528
529 buf->size--;
530 }
531
532 buf->ptr[buf->size] = '\0';
533 }
534
535 int git_buf_cmp(const git_buf *a, const git_buf *b)
536 {
537 int result = memcmp(a->ptr, b->ptr, min(a->size, b->size));
538 return (result != 0) ? result :
539 (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0;
540 }
541
542 int git_buf_splice(
543 git_buf *buf,
544 size_t where,
545 size_t nb_to_remove,
546 const char *data,
547 size_t nb_to_insert)
548 {
549 assert(buf &&
550 where <= git_buf_len(buf) &&
551 where + nb_to_remove <= git_buf_len(buf));
552
553 /* Ported from git.git
554 * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176
555 */
556 if (git_buf_grow(buf, git_buf_len(buf) + nb_to_insert - nb_to_remove) < 0)
557 return -1;
558
559 memmove(buf->ptr + where + nb_to_insert,
560 buf->ptr + where + nb_to_remove,
561 buf->size - where - nb_to_remove);
562
563 memcpy(buf->ptr + where, data, nb_to_insert);
564
565 buf->size = buf->size + nb_to_insert - nb_to_remove;
566 buf->ptr[buf->size] = '\0';
567 return 0;
568 }