You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1116 lines
25 KiB
1116 lines
25 KiB
/* 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_setup.h" |
|
#include "ares.h" |
|
#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_bool_t ares__isprint(int ch) |
|
{ |
|
if (ch >= 0x20 && ch <= 0x7E) { |
|
return ARES_TRUE; |
|
} |
|
return ARES_FALSE; |
|
} |
|
|
|
/* Character set allowed by hostnames. This is to include the normal |
|
* domain name character set plus: |
|
* - underscores which are used in SRV records. |
|
* - Forward slashes such as are used for classless in-addr.arpa |
|
* delegation (CNAMEs) |
|
* - Asterisks may be used for wildcard domains in CNAMEs as seen in the |
|
* real world. |
|
* While RFC 2181 section 11 does state not to do validation, |
|
* that applies to servers, not clients. Vulnerabilities have been |
|
* reported when this validation is not performed. Security is more |
|
* important than edge-case compatibility (which is probably invalid |
|
* anyhow). */ |
|
ares_bool_t ares__is_hostnamech(int ch) |
|
{ |
|
/* [A-Za-z0-9-*._/] |
|
* Don't use isalnum() as it is locale-specific |
|
*/ |
|
if (ch >= 'A' && ch <= 'Z') { |
|
return ARES_TRUE; |
|
} |
|
if (ch >= 'a' && ch <= 'z') { |
|
return ARES_TRUE; |
|
} |
|
if (ch >= '0' && ch <= '9') { |
|
return ARES_TRUE; |
|
} |
|
if (ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '*') { |
|
return ARES_TRUE; |
|
} |
|
|
|
return ARES_FALSE; |
|
} |
|
|
|
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; |
|
} |
|
|
|
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; |
|
} |
|
|
|
/* 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; |
|
} |
|
|
|
return; |
|
} |
|
|
|
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; |
|
} |
|
|
|
/* 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; |
|
} |
|
|
|
if (len >= buf->alloc_buf_len - buf->offset) { |
|
return ARES_EFORMERR; |
|
} |
|
|
|
buf->data_len = len; |
|
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; |
|
} |
|
|
|
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 byte) |
|
{ |
|
return ares__buf_append(buf, &byte, 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; |
|
} |
|
|
|
status = ares__buf_append_byte(buf, (unsigned char)(u16 & 0xff)); |
|
if (status != ARES_SUCCESS) { |
|
return status; |
|
} |
|
|
|
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; |
|
} |
|
|
|
status = ares__buf_append_byte(buf, ((unsigned char)(u32 >> 16) & 0xff)); |
|
if (status != ARES_SUCCESS) { |
|
return status; |
|
} |
|
|
|
status = ares__buf_append_byte(buf, ((unsigned char)(u32 >> 8) & 0xff)); |
|
if (status != ARES_SUCCESS) { |
|
return status; |
|
} |
|
|
|
status = ares__buf_append_byte(buf, ((unsigned char)u32 & 0xff)); |
|
if (status != ARES_SUCCESS) { |
|
return status; |
|
} |
|
|
|
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; |
|
} |
|
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_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; |
|
} |
|
|
|
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; |
|
} |
|
|
|
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; |
|
const unsigned char *ptr = ares__buf_fetch(buf, &remaining_len); |
|
|
|
if (buf == NULL || str == NULL || len == 0 || remaining_len < len) { |
|
return ARES_EBADRESP; |
|
} |
|
|
|
*str = ares_malloc(len + 1); |
|
if (*str == NULL) { |
|
return ARES_ENOMEM; |
|
} |
|
|
|
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); |
|
} |
|
|
|
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++) { |
|
switch (ptr[i]) { |
|
case '\r': |
|
case '\t': |
|
case ' ': |
|
case '\v': |
|
case '\f': |
|
break; |
|
case '\n': |
|
if (!include_linefeed) { |
|
goto done; |
|
} |
|
break; |
|
default: |
|
goto done; |
|
} |
|
} |
|
|
|
done: |
|
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++) { |
|
switch (ptr[i]) { |
|
case '\r': |
|
case '\t': |
|
case ' ': |
|
case '\v': |
|
case '\f': |
|
case '\n': |
|
goto done; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
done: |
|
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 i; |
|
ares_bool_t found = ARES_FALSE; |
|
|
|
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]) { |
|
found = ARES_TRUE; |
|
goto done; |
|
} |
|
} |
|
} |
|
|
|
done: |
|
if (require_charset && !found) { |
|
return 0; |
|
} |
|
|
|
if (i > 0) { |
|
ares__buf_consume(buf, i); |
|
} |
|
return i; |
|
} |
|
|
|
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_destroy(arg); |
|
} |
|
|
|
ares_status_t ares__buf_split(ares__buf_t *buf, const unsigned char *delims, |
|
size_t delims_len, ares__buf_split_t flags, |
|
ares__llist_t **list) |
|
{ |
|
ares_status_t status = ARES_SUCCESS; |
|
ares_bool_t first = ARES_TRUE; |
|
|
|
if (buf == NULL || delims == NULL || delims_len == 0 || list == NULL) { |
|
return ARES_EFORMERR; |
|
} |
|
|
|
*list = ares__llist_create(ares__buf_destroy_cb); |
|
if (*list == NULL) { |
|
status = ARES_ENOMEM; |
|
goto done; |
|
} |
|
|
|
while (ares__buf_len(buf)) { |
|
size_t len; |
|
|
|
ares__buf_tag(buf); |
|
|
|
len = ares__buf_consume_until_charset(buf, delims, delims_len, ARES_FALSE); |
|
|
|
/* Don't treat a delimiter as part of the length */ |
|
if (!first && len && flags & ARES_BUF_SPLIT_DONT_CONSUME_DELIMS) { |
|
len--; |
|
} |
|
|
|
if (len != 0 || flags & ARES_BUF_SPLIT_ALLOW_BLANK) { |
|
const unsigned char *ptr = ares__buf_tag_fetch(buf, &len); |
|
ares__buf_t *data; |
|
|
|
/* 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; |
|
} |
|
|
|
if (ares__llist_insert_last(*list, data) == NULL) { |
|
ares__buf_destroy(data); |
|
status = ARES_ENOMEM; |
|
goto done; |
|
} |
|
} |
|
|
|
if (!(flags & ARES_BUF_SPLIT_DONT_CONSUME_DELIMS) && |
|
ares__buf_len(buf) != 0) { |
|
/* Consume delimiter */ |
|
ares__buf_consume(buf, 1); |
|
} |
|
|
|
first = ARES_FALSE; |
|
} |
|
|
|
done: |
|
if (status != ARES_SUCCESS) { |
|
ares__llist_destroy(*list); |
|
*list = NULL; |
|
} |
|
|
|
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); |
|
} |
|
|
|
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; |
|
} |
|
|
|
buf->offset = idx; |
|
return ARES_SUCCESS; |
|
} |
|
|
|
ares_status_t ares__buf_parse_dns_binstr(ares__buf_t *buf, size_t remaining_len, |
|
unsigned char **bin, size_t *bin_len, |
|
ares_bool_t allow_multiple) |
|
{ |
|
unsigned char len; |
|
ares_status_t status; |
|
ares__buf_t *binbuf = NULL; |
|
size_t orig_len = ares__buf_len(buf); |
|
|
|
if (buf == NULL) { |
|
return ARES_EFORMERR; |
|
} |
|
|
|
if (remaining_len == 0) { |
|
return ARES_EBADRESP; |
|
} |
|
|
|
binbuf = ares__buf_create(); |
|
if (binbuf == NULL) { |
|
return ARES_ENOMEM; |
|
} |
|
|
|
while (orig_len - ares__buf_len(buf) < remaining_len) { |
|
status = ares__buf_fetch_bytes(buf, &len, 1); |
|
if (status != ARES_SUCCESS) { |
|
break; |
|
} |
|
|
|
if (len) { |
|
/* XXX: Maybe we should scan to make sure it is printable? */ |
|
if (bin != NULL) { |
|
status = ares__buf_fetch_bytes_into_buf(buf, binbuf, len); |
|
} else { |
|
status = ares__buf_consume(buf, len); |
|
} |
|
if (status != ARES_SUCCESS) { |
|
break; |
|
} |
|
} |
|
|
|
if (!allow_multiple) { |
|
break; |
|
} |
|
} |
|
|
|
|
|
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_str(ares__buf_t *buf, size_t remaining_len, |
|
char **str, ares_bool_t allow_multiple) |
|
{ |
|
size_t len; |
|
return ares__buf_parse_dns_binstr(buf, remaining_len, (unsigned char **)str, |
|
&len, allow_multiple); |
|
} |
|
|
|
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; |
|
} |
|
|
|
digit /= mod; |
|
status = ares__buf_append_byte(buf, '0' + (unsigned char)(digit & 0xFF)); |
|
if (status != ARES_SUCCESS) { |
|
return status; |
|
} |
|
} |
|
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; |
|
} |
|
} |
|
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; |
|
} |
|
|
|
/* | */ |
|
status = ares__buf_append_str(buf, " | "); |
|
if (status != ARES_SUCCESS) { |
|
return status; |
|
} |
|
|
|
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; |
|
} |
|
|
|
status = ares__buf_append_byte(buf, ' '); |
|
if (status != ARES_SUCCESS) { |
|
return status; |
|
} |
|
} |
|
|
|
/* | */ |
|
status = ares__buf_append_str(buf, " | "); |
|
if (status != ARES_SUCCESS) { |
|
return status; |
|
} |
|
|
|
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; |
|
} |
|
} |
|
|
|
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; |
|
} |
|
} |
|
|
|
return ARES_SUCCESS; |
|
}
|
|
|