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