]>
Commit | Line | Data |
---|---|---|
bb742ede | 1 | /* |
5e0de328 | 2 | * Copyright (C) 2009-2012 the libgit2 contributors |
bb742ede VM |
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 | */ | |
afeecf4f VM |
7 | #include "buffer.h" |
8 | #include "posix.h" | |
9 | #include <stdarg.h> | |
13224ea4 | 10 | #include <ctype.h> |
afeecf4f | 11 | |
309113c9 RB |
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 | */ | |
a0d95962 | 15 | char git_buf__initbuf[1]; |
309113c9 | 16 | |
a0d95962 | 17 | char git_buf__oom[1]; |
13224ea4 | 18 | |
afeecf4f | 19 | #define ENSURE_SIZE(b, d) \ |
0d0fa7c3 RB |
20 | if ((d) > buf->asize && git_buf_grow(b, (d)) < 0)\ |
21 | return -1; | |
97769280 | 22 | |
afeecf4f | 23 | |
309113c9 RB |
24 | void git_buf_init(git_buf *buf, size_t initial_size) |
25 | { | |
26 | buf->asize = 0; | |
27 | buf->size = 0; | |
a0d95962 | 28 | buf->ptr = git_buf__initbuf; |
309113c9 RB |
29 | |
30 | if (initial_size) | |
31 | git_buf_grow(buf, initial_size); | |
32 | } | |
33 | ||
afeecf4f | 34 | int git_buf_grow(git_buf *buf, size_t target_size) |
97769280 RB |
35 | { |
36 | int error = git_buf_try_grow(buf, target_size); | |
0d0fa7c3 | 37 | if (error != 0) |
a0d95962 | 38 | buf->ptr = git_buf__oom; |
97769280 RB |
39 | return error; |
40 | } | |
41 | ||
42 | int git_buf_try_grow(git_buf *buf, size_t target_size) | |
afeecf4f VM |
43 | { |
44 | char *new_ptr; | |
309113c9 | 45 | size_t new_size; |
afeecf4f | 46 | |
a0d95962 | 47 | if (buf->ptr == git_buf__oom) |
0d0fa7c3 | 48 | return -1; |
afeecf4f | 49 | |
13224ea4 | 50 | if (target_size <= buf->asize) |
0d0fa7c3 | 51 | return 0; |
8c74d22e | 52 | |
309113c9 RB |
53 | if (buf->asize == 0) { |
54 | new_size = target_size; | |
55 | new_ptr = NULL; | |
56 | } else { | |
13224ea4 | 57 | new_size = buf->asize; |
309113c9 RB |
58 | new_ptr = buf->ptr; |
59 | } | |
afeecf4f VM |
60 | |
61 | /* grow the buffer size by 1.5, until it's big enough | |
62 | * to fit our target size */ | |
309113c9 RB |
63 | while (new_size < target_size) |
64 | new_size = (new_size << 1) - (new_size >> 1); | |
afeecf4f | 65 | |
8c74d22e | 66 | /* round allocation up to multiple of 8 */ |
309113c9 | 67 | new_size = (new_size + 7) & ~7; |
8c74d22e | 68 | |
309113c9 | 69 | new_ptr = git__realloc(new_ptr, new_size); |
97769280 | 70 | if (!new_ptr) |
cb8a7961 | 71 | return -1; |
afeecf4f | 72 | |
309113c9 RB |
73 | buf->asize = new_size; |
74 | buf->ptr = new_ptr; | |
75 | ||
76 | /* truncate the existing buffer size if necessary */ | |
77 | if (buf->size >= buf->asize) | |
78 | buf->size = buf->asize - 1; | |
79 | buf->ptr[buf->size] = '\0'; | |
80 | ||
0d0fa7c3 | 81 | return 0; |
afeecf4f VM |
82 | } |
83 | ||
309113c9 RB |
84 | void git_buf_free(git_buf *buf) |
85 | { | |
86 | if (!buf) return; | |
87 | ||
a0d95962 | 88 | if (buf->ptr != git_buf__initbuf && buf->ptr != git_buf__oom) |
309113c9 RB |
89 | git__free(buf->ptr); |
90 | ||
91 | git_buf_init(buf, 0); | |
92 | } | |
93 | ||
94 | void git_buf_clear(git_buf *buf) | |
95 | { | |
96 | buf->size = 0; | |
97 | if (buf->asize > 0) | |
98 | buf->ptr[0] = '\0'; | |
99 | } | |
100 | ||
97769280 | 101 | int git_buf_set(git_buf *buf, const char *data, size_t len) |
8c74d22e RB |
102 | { |
103 | if (len == 0 || data == NULL) { | |
104 | git_buf_clear(buf); | |
105 | } else { | |
df743c7d RB |
106 | if (data != buf->ptr) { |
107 | ENSURE_SIZE(buf, len + 1); | |
108 | memmove(buf->ptr, data, len); | |
109 | } | |
8c74d22e | 110 | buf->size = len; |
c63728cd | 111 | buf->ptr[buf->size] = '\0'; |
8c74d22e | 112 | } |
0d0fa7c3 | 113 | return 0; |
8c74d22e RB |
114 | } |
115 | ||
97769280 | 116 | int git_buf_sets(git_buf *buf, const char *string) |
8c74d22e | 117 | { |
97769280 | 118 | return git_buf_set(buf, string, string ? strlen(string) : 0); |
8c74d22e RB |
119 | } |
120 | ||
97769280 | 121 | int git_buf_putc(git_buf *buf, char c) |
afeecf4f | 122 | { |
c63728cd | 123 | ENSURE_SIZE(buf, buf->size + 2); |
afeecf4f | 124 | buf->ptr[buf->size++] = c; |
c63728cd | 125 | buf->ptr[buf->size] = '\0'; |
0d0fa7c3 | 126 | return 0; |
afeecf4f VM |
127 | } |
128 | ||
97769280 | 129 | int git_buf_put(git_buf *buf, const char *data, size_t len) |
afeecf4f | 130 | { |
c63728cd | 131 | ENSURE_SIZE(buf, buf->size + len + 1); |
8c74d22e | 132 | memmove(buf->ptr + buf->size, data, len); |
afeecf4f | 133 | buf->size += len; |
c63728cd | 134 | buf->ptr[buf->size] = '\0'; |
0d0fa7c3 | 135 | return 0; |
afeecf4f VM |
136 | } |
137 | ||
97769280 | 138 | int git_buf_puts(git_buf *buf, const char *string) |
afeecf4f | 139 | { |
679b69c4 | 140 | assert(string); |
97769280 | 141 | return git_buf_put(buf, string, strlen(string)); |
afeecf4f VM |
142 | } |
143 | ||
039fc406 RB |
144 | int git_buf_puts_escaped( |
145 | git_buf *buf, const char *string, const char *esc_chars, const char *esc_with) | |
146 | { | |
0c8858de RB |
147 | const char *scan; |
148 | size_t total = 0, esc_len = strlen(esc_with), count; | |
039fc406 | 149 | |
0c8858de RB |
150 | if (!string) |
151 | return 0; | |
152 | ||
153 | for (scan = string; *scan; ) { | |
154 | /* count run of non-escaped characters */ | |
155 | count = strcspn(scan, esc_chars); | |
156 | total += count; | |
157 | scan += count; | |
158 | /* count run of escaped characters */ | |
159 | count = strspn(scan, esc_chars); | |
160 | total += count * (esc_len + 1); | |
161 | scan += count; | |
039fc406 RB |
162 | } |
163 | ||
164 | ENSURE_SIZE(buf, buf->size + total + 1); | |
165 | ||
166 | for (scan = string; *scan; ) { | |
0c8858de | 167 | count = strcspn(scan, esc_chars); |
039fc406 RB |
168 | |
169 | memmove(buf->ptr + buf->size, scan, count); | |
170 | scan += count; | |
171 | buf->size += count; | |
172 | ||
0c8858de RB |
173 | for (count = strspn(scan, esc_chars); count > 0; --count) { |
174 | /* copy escape sequence */ | |
175 | memmove(buf->ptr + buf->size, esc_with, esc_len); | |
176 | buf->size += esc_len; | |
177 | /* copy character to be escaped */ | |
178 | buf->ptr[buf->size] = *scan; | |
179 | buf->size++; | |
180 | scan++; | |
039fc406 RB |
181 | } |
182 | } | |
183 | ||
54e29b93 RB |
184 | buf->ptr[buf->size] = '\0'; |
185 | ||
039fc406 RB |
186 | return 0; |
187 | } | |
188 | ||
2d3579be RB |
189 | static const char b64str[64] = |
190 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |
191 | ||
192 | int git_buf_put_base64(git_buf *buf, const char *data, size_t len) | |
193 | { | |
194 | size_t extra = len % 3; | |
195 | uint8_t *write, a, b, c; | |
196 | const uint8_t *read = (const uint8_t *)data; | |
197 | ||
9d9288f4 | 198 | ENSURE_SIZE(buf, buf->size + 4 * ((len / 3) + !!extra) + 1); |
2d3579be RB |
199 | write = (uint8_t *)&buf->ptr[buf->size]; |
200 | ||
201 | /* convert each run of 3 bytes into 4 output bytes */ | |
202 | for (len -= extra; len > 0; len -= 3) { | |
203 | a = *read++; | |
204 | b = *read++; | |
205 | c = *read++; | |
206 | ||
207 | *write++ = b64str[a >> 2]; | |
208 | *write++ = b64str[(a & 0x03) << 4 | b >> 4]; | |
209 | *write++ = b64str[(b & 0x0f) << 2 | c >> 6]; | |
210 | *write++ = b64str[c & 0x3f]; | |
211 | } | |
212 | ||
213 | if (extra > 0) { | |
214 | a = *read++; | |
215 | b = (extra > 1) ? *read++ : 0; | |
216 | ||
217 | *write++ = b64str[a >> 2]; | |
218 | *write++ = b64str[(a & 0x03) << 4 | b >> 4]; | |
219 | *write++ = (extra > 1) ? b64str[(b & 0x0f) << 2] : '='; | |
220 | *write++ = '='; | |
221 | } | |
222 | ||
223 | buf->size = ((char *)write) - buf->ptr; | |
224 | buf->ptr[buf->size] = '\0'; | |
225 | ||
226 | return 0; | |
227 | } | |
228 | ||
baaf1c47 | 229 | int git_buf_vprintf(git_buf *buf, const char *format, va_list ap) |
afeecf4f VM |
230 | { |
231 | int len; | |
3f035860 | 232 | const size_t expected_size = buf->size + (strlen(format) * 2); |
afeecf4f | 233 | |
3f035860 | 234 | ENSURE_SIZE(buf, expected_size); |
afeecf4f VM |
235 | |
236 | while (1) { | |
baaf1c47 VM |
237 | va_list args; |
238 | va_copy(args, ap); | |
239 | ||
240 | len = p_vsnprintf( | |
241 | buf->ptr + buf->size, | |
242 | buf->asize - buf->size, | |
243 | format, args | |
244 | ); | |
afeecf4f VM |
245 | |
246 | if (len < 0) { | |
2bc8fa02 | 247 | git__free(buf->ptr); |
a0d95962 | 248 | buf->ptr = git_buf__oom; |
0d0fa7c3 | 249 | return -1; |
afeecf4f VM |
250 | } |
251 | ||
13224ea4 | 252 | if ((size_t)len + 1 <= buf->asize - buf->size) { |
afeecf4f | 253 | buf->size += len; |
97769280 | 254 | break; |
afeecf4f VM |
255 | } |
256 | ||
257 | ENSURE_SIZE(buf, buf->size + len + 1); | |
258 | } | |
97769280 | 259 | |
0d0fa7c3 | 260 | return 0; |
afeecf4f VM |
261 | } |
262 | ||
baaf1c47 VM |
263 | int git_buf_printf(git_buf *buf, const char *format, ...) |
264 | { | |
265 | int r; | |
266 | va_list ap; | |
267 | ||
268 | va_start(ap, format); | |
269 | r = git_buf_vprintf(buf, format, ap); | |
270 | va_end(ap); | |
271 | ||
272 | return r; | |
273 | } | |
274 | ||
97769280 | 275 | void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf) |
c63728cd RB |
276 | { |
277 | size_t copylen; | |
278 | ||
1dbcc9fc | 279 | assert(data && datasize && buf); |
c63728cd RB |
280 | |
281 | data[0] = '\0'; | |
282 | ||
283 | if (buf->size == 0 || buf->asize <= 0) | |
284 | return; | |
285 | ||
286 | copylen = buf->size; | |
287 | if (copylen > datasize - 1) | |
288 | copylen = datasize - 1; | |
289 | memmove(data, buf->ptr, copylen); | |
290 | data[copylen] = '\0'; | |
afeecf4f VM |
291 | } |
292 | ||
c7c30513 CMN |
293 | void git_buf_consume(git_buf *buf, const char *end) |
294 | { | |
8c74d22e RB |
295 | if (end > buf->ptr && end <= buf->ptr + buf->size) { |
296 | size_t consumed = end - buf->ptr; | |
297 | memmove(buf->ptr, end, buf->size - consumed); | |
298 | buf->size -= consumed; | |
c63728cd | 299 | buf->ptr[buf->size] = '\0'; |
8c74d22e RB |
300 | } |
301 | } | |
302 | ||
13224ea4 | 303 | void git_buf_truncate(git_buf *buf, size_t len) |
97769280 | 304 | { |
13224ea4 | 305 | if (len < buf->size) { |
97769280 RB |
306 | buf->size = len; |
307 | buf->ptr[buf->size] = '\0'; | |
308 | } | |
309 | } | |
310 | ||
b6c93aef RB |
311 | void git_buf_rtruncate_at_char(git_buf *buf, char separator) |
312 | { | |
deafee7b RB |
313 | ssize_t idx = git_buf_rfind_next(buf, separator); |
314 | git_buf_truncate(buf, idx < 0 ? 0 : (size_t)idx); | |
b6c93aef RB |
315 | } |
316 | ||
8c74d22e RB |
317 | void git_buf_swap(git_buf *buf_a, git_buf *buf_b) |
318 | { | |
319 | git_buf t = *buf_a; | |
320 | *buf_a = *buf_b; | |
321 | *buf_b = t; | |
c7c30513 | 322 | } |
8c74d22e | 323 | |
97769280 | 324 | char *git_buf_detach(git_buf *buf) |
8c74d22e | 325 | { |
309113c9 | 326 | char *data = buf->ptr; |
8c74d22e | 327 | |
a0d95962 | 328 | if (buf->asize == 0 || buf->ptr == git_buf__oom) |
8c74d22e RB |
329 | return NULL; |
330 | ||
309113c9 | 331 | git_buf_init(buf, 0); |
8c74d22e RB |
332 | |
333 | return data; | |
334 | } | |
335 | ||
13224ea4 | 336 | void git_buf_attach(git_buf *buf, char *ptr, size_t asize) |
8c74d22e | 337 | { |
97769280 RB |
338 | git_buf_free(buf); |
339 | ||
340 | if (ptr) { | |
341 | buf->ptr = ptr; | |
342 | buf->size = strlen(ptr); | |
343 | if (asize) | |
344 | buf->asize = (asize < buf->size) ? buf->size + 1 : asize; | |
345 | else /* pass 0 to fall back on strlen + 1 */ | |
346 | buf->asize = buf->size + 1; | |
347 | } else { | |
348 | git_buf_grow(buf, asize); | |
349 | } | |
350 | } | |
8c74d22e | 351 | |
97769280 RB |
352 | int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...) |
353 | { | |
8c74d22e | 354 | va_list ap; |
0d0fa7c3 | 355 | int i; |
952f94c8 RB |
356 | size_t total_size = 0, original_size = buf->size; |
357 | char *out, *original = buf->ptr; | |
8c74d22e RB |
358 | |
359 | if (buf->size > 0 && buf->ptr[buf->size - 1] != separator) | |
360 | ++total_size; /* space for initial separator */ | |
361 | ||
97769280 RB |
362 | /* Make two passes to avoid multiple reallocation */ |
363 | ||
8c74d22e RB |
364 | va_start(ap, nbuf); |
365 | for (i = 0; i < nbuf; ++i) { | |
366 | const char* segment; | |
679b69c4 | 367 | size_t segment_len; |
8c74d22e RB |
368 | |
369 | segment = va_arg(ap, const char *); | |
370 | if (!segment) | |
371 | continue; | |
372 | ||
373 | segment_len = strlen(segment); | |
374 | total_size += segment_len; | |
375 | if (segment_len == 0 || segment[segment_len - 1] != separator) | |
376 | ++total_size; /* space for separator */ | |
377 | } | |
378 | va_end(ap); | |
379 | ||
97769280 | 380 | /* expand buffer if needed */ |
952f94c8 RB |
381 | if (total_size == 0) |
382 | return 0; | |
383 | if (git_buf_grow(buf, buf->size + total_size + 1) < 0) | |
0d0fa7c3 | 384 | return -1; |
8c74d22e RB |
385 | |
386 | out = buf->ptr + buf->size; | |
387 | ||
388 | /* append separator to existing buf if needed */ | |
389 | if (buf->size > 0 && out[-1] != separator) | |
390 | *out++ = separator; | |
391 | ||
392 | va_start(ap, nbuf); | |
393 | for (i = 0; i < nbuf; ++i) { | |
394 | const char* segment; | |
679b69c4 | 395 | size_t segment_len; |
8c74d22e RB |
396 | |
397 | segment = va_arg(ap, const char *); | |
398 | if (!segment) | |
399 | continue; | |
400 | ||
952f94c8 RB |
401 | /* deal with join that references buffer's original content */ |
402 | if (segment >= original && segment < original + original_size) { | |
403 | size_t offset = (segment - original); | |
404 | segment = buf->ptr + offset; | |
405 | segment_len = original_size - offset; | |
406 | } else { | |
407 | segment_len = strlen(segment); | |
408 | } | |
409 | ||
8c74d22e RB |
410 | /* skip leading separators */ |
411 | if (out > buf->ptr && out[-1] == separator) | |
952f94c8 RB |
412 | while (segment_len > 0 && *segment == separator) { |
413 | segment++; | |
414 | segment_len--; | |
415 | } | |
8c74d22e RB |
416 | |
417 | /* copy over next buffer */ | |
8c74d22e RB |
418 | if (segment_len > 0) { |
419 | memmove(out, segment, segment_len); | |
420 | out += segment_len; | |
421 | } | |
422 | ||
423 | /* append trailing separator (except for last item) */ | |
424 | if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator) | |
425 | *out++ = separator; | |
426 | } | |
427 | va_end(ap); | |
428 | ||
429 | /* set size based on num characters actually written */ | |
430 | buf->size = out - buf->ptr; | |
c63728cd | 431 | buf->ptr[buf->size] = '\0'; |
97769280 | 432 | |
0d0fa7c3 | 433 | return 0; |
8c74d22e RB |
434 | } |
435 | ||
97769280 | 436 | int git_buf_join( |
3aa294fd RB |
437 | git_buf *buf, |
438 | char separator, | |
439 | const char *str_a, | |
440 | const char *str_b) | |
441 | { | |
b6c93aef | 442 | size_t strlen_a = str_a ? strlen(str_a) : 0; |
969d588d RB |
443 | size_t strlen_b = strlen(str_b); |
444 | int need_sep = 0; | |
b5daae68 RB |
445 | ssize_t offset_a = -1; |
446 | ||
447 | /* not safe to have str_b point internally to the buffer */ | |
448 | assert(str_b < buf->ptr || str_b > buf->ptr + buf->size); | |
969d588d RB |
449 | |
450 | /* figure out if we need to insert a separator */ | |
451 | if (separator && strlen_a) { | |
452 | while (*str_b == separator) { str_b++; strlen_b--; } | |
453 | if (str_a[strlen_a - 1] != separator) | |
454 | need_sep = 1; | |
3aa294fd | 455 | } |
3aa294fd | 456 | |
b5daae68 RB |
457 | /* str_a could be part of the buffer */ |
458 | if (str_a >= buf->ptr && str_a < buf->ptr + buf->size) | |
459 | offset_a = str_a - buf->ptr; | |
460 | ||
0d0fa7c3 RB |
461 | if (git_buf_grow(buf, strlen_a + strlen_b + need_sep + 1) < 0) |
462 | return -1; | |
3aa294fd | 463 | |
b5daae68 RB |
464 | /* fix up internal pointers */ |
465 | if (offset_a >= 0) | |
466 | str_a = buf->ptr + offset_a; | |
467 | ||
468 | /* do the actual copying */ | |
469 | if (offset_a != 0) | |
470 | memmove(buf->ptr, str_a, strlen_a); | |
969d588d RB |
471 | if (need_sep) |
472 | buf->ptr[strlen_a] = separator; | |
b5daae68 | 473 | memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b); |
3aa294fd | 474 | |
969d588d | 475 | buf->size = strlen_a + strlen_b + need_sep; |
c63728cd | 476 | buf->ptr[buf->size] = '\0'; |
97769280 | 477 | |
0d0fa7c3 | 478 | return 0; |
3aa294fd | 479 | } |
13224ea4 VM |
480 | |
481 | void git_buf_rtrim(git_buf *buf) | |
482 | { | |
483 | while (buf->size > 0) { | |
0f49200c | 484 | if (!git__isspace(buf->ptr[buf->size - 1])) |
13224ea4 VM |
485 | break; |
486 | ||
487 | buf->size--; | |
488 | } | |
eb8f90e5 VM |
489 | |
490 | buf->ptr[buf->size] = '\0'; | |
13224ea4 | 491 | } |
ce49c7a8 RB |
492 | |
493 | int git_buf_cmp(const git_buf *a, const git_buf *b) | |
494 | { | |
495 | int result = memcmp(a->ptr, b->ptr, min(a->size, b->size)); | |
496 | return (result != 0) ? result : | |
497 | (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0; | |
498 | } | |
41a82592 RB |
499 | |
500 | int git_buf_common_prefix(git_buf *buf, const git_strarray *strings) | |
501 | { | |
502 | size_t i; | |
503 | const char *str, *pfx; | |
504 | ||
505 | git_buf_clear(buf); | |
506 | ||
507 | if (!strings || !strings->count) | |
508 | return 0; | |
509 | ||
2c833917 | 510 | /* initialize common prefix to first string */ |
41a82592 RB |
511 | if (git_buf_sets(buf, strings->strings[0]) < 0) |
512 | return -1; | |
513 | ||
2c833917 | 514 | /* go through the rest of the strings, truncating to shared prefix */ |
41a82592 | 515 | for (i = 1; i < strings->count; ++i) { |
2c833917 | 516 | |
41a82592 RB |
517 | for (str = strings->strings[i], pfx = buf->ptr; |
518 | *str && *str == *pfx; str++, pfx++) | |
519 | /* scanning */; | |
520 | ||
521 | git_buf_truncate(buf, pfx - buf->ptr); | |
522 | ||
523 | if (!buf->size) | |
524 | break; | |
525 | } | |
526 | ||
527 | return 0; | |
528 | } | |
b59c73d3 RB |
529 | |
530 | bool git_buf_is_binary(const git_buf *buf) | |
531 | { | |
a0d95962 RB |
532 | size_t i; |
533 | int printable = 0, nonprintable = 0; | |
b59c73d3 RB |
534 | |
535 | for (i = 0; i < buf->size; i++) { | |
536 | unsigned char c = buf->ptr[i]; | |
a0d95962 | 537 | if (c > 0x1F && c < 0x7F) |
b59c73d3 RB |
538 | printable++; |
539 | else if (c == '\0') | |
540 | return true; | |
541 | else if (!git__isspace(c)) | |
542 | nonprintable++; | |
543 | } | |
544 | ||
545 | return ((printable >> 7) < nonprintable); | |
546 | } | |
547 | ||
02a0d651 | 548 | void git_buf_unescape(git_buf *buf) |
549 | { | |
550 | buf->size = git__unescape(buf->ptr); | |
551 | } |