libgit2/src/buffer.c
Patrick Steinhardt 4796c916d3 buffer: return errors for git_buf_init and git_buf_attach
Both the `git_buf_init` and `git_buf_attach` functions may call
`git_buf_grow` in case they were given an allocation length as
parameter. As such, it is possible for these functions to fail when we
run out of memory. While it won't probably be used anytime soon, it does
indeed make sense to also record this fact by returning an error code
from both functions. As they belong to the internal API only, this
change does not break our interface.
2017-06-08 11:58:22 +02:00

1000 lines
22 KiB
C

/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "buffer.h"
#include "posix.h"
#include "git2/buffer.h"
#include "buf_text.h"
#include <ctype.h>
/* Used as default value for git_buf->ptr so that people can always
* assume ptr is non-NULL and zero terminated even for new git_bufs.
*/
char git_buf__initbuf[1];
char git_buf__oom[1];
#define ENSURE_SIZE(b, d) \
if ((d) > (b)->asize && git_buf_grow((b), (d)) < 0)\
return -1;
int git_buf_init(git_buf *buf, size_t initial_size)
{
buf->asize = 0;
buf->size = 0;
buf->ptr = git_buf__initbuf;
ENSURE_SIZE(buf, initial_size);
return 0;
}
int git_buf_try_grow(
git_buf *buf, size_t target_size, bool mark_oom)
{
char *new_ptr;
size_t new_size;
if (buf->ptr == git_buf__oom)
return -1;
if (buf->asize == 0 && buf->size != 0) {
giterr_set(GITERR_INVALID, "cannot grow a borrowed buffer");
return GIT_EINVALID;
}
if (!target_size)
target_size = buf->size;
if (target_size <= buf->asize)
return 0;
if (buf->asize == 0) {
new_size = target_size;
new_ptr = NULL;
} else {
new_size = buf->asize;
new_ptr = buf->ptr;
}
/* grow the buffer size by 1.5, until it's big enough
* to fit our target size */
while (new_size < target_size)
new_size = (new_size << 1) - (new_size >> 1);
/* round allocation up to multiple of 8 */
new_size = (new_size + 7) & ~7;
if (new_size < buf->size) {
if (mark_oom)
buf->ptr = git_buf__oom;
giterr_set_oom();
return -1;
}
new_ptr = git__realloc(new_ptr, new_size);
if (!new_ptr) {
if (mark_oom) {
if (buf->ptr && (buf->ptr != git_buf__initbuf))
git__free(buf->ptr);
buf->ptr = git_buf__oom;
}
return -1;
}
buf->asize = new_size;
buf->ptr = new_ptr;
/* truncate the existing buffer size if necessary */
if (buf->size >= buf->asize)
buf->size = buf->asize - 1;
buf->ptr[buf->size] = '\0';
return 0;
}
int git_buf_grow(git_buf *buffer, size_t target_size)
{
return git_buf_try_grow(buffer, target_size, true);
}
int git_buf_grow_by(git_buf *buffer, size_t additional_size)
{
size_t newsize;
if (GIT_ADD_SIZET_OVERFLOW(&newsize, buffer->size, additional_size)) {
buffer->ptr = git_buf__oom;
return -1;
}
return git_buf_try_grow(buffer, newsize, true);
}
void git_buf_free(git_buf *buf)
{
if (!buf) return;
if (buf->asize > 0 && buf->ptr != NULL && buf->ptr != git_buf__oom)
git__free(buf->ptr);
git_buf_init(buf, 0);
}
void git_buf_sanitize(git_buf *buf)
{
if (buf->ptr == NULL) {
assert(buf->size == 0 && buf->asize == 0);
buf->ptr = git_buf__initbuf;
} else if (buf->asize > buf->size)
buf->ptr[buf->size] = '\0';
}
void git_buf_clear(git_buf *buf)
{
buf->size = 0;
if (!buf->ptr) {
buf->ptr = git_buf__initbuf;
buf->asize = 0;
}
if (buf->asize > 0)
buf->ptr[0] = '\0';
}
int git_buf_set(git_buf *buf, const void *data, size_t len)
{
size_t alloclen;
if (len == 0 || data == NULL) {
git_buf_clear(buf);
} else {
if (data != buf->ptr) {
GITERR_CHECK_ALLOC_ADD(&alloclen, len, 1);
ENSURE_SIZE(buf, alloclen);
memmove(buf->ptr, data, len);
}
buf->size = len;
if (buf->asize > buf->size)
buf->ptr[buf->size] = '\0';
}
return 0;
}
int git_buf_is_binary(const git_buf *buf)
{
return git_buf_text_is_binary(buf);
}
int git_buf_contains_nul(const git_buf *buf)
{
return git_buf_text_contains_nul(buf);
}
int git_buf_sets(git_buf *buf, const char *string)
{
return git_buf_set(buf, string, string ? strlen(string) : 0);
}
int git_buf_putc(git_buf *buf, char c)
{
size_t new_size;
GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, 2);
ENSURE_SIZE(buf, new_size);
buf->ptr[buf->size++] = c;
buf->ptr[buf->size] = '\0';
return 0;
}
int git_buf_putcn(git_buf *buf, char c, size_t len)
{
size_t new_size;
GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
ENSURE_SIZE(buf, new_size);
memset(buf->ptr + buf->size, c, len);
buf->size += len;
buf->ptr[buf->size] = '\0';
return 0;
}
int git_buf_put(git_buf *buf, const char *data, size_t len)
{
if (len) {
size_t new_size;
assert(data);
GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
ENSURE_SIZE(buf, new_size);
memmove(buf->ptr + buf->size, data, len);
buf->size += len;
buf->ptr[buf->size] = '\0';
}
return 0;
}
int git_buf_puts(git_buf *buf, const char *string)
{
assert(string);
return git_buf_put(buf, string, strlen(string));
}
static const char base64_encode[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int git_buf_encode_base64(git_buf *buf, const char *data, size_t len)
{
size_t extra = len % 3;
uint8_t *write, a, b, c;
const uint8_t *read = (const uint8_t *)data;
size_t blocks = (len / 3) + !!extra, alloclen;
GITERR_CHECK_ALLOC_ADD(&blocks, blocks, 1);
GITERR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 4);
GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size);
ENSURE_SIZE(buf, alloclen);
write = (uint8_t *)&buf->ptr[buf->size];
/* convert each run of 3 bytes into 4 output bytes */
for (len -= extra; len > 0; len -= 3) {
a = *read++;
b = *read++;
c = *read++;
*write++ = base64_encode[a >> 2];
*write++ = base64_encode[(a & 0x03) << 4 | b >> 4];
*write++ = base64_encode[(b & 0x0f) << 2 | c >> 6];
*write++ = base64_encode[c & 0x3f];
}
if (extra > 0) {
a = *read++;
b = (extra > 1) ? *read++ : 0;
*write++ = base64_encode[a >> 2];
*write++ = base64_encode[(a & 0x03) << 4 | b >> 4];
*write++ = (extra > 1) ? base64_encode[(b & 0x0f) << 2] : '=';
*write++ = '=';
}
buf->size = ((char *)write) - buf->ptr;
buf->ptr[buf->size] = '\0';
return 0;
}
/* The inverse of base64_encode */
static const int8_t base64_decode[] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len)
{
size_t i;
int8_t a, b, c, d;
size_t orig_size = buf->size, new_size;
if (len % 4) {
giterr_set(GITERR_INVALID, "invalid base64 input");
return -1;
}
assert(len % 4 == 0);
GITERR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size);
GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
ENSURE_SIZE(buf, new_size);
for (i = 0; i < len; i += 4) {
if ((a = base64_decode[(unsigned char)base64[i]]) < 0 ||
(b = base64_decode[(unsigned char)base64[i+1]]) < 0 ||
(c = base64_decode[(unsigned char)base64[i+2]]) < 0 ||
(d = base64_decode[(unsigned char)base64[i+3]]) < 0) {
buf->size = orig_size;
buf->ptr[buf->size] = '\0';
giterr_set(GITERR_INVALID, "invalid base64 input");
return -1;
}
buf->ptr[buf->size++] = ((a << 2) | (b & 0x30) >> 4);
buf->ptr[buf->size++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
buf->ptr[buf->size++] = (c & 0x03) << 6 | (d & 0x3f);
}
buf->ptr[buf->size] = '\0';
return 0;
}
static const char base85_encode[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
int git_buf_encode_base85(git_buf *buf, const char *data, size_t len)
{
size_t blocks = (len / 4) + !!(len % 4), alloclen;
GITERR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 5);
GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size);
GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
ENSURE_SIZE(buf, alloclen);
while (len) {
uint32_t acc = 0;
char b85[5];
int i;
for (i = 24; i >= 0; i -= 8) {
uint8_t ch = *data++;
acc |= ch << i;
if (--len == 0)
break;
}
for (i = 4; i >= 0; i--) {
int val = acc % 85;
acc /= 85;
b85[i] = base85_encode[val];
}
for (i = 0; i < 5; i++)
buf->ptr[buf->size++] = b85[i];
}
buf->ptr[buf->size] = '\0';
return 0;
}
/* The inverse of base85_encode */
static const int8_t base85_decode[] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 63, -1, 64, 65, 66, 67, -1, 68, 69, 70, 71, -1, 72, -1, -1,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, 73, 74, 75, 76, 77,
78, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, 79, 80,
81, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 82, 83, 84, 85, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
int git_buf_decode_base85(
git_buf *buf,
const char *base85,
size_t base85_len,
size_t output_len)
{
size_t orig_size = buf->size, new_size;
if (base85_len % 5 ||
output_len > base85_len * 4 / 5) {
giterr_set(GITERR_INVALID, "invalid base85 input");
return -1;
}
GITERR_CHECK_ALLOC_ADD(&new_size, output_len, buf->size);
GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
ENSURE_SIZE(buf, new_size);
while (output_len) {
unsigned acc = 0;
int de, cnt = 4;
unsigned char ch;
do {
ch = *base85++;
de = base85_decode[ch];
if (--de < 0)
goto on_error;
acc = acc * 85 + de;
} while (--cnt);
ch = *base85++;
de = base85_decode[ch];
if (--de < 0)
goto on_error;
/* Detect overflow. */
if (0xffffffff / 85 < acc ||
0xffffffff - de < (acc *= 85))
goto on_error;
acc += de;
cnt = (output_len < 4) ? output_len : 4;
output_len -= cnt;
do {
acc = (acc << 8) | (acc >> 24);
buf->ptr[buf->size++] = acc;
} while (--cnt);
}
buf->ptr[buf->size] = 0;
return 0;
on_error:
buf->size = orig_size;
buf->ptr[buf->size] = '\0';
giterr_set(GITERR_INVALID, "invalid base85 input");
return -1;
}
int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
{
size_t expected_size, new_size;
int len;
GITERR_CHECK_ALLOC_MULTIPLY(&expected_size, strlen(format), 2);
GITERR_CHECK_ALLOC_ADD(&expected_size, expected_size, buf->size);
ENSURE_SIZE(buf, expected_size);
while (1) {
va_list args;
va_copy(args, ap);
len = p_vsnprintf(
buf->ptr + buf->size,
buf->asize - buf->size,
format, args
);
va_end(args);
if (len < 0) {
git__free(buf->ptr);
buf->ptr = git_buf__oom;
return -1;
}
if ((size_t)len + 1 <= buf->asize - buf->size) {
buf->size += len;
break;
}
GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
ENSURE_SIZE(buf, new_size);
}
return 0;
}
int git_buf_printf(git_buf *buf, const char *format, ...)
{
int r;
va_list ap;
va_start(ap, format);
r = git_buf_vprintf(buf, format, ap);
va_end(ap);
return r;
}
void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf)
{
size_t copylen;
assert(data && datasize && buf);
data[0] = '\0';
if (buf->size == 0 || buf->asize <= 0)
return;
copylen = buf->size;
if (copylen > datasize - 1)
copylen = datasize - 1;
memmove(data, buf->ptr, copylen);
data[copylen] = '\0';
}
void git_buf_consume(git_buf *buf, const char *end)
{
if (end > buf->ptr && end <= buf->ptr + buf->size) {
size_t consumed = end - buf->ptr;
memmove(buf->ptr, end, buf->size - consumed);
buf->size -= consumed;
buf->ptr[buf->size] = '\0';
}
}
void git_buf_truncate(git_buf *buf, size_t len)
{
if (len >= buf->size)
return;
buf->size = len;
if (buf->size < buf->asize)
buf->ptr[buf->size] = '\0';
}
void git_buf_shorten(git_buf *buf, size_t amount)
{
if (buf->size > amount)
git_buf_truncate(buf, buf->size - amount);
else
git_buf_clear(buf);
}
void git_buf_rtruncate_at_char(git_buf *buf, char separator)
{
ssize_t idx = git_buf_rfind_next(buf, separator);
git_buf_truncate(buf, idx < 0 ? 0 : (size_t)idx);
}
void git_buf_swap(git_buf *buf_a, git_buf *buf_b)
{
git_buf t = *buf_a;
*buf_a = *buf_b;
*buf_b = t;
}
char *git_buf_detach(git_buf *buf)
{
char *data = buf->ptr;
if (buf->asize == 0 || buf->ptr == git_buf__oom)
return NULL;
git_buf_init(buf, 0);
return data;
}
int git_buf_attach(git_buf *buf, char *ptr, size_t asize)
{
git_buf_free(buf);
if (ptr) {
buf->ptr = ptr;
buf->size = strlen(ptr);
if (asize)
buf->asize = (asize < buf->size) ? buf->size + 1 : asize;
else /* pass 0 to fall back on strlen + 1 */
buf->asize = buf->size + 1;
}
ENSURE_SIZE(buf, asize);
return 0;
}
void git_buf_attach_notowned(git_buf *buf, const char *ptr, size_t size)
{
if (git_buf_is_allocated(buf))
git_buf_free(buf);
if (!size) {
git_buf_init(buf, 0);
} else {
buf->ptr = (char *)ptr;
buf->asize = 0;
buf->size = size;
}
}
int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...)
{
va_list ap;
int i;
size_t total_size = 0, original_size = buf->size;
char *out, *original = buf->ptr;
if (buf->size > 0 && buf->ptr[buf->size - 1] != separator)
++total_size; /* space for initial separator */
/* Make two passes to avoid multiple reallocation */
va_start(ap, nbuf);
for (i = 0; i < nbuf; ++i) {
const char* segment;
size_t segment_len;
segment = va_arg(ap, const char *);
if (!segment)
continue;
segment_len = strlen(segment);
GITERR_CHECK_ALLOC_ADD(&total_size, total_size, segment_len);
if (segment_len == 0 || segment[segment_len - 1] != separator)
GITERR_CHECK_ALLOC_ADD(&total_size, total_size, 1);
}
va_end(ap);
/* expand buffer if needed */
if (total_size == 0)
return 0;
GITERR_CHECK_ALLOC_ADD(&total_size, total_size, 1);
if (git_buf_grow_by(buf, total_size) < 0)
return -1;
out = buf->ptr + buf->size;
/* append separator to existing buf if needed */
if (buf->size > 0 && out[-1] != separator)
*out++ = separator;
va_start(ap, nbuf);
for (i = 0; i < nbuf; ++i) {
const char* segment;
size_t segment_len;
segment = va_arg(ap, const char *);
if (!segment)
continue;
/* deal with join that references buffer's original content */
if (segment >= original && segment < original + original_size) {
size_t offset = (segment - original);
segment = buf->ptr + offset;
segment_len = original_size - offset;
} else {
segment_len = strlen(segment);
}
/* skip leading separators */
if (out > buf->ptr && out[-1] == separator)
while (segment_len > 0 && *segment == separator) {
segment++;
segment_len--;
}
/* copy over next buffer */
if (segment_len > 0) {
memmove(out, segment, segment_len);
out += segment_len;
}
/* append trailing separator (except for last item) */
if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator)
*out++ = separator;
}
va_end(ap);
/* set size based on num characters actually written */
buf->size = out - buf->ptr;
buf->ptr[buf->size] = '\0';
return 0;
}
int git_buf_join(
git_buf *buf,
char separator,
const char *str_a,
const char *str_b)
{
size_t strlen_a = str_a ? strlen(str_a) : 0;
size_t strlen_b = strlen(str_b);
size_t alloc_len;
int need_sep = 0;
ssize_t offset_a = -1;
/* not safe to have str_b point internally to the buffer */
assert(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
/* figure out if we need to insert a separator */
if (separator && strlen_a) {
while (*str_b == separator) { str_b++; strlen_b--; }
if (str_a[strlen_a - 1] != separator)
need_sep = 1;
}
/* str_a could be part of the buffer */
if (str_a >= buf->ptr && str_a < buf->ptr + buf->size)
offset_a = str_a - buf->ptr;
GITERR_CHECK_ALLOC_ADD(&alloc_len, strlen_a, strlen_b);
GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, need_sep);
GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1);
ENSURE_SIZE(buf, alloc_len);
/* fix up internal pointers */
if (offset_a >= 0)
str_a = buf->ptr + offset_a;
/* do the actual copying */
if (offset_a != 0 && str_a)
memmove(buf->ptr, str_a, strlen_a);
if (need_sep)
buf->ptr[strlen_a] = separator;
memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b);
buf->size = strlen_a + strlen_b + need_sep;
buf->ptr[buf->size] = '\0';
return 0;
}
int git_buf_join3(
git_buf *buf,
char separator,
const char *str_a,
const char *str_b,
const char *str_c)
{
size_t len_a = strlen(str_a),
len_b = strlen(str_b),
len_c = strlen(str_c),
len_total;
int sep_a = 0, sep_b = 0;
char *tgt;
/* for this function, disallow pointers into the existing buffer */
assert(str_a < buf->ptr || str_a >= buf->ptr + buf->size);
assert(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
assert(str_c < buf->ptr || str_c >= buf->ptr + buf->size);
if (separator) {
if (len_a > 0) {
while (*str_b == separator) { str_b++; len_b--; }
sep_a = (str_a[len_a - 1] != separator);
}
if (len_a > 0 || len_b > 0)
while (*str_c == separator) { str_c++; len_c--; }
if (len_b > 0)
sep_b = (str_b[len_b - 1] != separator);
}
GITERR_CHECK_ALLOC_ADD(&len_total, len_a, sep_a);
GITERR_CHECK_ALLOC_ADD(&len_total, len_total, len_b);
GITERR_CHECK_ALLOC_ADD(&len_total, len_total, sep_b);
GITERR_CHECK_ALLOC_ADD(&len_total, len_total, len_c);
GITERR_CHECK_ALLOC_ADD(&len_total, len_total, 1);
ENSURE_SIZE(buf, len_total);
tgt = buf->ptr;
if (len_a) {
memcpy(tgt, str_a, len_a);
tgt += len_a;
}
if (sep_a)
*tgt++ = separator;
if (len_b) {
memcpy(tgt, str_b, len_b);
tgt += len_b;
}
if (sep_b)
*tgt++ = separator;
if (len_c)
memcpy(tgt, str_c, len_c);
buf->size = len_a + sep_a + len_b + sep_b + len_c;
buf->ptr[buf->size] = '\0';
return 0;
}
void git_buf_rtrim(git_buf *buf)
{
while (buf->size > 0) {
if (!git__isspace(buf->ptr[buf->size - 1]))
break;
buf->size--;
}
if (buf->asize > buf->size)
buf->ptr[buf->size] = '\0';
}
int git_buf_cmp(const git_buf *a, const git_buf *b)
{
int result = memcmp(a->ptr, b->ptr, min(a->size, b->size));
return (result != 0) ? result :
(a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0;
}
int git_buf_splice(
git_buf *buf,
size_t where,
size_t nb_to_remove,
const char *data,
size_t nb_to_insert)
{
char *splice_loc;
size_t new_size, alloc_size;
assert(buf && where <= buf->size && nb_to_remove <= buf->size - where);
splice_loc = buf->ptr + where;
/* Ported from git.git
* https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176
*/
GITERR_CHECK_ALLOC_ADD(&new_size, (buf->size - nb_to_remove), nb_to_insert);
GITERR_CHECK_ALLOC_ADD(&alloc_size, new_size, 1);
ENSURE_SIZE(buf, alloc_size);
memmove(splice_loc + nb_to_insert,
splice_loc + nb_to_remove,
buf->size - where - nb_to_remove);
memcpy(splice_loc, data, nb_to_insert);
buf->size = new_size;
buf->ptr[buf->size] = '\0';
return 0;
}
/* Quote per http://marc.info/?l=git&m=112927316408690&w=2 */
int git_buf_quote(git_buf *buf)
{
const char whitespace[] = { 'a', 'b', 't', 'n', 'v', 'f', 'r' };
git_buf quoted = GIT_BUF_INIT;
size_t i = 0;
bool quote = false;
int error = 0;
/* walk to the first char that needs quoting */
if (buf->size && buf->ptr[0] == '!')
quote = true;
for (i = 0; !quote && i < buf->size; i++) {
if (buf->ptr[i] == '"' || buf->ptr[i] == '\\' ||
buf->ptr[i] < ' ' || buf->ptr[i] > '~') {
quote = true;
break;
}
}
if (!quote)
goto done;
git_buf_putc(&quoted, '"');
git_buf_put(&quoted, buf->ptr, i);
for (; i < buf->size; i++) {
/* whitespace - use the map above, which is ordered by ascii value */
if (buf->ptr[i] >= '\a' && buf->ptr[i] <= '\r') {
git_buf_putc(&quoted, '\\');
git_buf_putc(&quoted, whitespace[buf->ptr[i] - '\a']);
}
/* double quote and backslash must be escaped */
else if (buf->ptr[i] == '"' || buf->ptr[i] == '\\') {
git_buf_putc(&quoted, '\\');
git_buf_putc(&quoted, buf->ptr[i]);
}
/* escape anything unprintable as octal */
else if (buf->ptr[i] != ' ' &&
(buf->ptr[i] < '!' || buf->ptr[i] > '~')) {
git_buf_printf(&quoted, "\\%03o", (unsigned char)buf->ptr[i]);
}
/* yay, printable! */
else {
git_buf_putc(&quoted, buf->ptr[i]);
}
}
git_buf_putc(&quoted, '"');
if (git_buf_oom(&quoted)) {
error = -1;
goto done;
}
git_buf_swap(&quoted, buf);
done:
git_buf_free(&quoted);
return error;
}
/* Unquote per http://marc.info/?l=git&m=112927316408690&w=2 */
int git_buf_unquote(git_buf *buf)
{
size_t i, j;
char ch;
git_buf_rtrim(buf);
if (buf->size < 2 || buf->ptr[0] != '"' || buf->ptr[buf->size-1] != '"')
goto invalid;
for (i = 0, j = 1; j < buf->size-1; i++, j++) {
ch = buf->ptr[j];
if (ch == '\\') {
if (j == buf->size-2)
goto invalid;
ch = buf->ptr[++j];
switch (ch) {
/* \" or \\ simply copy the char in */
case '"': case '\\':
break;
/* add the appropriate escaped char */
case 'a': ch = '\a'; break;
case 'b': ch = '\b'; break;
case 'f': ch = '\f'; break;
case 'n': ch = '\n'; break;
case 'r': ch = '\r'; break;
case 't': ch = '\t'; break;
case 'v': ch = '\v'; break;
/* \xyz digits convert to the char*/
case '0': case '1': case '2': case '3':
if (j == buf->size-3) {
giterr_set(GITERR_INVALID,
"truncated quoted character \\%c", ch);
return -1;
}
if (buf->ptr[j+1] < '0' || buf->ptr[j+1] > '7' ||
buf->ptr[j+2] < '0' || buf->ptr[j+2] > '7') {
giterr_set(GITERR_INVALID,
"truncated quoted character \\%c%c%c",
buf->ptr[j], buf->ptr[j+1], buf->ptr[j+2]);
return -1;
}
ch = ((buf->ptr[j] - '0') << 6) |
((buf->ptr[j+1] - '0') << 3) |
(buf->ptr[j+2] - '0');
j += 2;
break;
default:
giterr_set(GITERR_INVALID, "invalid quoted character \\%c", ch);
return -1;
}
}
buf->ptr[i] = ch;
}
buf->ptr[i] = '\0';
buf->size = i;
return 0;
invalid:
giterr_set(GITERR_INVALID, "invalid quoted line");
return -1;
}