ares_dns_multistring.c (7825B)
1 /* MIT License 2 * 3 * Copyright (c) 2024 Brad House 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining a copy 6 * of this software and associated documentation files (the "Software"), to deal 7 * in the Software without restriction, including without limitation the rights 8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 * copies of the Software, and to permit persons to whom the Software is 10 * furnished to do so, subject to the following conditions: 11 * 12 * The above copyright notice and this permission notice (including the next 13 * paragraph) shall be included in all copies or substantial portions of the 14 * Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 * SOFTWARE. 23 * 24 * SPDX-License-Identifier: MIT 25 */ 26 #include "ares_private.h" 27 #include "ares_dns_private.h" 28 29 typedef struct { 30 unsigned char *data; 31 size_t len; 32 } multistring_data_t; 33 34 struct ares_dns_multistring { 35 /*! whether or not cached concatenated string is valid */ 36 ares_bool_t cache_invalidated; 37 /*! combined/concatenated string cache */ 38 unsigned char *cache_str; 39 /*! length of combined/concatenated string */ 40 size_t cache_str_len; 41 /*! Data making up strings */ 42 ares_array_t *strs; /*!< multistring_data_t type */ 43 }; 44 45 static void ares_dns_multistring_free_cb(void *arg) 46 { 47 multistring_data_t *data = arg; 48 if (data == NULL) { 49 return; 50 } 51 ares_free(data->data); 52 } 53 54 ares_dns_multistring_t *ares_dns_multistring_create(void) 55 { 56 ares_dns_multistring_t *strs = ares_malloc_zero(sizeof(*strs)); 57 if (strs == NULL) { 58 return NULL; 59 } 60 61 strs->strs = 62 ares_array_create(sizeof(multistring_data_t), ares_dns_multistring_free_cb); 63 if (strs->strs == NULL) { 64 ares_free(strs); 65 return NULL; 66 } 67 68 return strs; 69 } 70 71 void ares_dns_multistring_clear(ares_dns_multistring_t *strs) 72 { 73 if (strs == NULL) { 74 return; 75 } 76 77 while (ares_array_len(strs->strs)) { 78 ares_array_remove_last(strs->strs); 79 } 80 } 81 82 void ares_dns_multistring_destroy(ares_dns_multistring_t *strs) 83 { 84 if (strs == NULL) { 85 return; 86 } 87 ares_dns_multistring_clear(strs); 88 ares_array_destroy(strs->strs); 89 ares_free(strs->cache_str); 90 ares_free(strs); 91 } 92 93 ares_status_t ares_dns_multistring_swap_own(ares_dns_multistring_t *strs, 94 size_t idx, unsigned char *str, 95 size_t len) 96 { 97 multistring_data_t *data; 98 99 if (strs == NULL || str == NULL || len == 0) { 100 return ARES_EFORMERR; 101 } 102 103 strs->cache_invalidated = ARES_TRUE; 104 105 data = ares_array_at(strs->strs, idx); 106 if (data == NULL) { 107 return ARES_EFORMERR; 108 } 109 110 ares_free(data->data); 111 data->data = str; 112 data->len = len; 113 return ARES_SUCCESS; 114 } 115 116 ares_status_t ares_dns_multistring_del(ares_dns_multistring_t *strs, size_t idx) 117 { 118 if (strs == NULL) { 119 return ARES_EFORMERR; 120 } 121 122 strs->cache_invalidated = ARES_TRUE; 123 124 return ares_array_remove_at(strs->strs, idx); 125 } 126 127 ares_status_t ares_dns_multistring_add_own(ares_dns_multistring_t *strs, 128 unsigned char *str, size_t len) 129 { 130 multistring_data_t *data; 131 ares_status_t status; 132 133 if (strs == NULL) { 134 return ARES_EFORMERR; 135 } 136 137 strs->cache_invalidated = ARES_TRUE; 138 139 /* NOTE: its ok to have an empty string added */ 140 if (str == NULL && len != 0) { 141 return ARES_EFORMERR; 142 } 143 144 status = ares_array_insert_last((void **)&data, strs->strs); 145 if (status != ARES_SUCCESS) { 146 return status; 147 } 148 149 /* Issue #921, ares_dns_multistring_get() doesn't have a way to indicate 150 * success or fail on a zero-length string which is actually valid. So we 151 * are going to allocate a 1-byte buffer to use as a placeholder in this 152 * case */ 153 if (str == NULL) { 154 str = ares_malloc_zero(1); 155 if (str == NULL) { 156 ares_array_remove_last(strs->strs); 157 return ARES_ENOMEM; 158 } 159 } 160 161 data->data = str; 162 data->len = len; 163 164 return ARES_SUCCESS; 165 } 166 167 size_t ares_dns_multistring_cnt(const ares_dns_multistring_t *strs) 168 { 169 if (strs == NULL) { 170 return 0; 171 } 172 return ares_array_len(strs->strs); 173 } 174 175 const unsigned char * 176 ares_dns_multistring_get(const ares_dns_multistring_t *strs, size_t idx, 177 size_t *len) 178 { 179 const multistring_data_t *data; 180 181 if (strs == NULL || len == NULL) { 182 return NULL; 183 } 184 185 data = ares_array_at_const(strs->strs, idx); 186 if (data == NULL) { 187 return NULL; 188 } 189 190 *len = data->len; 191 return data->data; 192 } 193 194 const unsigned char *ares_dns_multistring_combined(ares_dns_multistring_t *strs, 195 size_t *len) 196 { 197 ares_buf_t *buf = NULL; 198 size_t i; 199 200 if (strs == NULL || len == NULL) { 201 return NULL; 202 } 203 204 *len = 0; 205 206 /* Return cache if possible */ 207 if (!strs->cache_invalidated) { 208 *len = strs->cache_str_len; 209 return strs->cache_str; 210 } 211 212 /* Clear cache */ 213 ares_free(strs->cache_str); 214 strs->cache_str = NULL; 215 strs->cache_str_len = 0; 216 217 buf = ares_buf_create(); 218 219 for (i = 0; i < ares_array_len(strs->strs); i++) { 220 const multistring_data_t *data = ares_array_at_const(strs->strs, i); 221 if (data == NULL || 222 ares_buf_append(buf, data->data, data->len) != ARES_SUCCESS) { 223 ares_buf_destroy(buf); 224 return NULL; 225 } 226 } 227 228 strs->cache_str = 229 (unsigned char *)ares_buf_finish_str(buf, &strs->cache_str_len); 230 if (strs->cache_str != NULL) { 231 strs->cache_invalidated = ARES_FALSE; 232 } 233 *len = strs->cache_str_len; 234 return strs->cache_str; 235 } 236 237 ares_status_t ares_dns_multistring_parse_buf(ares_buf_t *buf, 238 size_t remaining_len, 239 ares_dns_multistring_t **strs, 240 ares_bool_t validate_printable) 241 { 242 unsigned char len; 243 ares_status_t status = ARES_EBADRESP; 244 size_t orig_len = ares_buf_len(buf); 245 246 if (buf == NULL) { 247 return ARES_EFORMERR; 248 } 249 250 if (remaining_len == 0) { 251 return ARES_EBADRESP; 252 } 253 254 if (strs != NULL) { 255 *strs = ares_dns_multistring_create(); 256 if (*strs == NULL) { 257 return ARES_ENOMEM; 258 } 259 } 260 261 while (orig_len - ares_buf_len(buf) < remaining_len) { 262 status = ares_buf_fetch_bytes(buf, &len, 1); 263 if (status != ARES_SUCCESS) { 264 break; /* LCOV_EXCL_LINE: DefensiveCoding */ 265 } 266 267 268 /* When used by the _str() parser, it really needs to be validated to 269 * be a valid printable ascii string. Do that here */ 270 if (len && validate_printable && ares_buf_len(buf) >= len) { 271 size_t mylen; 272 const char *data = (const char *)ares_buf_peek(buf, &mylen); 273 if (!ares_str_isprint(data, len)) { 274 status = ARES_EBADSTR; 275 break; 276 } 277 } 278 279 if (strs != NULL) { 280 unsigned char *data = NULL; 281 if (len) { 282 status = ares_buf_fetch_bytes_dup(buf, len, ARES_TRUE, &data); 283 if (status != ARES_SUCCESS) { 284 break; 285 } 286 } 287 status = ares_dns_multistring_add_own(*strs, data, len); 288 if (status != ARES_SUCCESS) { 289 ares_free(data); 290 break; 291 } 292 } else { 293 status = ares_buf_consume(buf, len); 294 if (status != ARES_SUCCESS) { 295 break; 296 } 297 } 298 299 } 300 301 if (status != ARES_SUCCESS && strs != NULL) { 302 ares_dns_multistring_destroy(*strs); 303 *strs = NULL; 304 } 305 306 return status; 307 }