1 #include "first.h" 2 3 #include "array.h" 4 #include "buffer.h" 5 6 #include <string.h> 7 #include <stdlib.h> 8 #include <limits.h> 9 10 11 __attribute_cold__ 12 static data_unset *array_data_string_copy(const data_unset *s) { 13 data_string *src = (data_string *)s; 14 data_string *ds = array_data_string_init(); 15 if (!buffer_is_empty(&src->key)) buffer_copy_buffer(&ds->key, &src->key); 16 buffer_copy_buffer(&ds->value, &src->value); 17 return (data_unset *)ds; 18 } 19 20 __attribute_cold__ 21 static void array_data_string_insert_dup(data_unset *dst, data_unset *src) { 22 data_string *ds_dst = (data_string *)dst; 23 data_string *ds_src = (data_string *)src; 24 if (!buffer_is_empty(&ds_dst->value)) 25 buffer_append_str2(&ds_dst->value, CONST_STR_LEN(", "), 26 CONST_BUF_LEN(&ds_src->value)); 27 else 28 buffer_copy_buffer(&ds_dst->value, &ds_src->value); 29 src->fn->free(src); 30 } 31 32 static void array_data_string_free(data_unset *du) { 33 data_string *ds = (data_string *)du; 34 free(ds->key.ptr); 35 free(ds->value.ptr); 36 free(ds); 37 } 38 39 __attribute_noinline__ 40 data_string *array_data_string_init(void) { 41 static const struct data_methods fn = { 42 array_data_string_copy, 43 array_data_string_free, 44 array_data_string_insert_dup, 45 }; 46 data_string *ds = calloc(1, sizeof(*ds)); 47 force_assert(NULL != ds); 48 ds->type = TYPE_STRING; 49 ds->fn = &fn; 50 return ds; 51 } 52 53 54 __attribute_cold__ 55 static data_unset *array_data_integer_copy(const data_unset *s) { 56 data_integer *src = (data_integer *)s; 57 data_integer *di = array_data_integer_init(); 58 if (!buffer_is_empty(&src->key)) buffer_copy_buffer(&di->key, &src->key); 59 di->value = src->value; 60 return (data_unset *)di; 61 } 62 63 __attribute_cold__ 64 static void array_data_integer_insert_dup(data_unset *dst, data_unset *src) { 65 UNUSED(dst); 66 src->fn->free(src); 67 } 68 69 static void array_data_integer_free(data_unset *du) { 70 data_integer *di = (data_integer *)du; 71 free(di->key.ptr); 72 free(di); 73 } 74 75 __attribute_noinline__ 76 data_integer *array_data_integer_init(void) { 77 static const struct data_methods fn = { 78 array_data_integer_copy, 79 array_data_integer_free, 80 array_data_integer_insert_dup, 81 }; 82 data_integer *di = calloc(1, sizeof(*di)); 83 force_assert(NULL != di); 84 di->type = TYPE_INTEGER; 85 di->fn = &fn; 86 return di; 87 } 88 89 90 __attribute_cold__ 91 static data_unset *array_data_array_copy(const data_unset *s) { 92 data_array *src = (data_array *)s; 93 data_array *da = array_data_array_init(); 94 if (!buffer_is_empty(&src->key)) buffer_copy_buffer(&da->key, &src->key); 95 array_copy_array(&da->value, &src->value); 96 return (data_unset *)da; 97 } 98 99 __attribute_cold__ 100 static void array_data_array_insert_dup(data_unset *dst, data_unset *src) { 101 UNUSED(dst); 102 src->fn->free(src); 103 } 104 105 static void array_data_array_free(data_unset *du) { 106 data_array *da = (data_array *)du; 107 free(da->key.ptr); 108 array_free_data(&da->value); 109 free(da); 110 } 111 112 __attribute_noinline__ 113 data_array *array_data_array_init(void) { 114 static const struct data_methods fn = { 115 array_data_array_copy, 116 array_data_array_free, 117 array_data_array_insert_dup, 118 }; 119 data_array *da = calloc(1, sizeof(*da)); 120 force_assert(NULL != da); 121 da->type = TYPE_ARRAY; 122 da->fn = &fn; 123 return da; 124 } 125 126 127 __attribute_cold__ 128 static void array_extend(array * const a, uint32_t n) { 129 /* This data structure should not be used for nearly so many entries */ 130 force_assert(a->size <= INT32_MAX-n); 131 a->size += n; 132 a->data = realloc(a->data, sizeof(*a->data) * a->size); 133 a->sorted = realloc(a->sorted, sizeof(*a->sorted) * a->size); 134 force_assert(a->data); 135 force_assert(a->sorted); 136 memset(a->data+a->used, 0, (a->size-a->used)*sizeof(*a->data)); 137 } 138 139 array *array_init(uint32_t n) { 140 array *a; 141 142 a = calloc(1, sizeof(*a)); 143 force_assert(a); 144 if (n) array_extend(a, n); 145 146 return a; 147 } 148 149 void array_free_data(array * const a) { 150 if (a->sorted) free(a->sorted); 151 data_unset ** const data = a->data; 152 const uint32_t sz = a->size; 153 for (uint32_t i = 0; i < sz; ++i) { 154 if (data[i]) data[i]->fn->free(data[i]); 155 } 156 free(data); 157 a->data = NULL; 158 a->sorted = NULL; 159 a->used = 0; 160 a->size = 0; 161 } 162 163 void array_copy_array(array * const dst, const array * const src) { 164 array_free_data(dst); 165 if (0 == src->size) return; 166 167 array_extend(dst, src->size); 168 for (uint32_t i = 0; i < src->used; ++i) { 169 array_insert_unique(dst, src->data[i]->fn->copy(src->data[i])); 170 } 171 } 172 173 void array_free(array * const a) { 174 if (!a) return; 175 array_free_data(a); 176 free(a); 177 } 178 179 void array_reset_data_strings(array * const a) { 180 if (!a) return; 181 182 data_string ** const data = (data_string **)a->data; 183 const uint32_t used = a->used; 184 a->used = 0; 185 for (uint32_t i = 0; i < used; ++i) { 186 data_string * const ds = data[i]; 187 /*force_assert(ds->type == TYPE_STRING);*/ 188 buffer_reset(&ds->key); 189 buffer_reset(&ds->value); 190 } 191 } 192 193 #if 0 /*(unused; see array_extract_element_klen())*/ 194 data_unset *array_pop(array * const a) { 195 data_unset *du; 196 197 force_assert(a->used != 0); 198 199 a->used --; 200 du = a->data[a->used]; 201 force_assert(a->sorted[a->used] == du); /* only works on "simple" lists */ 202 a->data[a->used] = NULL; 203 204 return du; 205 } 206 #endif 207 208 __attribute_pure__ 209 static int array_caseless_compare(const char * const a, const char * const b, const uint32_t len) { 210 for (uint32_t i = 0; i < len; ++i) { 211 unsigned int ca = ((unsigned char *)a)[i]; 212 unsigned int cb = ((unsigned char *)b)[i]; 213 if (ca == cb) continue; 214 215 /* always lowercase for transitive results */ 216 if (light_isupper(ca)) ca |= 0x20; 217 if (light_isupper(cb)) cb |= 0x20; 218 219 if (ca == cb) continue; 220 return (int)(ca - cb); 221 } 222 return 0; 223 } 224 225 __attribute_pure__ 226 static int array_keycmp(const char * const a, const uint32_t alen, const char * const b, const uint32_t blen) { 227 return alen < blen ? -1 : alen > blen ? 1 : array_caseless_compare(a, b, blen); 228 } 229 230 __attribute_cold__ 231 __attribute_pure__ 232 static int array_keycmpb(const char * const k, const uint32_t klen, const buffer * const b) { 233 /* key is non-empty (0==b->used), though possibly blank (1==b->used) 234 * if inserted into key-value array */ 235 /*force_assert(b && b->used);*/ 236 return array_keycmp(k, klen, b->ptr, b->used-1); 237 /*return array_keycmp(k, klen, CONST_BUF_LEN(b));*/ 238 } 239 240 /* returns pos into a->sorted[] which contains copy of data (ptr) in a->data[] 241 * if pos >= 0, or returns -pos-1 if that is the position-1 in a->sorted[] 242 * where the key needs to be inserted (-1 to avoid -0) 243 */ 244 __attribute_hot__ 245 __attribute_pure__ 246 static int32_t array_get_index_ext(const array * const a, const int ext, const char * const k, const uint32_t klen) { 247 /* invariant: [lower-1] < probe < [upper] 248 * invariant: 0 <= lower <= upper <= a->used 249 */ 250 uint32_t lower = 0, upper = a->used; 251 while (lower != upper) { 252 const uint32_t probe = (lower + upper) / 2; 253 const int x = ((data_string *)a->sorted[probe])->ext; 254 /* (compare strings only if ext is 0 for both)*/ 255 const int e = (ext|x) 256 ? ext 257 : array_keycmpb(k, klen, &a->sorted[probe]->key); 258 if (e < x) /* e < [probe] */ 259 upper = probe; /* still: lower <= upper */ 260 else if (e > x) /* e > [probe] */ 261 lower = probe + 1; /* still: lower <= upper */ 262 else /*(e == x)*/ /* found */ 263 return (int32_t)probe; 264 } 265 /* not found: [lower-1] < key < [upper] = [lower] ==> insert at [lower] */ 266 return -(int)lower - 1; 267 } 268 269 data_unset *array_get_element_klen_ext(const array * const a, const int ext, const char *key, const uint32_t klen) { 270 const int32_t ipos = array_get_index_ext(a, ext, key, klen); 271 return ipos >= 0 ? a->sorted[ipos] : NULL; 272 } 273 274 /* returns pos into a->sorted[] which contains copy of data (ptr) in a->data[] 275 * if pos >= 0, or returns -pos-1 if that is the position-1 in a->sorted[] 276 * where the key needs to be inserted (-1 to avoid -0) 277 */ 278 __attribute_hot__ 279 __attribute_pure__ 280 static int32_t array_get_index(const array * const a, const char * const k, const uint32_t klen) { 281 /* invariant: [lower-1] < probe < [upper] 282 * invariant: 0 <= lower <= upper <= a->used 283 */ 284 uint32_t lower = 0, upper = a->used; 285 while (lower != upper) { 286 uint32_t probe = (lower + upper) / 2; 287 const buffer * const b = &a->sorted[probe]->key; 288 /* key is non-empty (0==b->used), though possibly blank (1==b->used), 289 * if inserted into key-value array */ 290 /*force_assert(b && b->used);*/ 291 int cmp = array_keycmp(k, klen, b->ptr, b->used-1); 292 /*int cmp = array_keycmp(k, klen, CONST_BUF_LEN(b));*/ 293 if (cmp < 0) /* key < [probe] */ 294 upper = probe; /* still: lower <= upper */ 295 else if (cmp > 0) /* key > [probe] */ 296 lower = probe + 1; /* still: lower <= upper */ 297 else /*(cmp == 0)*/ /* found */ 298 return (int32_t)probe; 299 } 300 /* not found: [lower-1] < key < [upper] = [lower] ==> insert at [lower] */ 301 return -(int)lower - 1; 302 } 303 304 __attribute_hot__ 305 const data_unset *array_get_element_klen(const array * const a, const char *key, const uint32_t klen) { 306 const int32_t ipos = array_get_index(a, key, klen); 307 return ipos >= 0 ? a->sorted[ipos] : NULL; 308 } 309 310 /* non-const (data_config *) for configparser.y (not array_get_element_klen())*/ 311 data_unset *array_get_data_unset(const array * const a, const char *key, const uint32_t klen) { 312 const int32_t ipos = array_get_index(a, key, klen); 313 return ipos >= 0 ? a->sorted[ipos] : NULL; 314 } 315 316 data_unset *array_extract_element_klen(array * const a, const char *key, const uint32_t klen) { 317 const int32_t ipos = array_get_index(a, key, klen); 318 if (ipos < 0) return NULL; 319 320 /* remove entry from a->sorted: move everything after pos one step left */ 321 data_unset * const entry = a->sorted[ipos]; 322 const uint32_t last_ndx = --a->used; 323 if (last_ndx != (uint32_t)ipos) { 324 data_unset ** const d = a->sorted + ipos; 325 memmove(d, d+1, (last_ndx - (uint32_t)ipos) * sizeof(*d)); 326 } 327 328 if (entry != a->data[last_ndx]) { 329 /* walk a->data[] to find data ptr */ 330 /* (not checking (ndx <= last_ndx) since entry must be in a->data[]) */ 331 uint32_t ndx = 0; 332 while (entry != a->data[ndx]) ++ndx; 333 a->data[ndx] = a->data[last_ndx]; /* swap with last element */ 334 } 335 a->data[last_ndx] = NULL; 336 return entry; 337 } 338 339 static data_unset *array_get_unused_element(array * const a, const data_type_t t) { 340 /* After initial startup and config, most array usage is of homogeneous types 341 * and arrays are cleared once per request, so check only the first unused 342 * element to see if it can be reused */ 343 #if 1 344 data_unset * const du = (a->used < a->size) ? a->data[a->used] : NULL; 345 if (NULL != du && du->type == t) { 346 a->data[a->used] = NULL;/* make empty slot at a->used for next insert */ 347 return du; 348 } 349 return NULL; 350 #else 351 data_unset ** const data = a->data; 352 for (uint32_t i = a->used, sz = a->size; i < sz; ++i) { 353 if (data[i] && data[i]->type == t) { 354 data_unset * const ds = data[i]; 355 356 /* make empty slot at a->used for next insert */ 357 data[i] = data[a->used]; 358 data[a->used] = NULL; 359 360 return ds; 361 } 362 } 363 364 return NULL; 365 #endif 366 } 367 368 __attribute_hot__ 369 static void array_insert_data_at_pos(array * const a, data_unset * const entry, const uint32_t pos) { 370 if (a->used < a->size) { 371 data_unset * const prev = a->data[a->used]; 372 if (__builtin_expect( (prev != NULL), 0)) 373 prev->fn->free(prev); /* free prior data, if any, from slot */ 374 } 375 else { 376 array_extend(a, 16); 377 } 378 379 const uint32_t ndx = a->used++; 380 a->data[ndx] = entry; 381 382 /* move everything one step to the right */ 383 if (pos != ndx) { 384 data_unset ** const d = a->sorted + pos; 385 memmove(d+1, d, (ndx - pos) * sizeof(*a->sorted)); 386 } 387 a->sorted[pos] = entry; 388 } 389 390 static data_integer * array_insert_integer_at_pos(array * const a, const uint32_t pos) { 391 #if 0 /*(not currently used by lighttpd in way that reuse would occur)*/ 392 data_integer *di = (data_integer *)array_get_unused_element(a,TYPE_INTEGER); 393 if (NULL == di) di = array_data_integer_init(); 394 #else 395 data_integer * const di = array_data_integer_init(); 396 #endif 397 array_insert_data_at_pos(a, (data_unset *)di, pos); 398 return di; 399 } 400 401 __attribute_hot__ 402 static data_string * array_insert_string_at_pos(array * const a, const uint32_t pos) { 403 data_string *ds = (data_string *)array_get_unused_element(a, TYPE_STRING); 404 if (NULL == ds) ds = array_data_string_init(); 405 array_insert_data_at_pos(a, (data_unset *)ds, pos); 406 return ds; 407 } 408 409 __attribute_hot__ 410 buffer * array_get_buf_ptr_ext(array * const a, const int ext, const char * const k, const uint32_t klen) { 411 int32_t ipos = array_get_index_ext(a, ext, k, klen); 412 if (ipos >= 0) return &((data_string *)a->sorted[ipos])->value; 413 414 data_string * const ds = array_insert_string_at_pos(a, (uint32_t)(-ipos-1)); 415 ds->ext = ext; 416 buffer_copy_string_len(&ds->key, k, klen); 417 buffer_clear(&ds->value); 418 return &ds->value; 419 } 420 421 int * array_get_int_ptr(array * const a, const char * const k, const uint32_t klen) { 422 int32_t ipos = array_get_index(a, k, klen); 423 if (ipos >= 0) return &((data_integer *)a->sorted[ipos])->value; 424 425 data_integer * const di =array_insert_integer_at_pos(a,(uint32_t)(-ipos-1)); 426 buffer_copy_string_len(&di->key, k, klen); 427 di->value = 0; 428 return &di->value; 429 } 430 431 buffer * array_get_buf_ptr(array * const a, const char * const k, const uint32_t klen) { 432 int32_t ipos = array_get_index(a, k, klen); 433 if (ipos >= 0) return &((data_string *)a->sorted[ipos])->value; 434 435 data_string * const ds = array_insert_string_at_pos(a, (uint32_t)(-ipos-1)); 436 buffer_copy_string_len(&ds->key, k, klen); 437 buffer_clear(&ds->value); 438 return &ds->value; 439 } 440 441 void array_insert_value(array * const a, const char * const v, const uint32_t vlen) { 442 data_string * const ds = array_insert_string_at_pos(a, a->used); 443 buffer_clear(&ds->key); 444 buffer_copy_string_len(&ds->value, v, vlen); 445 } 446 447 /* if entry already exists return pointer to existing entry, otherwise insert entry and return NULL */ 448 __attribute_cold__ 449 static data_unset **array_find_or_insert(array * const a, data_unset * const entry) { 450 force_assert(NULL != entry); 451 452 /* push value onto end of array if there is no key */ 453 if (buffer_is_empty(&entry->key)) { 454 array_insert_data_at_pos(a, entry, a->used); 455 return NULL; 456 } 457 458 /* try to find the entry */ 459 const int32_t ipos = array_get_index(a, CONST_BUF_LEN(&entry->key)); 460 if (ipos >= 0) return &a->sorted[ipos]; 461 462 array_insert_data_at_pos(a, entry, (uint32_t)(-ipos - 1)); 463 return NULL; 464 } 465 466 /* replace or insert data (free existing entry) */ 467 void array_replace(array * const a, data_unset * const entry) { 468 if (NULL == array_find_or_insert(a, entry)) return; 469 470 /* find the entry (array_find_or_insert() returned non-NULL) */ 471 const int32_t ipos = array_get_index(a, CONST_BUF_LEN(&entry->key)); 472 force_assert(ipos >= 0); 473 data_unset *old = a->sorted[ipos]; 474 force_assert(old != entry); 475 a->sorted[ipos] = entry; 476 477 uint32_t i = 0; 478 while (i < a->used && a->data[i] != old) ++i; 479 force_assert(i != a->used); 480 a->data[i] = entry; 481 482 old->fn->free(old); 483 } 484 485 void array_insert_unique(array * const a, data_unset * const entry) { 486 data_unset **old; 487 488 if (NULL != (old = array_find_or_insert(a, entry))) { 489 force_assert((*old)->type == entry->type); 490 entry->fn->insert_dup(*old, entry); 491 } 492 } 493 494 int array_is_vlist(const array * const a) { 495 for (uint32_t i = 0; i < a->used; ++i) { 496 data_unset *du = a->data[i]; 497 if (!buffer_is_empty(&du->key) || du->type != TYPE_STRING) return 0; 498 } 499 return 1; 500 } 501 502 int array_is_kvany(const array * const a) { 503 for (uint32_t i = 0; i < a->used; ++i) { 504 data_unset *du = a->data[i]; 505 if (buffer_is_empty(&du->key)) return 0; 506 } 507 return 1; 508 } 509 510 int array_is_kvarray(const array * const a) { 511 for (uint32_t i = 0; i < a->used; ++i) { 512 data_unset *du = a->data[i]; 513 if (buffer_is_empty(&du->key) || du->type != TYPE_ARRAY) return 0; 514 } 515 return 1; 516 } 517 518 int array_is_kvstring(const array * const a) { 519 for (uint32_t i = 0; i < a->used; ++i) { 520 data_unset *du = a->data[i]; 521 if (buffer_is_empty(&du->key) || du->type != TYPE_STRING) return 0; 522 } 523 return 1; 524 } 525 526 /* array_match_*() routines follow very similar pattern, but operate on slightly 527 * different data: array key/value, prefix/suffix match, case-insensitive or not 528 * While these could be combined into fewer routines with flags to modify the 529 * behavior, the interface distinctions are useful to add clarity to the code, 530 * and the specialized routines run slightly faster */ 531 532 data_unset * 533 array_match_key_prefix_klen (const array * const a, const char * const s, const uint32_t slen) 534 { 535 for (uint32_t i = 0; i < a->used; ++i) { 536 const buffer * const key = &a->data[i]->key; 537 const uint32_t klen = buffer_string_length(key); 538 if (klen <= slen && 0 == memcmp(s, key->ptr, klen)) 539 return a->data[i]; 540 } 541 return NULL; 542 } 543 544 data_unset * 545 array_match_key_prefix_nc_klen (const array * const a, const char * const s, const uint32_t slen) 546 { 547 for (uint32_t i = 0; i < a->used; ++i) { 548 const buffer * const key = &a->data[i]->key; 549 const uint32_t klen = buffer_string_length(key); 550 if (klen <= slen && buffer_eq_icase_ssn(s, key->ptr, klen)) 551 return a->data[i]; 552 } 553 return NULL; 554 } 555 556 data_unset * 557 array_match_key_prefix (const array * const a, const buffer * const b) 558 { 559 #ifdef __clang_analyzer__ 560 force_assert(b); 561 #endif 562 return array_match_key_prefix_klen(a, CONST_BUF_LEN(b)); 563 } 564 565 data_unset * 566 array_match_key_prefix_nc (const array * const a, const buffer * const b) 567 { 568 return array_match_key_prefix_nc_klen(a, CONST_BUF_LEN(b)); 569 } 570 571 const buffer * 572 array_match_value_prefix (const array * const a, const buffer * const b) 573 { 574 const uint32_t blen = buffer_string_length(b); 575 576 for (uint32_t i = 0; i < a->used; ++i) { 577 const buffer * const value = &((data_string *)a->data[i])->value; 578 const uint32_t vlen = buffer_string_length(value); 579 if (vlen <= blen && 0 == memcmp(b->ptr, value->ptr, vlen)) 580 return value; 581 } 582 return NULL; 583 } 584 585 const buffer * 586 array_match_value_prefix_nc (const array * const a, const buffer * const b) 587 { 588 const uint32_t blen = buffer_string_length(b); 589 590 for (uint32_t i = 0; i < a->used; ++i) { 591 const buffer * const value = &((data_string *)a->data[i])->value; 592 const uint32_t vlen = buffer_string_length(value); 593 if (vlen <= blen && buffer_eq_icase_ssn(b->ptr, value->ptr, vlen)) 594 return value; 595 } 596 return NULL; 597 } 598 599 data_unset * 600 array_match_key_suffix (const array * const a, const buffer * const b) 601 { 602 const uint32_t blen = buffer_string_length(b); 603 const char * const end = b->ptr + blen; 604 605 for (uint32_t i = 0; i < a->used; ++i) { 606 const buffer * const key = &a->data[i]->key; 607 const uint32_t klen = buffer_string_length(key); 608 if (klen <= blen && 0 == memcmp(end - klen, key->ptr, klen)) 609 return a->data[i]; 610 } 611 return NULL; 612 } 613 614 data_unset * 615 array_match_key_suffix_nc (const array * const a, const buffer * const b) 616 { 617 const uint32_t blen = buffer_string_length(b); 618 const char * const end = b->ptr + blen; 619 620 for (uint32_t i = 0; i < a->used; ++i) { 621 const buffer * const key = &a->data[i]->key; 622 const uint32_t klen = buffer_string_length(key); 623 if (klen <= blen && buffer_eq_icase_ssn(end - klen, key->ptr, klen)) 624 return a->data[i]; 625 } 626 return NULL; 627 } 628 629 const buffer * 630 array_match_value_suffix (const array * const a, const buffer * const b) 631 { 632 const uint32_t blen = buffer_string_length(b); 633 const char * const end = b->ptr + blen; 634 635 for (uint32_t i = 0; i < a->used; ++i) { 636 const buffer * const value = &((data_string *)a->data[i])->value; 637 const uint32_t vlen = buffer_string_length(value); 638 if (vlen <= blen && 0 == memcmp(end - vlen, value->ptr, vlen)) 639 return value; 640 } 641 return NULL; 642 } 643 644 const buffer * 645 array_match_value_suffix_nc (const array * const a, const buffer * const b) 646 { 647 const uint32_t blen = buffer_string_length(b); 648 const char * const end = b->ptr + blen; 649 650 for (uint32_t i = 0; i < a->used; ++i) { 651 const buffer * const value = &((data_string *)a->data[i])->value; 652 const uint32_t vlen = buffer_string_length(value); 653 if (vlen <= blen && buffer_eq_icase_ssn(end - vlen, value->ptr, vlen)) 654 return value; 655 } 656 return NULL; 657 } 658 659 data_unset * 660 array_match_path_or_ext (const array * const a, const buffer * const b) 661 { 662 const uint32_t blen = buffer_string_length(b); 663 664 for (uint32_t i = 0; i < a->used; ++i) { 665 /* check extension in the form "^/path" or ".ext$" */ 666 const buffer * const key = &a->data[i]->key; 667 const uint32_t klen = buffer_string_length(key); 668 if (klen <= blen 669 && 0 == memcmp((*(key->ptr) == '/' ? b->ptr : b->ptr + blen - klen), 670 key->ptr, klen)) 671 return a->data[i]; 672 } 673 return NULL; 674 } 675