src/openvpn/buffer.c
6fbf66fa
 /*
  *  OpenVPN -- An application to securely tunnel IP networks
  *             over a single UDP port, with support for SSL/TLS-based
  *             session authentication and key exchange,
  *             packet encryption, packet authentication, and
  *             packet compression.
  *
58716979
  *  Copyright (C) 2002-2017 OpenVPN Technologies, Inc. <sales@openvpn.net>
6fbf66fa
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2
  *  as published by the Free Software Foundation.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
caa54ac3
  *  You should have received a copy of the GNU General Public License along
  *  with this program; if not, write to the Free Software Foundation, Inc.,
  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
6fbf66fa
  */
 
c110b289
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #elif defined(_MSC_VER)
 #include "config-msvc.h"
 #endif
 
6fbf66fa
 #include "syshead.h"
 
 #include "common.h"
 #include "buffer.h"
 #include "error.h"
 #include "mtu.h"
71bbbd76
 #include "misc.h"
6fbf66fa
 
 #include "memdbg.h"
 
8e986316
 size_t
81d882d5
 array_mult_safe(const size_t m1, const size_t m2, const size_t extra)
8e986316
 {
81d882d5
     const size_t limit = 0xFFFFFFFF;
     unsigned long long res = (unsigned long long)m1 * (unsigned long long)m2 + (unsigned long long)extra;
     if (unlikely(m1 > limit) || unlikely(m2 > limit) || unlikely(extra > limit) || unlikely(res > (unsigned long long)limit))
     {
         msg(M_FATAL, "attemped allocation of excessively large array");
     }
     return (size_t) res;
8e986316
 }
 
b551bec9
 void
81d882d5
 buf_size_error(const size_t size)
b551bec9
 {
81d882d5
     msg(M_FATAL, "fatal buffer size error, size=%lu", (unsigned long)size);
b551bec9
 }
 
6fbf66fa
 struct buffer
 #ifdef DMALLOC
81d882d5
 alloc_buf_debug(size_t size, const char *file, int line)
6fbf66fa
 #else
81d882d5
 alloc_buf(size_t size)
6fbf66fa
 #endif
 {
81d882d5
     struct buffer buf;
dc7be6d0
 
81d882d5
     if (!buf_size_valid(size))
     {
         buf_size_error(size);
     }
     buf.capacity = (int)size;
     buf.offset = 0;
     buf.len = 0;
6fbf66fa
 #ifdef DMALLOC
81d882d5
     buf.data = openvpn_dmalloc(file, line, size);
6fbf66fa
 #else
81d882d5
     buf.data = calloc(1, size);
6fbf66fa
 #endif
81d882d5
     check_malloc_return(buf.data);
dc7be6d0
 
81d882d5
     return buf;
6fbf66fa
 }
 
 struct buffer
 #ifdef DMALLOC
81d882d5
 alloc_buf_gc_debug(size_t size, struct gc_arena *gc, const char *file, int line)
6fbf66fa
 #else
81d882d5
 alloc_buf_gc(size_t size, struct gc_arena *gc)
6fbf66fa
 #endif
 {
81d882d5
     struct buffer buf;
     if (!buf_size_valid(size))
     {
         buf_size_error(size);
     }
     buf.capacity = (int)size;
     buf.offset = 0;
     buf.len = 0;
6fbf66fa
 #ifdef DMALLOC
81d882d5
     buf.data = (uint8_t *) gc_malloc_debug(size, false, gc, file, line);
6fbf66fa
 #else
81d882d5
     buf.data = (uint8_t *) gc_malloc(size, false, gc);
6fbf66fa
 #endif
81d882d5
     if (size)
     {
         *buf.data = 0;
     }
     return buf;
6fbf66fa
 }
 
 struct buffer
 #ifdef DMALLOC
81d882d5
 clone_buf_debug(const struct buffer *buf, const char *file, int line)
6fbf66fa
 #else
81d882d5
 clone_buf(const struct buffer *buf)
6fbf66fa
 #endif
 {
81d882d5
     struct buffer ret;
     ret.capacity = buf->capacity;
     ret.offset = buf->offset;
     ret.len = buf->len;
6fbf66fa
 #ifdef DMALLOC
81d882d5
     ret.data = (uint8_t *) openvpn_dmalloc(file, line, buf->capacity);
6fbf66fa
 #else
81d882d5
     ret.data = (uint8_t *) malloc(buf->capacity);
6fbf66fa
 #endif
81d882d5
     check_malloc_return(ret.data);
     memcpy(BPTR(&ret), BPTR(buf), BLEN(buf));
     return ret;
6fbf66fa
 }
 
 #ifdef BUF_INIT_TRACKING
 
 bool
81d882d5
 buf_init_debug(struct buffer *buf, int offset, const char *file, int line)
6fbf66fa
 {
81d882d5
     buf->debug_file = file;
     buf->debug_line = line;
     return buf_init_dowork(buf, offset);
6fbf66fa
 }
 
 static inline int
81d882d5
 buf_debug_line(const struct buffer *buf)
6fbf66fa
 {
81d882d5
     return buf->debug_line;
6fbf66fa
 }
 
 static const char *
81d882d5
 buf_debug_file(const struct buffer *buf)
6fbf66fa
 {
81d882d5
     return buf->debug_file;
6fbf66fa
 }
 
81d882d5
 #else  /* ifdef BUF_INIT_TRACKING */
6fbf66fa
 
 #define buf_debug_line(buf) 0
 #define buf_debug_file(buf) "[UNDEF]"
 
81d882d5
 #endif /* ifdef BUF_INIT_TRACKING */
6fbf66fa
 
 void
81d882d5
 buf_clear(struct buffer *buf)
6fbf66fa
 {
81d882d5
     if (buf->capacity > 0)
009521ac
     {
81d882d5
         secure_memzero(buf->data, buf->capacity);
009521ac
     }
81d882d5
     buf->len = 0;
     buf->offset = 0;
6fbf66fa
 }
 
 bool
81d882d5
 buf_assign(struct buffer *dest, const struct buffer *src)
6fbf66fa
 {
81d882d5
     if (!buf_init(dest, src->offset))
     {
         return false;
     }
     return buf_write(dest, BPTR(src), BLEN(src));
6fbf66fa
 }
 
 void
81d882d5
 free_buf(struct buffer *buf)
6fbf66fa
 {
81d882d5
     if (buf->data)
     {
         free(buf->data);
     }
     CLEAR(*buf);
6fbf66fa
 }
 
 /*
  * Return a buffer for write that is a subset of another buffer
  */
 struct buffer
81d882d5
 buf_sub(struct buffer *buf, int size, bool prepend)
6fbf66fa
 {
81d882d5
     struct buffer ret;
     uint8_t *data;
6fbf66fa
 
81d882d5
     CLEAR(ret);
     data = prepend ? buf_prepend(buf, size) : buf_write_alloc(buf, size);
     if (data)
6fbf66fa
     {
81d882d5
         ret.capacity = size;
         ret.data = data;
6fbf66fa
     }
81d882d5
     return ret;
6fbf66fa
 }
 
 /*
  * printf append to a buffer with overflow check
  */
bda8d38b
 bool
81d882d5
 buf_printf(struct buffer *buf, const char *format, ...)
6fbf66fa
 {
81d882d5
     int ret = false;
     if (buf_defined(buf))
     {
         va_list arglist;
         uint8_t *ptr = BEND(buf);
         int cap = buf_forward_capacity(buf);
 
         if (cap > 0)
         {
             int stat;
             va_start(arglist, format);
             stat = vsnprintf((char *)ptr, cap, format, arglist);
             va_end(arglist);
             *(buf->data + buf->capacity - 1) = 0; /* windows vsnprintf needs this */
             buf->len += (int) strlen((char *)ptr);
             if (stat >= 0 && stat < cap)
             {
                 ret = true;
             }
         }
     }
     return ret;
6fbf66fa
 }
 
4d3df224
 bool
 buf_puts(struct buffer *buf, const char *str)
8335caf9
 {
81d882d5
     int ret = false;
     uint8_t *ptr = BEND(buf);
     int cap = buf_forward_capacity(buf);
     if (cap > 0)
8335caf9
     {
81d882d5
         strncpynt((char *)ptr,str, cap);
         *(buf->data + buf->capacity - 1) = 0; /* windows vsnprintf needs this */
         buf->len += (int) strlen((char *)ptr);
         ret = true;
8335caf9
     }
81d882d5
     return ret;
8335caf9
 }
81d882d5
 
8335caf9
 
6fbf66fa
 /*
  * This is necessary due to certain buggy implementations of snprintf,
  * that don't guarantee null termination for size > 0.
14708eb6
  *
d5497262
  * Return false on overflow.
20b18fd7
  *
a24dd2e3
  * This functionality is duplicated in src/openvpnserv/common.c
14708eb6
  * Any modifications here should be done to the other place as well.
6fbf66fa
  */
 
81d882d5
 bool
 openvpn_snprintf(char *str, size_t size, const char *format, ...)
6fbf66fa
 {
81d882d5
     va_list arglist;
     int len = -1;
     if (size > 0)
6fbf66fa
     {
81d882d5
         va_start(arglist, format);
         len = vsnprintf(str, size, format, arglist);
         va_end(arglist);
         str[size - 1] = 0;
6fbf66fa
     }
81d882d5
     return (len >= 0 && len < size);
6fbf66fa
 }
 
 /*
  * write a string to the end of a buffer that was
  * truncated by buf_printf
  */
 void
81d882d5
 buf_catrunc(struct buffer *buf, const char *str)
6fbf66fa
 {
81d882d5
     if (buf_forward_capacity(buf) <= 1)
6fbf66fa
     {
81d882d5
         int len = (int) strlen(str) + 1;
         if (len < buf_forward_capacity_total(buf))
         {
             strncpynt((char *)(buf->data + buf->capacity - len), str, len);
         }
6fbf66fa
     }
 }
 
 /*
  * convert a multi-line output to one line
  */
 void
81d882d5
 convert_to_one_line(struct buffer *buf)
6fbf66fa
 {
81d882d5
     uint8_t *cp = BPTR(buf);
     int len = BLEN(buf);
     while (len--)
6fbf66fa
     {
81d882d5
         if (*cp == '\n')
         {
             *cp = '|';
         }
         ++cp;
6fbf66fa
     }
 }
 
 /* NOTE: requires that string be null terminated */
 void
81d882d5
 buf_write_string_file(const struct buffer *buf, const char *filename, int fd)
6fbf66fa
 {
81d882d5
     const int len = strlen((char *) BPTR(buf));
     const int size = write(fd, BPTR(buf), len);
     if (size != len)
     {
         msg(M_ERR, "Write error on file '%s'", filename);
     }
6fbf66fa
 }
 
 /*
  * Garbage collection
  */
 
 void *
 #ifdef DMALLOC
81d882d5
 gc_malloc_debug(size_t size, bool clear, struct gc_arena *a, const char *file, int line)
6fbf66fa
 #else
81d882d5
 gc_malloc(size_t size, bool clear, struct gc_arena *a)
6fbf66fa
 #endif
 {
81d882d5
     void *ret;
     if (a)
43681479
     {
81d882d5
         struct gc_entry *e;
6fbf66fa
 #ifdef DMALLOC
81d882d5
         e = (struct gc_entry *) openvpn_dmalloc(file, line, size + sizeof(struct gc_entry));
6fbf66fa
 #else
81d882d5
         e = (struct gc_entry *) malloc(size + sizeof(struct gc_entry));
6fbf66fa
 #endif
81d882d5
         check_malloc_return(e);
         ret = (char *) e + sizeof(struct gc_entry);
         e->next = a->list;
         a->list = e;
43681479
     }
81d882d5
     else
43681479
     {
 #ifdef DMALLOC
81d882d5
         ret = openvpn_dmalloc(file, line, size);
43681479
 #else
81d882d5
         ret = malloc(size);
43681479
 #endif
81d882d5
         check_malloc_return(ret);
43681479
     }
6fbf66fa
 #ifndef ZERO_BUFFER_ON_ALLOC
81d882d5
     if (clear)
6fbf66fa
 #endif
81d882d5
     memset(ret, 0, size);
     return ret;
6fbf66fa
 }
 
 void
81d882d5
 x_gc_free(struct gc_arena *a)
6fbf66fa
 {
81d882d5
     struct gc_entry *e;
     e = a->list;
     a->list = NULL;
 
     while (e != NULL)
6fbf66fa
     {
81d882d5
         struct gc_entry *next = e->next;
         free(e);
         e = next;
6fbf66fa
     }
 }
 
 /*
e719a053
  * Functions to handle special objects in gc_entries
  */
 
 void
81d882d5
 x_gc_freespecial(struct gc_arena *a)
e719a053
 {
81d882d5
     struct gc_entry_special *e;
     e = a->list_special;
     a->list_special = NULL;
e719a053
 
81d882d5
     while (e != NULL)
e719a053
     {
81d882d5
         struct gc_entry_special *next = e->next;
         e->free_fnc(e->addr);
         free(e);
         e = next;
e719a053
     }
 }
 
81d882d5
 void
 gc_addspecial(void *addr, void(free_function)(void *), struct gc_arena *a)
e719a053
 {
81d882d5
     ASSERT(a);
     struct gc_entry_special *e;
e719a053
 #ifdef DMALLOC
81d882d5
     e = (struct gc_entry_special *) openvpn_dmalloc(file, line, sizeof(struct gc_entry_special));
e719a053
 #else
81d882d5
     e = (struct gc_entry_special *) malloc(sizeof(struct gc_entry_special));
e719a053
 #endif
81d882d5
     check_malloc_return(e);
     e->free_fnc = free_function;
     e->addr = addr;
e719a053
 
81d882d5
     e->next = a->list_special;
     a->list_special = e;
e719a053
 }
 
 
 /*
4e9a51d7
  * Transfer src arena to dest, resetting src to an empty arena.
  */
 void
81d882d5
 gc_transfer(struct gc_arena *dest, struct gc_arena *src)
4e9a51d7
 {
81d882d5
     if (dest && src)
     {
         struct gc_entry *e = src->list;
         if (e)
         {
             while (e->next != NULL)
4cd4899e
             {
81d882d5
                 e = e->next;
4cd4899e
             }
81d882d5
             e->next = dest->list;
             dest->list = src->list;
             src->list = NULL;
         }
4e9a51d7
     }
 }
 
 /*
6fbf66fa
  * Hex dump -- Output a binary buffer to a hex string and return it.
  */
 
 char *
81d882d5
 format_hex_ex(const uint8_t *data, int size, int maxoutput,
               unsigned int space_break_flags, const char *separator,
               struct gc_arena *gc)
6fbf66fa
 {
81d882d5
     const size_t bytes_per_hexblock = space_break_flags & FHE_SPACE_BREAK_MASK;
     const size_t separator_len = separator ? strlen(separator) : 0;
     static_assert(INT_MAX <= SIZE_MAX, "Code assumes INT_MAX <= SIZE_MAX");
     const size_t out_len = maxoutput > 0 ? maxoutput :
                            ((size * 2) + ((size / bytes_per_hexblock) * separator_len) + 2);
 
     struct buffer out = alloc_buf_gc(out_len, gc);
     for (int i = 0; i < size; ++i)
     {
         if (separator && i && !(i % bytes_per_hexblock))
         {
             buf_printf(&out, "%s", separator);
         }
         if (space_break_flags & FHE_CAPS)
         {
             buf_printf(&out, "%02X", data[i]);
         }
         else
         {
             buf_printf(&out, "%02x", data[i]);
         }
     }
     buf_catrunc(&out, "[more...]");
     return (char *)out.data;
6fbf66fa
 }
 
 /*
  * remove specific trailing character
  */
 
 void
81d882d5
 buf_rmtail(struct buffer *buf, uint8_t remove)
6fbf66fa
 {
81d882d5
     uint8_t *cp = BLAST(buf);
     if (cp && *cp == remove)
6fbf66fa
     {
81d882d5
         *cp = '\0';
         --buf->len;
6fbf66fa
     }
 }
 
 /*
  * force a null termination even it requires
  * truncation of the last char.
  */
 void
81d882d5
 buf_null_terminate(struct buffer *buf)
6fbf66fa
 {
81d882d5
     char *last = (char *) BLAST(buf);
     if (last && *last == '\0') /* already terminated? */
     {
         return;
     }
6fbf66fa
 
81d882d5
     if (!buf_safe(buf, 1))   /* make space for trailing null */
     {
         buf_inc_len(buf, -1);
     }
6fbf66fa
 
81d882d5
     buf_write_u8(buf, 0);
6fbf66fa
 }
 
 /*
  * Remove trailing \r and \n chars and ensure
  * null termination.
  */
 void
81d882d5
 buf_chomp(struct buffer *buf)
6fbf66fa
 {
81d882d5
     while (true)
     {
         char *last = (char *) BLAST(buf);
         if (!last)
         {
             break;
         }
         if (char_class(*last, CC_CRLF|CC_NULL))
         {
             if (!buf_inc_len(buf, -1))
             {
                 break;
             }
         }
         else
         {
             break;
         }
     }
     buf_null_terminate(buf);
6fbf66fa
 }
 
eadf16a6
 const char *
81d882d5
 skip_leading_whitespace(const char *str)
eadf16a6
 {
81d882d5
     while (*str)
eadf16a6
     {
81d882d5
         const char c = *str;
         if (!(c == ' ' || c == '\t'))
         {
             break;
         }
         ++str;
eadf16a6
     }
81d882d5
     return str;
eadf16a6
 }
 
6fbf66fa
 /*
  * like buf_null_terminate, but operate on strings
  */
 void
81d882d5
 string_null_terminate(char *str, int len, int capacity)
6fbf66fa
 {
81d882d5
     ASSERT(len >= 0 && len <= capacity && capacity > 0);
     if (len < capacity)
     {
         *(str + len) = '\0';
     }
     else if (len == capacity)
     {
         *(str + len - 1) = '\0';
     }
6fbf66fa
 }
 
 /*
  * Remove trailing \r and \n chars.
  */
 void
81d882d5
 chomp(char *str)
6fbf66fa
 {
81d882d5
     rm_trailing_chars(str, "\r\n");
47ae8457
 }
 
 /*
  * Remove trailing chars
  */
 void
81d882d5
 rm_trailing_chars(char *str, const char *what_to_delete)
47ae8457
 {
81d882d5
     bool modified;
4cd4899e
     do
     {
81d882d5
         const int len = strlen(str);
         modified = false;
         if (len > 0)
         {
             char *cp = str + (len - 1);
             if (strchr(what_to_delete, *cp) != NULL)
             {
                 *cp = '\0';
                 modified = true;
             }
         }
     } while (modified);
6fbf66fa
 }
 
 /*
  * Allocate a string
  */
 char *
 #ifdef DMALLOC
81d882d5
 string_alloc_debug(const char *str, struct gc_arena *gc, const char *file, int line)
6fbf66fa
 #else
81d882d5
 string_alloc(const char *str, struct gc_arena *gc)
6fbf66fa
 #endif
 {
81d882d5
     if (str)
6fbf66fa
     {
81d882d5
         const int n = strlen(str) + 1;
         char *ret;
6fbf66fa
 
81d882d5
         if (gc)
         {
dc7be6d0
 #ifdef DMALLOC
81d882d5
             ret = (char *) gc_malloc_debug(n, false, gc, file, line);
dc7be6d0
 #else
81d882d5
             ret = (char *) gc_malloc(n, false, gc);
dc7be6d0
 #endif
81d882d5
         }
         else
         {
             /* If there are no garbage collector available, it's expected
              * that the caller cleans up afterwards.  This is coherent with the
              * earlier behaviour when gc_malloc() would be called with gc == NULL
              */
6fbf66fa
 #ifdef DMALLOC
81d882d5
             ret = openvpn_dmalloc(file, line, n);
             memset(ret, 0, n);
6fbf66fa
 #else
81d882d5
             ret = calloc(1, n);
6fbf66fa
 #endif
81d882d5
             check_malloc_return(ret);
         }
         memcpy(ret, str, n);
         return ret;
     }
     else
     {
         return NULL;
6fbf66fa
     }
 }
 
 /*
d40f2b20
  * Erase all characters in a string
  */
 void
81d882d5
 string_clear(char *str)
d40f2b20
 {
81d882d5
     if (str)
d40f2b20
     {
81d882d5
         secure_memzero(str, strlen(str));
d40f2b20
     }
 }
 
 /*
eadf16a6
  * Return the length of a string array
  */
 int
81d882d5
 string_array_len(const char **array)
eadf16a6
 {
81d882d5
     int i = 0;
     if (array)
eadf16a6
     {
81d882d5
         while (array[i])
4cd4899e
         {
81d882d5
             ++i;
4cd4899e
         }
eadf16a6
     }
81d882d5
     return i;
eadf16a6
 }
 
 char *
81d882d5
 print_argv(const char **p, struct gc_arena *gc, const unsigned int flags)
eadf16a6
 {
81d882d5
     struct buffer out = alloc_buf_gc(256, gc);
     int i = 0;
     for (;; )
     {
         const char *cp = *p++;
         if (!cp)
         {
             break;
         }
         if (i)
         {
             buf_printf(&out, " ");
         }
         if (flags & PA_BRACKET)
         {
             buf_printf(&out, "[%s]", cp);
         }
         else
         {
             buf_printf(&out, "%s", cp);
         }
         ++i;
     }
     return BSTR(&out);
eadf16a6
 }
 
 /*
6fbf66fa
  * Allocate a string inside a buffer
  */
 struct buffer
 #ifdef DMALLOC
81d882d5
 string_alloc_buf_debug(const char *str, struct gc_arena *gc, const char *file, int line)
6fbf66fa
 #else
81d882d5
 string_alloc_buf(const char *str, struct gc_arena *gc)
6fbf66fa
 #endif
 {
81d882d5
     struct buffer buf;
6fbf66fa
 
81d882d5
     ASSERT(str);
6fbf66fa
 
 #ifdef DMALLOC
81d882d5
     buf_set_read(&buf, (uint8_t *) string_alloc_debug(str, gc, file, line), strlen(str) + 1);
6fbf66fa
 #else
81d882d5
     buf_set_read(&buf, (uint8_t *) string_alloc(str, gc), strlen(str) + 1);
6fbf66fa
 #endif
 
81d882d5
     if (buf.len > 0) /* Don't count trailing '\0' as part of length */
     {
         --buf.len;
     }
6fbf66fa
 
81d882d5
     return buf;
6fbf66fa
 }
 
 /*
  * String comparison
  */
 
 bool
81d882d5
 buf_string_match_head_str(const struct buffer *src, const char *match)
6fbf66fa
 {
81d882d5
     const int size = strlen(match);
     if (size < 0 || size > src->len)
     {
         return false;
     }
     return memcmp(BPTR(src), match, size) == 0;
6fbf66fa
 }
 
 bool
81d882d5
 buf_string_compare_advance(struct buffer *src, const char *match)
6fbf66fa
 {
81d882d5
     if (buf_string_match_head_str(src, match))
6fbf66fa
     {
81d882d5
         buf_advance(src, strlen(match));
         return true;
     }
     else
     {
         return false;
6fbf66fa
     }
 }
 
 int
81d882d5
 buf_substring_len(const struct buffer *buf, int delim)
6fbf66fa
 {
81d882d5
     int i = 0;
     struct buffer tmp = *buf;
     int c;
6fbf66fa
 
81d882d5
     while ((c = buf_read_u8(&tmp)) >= 0)
6fbf66fa
     {
81d882d5
         ++i;
         if (c == delim)
         {
             return i;
         }
6fbf66fa
     }
81d882d5
     return -1;
6fbf66fa
 }
 
 /*
  * String parsing
  */
 
 bool
81d882d5
 buf_parse(struct buffer *buf, const int delim, char *line, const int size)
6fbf66fa
 {
81d882d5
     bool eol = false;
     int n = 0;
     int c;
6fbf66fa
 
81d882d5
     ASSERT(size > 0);
6fbf66fa
 
81d882d5
     do
6fbf66fa
     {
81d882d5
         c = buf_read_u8(buf);
         if (c < 0)
         {
             eol = true;
         }
         if (c <= 0 || c == delim)
         {
             c = 0;
         }
         if (n >= size)
         {
             break;
         }
         line[n++] = c;
6fbf66fa
     }
81d882d5
     while (c);
6fbf66fa
 
81d882d5
     line[size-1] = '\0';
     return !(eol && !strlen(line));
6fbf66fa
 }
 
 /*
f214bb21
  * Print a string which might be NULL
  */
 const char *
81d882d5
 np(const char *str)
f214bb21
 {
81d882d5
     if (str)
     {
         return str;
     }
     else
     {
         return "[NULL]";
     }
f214bb21
 }
 
 /*
6fbf66fa
  * Classify and mutate strings based on character types.
  */
 
 bool
81d882d5
 char_class(const unsigned char c, const unsigned int flags)
6fbf66fa
 {
81d882d5
     if (!flags)
     {
         return false;
     }
     if (flags & CC_ANY)
     {
         return true;
     }
6fbf66fa
 
81d882d5
     if ((flags & CC_NULL) && c == '\0')
     {
         return true;
     }
6fbf66fa
 
81d882d5
     if ((flags & CC_ALNUM) && isalnum(c))
     {
         return true;
     }
     if ((flags & CC_ALPHA) && isalpha(c))
     {
         return true;
     }
     if ((flags & CC_ASCII) && isascii(c))
     {
         return true;
     }
     if ((flags & CC_CNTRL) && iscntrl(c))
     {
         return true;
     }
     if ((flags & CC_DIGIT) && isdigit(c))
     {
         return true;
     }
     if ((flags & CC_PRINT) && (c >= 32 && c != 127)) /* allow ascii non-control and UTF-8, consider DEL to be a control */
     {
         return true;
     }
     if ((flags & CC_PUNCT) && ispunct(c))
     {
         return true;
     }
     if ((flags & CC_SPACE) && isspace(c))
     {
         return true;
     }
     if ((flags & CC_XDIGIT) && isxdigit(c))
     {
         return true;
     }
6fbf66fa
 
81d882d5
     if ((flags & CC_BLANK) && (c == ' ' || c == '\t'))
     {
         return true;
     }
     if ((flags & CC_NEWLINE) && c == '\n')
     {
         return true;
     }
     if ((flags & CC_CR) && c == '\r')
     {
         return true;
     }
6fbf66fa
 
81d882d5
     if ((flags & CC_BACKSLASH) && c == '\\')
     {
         return true;
     }
     if ((flags & CC_UNDERBAR) && c == '_')
     {
         return true;
     }
     if ((flags & CC_DASH) && c == '-')
     {
         return true;
     }
     if ((flags & CC_DOT) && c == '.')
     {
         return true;
     }
     if ((flags & CC_COMMA) && c == ',')
     {
         return true;
     }
     if ((flags & CC_COLON) && c == ':')
     {
         return true;
     }
     if ((flags & CC_SLASH) && c == '/')
     {
         return true;
     }
     if ((flags & CC_SINGLE_QUOTE) && c == '\'')
     {
         return true;
     }
     if ((flags & CC_DOUBLE_QUOTE) && c == '\"')
     {
         return true;
     }
     if ((flags & CC_REVERSE_QUOTE) && c == '`')
     {
         return true;
     }
     if ((flags & CC_AT) && c == '@')
     {
         return true;
     }
     if ((flags & CC_EQUAL) && c == '=')
     {
         return true;
     }
     if ((flags & CC_LESS_THAN) && c == '<')
     {
         return true;
     }
     if ((flags & CC_GREATER_THAN) && c == '>')
     {
         return true;
     }
     if ((flags & CC_PIPE) && c == '|')
     {
         return true;
     }
     if ((flags & CC_QUESTION_MARK) && c == '?')
     {
         return true;
     }
     if ((flags & CC_ASTERISK) && c == '*')
     {
         return true;
     }
6fbf66fa
 
81d882d5
     return false;
6fbf66fa
 }
 
 static inline bool
81d882d5
 char_inc_exc(const char c, const unsigned int inclusive, const unsigned int exclusive)
6fbf66fa
 {
81d882d5
     return char_class(c, inclusive) && !char_class(c, exclusive);
6fbf66fa
 }
 
 bool
81d882d5
 string_class(const char *str, const unsigned int inclusive, const unsigned int exclusive)
6fbf66fa
 {
81d882d5
     char c;
     ASSERT(str);
     while ((c = *str++))
6fbf66fa
     {
81d882d5
         if (!char_inc_exc(c, inclusive, exclusive))
         {
             return false;
         }
6fbf66fa
     }
81d882d5
     return true;
6fbf66fa
 }
 
 /*
  * Modify string in place.
  * Guaranteed to not increase string length.
  */
 bool
81d882d5
 string_mod(char *str, const unsigned int inclusive, const unsigned int exclusive, const char replace)
6fbf66fa
 {
81d882d5
     const char *in = str;
     bool ret = true;
 
     ASSERT(str);
 
     while (true)
     {
         char c = *in++;
         if (c)
         {
             if (!char_inc_exc(c, inclusive, exclusive))
             {
                 c = replace;
                 ret = false;
             }
             if (c)
             {
                 *str++ = c;
             }
         }
         else
         {
             *str = '\0';
             break;
         }
     }
     return ret;
6fbf66fa
 }
 
 const char *
81d882d5
 string_mod_const(const char *str,
                  const unsigned int inclusive,
                  const unsigned int exclusive,
                  const char replace,
                  struct gc_arena *gc)
6fbf66fa
 {
81d882d5
     if (str)
6fbf66fa
     {
81d882d5
         char *buf = string_alloc(str, gc);
         string_mod(buf, inclusive, exclusive, replace);
         return buf;
     }
     else
     {
         return NULL;
6fbf66fa
     }
 }
 
76218836
 void
81d882d5
 string_replace_leading(char *str, const char match, const char replace)
76218836
 {
81d882d5
     ASSERT(match != '\0');
     while (*str)
76218836
     {
81d882d5
         if (*str == match)
         {
             *str = replace;
         }
         else
         {
             break;
         }
         ++str;
76218836
     }
 }
 
6fbf66fa
 #ifdef CHARACTER_CLASS_DEBUG
 
 #define CC_INCLUDE    (CC_PRINT)
 #define CC_EXCLUDE    (0)
 #define CC_REPLACE    ('.')
 
 void
81d882d5
 character_class_debug(void)
6fbf66fa
 {
81d882d5
     char buf[256];
6fbf66fa
 
81d882d5
     while (fgets(buf, sizeof(buf), stdin) != NULL)
6fbf66fa
     {
81d882d5
         string_mod(buf, CC_INCLUDE, CC_EXCLUDE, CC_REPLACE);
         printf("%s", buf);
6fbf66fa
     }
 }
 
 #endif
 
 #ifdef VERIFY_ALIGNMENT
 void
81d882d5
 valign4(const struct buffer *buf, const char *file, const int line)
6fbf66fa
 {
81d882d5
     if (buf && buf->len)
6fbf66fa
     {
81d882d5
         int msglevel = D_ALIGN_DEBUG;
         const unsigned int u = (unsigned int) BPTR(buf);
 
         if (u & (PAYLOAD_ALIGN-1))
         {
             msglevel = D_ALIGN_ERRORS;
         }
 
         msg(msglevel, "%sAlignment at %s/%d ptr=" ptr_format " OLC=%d/%d/%d I=%s/%d",
             (msglevel == D_ALIGN_ERRORS) ? "ERROR: " : "",
             file,
             line,
             (ptr_type)buf->data,
             buf->offset,
             buf->len,
             buf->capacity,
             buf_debug_file(buf),
             buf_debug_line(buf));
6fbf66fa
     }
 }
81d882d5
 #endif /* ifdef VERIFY_ALIGNMENT */
90efcacb
 
 /*
  * struct buffer_list
  */
 struct buffer_list *
81d882d5
 buffer_list_new(const int max_size)
90efcacb
 {
81d882d5
     struct buffer_list *ret;
     ALLOC_OBJ_CLEAR(ret, struct buffer_list);
     ret->max_size = max_size;
     ret->size = 0;
     return ret;
90efcacb
 }
 
 void
81d882d5
 buffer_list_free(struct buffer_list *ol)
90efcacb
 {
81d882d5
     if (ol)
dc85dae6
     {
81d882d5
         buffer_list_reset(ol);
         free(ol);
dc85dae6
     }
90efcacb
 }
 
 bool
81d882d5
 buffer_list_defined(const struct buffer_list *ol)
90efcacb
 {
81d882d5
     return ol && ol->head != NULL;
90efcacb
 }
 
 void
81d882d5
 buffer_list_reset(struct buffer_list *ol)
90efcacb
 {
81d882d5
     struct buffer_entry *e = ol->head;
     while (e)
90efcacb
     {
81d882d5
         struct buffer_entry *next = e->next;
         free_buf(&e->buf);
         free(e);
         e = next;
90efcacb
     }
81d882d5
     ol->head = ol->tail = NULL;
     ol->size = 0;
90efcacb
 }
 
 void
b395f36e
 buffer_list_push(struct buffer_list *ol, const char *str)
90efcacb
 {
81d882d5
     if (str)
dc85dae6
     {
81d882d5
         const size_t len = strlen((const char *)str);
         struct buffer_entry *e = buffer_list_push_data(ol, str, len+1);
         if (e)
         {
             e->buf.len = len; /* Don't count trailing '\0' as part of length */
         }
dc85dae6
     }
 }
 
 struct buffer_entry *
b395f36e
 buffer_list_push_data(struct buffer_list *ol, const void *data, size_t size)
dc85dae6
 {
81d882d5
     struct buffer_entry *e = NULL;
     if (data && (!ol->max_size || ol->size < ol->max_size))
     {
         ALLOC_OBJ_CLEAR(e, struct buffer_entry);
 
         ++ol->size;
         if (ol->tail)
         {
             ASSERT(ol->head);
             ol->tail->next = e;
         }
         else
         {
             ASSERT(!ol->head);
             ol->head = e;
         }
         e->buf = alloc_buf(size);
         memcpy(e->buf.data, data, size);
         e->buf.len = (int)size;
         ol->tail = e;
     }
     return e;
90efcacb
 }
 
7e1c085d
 struct buffer *
81d882d5
 buffer_list_peek(struct buffer_list *ol)
90efcacb
 {
81d882d5
     if (ol && ol->head)
     {
         return &ol->head->buf;
     }
     else
     {
         return NULL;
     }
90efcacb
 }
 
7e1c085d
 void
fb6138dd
 buffer_list_aggregate_separator(struct buffer_list *bl, const size_t max_len,
                                 const char *sep)
7e1c085d
 {
81d882d5
     int sep_len = strlen(sep);
 
     if (bl->head)
     {
         struct buffer_entry *more = bl->head;
         size_t size = 0;
         int count = 0;
fb6138dd
         for (count = 0; more; ++count)
81d882d5
         {
fb6138dd
             size_t extra_len = BLEN(&more->buf) + sep_len;
             if (size + extra_len > max_len)
             {
                 break;
             }
 
             size += extra_len;
81d882d5
             more = more->next;
         }
 
         if (count >= 2)
         {
             int i;
             struct buffer_entry *e = bl->head, *f;
 
             ALLOC_OBJ_CLEAR(f, struct buffer_entry);
748902f4
             f->buf = alloc_buf(size + 1); /* prevent 0-byte malloc */
81d882d5
             f->buf.capacity = size;
             for (i = 0; e && i < count; ++i)
             {
                 struct buffer_entry *next = e->next;
                 buf_copy(&f->buf, &e->buf);
                 buf_write(&f->buf, sep, sep_len);
                 free_buf(&e->buf);
                 free(e);
                 e = next;
             }
             bl->head = f;
463afdf5
             bl->size -= count - 1;
81d882d5
             f->next = more;
             if (!more)
             {
                 bl->tail = f;
             }
         }
7e1c085d
     }
 }
 
dc85dae6
 void
81d882d5
 buffer_list_aggregate(struct buffer_list *bl, const size_t max)
39e3d336
 {
81d882d5
     buffer_list_aggregate_separator(bl, max, "");
39e3d336
 }
 
 void
81d882d5
 buffer_list_pop(struct buffer_list *ol)
90efcacb
 {
81d882d5
     if (ol && ol->head)
90efcacb
     {
81d882d5
         struct buffer_entry *e = ol->head->next;
         free_buf(&ol->head->buf);
         free(ol->head);
         ol->head = e;
         --ol->size;
         if (!e)
         {
             ol->tail = NULL;
         }
90efcacb
     }
 }
 
 void
81d882d5
 buffer_list_advance(struct buffer_list *ol, int n)
90efcacb
 {
81d882d5
     if (ol->head)
90efcacb
     {
81d882d5
         struct buffer *buf = &ol->head->buf;
         ASSERT(buf_advance(buf, n));
         if (!BLEN(buf))
         {
             buffer_list_pop(ol);
         }
90efcacb
     }
 }
 
 struct buffer_list *
81d882d5
 buffer_list_file(const char *fn, int max_line_len)
90efcacb
 {
81d882d5
     FILE *fp = platform_fopen(fn, "r");
     struct buffer_list *bl = NULL;
 
     if (fp)
     {
         char *line = (char *) malloc(max_line_len);
         if (line)
         {
             bl = buffer_list_new(0);
             while (fgets(line, max_line_len, fp) != NULL)
4cd4899e
             {
b395f36e
                 buffer_list_push(bl, line);
4cd4899e
             }
81d882d5
             free(line);
         }
         fclose(fp);
     }
     return bl;
90efcacb
 }