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