]> granicus.if.org Git - apache/blob - modules/md/md_util.c
*) mod_md: bringing over v2.0.6 from github.
[apache] / modules / md / md_util.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16  
17 #include <stdio.h>
18
19 #include <apr_lib.h>
20 #include <apr_strings.h>
21 #include <apr_portable.h>
22 #include <apr_file_info.h>
23 #include <apr_fnmatch.h>
24 #include <apr_tables.h>
25 #include <apr_uri.h>
26
27 #include "md.h"
28 #include "md_log.h"
29 #include "md_util.h"
30
31 /**************************************************************************************************/
32 /* pool utils */
33
34 apr_status_t md_util_pool_do(md_util_action *cb, void *baton, apr_pool_t *p)
35 {
36     apr_pool_t *ptemp;
37     apr_status_t rv = apr_pool_create(&ptemp, p);
38     if (APR_SUCCESS == rv) {
39         rv = cb(baton, p, ptemp);
40         
41         apr_pool_destroy(ptemp);
42     }
43     return rv;
44 }
45  
46 static apr_status_t pool_vado(md_util_vaction *cb, void *baton, apr_pool_t *p, va_list ap)
47 {
48     apr_pool_t *ptemp;
49     apr_status_t rv;
50     
51     rv = apr_pool_create(&ptemp, p);
52     if (APR_SUCCESS == rv) {
53         rv = cb(baton, p, ptemp, ap);
54         apr_pool_destroy(ptemp);
55     }
56     return rv;
57 }
58  
59 apr_status_t md_util_pool_vdo(md_util_vaction *cb, void *baton, apr_pool_t *p, ...)
60 {
61     va_list ap;
62     apr_status_t rv;
63     
64     va_start(ap, p);
65     rv = pool_vado(cb, baton, p, ap);
66     va_end(ap);
67     return rv;
68 }
69  
70 /**************************************************************************************************/
71 /* data chunks */
72
73 md_data *md_data_create(apr_pool_t *p, const char *data, apr_size_t len)
74 {
75     md_data *d;
76     
77     d = apr_palloc(p, sizeof(*d));
78     d->len = len;
79     d->data = len? apr_pstrndup(p, data, len) : NULL;
80     return d;
81 }
82
83 static const char * const hex_const[] = {
84     "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f", 
85     "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e", "1f", 
86     "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2a", "2b", "2c", "2d", "2e", "2f", 
87     "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e", "3f", 
88     "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4a", "4b", "4c", "4d", "4e", "4f", 
89     "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5a", "5b", "5c", "5d", "5e", "5f", 
90     "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6a", "6b", "6c", "6d", "6e", "6f", 
91     "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7a", "7b", "7c", "7d", "7e", "7f", 
92     "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8a", "8b", "8c", "8d", "8e", "8f", 
93     "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9a", "9b", "9c", "9d", "9e", "9f", 
94     "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af", 
95     "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf", 
96     "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf", 
97     "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "da", "db", "dc", "dd", "de", "df", 
98     "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef", 
99     "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff", 
100 };
101
102 apr_status_t md_data_to_hex(const char **phex, char separator,
103                             apr_pool_t *p, const md_data *data)
104 {
105     char *hex, *cp;
106     const char * x;
107     unsigned int i;
108     
109     cp = hex = apr_pcalloc(p, ((separator? 3 : 2) * data->len) + 1);
110     if (!hex) {
111         *phex = NULL;
112         return APR_ENOMEM;
113     }
114     for (i = 0; i < data->len; ++i) {
115         x = hex_const[(unsigned char)data->data[i]];
116         if (i && separator) *cp++ = separator;
117         *cp++ = x[0];
118         *cp++ = x[1];
119     }
120     *phex = hex;
121     return APR_SUCCESS;
122 }
123
124 /**************************************************************************************************/
125 /* string related */
126
127 int md_array_is_empty(const struct apr_array_header_t *array)
128 {
129     return (array == NULL) || (array->nelts == 0);
130 }
131
132 char *md_util_str_tolower(char *s)
133 {
134     char *orig = s;
135     while (*s) {
136         *s = (char)apr_tolower(*s);
137         ++s;
138     }
139     return orig;
140 }
141
142 int md_array_str_index(const apr_array_header_t *array, const char *s, 
143                        int start, int case_sensitive)
144 {
145     if (start >= 0) {
146         int i;
147         
148         for (i = start; i < array->nelts; i++) {
149             const char *p = APR_ARRAY_IDX(array, i, const char *);
150             if ((case_sensitive && !strcmp(p, s))
151                 || (!case_sensitive && !apr_strnatcasecmp(p, s))) {
152                 return i;
153             }
154         }
155     }
156     
157     return -1;
158 }
159
160 int md_array_str_eq(const struct apr_array_header_t *a1, 
161                     const struct apr_array_header_t *a2, int case_sensitive)
162 {
163     int i;
164     const char *s1, *s2;
165     
166     if (a1 == a2) return 1;
167     if (!a1) return 0;
168     if (a1->nelts != a2->nelts) return 0;
169     for (i = 0; i < a1->nelts; ++i) {
170         s1 = APR_ARRAY_IDX(a1, i, const char *);
171         s2 = APR_ARRAY_IDX(a2, i, const char *);
172         if ((case_sensitive && strcmp(s1, s2))
173             || (!case_sensitive && apr_strnatcasecmp(s1, s2))) {
174             return 0;
175         }
176     }
177     return 1;
178 }
179
180 apr_array_header_t *md_array_str_clone(apr_pool_t *p, apr_array_header_t *src)
181 {
182     apr_array_header_t *dest = apr_array_make(p, src->nelts, sizeof(const char*));
183     if (dest) {
184         int i;
185         for (i = 0; i < src->nelts; i++) {
186             const char *s = APR_ARRAY_IDX(src, i, const char *);
187             APR_ARRAY_PUSH(dest, const char *) = apr_pstrdup(p, s); 
188         }
189     }
190     return dest;
191 }
192
193 struct apr_array_header_t *md_array_str_compact(apr_pool_t *p, struct apr_array_header_t *src,
194                                                 int case_sensitive)
195 {
196     apr_array_header_t *dest = apr_array_make(p, src->nelts, sizeof(const char*));
197     if (dest) {
198         const char *s;
199         int i;
200         for (i = 0; i < src->nelts; ++i) {
201             s = APR_ARRAY_IDX(src, i, const char *);
202             if (md_array_str_index(dest, s, 0, case_sensitive) < 0) {
203                 APR_ARRAY_PUSH(dest, char *) = md_util_str_tolower(apr_pstrdup(p, s));
204             }
205         }
206     }
207     return dest;
208 }
209
210 apr_array_header_t *md_array_str_remove(apr_pool_t *p, apr_array_header_t *src, 
211                                         const char *exclude, int case_sensitive)
212 {
213     apr_array_header_t *dest = apr_array_make(p, src->nelts, sizeof(const char*));
214     if (dest) {
215         int i;
216         for (i = 0; i < src->nelts; i++) {
217             const char *s = APR_ARRAY_IDX(src, i, const char *);
218             if (!exclude 
219                 || (case_sensitive && strcmp(exclude, s))
220                 || (!case_sensitive && apr_strnatcasecmp(exclude, s))) {
221                 APR_ARRAY_PUSH(dest, const char *) = apr_pstrdup(p, s); 
222             }
223         }
224     }
225     return dest;
226 }
227
228 int md_array_str_add_missing(apr_array_header_t *dest, apr_array_header_t *src, int case_sensitive)
229 {
230     int i, added = 0;
231     for (i = 0; i < src->nelts; i++) {
232         const char *s = APR_ARRAY_IDX(src, i, const char *);
233         if (md_array_str_index(dest, s, 0, case_sensitive) < 0) {
234             APR_ARRAY_PUSH(dest, const char *) = s;
235             ++added; 
236         }
237     }
238     return added;
239 }
240
241 /**************************************************************************************************/
242 /* file system related */
243
244 apr_status_t md_util_fopen(FILE **pf, const char *fn, const char *mode)
245 {
246     *pf = fopen(fn, mode);
247     if (*pf == NULL) {
248         return errno;
249     }
250
251     return APR_SUCCESS;
252 }
253
254 apr_status_t md_util_fcreatex(apr_file_t **pf, const char *fn, 
255                               apr_fileperms_t perms, apr_pool_t *p)
256 {
257     apr_status_t rv;
258     rv = apr_file_open(pf, fn, (APR_FOPEN_WRITE|APR_FOPEN_CREATE|APR_FOPEN_EXCL),
259                        perms, p);
260     if (APR_SUCCESS == rv) {
261         /* See <https://github.com/icing/mod_md/issues/117>
262          * Some people set umask 007 to deny all world read/writability to files
263          * created by apache. While this is a noble effort, we need the store files
264          * to have the permissions as specified. */
265         rv = apr_file_perms_set(fn, perms);
266         if (APR_STATUS_IS_ENOTIMPL(rv)) {
267             rv = APR_SUCCESS;
268         }
269     }
270     return rv;
271 }
272
273 apr_status_t md_util_is_dir(const char *path, apr_pool_t *pool)
274 {
275     apr_finfo_t info;
276     apr_status_t rv = apr_stat(&info, path, APR_FINFO_TYPE, pool);
277     if (rv == APR_SUCCESS) {
278         rv = (info.filetype == APR_DIR)? APR_SUCCESS : APR_EINVAL;
279     }
280     return rv;
281 }
282
283 apr_status_t md_util_is_file(const char *path, apr_pool_t *pool)
284 {
285     apr_finfo_t info;
286     apr_status_t rv = apr_stat(&info, path, APR_FINFO_TYPE, pool);
287     if (rv == APR_SUCCESS) {
288         rv = (info.filetype == APR_REG)? APR_SUCCESS : APR_EINVAL;
289     }
290     return rv;
291 }
292
293 int md_file_exists(const char *fname, apr_pool_t *p)
294 {
295     return (fname && *fname && APR_SUCCESS == md_util_is_file(fname, p));
296 }
297
298 apr_status_t md_util_path_merge(const char **ppath, apr_pool_t *p, ...)
299 {
300     const char *segment, *path;
301     va_list ap;
302     apr_status_t rv = APR_SUCCESS;
303     
304     va_start(ap, p);
305     path = va_arg(ap, char *);
306     while (path && APR_SUCCESS == rv && (segment = va_arg(ap, char *))) {
307         rv = apr_filepath_merge((char **)&path, path, segment, APR_FILEPATH_SECUREROOT , p);
308     }
309     va_end(ap);
310     
311     *ppath = (APR_SUCCESS == rv)? (path? path : "") : NULL;
312     return rv;
313 }
314
315 apr_status_t md_util_freplace(const char *fpath, apr_fileperms_t perms, apr_pool_t *p, 
316                               md_util_file_cb *write_cb, void *baton)
317 {
318     apr_status_t rv = APR_EEXIST;
319     apr_file_t *f;
320     const char *tmp;
321     int i, max;
322     
323     tmp = apr_psprintf(p, "%s.tmp", fpath);
324     i = 0; max = 20;
325 creat:
326     while (i < max && APR_EEXIST == (rv = md_util_fcreatex(&f, tmp, perms, p))) {
327         ++i;
328         apr_sleep(apr_time_msec(50));
329     } 
330     if (APR_EEXIST == rv 
331         && APR_SUCCESS == (rv = apr_file_remove(tmp, p))
332         && max <= 20) {
333         max *= 2;
334         goto creat;
335     }
336     
337     if (APR_SUCCESS == rv) {
338         rv = write_cb(baton, f, p);
339         apr_file_close(f);
340         
341         if (APR_SUCCESS == rv) {
342             rv = apr_file_rename(tmp, fpath, p);
343             if (APR_SUCCESS != rv) {
344                 apr_file_remove(tmp, p);
345             }
346         }
347     }
348     return rv;
349 }                            
350
351 /**************************************************************************************************/
352 /* text files */
353
354 apr_status_t md_text_fread8k(const char **ptext, apr_pool_t *p, const char *fpath)
355 {
356     apr_status_t rv;
357     apr_file_t *f;
358     char buffer[8 * 1024];
359
360     *ptext = NULL;
361     if (APR_SUCCESS == (rv = apr_file_open(&f, fpath, APR_FOPEN_READ, 0, p))) {
362         apr_size_t blen = sizeof(buffer)/sizeof(buffer[0]) - 1;
363         rv = apr_file_read_full(f, buffer, blen, &blen);
364         if (APR_SUCCESS == rv || APR_STATUS_IS_EOF(rv)) {
365             *ptext = apr_pstrndup(p, buffer, blen);
366             rv = APR_SUCCESS;
367         }
368         apr_file_close(f);
369     }
370     return rv;
371 }
372
373 static apr_status_t write_text(void *baton, struct apr_file_t *f, apr_pool_t *p)
374 {
375     const char *text = baton;
376     apr_size_t len = strlen(text);
377     
378     (void)p;
379     return apr_file_write_full(f, text, len, &len);
380 }
381
382 apr_status_t md_text_fcreatex(const char *fpath, apr_fileperms_t perms, 
383                               apr_pool_t *p, const char *text)
384 {
385     apr_status_t rv;
386     apr_file_t *f;
387     
388     rv = md_util_fcreatex(&f, fpath, perms, p);
389     if (APR_SUCCESS == rv) {
390         rv = write_text((void*)text, f, p);
391         apr_file_close(f);
392         /* See <https://github.com/icing/mod_md/issues/117>: when a umask
393          * is set, files need to be assigned permissions explicitly.
394          * Otherwise, as in the issues reported, it will break our access model. */
395         rv = apr_file_perms_set(fpath, perms);
396         if (APR_STATUS_IS_ENOTIMPL(rv)) {
397             rv = APR_SUCCESS;
398         }
399     }
400     return rv;
401 }
402
403 apr_status_t md_text_freplace(const char *fpath, apr_fileperms_t perms, 
404                               apr_pool_t *p, const char *text)
405 {
406     return md_util_freplace(fpath, perms, p, write_text, (void*)text);
407 }
408
409 typedef struct {
410     const char *path;
411     apr_array_header_t *patterns;
412     int follow_links;
413     void *baton;
414     md_util_fdo_cb *cb;
415 } md_util_fwalk_t;
416
417 static apr_status_t rm_recursive(const char *fpath, apr_pool_t *p, int max_level)
418 {
419     apr_finfo_t info;
420     apr_status_t rv;
421     const char *npath;
422     
423     if (APR_SUCCESS != (rv = apr_stat(&info, fpath, (APR_FINFO_TYPE|APR_FINFO_LINK), p))) {
424         return rv;
425     }
426     
427     if (info.filetype == APR_DIR) {
428         if (max_level > 0) {
429             apr_dir_t *d;
430             
431             if (APR_SUCCESS == (rv = apr_dir_open(&d, fpath, p))) {
432             
433                 while (APR_SUCCESS == rv && 
434                        APR_SUCCESS == (rv = apr_dir_read(&info, APR_FINFO_TYPE, d))) {
435                     if (!strcmp(".", info.name) || !strcmp("..", info.name)) {
436                         continue;
437                     }
438                     
439                     rv = md_util_path_merge(&npath, p, fpath, info.name, NULL);
440                     if (APR_SUCCESS == rv) {
441                         rv = rm_recursive(npath, p, max_level - 1);
442                     }
443                 }
444                 apr_dir_close(d);
445                 if (APR_STATUS_IS_ENOENT(rv)) {
446                     rv = APR_SUCCESS;
447                 }
448             }
449         }
450         if (APR_SUCCESS == rv) {
451             rv = apr_dir_remove(fpath, p);
452         }
453     }
454     else {
455         rv = apr_file_remove(fpath, p);
456     }
457     return rv;
458 }
459
460 static apr_status_t prm_recursive(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
461 {
462     int max_level = va_arg(ap, int);
463     
464     (void)p;
465     return rm_recursive(baton, ptemp, max_level); 
466 }
467
468 apr_status_t md_util_rm_recursive(const char *fpath, apr_pool_t *p, int max_level)
469 {
470     return md_util_pool_vdo(prm_recursive, (void*)fpath, p, max_level, NULL);
471 }
472
473 static apr_status_t match_and_do(md_util_fwalk_t *ctx, const char *path, int depth, 
474                                  apr_pool_t *p, apr_pool_t *ptemp)
475 {
476     apr_status_t rv = APR_SUCCESS;
477     const char *pattern, *npath;
478     apr_dir_t *d;
479     apr_finfo_t finfo;
480     int ndepth = depth + 1;
481     apr_int32_t wanted = (APR_FINFO_TYPE);
482
483     if (depth >= ctx->patterns->nelts) {
484         return APR_SUCCESS;
485     }
486     pattern = APR_ARRAY_IDX(ctx->patterns, depth, const char *);
487     
488     md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do "
489                   "path=%s depth=%d pattern=%s", path, depth, pattern);
490     rv = apr_dir_open(&d, path, ptemp);
491     if (APR_SUCCESS != rv) {
492         return rv;
493     }
494     
495     while (APR_SUCCESS == (rv = apr_dir_read(&finfo, wanted, d))) {
496         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do "
497                       "candidate=%s", finfo.name);
498         if (!strcmp(".", finfo.name) || !strcmp("..", finfo.name)) {
499             continue;
500         } 
501         if (APR_SUCCESS == apr_fnmatch(pattern, finfo.name, 0)) {
502             md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do "
503                           "candidate=%s matches pattern", finfo.name);
504             if (ndepth < ctx->patterns->nelts) {
505                 md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do "
506                               "need to go deepter");
507                 if (APR_DIR == finfo.filetype) { 
508                     /* deeper and deeper, irgendwo in der tiefe leuchtet ein licht */
509                     rv = md_util_path_merge(&npath, ptemp, path, finfo.name, NULL);
510                     if (APR_SUCCESS == rv) {
511                         rv = match_and_do(ctx, npath, ndepth, p, ptemp);
512                     }
513                 }
514             }
515             else {
516                 md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do "
517                               "invoking inspector on name=%s", finfo.name);
518                 rv = ctx->cb(ctx->baton, p, ptemp, path, finfo.name, finfo.filetype);
519             }
520         }
521         if (APR_SUCCESS != rv) {
522             break;
523         }
524     }
525
526     if (APR_STATUS_IS_ENOENT(rv)) {
527         rv = APR_SUCCESS;
528     }
529
530     apr_dir_close(d);
531     return rv;
532 }
533
534 static apr_status_t files_do_start(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
535 {
536     md_util_fwalk_t *ctx = baton;
537     const char *segment;
538
539     ctx->patterns = apr_array_make(ptemp, 5, sizeof(const char*));
540     
541     segment = va_arg(ap, char *);
542     while (segment) {
543         APR_ARRAY_PUSH(ctx->patterns, const char *) = segment;
544         segment = va_arg(ap, char *);
545     }
546     
547     return match_and_do(ctx, ctx->path, 0, p, ptemp);
548 }
549
550 apr_status_t md_util_files_do(md_util_fdo_cb *cb, void *baton, apr_pool_t *p,
551                               const char *path, ...)
552 {
553     apr_status_t rv;
554     va_list ap;
555     md_util_fwalk_t ctx;
556
557     memset(&ctx, 0, sizeof(ctx));
558     ctx.path = path;
559     ctx.follow_links = 1;
560     ctx.cb = cb;
561     ctx.baton = baton;
562     
563     va_start(ap, path);
564     rv = pool_vado(files_do_start, &ctx, p, ap);
565     va_end(ap);
566     
567     return rv;
568 }
569
570 static apr_status_t tree_do(void *baton, apr_pool_t *p, apr_pool_t *ptemp, const char *path)
571 {
572     md_util_fwalk_t *ctx = baton;
573
574     apr_status_t rv = APR_SUCCESS;
575     const char *name, *fpath;
576     apr_filetype_e ftype;
577     apr_dir_t *d;
578     apr_int32_t wanted = APR_FINFO_TYPE;
579     apr_finfo_t finfo;
580
581     if (APR_SUCCESS == (rv = apr_dir_open(&d, path, ptemp))) {
582         while (APR_SUCCESS == (rv = apr_dir_read(&finfo, wanted, d))) {
583             name = finfo.name;
584             if (!strcmp(".", name) || !strcmp("..", name)) {
585                 continue;
586             }
587
588             fpath = NULL;
589             ftype = finfo.filetype;
590             
591             if (APR_LNK == ftype && ctx->follow_links) {
592                 rv = md_util_path_merge(&fpath, ptemp, path, name, NULL);
593                 if (APR_SUCCESS == rv) {
594                     rv = apr_stat(&finfo, ctx->path, wanted, ptemp);
595                 }
596             }
597             
598             if (APR_DIR == finfo.filetype) {
599                 if (!fpath) {
600                     rv = md_util_path_merge(&fpath, ptemp, path, name, NULL);
601                 }
602                 if (APR_SUCCESS == rv) {
603                     rv = tree_do(ctx, p, ptemp, fpath);
604                     md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, ptemp, "dir cb(%s/%s)", 
605                                   path, name);
606                     rv = ctx->cb(ctx->baton, p, ptemp, path, name, ftype);
607                 }
608             }
609             else {
610                 md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, ptemp, "file cb(%s/%s)", 
611                               path, name);
612                 rv = ctx->cb(ctx->baton, p, ptemp, path, name, finfo.filetype);
613             }
614         }
615
616         apr_dir_close(d);
617         
618         if (APR_STATUS_IS_ENOENT(rv)) {
619             rv = APR_SUCCESS;
620         }
621     }
622     return rv;
623 }
624
625 static apr_status_t tree_start_do(void *baton, apr_pool_t *p, apr_pool_t *ptemp)
626 {
627     md_util_fwalk_t *ctx = baton;
628     apr_finfo_t info;
629     apr_status_t rv;
630     apr_int32_t wanted = ctx->follow_links? APR_FINFO_TYPE : (APR_FINFO_TYPE|APR_FINFO_LINK);
631     
632     rv = apr_stat(&info, ctx->path, wanted, ptemp);
633     if (rv == APR_SUCCESS) {
634         switch (info.filetype) {
635             case APR_DIR:
636                 rv = tree_do(ctx, p, ptemp, ctx->path);
637                 break;
638             default:
639                 rv = APR_EINVAL;
640         }
641     }
642     return rv;
643 }
644
645 apr_status_t md_util_tree_do(md_util_fdo_cb *cb, void *baton, apr_pool_t *p, 
646                              const char *path, int follow_links)
647 {
648     apr_status_t rv;
649     md_util_fwalk_t ctx;
650
651     memset(&ctx, 0, sizeof(ctx));
652     ctx.path = path;
653     ctx.follow_links = follow_links;
654     ctx.cb = cb;
655     ctx.baton = baton;
656     
657     rv = md_util_pool_do(tree_start_do, &ctx, p);
658     
659     return rv;
660 }
661
662 static apr_status_t rm_cb(void *baton, apr_pool_t *p, apr_pool_t *ptemp, 
663                           const char *path, const char *name, apr_filetype_e ftype)
664 {
665     apr_status_t rv;
666     const char *fpath;
667     
668     (void)baton;
669     (void)p;
670     rv = md_util_path_merge(&fpath, ptemp, path, name, NULL);
671     if (APR_SUCCESS == rv) {
672         if (APR_DIR == ftype) {
673             rv = apr_dir_remove(fpath, ptemp);
674         }
675         else {
676             rv = apr_file_remove(fpath, ptemp);
677         }
678     }
679     return rv;
680 }
681
682 apr_status_t md_util_ftree_remove(const char *path, apr_pool_t *p)
683 {
684     apr_status_t rv = md_util_tree_do(rm_cb, NULL, p, path, 0);
685     if (APR_SUCCESS == rv) {
686         rv = apr_dir_remove(path, p);
687     }
688     return rv;
689 }
690
691 /* DNS name checks ********************************************************************************/
692
693 int md_dns_is_name(apr_pool_t *p, const char *hostname, int need_fqdn)
694 {
695     char c, last = 0;
696     const char *cp = hostname;
697     int dots = 0;
698     
699     /* Since we use the names in certificates, we need pure ASCII domain names
700      * and IDN need to be converted to unicode. */
701     while ((c = *cp++)) {
702         switch (c) {
703             case '.':
704                 if (last == '.') {
705                     md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "dns name with ..: %s", 
706                                   hostname);
707                     return 0;
708                 }
709                 ++dots;
710                 break;
711             case '-':
712                 break;
713             default:
714                 if (!apr_isalnum(c)) {
715                     md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "dns invalid char %c: %s", 
716                                   c, hostname);
717                     return 0;
718                 }
719                 break;
720         }
721         last = c;
722     }
723     
724     if (last == '.') { /* DNS names may end with '.' */
725         --dots;
726     }
727     if (need_fqdn && dots <= 0) { /* do not accept just top level domains */
728         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "not a FQDN: %s", hostname);
729         return 0;
730     }
731     return 1; /* empty string not allowed */
732 }
733
734 int md_dns_is_wildcard(apr_pool_t *p, const char *domain)
735 {
736     if (domain[0] != '*' || domain[1] != '.') return 0;
737     return md_dns_is_name(p, domain+2, 1);
738 }
739
740 int md_dns_matches(const char *pattern, const char *domain)
741 {
742     const char *s;
743     
744     if (!apr_strnatcasecmp(pattern, domain)) return 1;
745     if (pattern[0] == '*' && pattern[1] == '.') {
746         s = strchr(domain, '.');
747         if (s && !apr_strnatcasecmp(pattern+1, s)) return 1;
748     }
749     return 0;
750 }
751
752 apr_array_header_t *md_dns_make_minimal(apr_pool_t *p, apr_array_header_t *domains)
753 {
754     apr_array_header_t *minimal;
755     const char *domain, *pattern;
756     int i, j, duplicate;
757     
758     minimal = apr_array_make(p, domains->nelts, sizeof(const char *));
759     for (i = 0; i < domains->nelts; ++i) {
760         domain = APR_ARRAY_IDX(domains, i, const char*);
761         duplicate = 0;
762         /* is it matched in minimal already? */
763         for (j = 0; j < minimal->nelts; ++j) {
764             pattern = APR_ARRAY_IDX(minimal, j, const char*);
765             if (md_dns_matches(pattern, domain)) {
766                 duplicate = 1;
767                 break;
768             }
769         }
770         if (!duplicate) {
771             if (!md_dns_is_wildcard(p, domain)) {
772                 /* plain name, will we see a wildcard that replaces it? */
773                 for (j = i+1; j < domains->nelts; ++j) {
774                     pattern = APR_ARRAY_IDX(domains, j, const char*);
775                     if (md_dns_is_wildcard(p, pattern) && md_dns_matches(pattern, domain)) {
776                         duplicate = 1;
777                         break;
778                     }
779                 }
780             }
781             if (!duplicate) {
782                 APR_ARRAY_PUSH(minimal, const char *) = domain; 
783             }
784         }
785     }
786     return minimal;
787 }
788
789 int md_dns_domains_match(const apr_array_header_t *domains, const char *name)
790 {
791     const char *domain;
792     int i;
793     
794     for (i = 0; i < domains->nelts; ++i) {
795         domain = APR_ARRAY_IDX(domains, i, const char*);
796         if (md_dns_matches(domain, name)) return 1;
797     }
798     return 0;
799 }
800
801 const char *md_util_schemify(apr_pool_t *p, const char *s, const char *def_scheme)
802 {
803     const char *cp = s;
804     while (*cp) {
805         if (*cp == ':') {
806             /* could be an url scheme, leave unchanged */
807             return s;
808         }
809         else if (!apr_isalnum(*cp)) {
810             break;
811         }
812         ++cp;
813     }
814     return apr_psprintf(p, "%s:%s", def_scheme, s);
815 }
816
817 static apr_status_t uri_check(apr_uri_t *uri_parsed, apr_pool_t *p, 
818                               const char *uri, const char **perr)
819 {
820     const char *s, *err = NULL;
821     apr_status_t rv;
822     
823     if (APR_SUCCESS != (rv = apr_uri_parse(p, uri, uri_parsed))) {
824         err = "not an uri";
825     }
826     else if (uri_parsed->scheme) {
827         if (strlen(uri_parsed->scheme) + 1 >= strlen(uri)) {
828             err = "missing uri identifier";
829         }
830         else if (!strncmp("http", uri_parsed->scheme, 4)) {
831             if (!uri_parsed->hostname) {
832                 err = "missing hostname";
833             }
834             else if (!md_dns_is_name(p, uri_parsed->hostname, 0)) {
835                 err = "invalid hostname";
836             }
837             if (uri_parsed->port_str 
838                 && (!apr_isdigit(uri_parsed->port_str[0])
839                 || uri_parsed->port == 0
840                 || uri_parsed->port > 65353)) {
841                 err = "invalid port";
842             }
843         }
844         else if (!strcmp("mailto", uri_parsed->scheme)) {
845             s = strchr(uri, '@');
846             if (!s) {
847                 err = "missing @";
848             }
849             else if (strchr(s+1, '@')) {
850                 err = "duplicate @";
851             }
852             else if (s == uri + strlen(uri_parsed->scheme) + 1) {
853                 err = "missing local part";
854             }
855             else if (s == (uri + strlen(uri)-1)) {
856                 err = "missing hostname";
857             }
858             else if (strstr(uri, "..")) {
859                 err = "double period";
860             }
861         }
862     }
863     if (strchr(uri, ' ') || strchr(uri, '\t') ) {
864         err = "whitespace in uri";
865     }
866     
867     if (err) {
868         rv = APR_EINVAL;
869     }
870     *perr = err;
871     return rv;
872 }
873
874 apr_status_t md_util_abs_uri_check(apr_pool_t *p, const char *uri, const char **perr)
875 {
876     apr_uri_t uri_parsed;
877     apr_status_t rv;
878
879     if (APR_SUCCESS == (rv = uri_check(&uri_parsed, p, uri, perr))) {
880         if (!uri_parsed.scheme) {
881             *perr = "missing uri scheme";
882             return APR_EINVAL;
883         }
884     }
885     return rv;
886 }
887
888 apr_status_t md_util_abs_http_uri_check(apr_pool_t *p, const char *uri, const char **perr)
889 {
890     apr_uri_t uri_parsed;
891     apr_status_t rv;
892
893     if (APR_SUCCESS == (rv = uri_check(&uri_parsed, p, uri, perr))) {
894         if (!uri_parsed.scheme) {
895             *perr = "missing uri scheme";
896             return APR_EINVAL;
897         }
898         if (apr_strnatcasecmp("http", uri_parsed.scheme) 
899             && apr_strnatcasecmp("https", uri_parsed.scheme)) {
900             *perr = "uri scheme must be http or https";
901             return APR_EINVAL;
902         }
903     }
904     return rv;
905 }
906
907 /* try and retry for a while **********************************************************************/
908
909 apr_status_t md_util_try(md_util_try_fn *fn, void *baton, int ignore_errs, 
910                          apr_interval_time_t timeout, apr_interval_time_t start_delay, 
911                          apr_interval_time_t max_delay, int backoff)
912 {
913     apr_status_t rv;
914     apr_time_t now = apr_time_now();
915     apr_time_t giveup = now + timeout;
916     apr_interval_time_t nap_duration = start_delay? start_delay : apr_time_from_msec(100);
917     apr_interval_time_t nap_max = max_delay? max_delay : apr_time_from_sec(10);
918     apr_interval_time_t left;
919     int i = 0;
920     
921     while (1) {
922         if (APR_SUCCESS == (rv = fn(baton, i++))) {
923             break;
924         }
925         else if (!APR_STATUS_IS_EAGAIN(rv) && !ignore_errs) {
926             break;
927         }
928         
929         now = apr_time_now();
930         if (now > giveup) {
931             rv = APR_TIMEUP;
932             break;
933         }
934         
935         left = giveup - now;
936         if (nap_duration > left) {
937             nap_duration = left;
938         }
939         if (nap_duration > nap_max) {
940             nap_duration = nap_max;
941         }
942         
943         apr_sleep(nap_duration);
944         if (backoff) {
945             nap_duration *= 2;
946         } 
947     }
948     return rv;
949 }
950
951 /* execute process ********************************************************************************/
952
953 apr_status_t md_util_exec(apr_pool_t *p, const char *cmd, const char * const *argv,
954                           int *exit_code)
955 {
956     apr_status_t rv;
957     apr_procattr_t *procattr;
958     apr_proc_t *proc;
959     apr_exit_why_e ewhy;
960     char buffer[1024];
961     
962     *exit_code = 0;
963     if (!(proc = apr_pcalloc(p, sizeof(*proc)))) {
964         return APR_ENOMEM;
965     }
966     if (   APR_SUCCESS == (rv = apr_procattr_create(&procattr, p))
967         && APR_SUCCESS == (rv = apr_procattr_io_set(procattr, APR_NO_FILE, 
968                                                     APR_NO_PIPE, APR_FULL_BLOCK))
969         && APR_SUCCESS == (rv = apr_procattr_cmdtype_set(procattr, APR_PROGRAM))
970         && APR_SUCCESS == (rv = apr_proc_create(proc, cmd, argv, NULL, procattr, p))) {
971         
972         /* read stderr and log on INFO for possible fault analysis. */
973         while(APR_SUCCESS == (rv = apr_file_gets(buffer, sizeof(buffer)-1, proc->err))) {
974             md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p, "cmd(%s) stderr: %s", cmd, buffer);
975         }
976         if (!APR_STATUS_IS_EOF(rv)) goto out;
977         apr_file_close(proc->err);
978         
979         if (APR_CHILD_DONE == (rv = apr_proc_wait(proc, exit_code, &ewhy, APR_WAIT))) {
980             /* let's not dwell on exit stati, but core should signal something's bad */
981             if (*exit_code > 127 || APR_PROC_SIGNAL_CORE == ewhy) {
982                 return APR_EINCOMPLETE;
983             }
984             return APR_SUCCESS;
985         }
986     }
987 out:
988     return rv;
989 }
990
991 /* base64 url encoding ****************************************************************************/
992
993 #define N6 (unsigned int)-1
994
995 static const unsigned int BASE64URL_UINT6[] = {
996 /*   0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f        */
997     N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  0 */
998     N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  1 */ 
999     N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, 62, N6, N6, /*  2 */
1000     52, 53, 54, 55, 56, 57, 58, 59, 60, 61, N6, N6, N6, N6, N6, N6, /*  3 */ 
1001     N6, 0,  1,  2,  3,  4,  5,  6,   7,  8,  9, 10, 11, 12, 13, 14, /*  4 */
1002     15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, N6, N6, N6, N6, 63, /*  5 */
1003     N6, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /*  6 */
1004     41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, N6, N6, N6, N6, N6, /*  7 */
1005     N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  8 */
1006     N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  9 */
1007     N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  a */
1008     N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  b */
1009     N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  c */
1010     N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  d */
1011     N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  e */
1012     N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6  /*  f */
1013 };
1014 static const unsigned char BASE64URL_CHARS[] = {
1015     'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', /*  0 -  9 */
1016     'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', /* 10 - 19 */
1017     'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', /* 20 - 29 */
1018     'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', /* 30 - 39 */
1019     'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', /* 40 - 49 */
1020     'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', /* 50 - 59 */
1021     '8', '9', '-', '_', ' ', ' ', ' ', ' ', ' ', ' ', /* 60 - 69 */
1022 };
1023
1024 #define BASE64URL_CHAR(x)    BASE64URL_CHARS[ (unsigned int)(x) & 0x3fu ]
1025    
1026 apr_size_t md_util_base64url_decode(const char **decoded, const char *encoded, 
1027                                     apr_pool_t *pool)
1028 {
1029     const unsigned char *e = (const unsigned char *)encoded;
1030     const unsigned char *p = e;
1031     unsigned char *d;
1032     unsigned int n;
1033     long len, mlen, remain, i;
1034     
1035     while (*p && BASE64URL_UINT6[ *p ] != N6) {
1036         ++p;
1037     }
1038     len = (int)(p - e);
1039     mlen = (len/4)*4;
1040     *decoded = apr_pcalloc(pool, (apr_size_t)len + 1);
1041     
1042     i = 0;
1043     d = (unsigned char*)*decoded;
1044     for (; i < mlen; i += 4) {
1045         n = ((BASE64URL_UINT6[ e[i+0] ] << 18) +
1046              (BASE64URL_UINT6[ e[i+1] ] << 12) +
1047              (BASE64URL_UINT6[ e[i+2] ] << 6) +
1048              (BASE64URL_UINT6[ e[i+3] ]));
1049         *d++ = (unsigned char)(n >> 16);
1050         *d++ = (unsigned char)(n >> 8 & 0xffu);
1051         *d++ = (unsigned char)(n & 0xffu);
1052     }
1053     remain = len - mlen;
1054     switch (remain) {
1055         case 2:
1056             n = ((BASE64URL_UINT6[ e[mlen+0] ] << 18) +
1057                  (BASE64URL_UINT6[ e[mlen+1] ] << 12));
1058             *d++ = (unsigned char)(n >> 16);
1059             remain = 1;
1060             break;
1061         case 3:
1062             n = ((BASE64URL_UINT6[ e[mlen+0] ] << 18) +
1063                  (BASE64URL_UINT6[ e[mlen+1] ] << 12) +
1064                  (BASE64URL_UINT6[ e[mlen+2] ] << 6));
1065             *d++ = (unsigned char)(n >> 16);
1066             *d++ = (unsigned char)(n >> 8 & 0xffu);
1067             remain = 2;
1068             break;
1069         default: /* do nothing */
1070             break;
1071     }
1072     return (apr_size_t)(mlen/4*3 + remain);
1073 }
1074
1075 const char *md_util_base64url_encode(const char *data, apr_size_t dlen, apr_pool_t *pool)
1076 {
1077     int i, len = (int)dlen;
1078     apr_size_t slen = ((dlen+2)/3)*4 + 1; /* 0 terminated */
1079     const unsigned char *udata = (const unsigned char*)data;
1080     unsigned char *enc, *p = apr_pcalloc(pool, slen);
1081     
1082     enc = p;
1083     for (i = 0; i < len-2; i+= 3) {
1084         *p++ = BASE64URL_CHAR( (udata[i]   >> 2) );
1085         *p++ = BASE64URL_CHAR( (udata[i]   << 4) + (udata[i+1] >> 4) );
1086         *p++ = BASE64URL_CHAR( (udata[i+1] << 2) + (udata[i+2] >> 6) );
1087         *p++ = BASE64URL_CHAR( (udata[i+2]) );
1088     }
1089     
1090     if (i < len) {
1091         *p++ = BASE64URL_CHAR( (udata[i] >> 2) );
1092         if (i == (len - 1)) {
1093             *p++ = BASE64URL_CHARS[ ((unsigned int)udata[i] << 4) & 0x3fu ];
1094         }
1095         else {
1096             *p++ = BASE64URL_CHAR( (udata[i] << 4) + (udata[i+1] >> 4) );
1097             *p++ = BASE64URL_CHAR( (udata[i+1] << 2) );
1098         }
1099     }
1100     *p++ = '\0';
1101     return (char *)enc;
1102 }
1103
1104 /*******************************************************************************
1105  * link header handling 
1106  ******************************************************************************/
1107
1108 typedef struct {
1109     const char *s;
1110     apr_size_t slen;
1111     apr_size_t i;
1112     apr_size_t link_start;
1113     apr_size_t link_len;
1114     apr_size_t pn_start;
1115     apr_size_t pn_len;
1116     apr_size_t pv_start;
1117     apr_size_t pv_len;
1118 } link_ctx;
1119
1120 static int attr_char(char c) 
1121 {
1122     switch (c) {
1123         case '!':
1124         case '#':
1125         case '$':
1126         case '&':
1127         case '+':
1128         case '-':
1129         case '.':
1130         case '^':
1131         case '_':
1132         case '`':
1133         case '|':
1134         case '~':
1135             return 1;
1136         default:
1137             return apr_isalnum(c);
1138     }
1139 }
1140
1141 static int ptoken_char(char c) 
1142 {
1143     switch (c) {
1144         case '!':
1145         case '#':
1146         case '$':
1147         case '&':
1148         case '\'':
1149         case '(':
1150         case ')':
1151         case '*':
1152         case '+':
1153         case '-':
1154         case '.':
1155         case '/':
1156         case ':':
1157         case '<':
1158         case '=':
1159         case '>':
1160         case '?':
1161         case '@':
1162         case '[':
1163         case ']':
1164         case '^':
1165         case '_':
1166         case '`':
1167         case '{':
1168         case '|':
1169         case '}':
1170         case '~':
1171             return 1;
1172         default:
1173             return apr_isalnum(c);
1174     }
1175 }
1176
1177 static int skip_ws(link_ctx *ctx)
1178 {
1179     char c;
1180     while (ctx->i < ctx->slen 
1181            && (((c = ctx->s[ctx->i]) == ' ') || (c == '\t'))) {
1182         ++ctx->i;
1183     }
1184     return (ctx->i < ctx->slen);
1185 }
1186
1187 static int skip_nonws(link_ctx *ctx)
1188 {
1189     char c;
1190     while (ctx->i < ctx->slen 
1191            && (((c = ctx->s[ctx->i]) != ' ') && (c != '\t'))) {
1192         ++ctx->i;
1193     }
1194     return (ctx->i < ctx->slen);
1195 }
1196
1197 static unsigned int find_chr(link_ctx *ctx, char c, apr_size_t *pidx)
1198 {
1199     apr_size_t j;
1200     for (j = ctx->i; j < ctx->slen; ++j) {
1201         if (ctx->s[j] == c) {
1202             *pidx = j;
1203             return 1;
1204         }
1205     } 
1206     return 0;
1207 }
1208
1209 static int read_chr(link_ctx *ctx, char c)
1210 {
1211     if (ctx->i < ctx->slen && ctx->s[ctx->i] == c) {
1212         ++ctx->i;
1213         return 1;
1214     }
1215     return 0;
1216 }
1217
1218 static int skip_qstring(link_ctx *ctx)
1219 {
1220     if (skip_ws(ctx) && read_chr(ctx, '\"')) {
1221         apr_size_t end;
1222         if (find_chr(ctx, '\"', &end)) {
1223             ctx->i = end + 1;
1224             return 1;
1225         }
1226     }
1227     return 0;
1228 }
1229
1230 static int skip_ptoken(link_ctx *ctx)
1231 {
1232     if (skip_ws(ctx)) {
1233         apr_size_t i;
1234         for (i = ctx->i; i < ctx->slen && ptoken_char(ctx->s[i]); ++i) {
1235             /* nop */
1236         }
1237         if (i > ctx->i) {
1238             ctx->i = i;
1239             return 1;
1240         }
1241     }
1242     return 0;
1243 }
1244
1245
1246 static int read_link(link_ctx *ctx)
1247 {
1248     ctx->link_start = ctx->link_len = 0;
1249     if (skip_ws(ctx) && read_chr(ctx, '<')) {
1250         apr_size_t end;
1251         if (find_chr(ctx, '>', &end)) {
1252             ctx->link_start = ctx->i;
1253             ctx->link_len = end - ctx->link_start;
1254             ctx->i = end + 1;
1255             return 1;
1256         }
1257     }
1258     return 0;
1259 }
1260
1261 static int skip_pname(link_ctx *ctx)
1262 {
1263     if (skip_ws(ctx)) {
1264         apr_size_t i;
1265         for (i = ctx->i; i < ctx->slen && attr_char(ctx->s[i]); ++i) {
1266             /* nop */
1267         }
1268         if (i > ctx->i) {
1269             ctx->i = i;
1270             return 1;
1271         }
1272     }
1273     return 0;
1274 }
1275
1276 static int skip_pvalue(link_ctx *ctx)
1277 {
1278     if (skip_ws(ctx) && read_chr(ctx, '=')) {
1279         ctx->pv_start = ctx->i;
1280         if (skip_qstring(ctx) || skip_ptoken(ctx)) {
1281             ctx->pv_len = ctx->i - ctx->pv_start;
1282             return 1;
1283         }
1284     }
1285     return 0;
1286 }
1287
1288 static int skip_param(link_ctx *ctx)
1289 {
1290     if (skip_ws(ctx) && read_chr(ctx, ';')) {
1291         ctx->pn_start = ctx->i;
1292         ctx->pn_len = 0;
1293         if (skip_pname(ctx)) {
1294             ctx->pn_len = ctx->i - ctx->pn_start;
1295             ctx->pv_len = 0;
1296             skip_pvalue(ctx); /* value is optional */
1297             return 1;
1298         }
1299     }
1300     return 0;
1301 }
1302
1303 static int pv_contains(link_ctx *ctx, const char *s)
1304 {
1305     apr_size_t pvstart = ctx->pv_start;
1306     apr_size_t pvlen = ctx->pv_len;
1307     
1308     if (ctx->s[pvstart] == '\"' && pvlen > 1) {
1309         ++pvstart;
1310         pvlen -= 2;
1311     }
1312     if (pvlen > 0) {
1313         apr_size_t slen = strlen(s);
1314         link_ctx pvctx;
1315         apr_size_t i;
1316         
1317         memset(&pvctx, 0, sizeof(pvctx));
1318         pvctx.s = ctx->s + pvstart;
1319         pvctx.slen = pvlen;
1320
1321         for (i = 0; i < pvctx.slen; i = pvctx.i) {
1322             skip_nonws(&pvctx);
1323             if ((pvctx.i - i) == slen && !strncmp(s, pvctx.s + i, slen)) {
1324                 return 1;
1325             }
1326             skip_ws(&pvctx);
1327         }
1328     }
1329     return 0;
1330 }
1331
1332 /* RFC 5988 <https://tools.ietf.org/html/rfc5988#section-6.2.1>
1333   Link           = "Link" ":" #link-value
1334   link-value     = "<" URI-Reference ">" *( ";" link-param )
1335   link-param     = ( ( "rel" "=" relation-types )
1336                  | ( "anchor" "=" <"> URI-Reference <"> )
1337                  | ( "rev" "=" relation-types )
1338                  | ( "hreflang" "=" Language-Tag )
1339                  | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) )
1340                  | ( "title" "=" quoted-string )
1341                  | ( "title*" "=" ext-value )
1342                  | ( "type" "=" ( media-type | quoted-mt ) )
1343                  | ( link-extension ) )
1344   link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] )
1345                  | ( ext-name-star "=" ext-value )
1346   ext-name-star  = parmname "*" ; reserved for RFC2231-profiled
1347                                 ; extensions.  Whitespace NOT
1348                                 ; allowed in between.
1349   ptoken         = 1*ptokenchar
1350   ptokenchar     = "!" | "#" | "$" | "%" | "&" | "'" | "("
1351                  | ")" | "*" | "+" | "-" | "." | "/" | DIGIT
1352                  | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA
1353                  | "[" | "]" | "^" | "_" | "`" | "{" | "|"
1354                  | "}" | "~"
1355   media-type     = type-name "/" subtype-name
1356   quoted-mt      = <"> media-type <">
1357   relation-types = relation-type
1358                  | <"> relation-type *( 1*SP relation-type ) <">
1359   relation-type  = reg-rel-type | ext-rel-type
1360   reg-rel-type   = LOALPHA *( LOALPHA | DIGIT | "." | "-" )
1361   ext-rel-type   = URI
1362   
1363   and from <https://tools.ietf.org/html/rfc5987>
1364   parmname      = 1*attr-char
1365   attr-char     = ALPHA / DIGIT
1366                    / "!" / "#" / "$" / "&" / "+" / "-" / "."
1367                    / "^" / "_" / "`" / "|" / "~"
1368  */
1369
1370 typedef struct {
1371     apr_pool_t *pool;
1372     const char *relation;
1373     const char *url;
1374 } find_ctx;
1375
1376 static int find_url(void *baton, const char *key, const char *value)
1377 {
1378     find_ctx *outer = baton;
1379     
1380     if (!apr_strnatcasecmp("link", key)) {
1381         link_ctx ctx;
1382         
1383         memset(&ctx, 0, sizeof(ctx));
1384         ctx.s = value;
1385         ctx.slen = strlen(value);
1386         
1387         while (read_link(&ctx)) {
1388             while (skip_param(&ctx)) {
1389                 if (ctx.pn_len == 3 && !strncmp("rel", ctx.s + ctx.pn_start, 3)
1390                     && pv_contains(&ctx, outer->relation)) {
1391                     /* this is the link relation we are looking for */
1392                     outer->url = apr_pstrndup(outer->pool, ctx.s + ctx.link_start, ctx.link_len);
1393                     return 0;
1394                 }
1395             }
1396         }
1397     }
1398     return 1;
1399 }
1400
1401 const char *md_link_find_relation(const apr_table_t *headers, 
1402                                   apr_pool_t *pool, const char *relation)
1403 {
1404     find_ctx ctx;
1405     
1406     memset(&ctx, 0, sizeof(ctx));
1407     ctx.pool = pool;
1408     ctx.relation = relation;
1409     
1410     apr_table_do(find_url, &ctx, headers, NULL);
1411     
1412     return ctx.url;
1413 }
1414