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 |
} |