]> git.proxmox.com Git - libgit2.git/blobdiff - src/buffer.c
New upstream version 1.1.0+dfsg.1
[libgit2.git] / src / buffer.c
index a83ca87928a53c46c8ebf9be31255a19b3446f20..c203650c752c77c5b1c88a3d85aa0a3af9e9a3fb 100644 (file)
@@ -7,6 +7,7 @@
 #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
@@ -17,22 +18,24 @@ char git_buf__initbuf[1];
 char git_buf__oom[1];
 
 #define ENSURE_SIZE(b, d) \
-       if ((d) > buf->asize && git_buf_grow(b, (d)) < 0)\
+       if ((b)->ptr == git_buf__oom || \
+           ((d) > (b)->asize && git_buf_grow((b), (d)) < 0))\
                return -1;
 
 
-void git_buf_init(git_buf *buf, size_t initial_size)
+int git_buf_init(git_buf *buf, size_t initial_size)
 {
        buf->asize = 0;
        buf->size = 0;
        buf->ptr = git_buf__initbuf;
 
-       if (initial_size)
-               git_buf_grow(buf, initial_size);
+       ENSURE_SIZE(buf, initial_size);
+
+       return 0;
 }
 
 int git_buf_try_grow(
-       git_buf *buf, size_t target_size, bool mark_oom, bool preserve_external)
+       git_buf *buf, size_t target_size, bool mark_oom)
 {
        char *new_ptr;
        size_t new_size;
@@ -40,6 +43,11 @@ int git_buf_try_grow(
        if (buf->ptr == git_buf__oom)
                return -1;
 
+       if (buf->asize == 0 && buf->size != 0) {
+               git_error_set(GIT_ERROR_INVALID, "cannot grow a borrowed buffer");
+               return GIT_EINVALID;
+       }
+
        if (!target_size)
                target_size = buf->size;
 
@@ -51,30 +59,42 @@ int git_buf_try_grow(
                new_ptr = NULL;
        } else {
                new_size = buf->asize;
+               /*
+                * Grow the allocated buffer by 1.5 to allow
+                * re-use of memory holes resulting from the
+                * realloc. If this is still too small, then just
+                * use the target size.
+                */
+               if ((new_size = (new_size << 1) - (new_size >> 1)) < target_size)
+                       new_size = target_size;
                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) {
+                       if (buf->ptr && buf->ptr != git_buf__initbuf)
+                               git__free(buf->ptr);
+                       buf->ptr = git_buf__oom;
+               }
+
+               git_error_set_oom();
+               return -1;
+       }
+
        new_ptr = git__realloc(new_ptr, new_size);
 
        if (!new_ptr) {
                if (mark_oom) {
-                       if (buf->ptr) git__free(buf->ptr);
+                       if (buf->ptr && (buf->ptr != git_buf__initbuf))
+                               git__free(buf->ptr);
                        buf->ptr = git_buf__oom;
                }
                return -1;
        }
 
-       if (preserve_external && !buf->asize && buf->ptr != NULL && buf->size > 0)
-               memcpy(new_ptr, buf->ptr, min(buf->size, new_size));
-
        buf->asize = new_size;
        buf->ptr   = new_ptr;
 
@@ -88,10 +108,22 @@ int git_buf_try_grow(
 
 int git_buf_grow(git_buf *buffer, size_t target_size)
 {
-       return git_buf_try_grow(buffer, target_size, true, true);
+       return git_buf_try_grow(buffer, target_size, true);
 }
 
-void git_buf_free(git_buf *buf)
+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_dispose(git_buf *buf)
 {
        if (!buf) return;
 
@@ -101,20 +133,30 @@ void git_buf_free(git_buf *buf)
        git_buf_init(buf, 0);
 }
 
+#ifndef GIT_DEPRECATE_HARD
+void git_buf_free(git_buf *buf)
+{
+       git_buf_dispose(buf);
+}
+#endif
+
 void git_buf_sanitize(git_buf *buf)
 {
        if (buf->ptr == NULL) {
-               assert (buf->size == 0 && buf->asize == 0);
+               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)
+       if (!buf->ptr) {
                buf->ptr = git_buf__initbuf;
+               buf->asize = 0;
+       }
 
        if (buf->asize > 0)
                buf->ptr[0] = '\0';
@@ -122,19 +164,35 @@ void git_buf_clear(git_buf *buf)
 
 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) {
-                       ENSURE_SIZE(buf, len + 1);
+                       GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1);
+                       ENSURE_SIZE(buf, alloclen);
                        memmove(buf->ptr, data, len);
                }
+
                buf->size = len;
-               buf->ptr[buf->size] = '\0';
+               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);
@@ -142,37 +200,64 @@ int git_buf_sets(git_buf *buf, const char *string)
 
 int git_buf_putc(git_buf *buf, char c)
 {
-       ENSURE_SIZE(buf, buf->size + 2);
+       size_t new_size;
+       GIT_ERROR_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_put(git_buf *buf, const char *data, size_t len)
+int git_buf_putcn(git_buf *buf, char c, size_t len)
 {
-       ENSURE_SIZE(buf, buf->size + len + 1);
-       memmove(buf->ptr + buf->size, data, len);
+       size_t new_size;
+       GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
+       GIT_ERROR_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);
+
+               GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
+               GIT_ERROR_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 b64str[] =
+static const char base64_encode[] =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 
-int git_buf_put_base64(git_buf *buf, const char *data, size_t len)
+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;
+
+       GIT_ERROR_CHECK_ALLOC_ADD(&blocks, blocks, 1);
+       GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 4);
+       GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size);
 
-       ENSURE_SIZE(buf, buf->size + 4 * ((len / 3) + !!extra) + 1);
+       ENSURE_SIZE(buf, alloclen);
        write = (uint8_t *)&buf->ptr[buf->size];
 
        /* convert each run of 3 bytes into 4 output bytes */
@@ -181,19 +266,19 @@ int git_buf_put_base64(git_buf *buf, const char *data, size_t len)
                b = *read++;
                c = *read++;
 
-               *write++ = b64str[a >> 2];
-               *write++ = b64str[(a & 0x03) << 4 | b >> 4];
-               *write++ = b64str[(b & 0x0f) << 2 | c >> 6];
-               *write++ = b64str[c & 0x3f];
+               *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++ = b64str[a >> 2];
-               *write++ = b64str[(a & 0x03) << 4 | b >> 4];
-               *write++ = (extra > 1) ? b64str[(b & 0x0f) << 2] : '=';
+               *write++ = base64_encode[a >> 2];
+               *write++ = base64_encode[(a & 0x03) << 4 | b >> 4];
+               *write++ = (extra > 1) ? base64_encode[(b & 0x0f) << 2] : '=';
                *write++ = '=';
        }
 
@@ -203,11 +288,224 @@ int git_buf_put_base64(git_buf *buf, const char *data, size_t len)
        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) {
+               git_error_set(GIT_ERROR_INVALID, "invalid base64 input");
+               return -1;
+       }
+
+       assert(len % 4 == 0);
+       GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size);
+       GIT_ERROR_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';
+
+                       git_error_set(GIT_ERROR_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;
+
+       GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 5);
+       GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size);
+       GIT_ERROR_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 |= (uint32_t)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) {
+               git_error_set(GIT_ERROR_INVALID, "invalid base85 input");
+               return -1;
+       }
+
+       GIT_ERROR_CHECK_ALLOC_ADD(&new_size, output_len, buf->size);
+       GIT_ERROR_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) ? (int)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';
+
+       git_error_set(GIT_ERROR_INVALID, "invalid base85 input");
+       return -1;
+}
+
+#define HEX_DECODE(c) ((c | 32) % 39 - 9)
+
+int git_buf_decode_percent(
+       git_buf *buf,
+       const char *str,
+       size_t str_len)
+{
+       size_t str_pos, new_size;
+
+       GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, str_len);
+       GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+       ENSURE_SIZE(buf, new_size);
+
+       for (str_pos = 0; str_pos < str_len; buf->size++, str_pos++) {
+               if (str[str_pos] == '%' &&
+                       str_len > str_pos + 2 &&
+                       isxdigit(str[str_pos + 1]) &&
+                       isxdigit(str[str_pos + 2])) {
+                       buf->ptr[buf->size] = (HEX_DECODE(str[str_pos + 1]) << 4) +
+                               HEX_DECODE(str[str_pos + 2]);
+                       str_pos += 2;
+               } else {
+                       buf->ptr[buf->size] = str[str_pos];
+               }
+       }
+
+       buf->ptr[buf->size] = '\0';
+       return 0;
+}
+
 int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
 {
+       size_t expected_size, new_size;
        int len;
-       const size_t expected_size = buf->size + (strlen(format) * 2);
 
+       GIT_ERROR_CHECK_ALLOC_MULTIPLY(&expected_size, strlen(format), 2);
+       GIT_ERROR_CHECK_ALLOC_ADD(&expected_size, expected_size, buf->size);
        ENSURE_SIZE(buf, expected_size);
 
        while (1) {
@@ -233,7 +531,9 @@ int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
                        break;
                }
 
-               ENSURE_SIZE(buf, buf->size + len + 1);
+               GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
+               GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+               ENSURE_SIZE(buf, new_size);
        }
 
        return 0;
@@ -269,6 +569,11 @@ void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf)
        data[copylen] = '\0';
 }
 
+void git_buf_consume_bytes(git_buf *buf, size_t len)
+{
+       git_buf_consume(buf, buf->ptr + len);
+}
+
 void git_buf_consume(git_buf *buf, const char *end)
 {
        if (end > buf->ptr && end <= buf->ptr + buf->size) {
@@ -281,19 +586,20 @@ void git_buf_consume(git_buf *buf, const char *end)
 
 void git_buf_truncate(git_buf *buf, size_t len)
 {
-       if (len < buf->size) {
-               buf->size = 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 (amount > buf->size)
-               amount = buf->size;
-
-       buf->size = buf->size - amount;
-       buf->ptr[buf->size] = '\0';
+       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)
@@ -321,9 +627,9 @@ char *git_buf_detach(git_buf *buf)
        return data;
 }
 
-void git_buf_attach(git_buf *buf, char *ptr, size_t asize)
+int git_buf_attach(git_buf *buf, char *ptr, size_t asize)
 {
-       git_buf_free(buf);
+       git_buf_dispose(buf);
 
        if (ptr) {
                buf->ptr = ptr;
@@ -332,8 +638,23 @@ void git_buf_attach(git_buf *buf, char *ptr, size_t 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_dispose(buf);
+
+       if (!size) {
+               git_buf_init(buf, 0);
        } else {
-               git_buf_grow(buf, asize);
+               buf->ptr = (char *)ptr;
+               buf->asize = 0;
+               buf->size = size;
        }
 }
 
@@ -359,16 +680,20 @@ int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...)
                        continue;
 
                segment_len = strlen(segment);
-               total_size += segment_len;
+
+               GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, segment_len);
+
                if (segment_len == 0 || segment[segment_len - 1] != separator)
-                       ++total_size; /* space for separator */
+                       GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1);
        }
        va_end(ap);
 
        /* expand buffer if needed */
        if (total_size == 0)
                return 0;
-       if (git_buf_grow(buf, buf->size + total_size + 1) < 0)
+
+       GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1);
+       if (git_buf_grow_by(buf, total_size) < 0)
                return -1;
 
        out = buf->ptr + buf->size;
@@ -429,11 +754,13 @@ int git_buf_join(
 {
        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);
+       if (buf->size)
+               assert(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
 
        /* figure out if we need to insert a separator */
        if (separator && strlen_a) {
@@ -443,12 +770,13 @@ int git_buf_join(
        }
 
        /* str_a could be part of the buffer */
-       if (str_a >= buf->ptr && str_a < buf->ptr + buf->size)
+       if (buf->size && str_a >= buf->ptr && str_a < buf->ptr + buf->size)
                offset_a = str_a - buf->ptr;
 
-       if (git_buf_grow(buf, strlen_a + strlen_b + need_sep + 1) < 0)
-               return -1;
-       assert(buf->ptr);
+       GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, strlen_a, strlen_b);
+       GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, need_sep);
+       GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1);
+       ENSURE_SIZE(buf, alloc_len);
 
        /* fix up internal pointers */
        if (offset_a >= 0)
@@ -467,6 +795,66 @@ int git_buf_join(
        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);
+       }
+
+       GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_a, sep_a);
+       GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_b);
+       GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, sep_b);
+       GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_c);
+       GIT_ERROR_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) {
@@ -476,7 +864,8 @@ void git_buf_rtrim(git_buf *buf)
                buf->size--;
        }
 
-       buf->ptr[buf->size] = '\0';
+       if (buf->asize > buf->size)
+               buf->ptr[buf->size] = '\0';
 }
 
 int git_buf_cmp(const git_buf *a, const git_buf *b)
@@ -493,23 +882,168 @@ int git_buf_splice(
        const char *data,
        size_t nb_to_insert)
 {
-       assert(buf &&
-               where <= git_buf_len(buf) &&
-               where + nb_to_remove <= git_buf_len(buf));
+       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
         */
-       if (git_buf_grow(buf, git_buf_len(buf) + nb_to_insert - nb_to_remove) < 0)
-               return -1;
+       GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (buf->size - nb_to_remove), nb_to_insert);
+       GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, new_size, 1);
+       ENSURE_SIZE(buf, alloc_size);
 
-       memmove(buf->ptr + where + nb_to_insert,
-                       buf->ptr + where + nb_to_remove,
-                       buf->size - where - nb_to_remove);
+       memmove(splice_loc + nb_to_insert,
+               splice_loc + nb_to_remove,
+               buf->size - where - nb_to_remove);
 
-       memcpy(buf->ptr + where, data, nb_to_insert);
+       memcpy(splice_loc, data, nb_to_insert);
 
-       buf->size = buf->size + nb_to_insert - nb_to_remove;
+       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_dispose(&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) {
+                                       git_error_set(GIT_ERROR_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') {
+                                       git_error_set(GIT_ERROR_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:
+                               git_error_set(GIT_ERROR_INVALID, "invalid quoted character \\%c", ch);
+                               return -1;
+                       }
+               }
+
+               buf->ptr[i] = ch;
+       }
+
+       buf->ptr[i] = '\0';
+       buf->size = i;
+
+       return 0;
+
+invalid:
+       git_error_set(GIT_ERROR_INVALID, "invalid quoted line");
+       return -1;
+}