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