blob: 63acc6cf7714d3c93d78c7b7ab54485c19202ae8 [file] [log] [blame]
/* MIT License
*
* Copyright (c) 2023 Brad House
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*/
#include "ares_private.h"
#include "ares_buf.h"
#include <limits.h>
#ifdef HAVE_STDINT_H
# include <stdint.h>
#endif
struct ares_buf {
const unsigned char *data; /*!< pointer to start of data buffer */
size_t data_len; /*!< total size of data in buffer */
unsigned char *alloc_buf; /*!< Pointer to allocated data buffer,
* not used for const buffers */
size_t alloc_buf_len; /*!< Size of allocated data buffer */
size_t offset; /*!< Current working offset in buffer */
size_t tag_offset; /*!< Tagged offset in buffer. Uses
* SIZE_MAX if not set. */
};
ares_buf_t *ares_buf_create(void)
{
ares_buf_t *buf = ares_malloc_zero(sizeof(*buf));
if (buf == NULL) {
return NULL;
}
buf->tag_offset = SIZE_MAX;
return buf;
}
ares_buf_t *ares_buf_create_const(const unsigned char *data, size_t data_len)
{
ares_buf_t *buf;
if (data == NULL || data_len == 0) {
return NULL;
}
buf = ares_buf_create();
if (buf == NULL) {
return NULL;
}
buf->data = data;
buf->data_len = data_len;
return buf;
}
void ares_buf_destroy(ares_buf_t *buf)
{
if (buf == NULL) {
return;
}
ares_free(buf->alloc_buf);
ares_free(buf);
}
static ares_bool_t ares_buf_is_const(const ares_buf_t *buf)
{
if (buf == NULL) {
return ARES_FALSE; /* LCOV_EXCL_LINE: DefensiveCoding */
}
if (buf->data != NULL && buf->alloc_buf == NULL) {
return ARES_TRUE;
}
return ARES_FALSE;
}
void ares_buf_reclaim(ares_buf_t *buf)
{
size_t prefix_size;
size_t data_size;
if (buf == NULL) {
return;
}
if (ares_buf_is_const(buf)) {
return; /* LCOV_EXCL_LINE: DefensiveCoding */
}
/* Silence coverity. All lengths are zero so would bail out later but
* coverity doesn't know this */
if (buf->alloc_buf == NULL) {
return;
}
if (buf->tag_offset != SIZE_MAX && buf->tag_offset < buf->offset) {
prefix_size = buf->tag_offset;
} else {
prefix_size = buf->offset;
}
if (prefix_size == 0) {
return;
}
data_size = buf->data_len - prefix_size;
memmove(buf->alloc_buf, buf->alloc_buf + prefix_size, data_size);
buf->data = buf->alloc_buf;
buf->data_len = data_size;
buf->offset -= prefix_size;
if (buf->tag_offset != SIZE_MAX) {
buf->tag_offset -= prefix_size;
}
}
static ares_status_t ares_buf_ensure_space(ares_buf_t *buf, size_t needed_size)
{
size_t remaining_size;
size_t alloc_size;
unsigned char *ptr;
if (buf == NULL) {
return ARES_EFORMERR;
}
if (ares_buf_is_const(buf)) {
return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
}
/* When calling ares_buf_finish_str() we end up adding a null terminator,
* so we want to ensure the size is always sufficient for this as we don't
* want an ARES_ENOMEM at that point */
needed_size++;
/* No need to do an expensive move operation, we have enough to just append */
remaining_size = buf->alloc_buf_len - buf->data_len;
if (remaining_size >= needed_size) {
return ARES_SUCCESS;
}
/* See if just moving consumed data frees up enough space */
ares_buf_reclaim(buf);
remaining_size = buf->alloc_buf_len - buf->data_len;
if (remaining_size >= needed_size) {
return ARES_SUCCESS;
}
alloc_size = buf->alloc_buf_len;
/* Not yet started */
if (alloc_size == 0) {
alloc_size = 16; /* Always shifts 1, so ends up being 32 minimum */
}
/* Increase allocation by powers of 2 */
do {
alloc_size <<= 1;
remaining_size = alloc_size - buf->data_len;
} while (remaining_size < needed_size);
ptr = ares_realloc(buf->alloc_buf, alloc_size);
if (ptr == NULL) {
return ARES_ENOMEM;
}
buf->alloc_buf = ptr;
buf->alloc_buf_len = alloc_size;
buf->data = ptr;
return ARES_SUCCESS;
}
ares_status_t ares_buf_set_length(ares_buf_t *buf, size_t len)
{
if (buf == NULL || ares_buf_is_const(buf)) {
return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
}
if (len >= buf->alloc_buf_len - buf->offset) {
return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
}
buf->data_len = len + buf->offset;
return ARES_SUCCESS;
}
ares_status_t ares_buf_append(ares_buf_t *buf, const unsigned char *data,
size_t data_len)
{
ares_status_t status;
if (data == NULL && data_len != 0) {
return ARES_EFORMERR;
}
if (data_len == 0) {
return ARES_SUCCESS;
}
status = ares_buf_ensure_space(buf, data_len);
if (status != ARES_SUCCESS) {
return status;
}
memcpy(buf->alloc_buf + buf->data_len, data, data_len);
buf->data_len += data_len;
return ARES_SUCCESS;
}
ares_status_t ares_buf_append_byte(ares_buf_t *buf, unsigned char b)
{
return ares_buf_append(buf, &b, 1);
}
ares_status_t ares_buf_append_be16(ares_buf_t *buf, unsigned short u16)
{
ares_status_t status;
status = ares_buf_append_byte(buf, (unsigned char)((u16 >> 8) & 0xff));
if (status != ARES_SUCCESS) {
return status; /* LCOV_EXCL_LINE: OutOfMemory */
}
status = ares_buf_append_byte(buf, (unsigned char)(u16 & 0xff));
if (status != ARES_SUCCESS) {
return status; /* LCOV_EXCL_LINE: OutOfMemory */
}
return ARES_SUCCESS;
}
ares_status_t ares_buf_append_be32(ares_buf_t *buf, unsigned int u32)
{
ares_status_t status;
status = ares_buf_append_byte(buf, ((unsigned char)(u32 >> 24) & 0xff));
if (status != ARES_SUCCESS) {
return status; /* LCOV_EXCL_LINE: OutOfMemory */
}
status = ares_buf_append_byte(buf, ((unsigned char)(u32 >> 16) & 0xff));
if (status != ARES_SUCCESS) {
return status; /* LCOV_EXCL_LINE: OutOfMemory */
}
status = ares_buf_append_byte(buf, ((unsigned char)(u32 >> 8) & 0xff));
if (status != ARES_SUCCESS) {
return status; /* LCOV_EXCL_LINE: OutOfMemory */
}
status = ares_buf_append_byte(buf, ((unsigned char)u32 & 0xff));
if (status != ARES_SUCCESS) {
return status; /* LCOV_EXCL_LINE: OutOfMemory */
}
return ARES_SUCCESS;
}
unsigned char *ares_buf_append_start(ares_buf_t *buf, size_t *len)
{
ares_status_t status;
if (len == NULL || *len == 0) {
return NULL;
}
status = ares_buf_ensure_space(buf, *len);
if (status != ARES_SUCCESS) {
return NULL;
}
/* -1 for possible null terminator for ares_buf_finish_str() */
*len = buf->alloc_buf_len - buf->data_len - 1;
return buf->alloc_buf + buf->data_len;
}
void ares_buf_append_finish(ares_buf_t *buf, size_t len)
{
if (buf == NULL) {
return;
}
buf->data_len += len;
}
unsigned char *ares_buf_finish_bin(ares_buf_t *buf, size_t *len)
{
unsigned char *ptr = NULL;
if (buf == NULL || len == NULL || ares_buf_is_const(buf)) {
return NULL;
}
ares_buf_reclaim(buf);
/* We don't want to return NULL except on failure, may be zero-length */
if (buf->alloc_buf == NULL && ares_buf_ensure_space(buf, 1) != ARES_SUCCESS) {
return NULL; /* LCOV_EXCL_LINE: OutOfMemory */
}
ptr = buf->alloc_buf;
*len = buf->data_len;
ares_free(buf);
return ptr;
}
char *ares_buf_finish_str(ares_buf_t *buf, size_t *len)
{
char *ptr;
size_t mylen;
ptr = (char *)ares_buf_finish_bin(buf, &mylen);
if (ptr == NULL) {
return NULL;
}
if (len != NULL) {
*len = mylen;
}
/* NOTE: ensured via ares_buf_ensure_space() that there is always at least
* 1 extra byte available for this specific use-case */
ptr[mylen] = 0;
return ptr;
}
void ares_buf_tag(ares_buf_t *buf)
{
if (buf == NULL) {
return;
}
buf->tag_offset = buf->offset;
}
ares_status_t ares_buf_tag_rollback(ares_buf_t *buf)
{
if (buf == NULL || buf->tag_offset == SIZE_MAX) {
return ARES_EFORMERR;
}
buf->offset = buf->tag_offset;
buf->tag_offset = SIZE_MAX;
return ARES_SUCCESS;
}
ares_status_t ares_buf_tag_clear(ares_buf_t *buf)
{
if (buf == NULL || buf->tag_offset == SIZE_MAX) {
return ARES_EFORMERR;
}
buf->tag_offset = SIZE_MAX;
return ARES_SUCCESS;
}
const unsigned char *ares_buf_tag_fetch(const ares_buf_t *buf, size_t *len)
{
if (buf == NULL || buf->tag_offset == SIZE_MAX || len == NULL) {
return NULL;
}
*len = buf->offset - buf->tag_offset;
return buf->data + buf->tag_offset;
}
size_t ares_buf_tag_length(const ares_buf_t *buf)
{
if (buf == NULL || buf->tag_offset == SIZE_MAX) {
return 0;
}
return buf->offset - buf->tag_offset;
}
ares_status_t ares_buf_tag_fetch_bytes(const ares_buf_t *buf,
unsigned char *bytes, size_t *len)
{
size_t ptr_len = 0;
const unsigned char *ptr = ares_buf_tag_fetch(buf, &ptr_len);
if (ptr == NULL || bytes == NULL || len == NULL) {
return ARES_EFORMERR;
}
if (*len < ptr_len) {
return ARES_EFORMERR;
}
*len = ptr_len;
if (ptr_len > 0) {
memcpy(bytes, ptr, ptr_len);
}
return ARES_SUCCESS;
}
ares_status_t ares_buf_tag_fetch_constbuf(const ares_buf_t *buf,
ares_buf_t **newbuf)
{
size_t ptr_len = 0;
const unsigned char *ptr = ares_buf_tag_fetch(buf, &ptr_len);
if (ptr == NULL || newbuf == NULL) {
return ARES_EFORMERR;
}
*newbuf = ares_buf_create_const(ptr, ptr_len);
if (*newbuf == NULL) {
return ARES_ENOMEM;
}
return ARES_SUCCESS;
}
ares_status_t ares_buf_tag_fetch_string(const ares_buf_t *buf, char *str,
size_t len)
{
size_t out_len;
ares_status_t status;
size_t i;
if (str == NULL || len == 0) {
return ARES_EFORMERR;
}
/* Space for NULL terminator */
out_len = len - 1;
status = ares_buf_tag_fetch_bytes(buf, (unsigned char *)str, &out_len);
if (status != ARES_SUCCESS) {
return status;
}
/* NULL terminate */
str[out_len] = 0;
/* Validate string is printable */
for (i = 0; i < out_len; i++) {
if (!ares_isprint(str[i])) {
return ARES_EBADSTR;
}
}
return ARES_SUCCESS;
}
ares_status_t ares_buf_tag_fetch_strdup(const ares_buf_t *buf, char **str)
{
size_t ptr_len = 0;
const unsigned char *ptr = ares_buf_tag_fetch(buf, &ptr_len);
if (ptr == NULL || str == NULL) {
return ARES_EFORMERR;
}
if (!ares_str_isprint((const char *)ptr, ptr_len)) {
return ARES_EBADSTR;
}
*str = ares_malloc(ptr_len + 1);
if (*str == NULL) {
return ARES_ENOMEM;
}
if (ptr_len > 0) {
memcpy(*str, ptr, ptr_len);
}
(*str)[ptr_len] = 0;
return ARES_SUCCESS;
}
static const unsigned char *ares_buf_fetch(const ares_buf_t *buf, size_t *len)
{
if (len != NULL) {
*len = 0;
}
if (buf == NULL || len == NULL || buf->data == NULL) {
return NULL;
}
*len = buf->data_len - buf->offset;
if (*len == 0) {
return NULL;
}
return buf->data + buf->offset;
}
ares_status_t ares_buf_consume(ares_buf_t *buf, size_t len)
{
size_t remaining_len = ares_buf_len(buf);
if (remaining_len < len) {
return ARES_EBADRESP;
}
buf->offset += len;
return ARES_SUCCESS;
}
ares_status_t ares_buf_fetch_be16(ares_buf_t *buf, unsigned short *u16)
{
size_t remaining_len;
const unsigned char *ptr = ares_buf_fetch(buf, &remaining_len);
unsigned int u32;
if (buf == NULL || u16 == NULL || remaining_len < sizeof(*u16)) {
return ARES_EBADRESP;
}
/* Do math in an unsigned int in order to prevent warnings due to automatic
* conversion by the compiler from short to int during shifts */
u32 = ((unsigned int)(ptr[0]) << 8 | (unsigned int)ptr[1]);
*u16 = (unsigned short)(u32 & 0xFFFF);
return ares_buf_consume(buf, sizeof(*u16));
}
ares_status_t ares_buf_fetch_be32(ares_buf_t *buf, unsigned int *u32)
{
size_t remaining_len;
const unsigned char *ptr = ares_buf_fetch(buf, &remaining_len);
if (buf == NULL || u32 == NULL || remaining_len < sizeof(*u32)) {
return ARES_EBADRESP;
}
*u32 = ((unsigned int)(ptr[0]) << 24 | (unsigned int)(ptr[1]) << 16 |
(unsigned int)(ptr[2]) << 8 | (unsigned int)(ptr[3]));
return ares_buf_consume(buf, sizeof(*u32));
}
ares_status_t ares_buf_fetch_bytes(ares_buf_t *buf, unsigned char *bytes,
size_t len)
{
size_t remaining_len;
const unsigned char *ptr = ares_buf_fetch(buf, &remaining_len);
if (buf == NULL || bytes == NULL || len == 0 || remaining_len < len) {
return ARES_EBADRESP;
}
memcpy(bytes, ptr, len);
return ares_buf_consume(buf, len);
}
ares_status_t ares_buf_fetch_bytes_dup(ares_buf_t *buf, size_t len,
ares_bool_t null_term,
unsigned char **bytes)
{
size_t remaining_len;
const unsigned char *ptr = ares_buf_fetch(buf, &remaining_len);
if (buf == NULL || bytes == NULL || len == 0 || remaining_len < len) {
return ARES_EBADRESP;
}
*bytes = ares_malloc(null_term ? len + 1 : len);
if (*bytes == NULL) {
return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
}
memcpy(*bytes, ptr, len);
if (null_term) {
(*bytes)[len] = 0;
}
return ares_buf_consume(buf, len);
}
ares_status_t ares_buf_fetch_str_dup(ares_buf_t *buf, size_t len, char **str)
{
size_t remaining_len;
size_t i;
const unsigned char *ptr = ares_buf_fetch(buf, &remaining_len);
if (buf == NULL || str == NULL || len == 0 || remaining_len < len) {
return ARES_EBADRESP;
}
/* Validate string is printable */
for (i = 0; i < len; i++) {
if (!ares_isprint(ptr[i])) {
return ARES_EBADSTR;
}
}
*str = ares_malloc(len + 1);
if (*str == NULL) {
return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
}
memcpy(*str, ptr, len);
(*str)[len] = 0;
return ares_buf_consume(buf, len);
}
ares_status_t ares_buf_fetch_bytes_into_buf(ares_buf_t *buf, ares_buf_t *dest,
size_t len)
{
size_t remaining_len;
const unsigned char *ptr = ares_buf_fetch(buf, &remaining_len);
ares_status_t status;
if (buf == NULL || dest == NULL || len == 0 || remaining_len < len) {
return ARES_EBADRESP;
}
status = ares_buf_append(dest, ptr, len);
if (status != ARES_SUCCESS) {
return status;
}
return ares_buf_consume(buf, len);
}
static ares_bool_t ares_is_whitespace(unsigned char c,
ares_bool_t include_linefeed)
{
switch (c) {
case '\r':
case '\t':
case ' ':
case '\v':
case '\f':
return ARES_TRUE;
case '\n':
return include_linefeed;
default:
break;
}
return ARES_FALSE;
}
size_t ares_buf_consume_whitespace(ares_buf_t *buf,
ares_bool_t include_linefeed)
{
size_t remaining_len = 0;
const unsigned char *ptr = ares_buf_fetch(buf, &remaining_len);
size_t i;
if (ptr == NULL) {
return 0;
}
for (i = 0; i < remaining_len; i++) {
if (!ares_is_whitespace(ptr[i], include_linefeed)) {
break;
}
}
if (i > 0) {
ares_buf_consume(buf, i);
}
return i;
}
size_t ares_buf_consume_nonwhitespace(ares_buf_t *buf)
{
size_t remaining_len = 0;
const unsigned char *ptr = ares_buf_fetch(buf, &remaining_len);
size_t i;
if (ptr == NULL) {
return 0;
}
for (i = 0; i < remaining_len; i++) {
if (ares_is_whitespace(ptr[i], ARES_TRUE)) {
break;
}
}
if (i > 0) {
ares_buf_consume(buf, i);
}
return i;
}
size_t ares_buf_consume_line(ares_buf_t *buf, ares_bool_t include_linefeed)
{
size_t remaining_len = 0;
const unsigned char *ptr = ares_buf_fetch(buf, &remaining_len);
size_t i;
if (ptr == NULL) {
return 0;
}
for (i = 0; i < remaining_len; i++) {
if (ptr[i] == '\n') {
goto done;
}
}
done:
if (include_linefeed && i < remaining_len && ptr[i] == '\n') {
i++;
}
if (i > 0) {
ares_buf_consume(buf, i);
}
return i;
}
size_t ares_buf_consume_until_charset(ares_buf_t *buf,
const unsigned char *charset, size_t len,
ares_bool_t require_charset)
{
size_t remaining_len = 0;
const unsigned char *ptr = ares_buf_fetch(buf, &remaining_len);
size_t pos;
ares_bool_t found = ARES_FALSE;
if (ptr == NULL || charset == NULL || len == 0) {
return 0;
}
/* Optimize for single character searches */
if (len == 1) {
const unsigned char *p = memchr(ptr, charset[0], remaining_len);
if (p != NULL) {
found = ARES_TRUE;
pos = (size_t)(p - ptr);
} else {
pos = remaining_len;
}
goto done;
}
for (pos = 0; pos < remaining_len; pos++) {
size_t j;
for (j = 0; j < len; j++) {
if (ptr[pos] == charset[j]) {
found = ARES_TRUE;
goto done;
}
}
}
done:
if (require_charset && !found) {
return SIZE_MAX;
}
if (pos > 0) {
ares_buf_consume(buf, pos);
}
return pos;
}
size_t ares_buf_consume_until_seq(ares_buf_t *buf, const unsigned char *seq,
size_t len, ares_bool_t require_seq)
{
size_t remaining_len = 0;
const unsigned char *ptr = ares_buf_fetch(buf, &remaining_len);
const unsigned char *p;
size_t consume_len = 0;
if (ptr == NULL || seq == NULL || len == 0) {
return 0;
}
p = ares_memmem(ptr, remaining_len, seq, len);
if (require_seq && p == NULL) {
return SIZE_MAX;
}
if (p != NULL) {
consume_len = (size_t)(p - ptr);
} else {
consume_len = remaining_len;
}
if (consume_len > 0) {
ares_buf_consume(buf, consume_len);
}
return consume_len;
}
size_t ares_buf_consume_charset(ares_buf_t *buf, const unsigned char *charset,
size_t len)
{
size_t remaining_len = 0;
const unsigned char *ptr = ares_buf_fetch(buf, &remaining_len);
size_t i;
if (ptr == NULL || charset == NULL || len == 0) {
return 0;
}
for (i = 0; i < remaining_len; i++) {
size_t j;
for (j = 0; j < len; j++) {
if (ptr[i] == charset[j]) {
break;
}
}
/* Not found */
if (j == len) {
break;
}
}
if (i > 0) {
ares_buf_consume(buf, i);
}
return i;
}
static void ares_buf_destroy_cb(void *arg)
{
ares_buf_t **buf = arg;
ares_buf_destroy(*buf);
}
static ares_bool_t ares_buf_split_isduplicate(ares_array_t *arr,
const unsigned char *val,
size_t len,
ares_buf_split_t flags)
{
size_t i;
size_t num = ares_array_len(arr);
for (i = 0; i < num; i++) {
ares_buf_t **bufptr = ares_array_at(arr, i);
const ares_buf_t *buf = *bufptr;
size_t plen = 0;
const unsigned char *ptr = ares_buf_peek(buf, &plen);
/* Can't be duplicate if lengths mismatch */
if (plen != len) {
continue;
}
if (flags & ARES_BUF_SPLIT_CASE_INSENSITIVE) {
if (ares_memeq_ci(ptr, val, len)) {
return ARES_TRUE;
}
} else {
if (ares_memeq(ptr, val, len)) {
return ARES_TRUE;
}
}
}
return ARES_FALSE;
}
ares_status_t ares_buf_split(ares_buf_t *buf, const unsigned char *delims,
size_t delims_len, ares_buf_split_t flags,
size_t max_sections, ares_array_t **arr)
{
ares_status_t status = ARES_SUCCESS;
ares_bool_t first = ARES_TRUE;
if (buf == NULL || delims == NULL || delims_len == 0 || arr == NULL) {
return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
}
*arr = ares_array_create(sizeof(ares_buf_t *), ares_buf_destroy_cb);
if (*arr == NULL) {
status = ARES_ENOMEM;
goto done;
}
while (ares_buf_len(buf)) {
size_t len = 0;
const unsigned char *ptr;
if (first) {
/* No delimiter yet, just tag the start */
ares_buf_tag(buf);
} else {
if (flags & ARES_BUF_SPLIT_KEEP_DELIMS) {
/* tag then eat delimiter so its first byte in buffer */
ares_buf_tag(buf);
ares_buf_consume(buf, 1);
} else {
/* throw away delimiter */
ares_buf_consume(buf, 1);
ares_buf_tag(buf);
}
}
if (max_sections && ares_array_len(*arr) >= max_sections - 1) {
ares_buf_consume(buf, ares_buf_len(buf));
} else {
ares_buf_consume_until_charset(buf, delims, delims_len, ARES_FALSE);
}
ptr = ares_buf_tag_fetch(buf, &len);
/* Shouldn't be possible */
if (ptr == NULL) {
status = ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
goto done;
}
if (flags & ARES_BUF_SPLIT_LTRIM) {
size_t i;
for (i = 0; i < len; i++) {
if (!ares_is_whitespace(ptr[i], ARES_TRUE)) {
break;
}
}
ptr += i;
len -= i;
}
if (flags & ARES_BUF_SPLIT_RTRIM) {
while (len > 0 && ares_is_whitespace(ptr[len - 1], ARES_TRUE)) {
len--;
}
}
if (len != 0 || flags & ARES_BUF_SPLIT_ALLOW_BLANK) {
ares_buf_t *data;
if (!(flags & ARES_BUF_SPLIT_NO_DUPLICATES) ||
!ares_buf_split_isduplicate(*arr, ptr, len, flags)) {
/* Since we don't allow const buffers of 0 length, and user wants
* 0-length buffers, swap what we do here */
if (len) {
data = ares_buf_create_const(ptr, len);
} else {
data = ares_buf_create();
}
if (data == NULL) {
status = ARES_ENOMEM;
goto done;
}
status = ares_array_insertdata_last(*arr, &data);
if (status != ARES_SUCCESS) {
ares_buf_destroy(data);
goto done;
}
}
}
first = ARES_FALSE;
}
done:
if (status != ARES_SUCCESS) {
ares_array_destroy(*arr);
*arr = NULL;
}
return status;
}
static void ares_free_split_array(void *arg)
{
void **ptr = arg;
ares_free(*ptr);
}
ares_status_t ares_buf_split_str_array(ares_buf_t *buf,
const unsigned char *delims,
size_t delims_len,
ares_buf_split_t flags,
size_t max_sections, ares_array_t **arr)
{
ares_status_t status;
ares_array_t *split = NULL;
size_t i;
size_t len;
if (arr == NULL) {
return ARES_EFORMERR;
}
*arr = NULL;
status = ares_buf_split(buf, delims, delims_len, flags, max_sections, &split);
if (status != ARES_SUCCESS) {
goto done;
}
*arr = ares_array_create(sizeof(char *), ares_free_split_array);
if (*arr == NULL) {
status = ARES_ENOMEM;
goto done;
}
len = ares_array_len(split);
for (i = 0; i < len; i++) {
ares_buf_t **bufptr = ares_array_at(split, i);
ares_buf_t *lbuf = *bufptr;
char *str = NULL;
status = ares_buf_fetch_str_dup(lbuf, ares_buf_len(lbuf), &str);
if (status != ARES_SUCCESS) {
goto done;
}
status = ares_array_insertdata_last(*arr, &str);
if (status != ARES_SUCCESS) {
ares_free(str);
goto done;
}
}
done:
ares_array_destroy(split);
if (status != ARES_SUCCESS) {
ares_array_destroy(*arr);
*arr = NULL;
}
return status;
}
ares_status_t ares_buf_split_str(ares_buf_t *buf, const unsigned char *delims,
size_t delims_len, ares_buf_split_t flags,
size_t max_sections, char ***strs,
size_t *nstrs)
{
ares_status_t status;
ares_array_t *arr = NULL;
if (strs == NULL || nstrs == NULL) {
return ARES_EFORMERR;
}
*strs = NULL;
*nstrs = 0;
status = ares_buf_split_str_array(buf, delims, delims_len, flags,
max_sections, &arr);
if (status != ARES_SUCCESS) {
goto done;
}
done:
if (status == ARES_SUCCESS) {
*strs = ares_array_finish(arr, nstrs);
} else {
ares_array_destroy(arr);
}
return status;
}
ares_bool_t ares_buf_begins_with(const ares_buf_t *buf,
const unsigned char *data, size_t data_len)
{
size_t remaining_len = 0;
const unsigned char *ptr = ares_buf_fetch(buf, &remaining_len);
if (ptr == NULL || data == NULL || data_len == 0) {
return ARES_FALSE;
}
if (data_len > remaining_len) {
return ARES_FALSE;
}
if (memcmp(ptr, data, data_len) != 0) {
return ARES_FALSE;
}
return ARES_TRUE;
}
size_t ares_buf_len(const ares_buf_t *buf)
{
if (buf == NULL) {
return 0;
}
return buf->data_len - buf->offset;
}
const unsigned char *ares_buf_peek(const ares_buf_t *buf, size_t *len)
{
return ares_buf_fetch(buf, len);
}
ares_status_t ares_buf_replace(ares_buf_t *buf, const unsigned char *srch,
size_t srch_size, const unsigned char *rplc,
size_t rplc_size)
{
size_t processed_len = 0;
ares_status_t status;
if (buf->alloc_buf == NULL || srch == NULL || srch_size == 0 ||
(rplc == NULL && rplc_size != 0)) {
return ARES_EFORMERR;
}
while (1) {
unsigned char *ptr = buf->alloc_buf + buf->offset + processed_len;
size_t remaining_len = buf->data_len - buf->offset - processed_len;
size_t found_offset = 0;
size_t move_data_len;
/* Find pattern */
ptr = ares_memmem(ptr, remaining_len, srch, srch_size);
if (ptr == NULL) {
break;
}
/* Store the offset this was found because our actual pointer might be
* switched out from under us by the call to ensure_space() if the
* replacement pattern is larger than the search pattern */
found_offset = (size_t)(ptr - (size_t)(buf->alloc_buf + buf->offset));
if (rplc_size > srch_size) {
status = ares_buf_ensure_space(buf, rplc_size - srch_size);
if (status != ARES_SUCCESS) {
return status;
}
}
/* Impossible, but silence clang */
if (buf->alloc_buf == NULL) {
return ARES_ENOMEM;
}
/* Recalculate actual pointer */
ptr = buf->alloc_buf + buf->offset + found_offset;
/* Move the data */
move_data_len = buf->data_len - buf->offset - found_offset - srch_size;
memmove(ptr + rplc_size,
ptr + srch_size,
move_data_len);
/* Copy in the replacement data */
if (rplc != NULL && rplc_size > 0) {
memcpy(ptr, rplc, rplc_size);
}
if (rplc_size > srch_size) {
buf->data_len += rplc_size - srch_size;
} else {
buf->data_len -= srch_size - rplc_size;
}
processed_len = found_offset + rplc_size;
}
return ARES_SUCCESS;
}
ares_status_t ares_buf_peek_byte(const ares_buf_t *buf, unsigned char *b)
{
size_t remaining_len = 0;
const unsigned char *ptr = ares_buf_fetch(buf, &remaining_len);
if (buf == NULL || b == NULL) {
return ARES_EFORMERR;
}
if (remaining_len == 0) {
return ARES_EBADRESP;
}
*b = ptr[0];
return ARES_SUCCESS;
}
size_t ares_buf_get_position(const ares_buf_t *buf)
{
if (buf == NULL) {
return 0;
}
return buf->offset;
}
ares_status_t ares_buf_set_position(ares_buf_t *buf, size_t idx)
{
if (buf == NULL) {
return ARES_EFORMERR;
}
if (idx > buf->data_len) {
return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
}
buf->offset = idx;
return ARES_SUCCESS;
}
static ares_status_t
ares_buf_parse_dns_binstr_int(ares_buf_t *buf, size_t remaining_len,
unsigned char **bin, size_t *bin_len,
ares_bool_t validate_printable)
{
unsigned char len;
ares_status_t status = ARES_EBADRESP;
ares_buf_t *binbuf = NULL;
if (buf == NULL) {
return ARES_EFORMERR;
}
if (remaining_len == 0) {
return ARES_EBADRESP;
}
binbuf = ares_buf_create();
if (binbuf == NULL) {
return ARES_ENOMEM;
}
status = ares_buf_fetch_bytes(buf, &len, 1);
if (status != ARES_SUCCESS) {
goto done; /* LCOV_EXCL_LINE: DefensiveCoding */
}
remaining_len--;
if (len > remaining_len) {
status = ARES_EBADRESP;
goto done;
}
if (len) {
/* When used by the _str() parser, it really needs to be validated to
* be a valid printable ascii string. Do that here */
if (validate_printable && ares_buf_len(buf) >= len) {
size_t mylen;
const char *data = (const char *)ares_buf_peek(buf, &mylen);
if (!ares_str_isprint(data, len)) {
status = ARES_EBADSTR;
goto done;
}
}
if (bin != NULL) {
status = ares_buf_fetch_bytes_into_buf(buf, binbuf, len);
} else {
status = ares_buf_consume(buf, len);
}
}
done:
if (status != ARES_SUCCESS) {
ares_buf_destroy(binbuf);
} else {
if (bin != NULL) {
size_t mylen = 0;
/* NOTE: we use ares_buf_finish_str() here as we guarantee NULL
* Termination even though we are technically returning binary data.
*/
*bin = (unsigned char *)ares_buf_finish_str(binbuf, &mylen);
*bin_len = mylen;
}
}
return status;
}
ares_status_t ares_buf_parse_dns_binstr(ares_buf_t *buf, size_t remaining_len,
unsigned char **bin, size_t *bin_len)
{
return ares_buf_parse_dns_binstr_int(buf, remaining_len, bin, bin_len,
ARES_FALSE);
}
ares_status_t ares_buf_parse_dns_str(ares_buf_t *buf, size_t remaining_len,
char **str)
{
size_t len;
return ares_buf_parse_dns_binstr_int(buf, remaining_len,
(unsigned char **)str, &len, ARES_TRUE);
}
ares_status_t ares_buf_append_num_dec(ares_buf_t *buf, size_t num, size_t len)
{
size_t i;
size_t mod;
if (len == 0) {
len = ares_count_digits(num);
}
mod = ares_pow(10, len);
for (i = len; i > 0; i--) {
size_t digit = (num % mod);
ares_status_t status;
mod /= 10;
/* Silence coverity. Shouldn't be possible since we calculate it above */
if (mod == 0) {
return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
}
digit /= mod;
status = ares_buf_append_byte(buf, '0' + (unsigned char)(digit & 0xFF));
if (status != ARES_SUCCESS) {
return status; /* LCOV_EXCL_LINE: OutOfMemory */
}
}
return ARES_SUCCESS;
}
ares_status_t ares_buf_append_num_hex(ares_buf_t *buf, size_t num, size_t len)
{
size_t i;
static const unsigned char hexbytes[] = "0123456789ABCDEF";
if (len == 0) {
len = ares_count_hexdigits(num);
}
for (i = len; i > 0; i--) {
ares_status_t status;
status = ares_buf_append_byte(buf, hexbytes[(num >> ((i - 1) * 4)) & 0xF]);
if (status != ARES_SUCCESS) {
return status; /* LCOV_EXCL_LINE: OutOfMemory */
}
}
return ARES_SUCCESS;
}
ares_status_t ares_buf_append_str(ares_buf_t *buf, const char *str)
{
return ares_buf_append(buf, (const unsigned char *)str, ares_strlen(str));
}
static ares_status_t ares_buf_hexdump_line(ares_buf_t *buf, size_t idx,
const unsigned char *data,
size_t len)
{
size_t i;
ares_status_t status;
/* Address */
status = ares_buf_append_num_hex(buf, idx, 6);
if (status != ARES_SUCCESS) {
return status; /* LCOV_EXCL_LINE: OutOfMemory */
}
/* | */
status = ares_buf_append_str(buf, " | ");
if (status != ARES_SUCCESS) {
return status; /* LCOV_EXCL_LINE: OutOfMemory */
}
for (i = 0; i < 16; i++) {
if (i >= len) {
status = ares_buf_append_str(buf, " ");
} else {
status = ares_buf_append_num_hex(buf, data[i], 2);
}
if (status != ARES_SUCCESS) {
return status; /* LCOV_EXCL_LINE: OutOfMemory */
}
status = ares_buf_append_byte(buf, ' ');
if (status != ARES_SUCCESS) {
return status; /* LCOV_EXCL_LINE: OutOfMemory */
}
}
/* | */
status = ares_buf_append_str(buf, " | ");
if (status != ARES_SUCCESS) {
return status; /* LCOV_EXCL_LINE: OutOfMemory */
}
for (i = 0; i < 16; i++) {
if (i >= len) {
break;
}
status = ares_buf_append_byte(buf, ares_isprint(data[i]) ? data[i] : '.');
if (status != ARES_SUCCESS) {
return status; /* LCOV_EXCL_LINE: OutOfMemory */
}
}
return ares_buf_append_byte(buf, '\n');
}
ares_status_t ares_buf_hexdump(ares_buf_t *buf, const unsigned char *data,
size_t len)
{
size_t i;
/* Each line is 16 bytes */
for (i = 0; i < len; i += 16) {
ares_status_t status;
status = ares_buf_hexdump_line(buf, i, data + i, len - i);
if (status != ARES_SUCCESS) {
return status; /* LCOV_EXCL_LINE: OutOfMemory */
}
}
return ARES_SUCCESS;
}
ares_status_t ares_buf_load_file(const char *filename, ares_buf_t *buf)
{
FILE *fp = NULL;
unsigned char *ptr = NULL;
size_t len = 0;
size_t ptr_len = 0;
long ftell_len = 0;
ares_status_t status;
if (filename == NULL || buf == NULL) {
return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
}
fp = fopen(filename, "rb");
if (fp == NULL) {
int error = errno;
switch (error) {
case ENOENT:
case ESRCH:
status = ARES_ENOTFOUND;
goto done;
default:
DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n", error,
strerror(error)));
DEBUGF(fprintf(stderr, "Error opening file: %s\n", filename));
status = ARES_EFILE;
goto done;
}
}
/* Get length portably, fstat() is POSIX, not C */
if (fseek(fp, 0, SEEK_END) != 0) {
status = ARES_EFILE; /* LCOV_EXCL_LINE: DefensiveCoding */
goto done; /* LCOV_EXCL_LINE: DefensiveCoding */
}
ftell_len = ftell(fp);
if (ftell_len < 0) {
status = ARES_EFILE; /* LCOV_EXCL_LINE: DefensiveCoding */
goto done; /* LCOV_EXCL_LINE: DefensiveCoding */
}
len = (size_t)ftell_len;
if (fseek(fp, 0, SEEK_SET) != 0) {
status = ARES_EFILE; /* LCOV_EXCL_LINE: DefensiveCoding */
goto done; /* LCOV_EXCL_LINE: DefensiveCoding */
}
if (len == 0) {
status = ARES_SUCCESS; /* LCOV_EXCL_LINE: DefensiveCoding */
goto done; /* LCOV_EXCL_LINE: DefensiveCoding */
}
/* Read entire data into buffer */
ptr_len = len;
ptr = ares_buf_append_start(buf, &ptr_len);
if (ptr == NULL) {
status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
goto done; /* LCOV_EXCL_LINE: OutOfMemory */
}
ptr_len = fread(ptr, 1, len, fp);
if (ptr_len != len) {
status = ARES_EFILE; /* LCOV_EXCL_LINE: DefensiveCoding */
goto done; /* LCOV_EXCL_LINE: DefensiveCoding */
}
ares_buf_append_finish(buf, len);
status = ARES_SUCCESS;
done:
if (fp != NULL) {
fclose(fp);
}
return status;
}