xref: /lighttpd1.4/src/array.c (revision 4f8f83ea)
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