diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 06:33:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 06:33:50 +0000 |
commit | fe39ffb8b90ae4e002ed73fe98617cd590abb467 (patch) | |
tree | b80e5956907d8aeaaffe4e4f0c068c0e6157ce8b /modules/md/md_util.c | |
parent | Initial commit. (diff) | |
download | apache2-upstream.tar.xz apache2-upstream.zip |
Adding upstream version 2.4.56.upstream/2.4.56upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/md/md_util.c')
-rw-r--r-- | modules/md/md_util.c | 1561 |
1 files changed, 1561 insertions, 0 deletions
diff --git a/modules/md/md_util.c b/modules/md/md_util.c new file mode 100644 index 0000000..884c0bb --- /dev/null +++ b/modules/md/md_util.c @@ -0,0 +1,1561 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <assert.h> +#include <stdio.h> + +#include <apr_lib.h> +#include <apr_strings.h> +#include <apr_portable.h> +#include <apr_file_info.h> +#include <apr_fnmatch.h> +#include <apr_tables.h> +#include <apr_uri.h> + +#if APR_HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include "md.h" +#include "md_log.h" +#include "md_util.h" + +/**************************************************************************************************/ +/* pool utils */ + +apr_status_t md_util_pool_do(md_util_action *cb, void *baton, apr_pool_t *p) +{ + apr_pool_t *ptemp; + apr_status_t rv = apr_pool_create(&ptemp, p); + if (APR_SUCCESS == rv) { + apr_pool_tag(ptemp, "md_pool_do"); + rv = cb(baton, p, ptemp); + apr_pool_destroy(ptemp); + } + return rv; +} + +static apr_status_t pool_vado(md_util_vaction *cb, void *baton, apr_pool_t *p, va_list ap) +{ + apr_pool_t *ptemp; + apr_status_t rv; + + rv = apr_pool_create(&ptemp, p); + if (APR_SUCCESS == rv) { + apr_pool_tag(ptemp, "md_pool_vado"); + rv = cb(baton, p, ptemp, ap); + apr_pool_destroy(ptemp); + } + return rv; +} + +apr_status_t md_util_pool_vdo(md_util_vaction *cb, void *baton, apr_pool_t *p, ...) +{ + va_list ap; + apr_status_t rv; + + va_start(ap, p); + rv = pool_vado(cb, baton, p, ap); + va_end(ap); + return rv; +} + +/**************************************************************************************************/ +/* data chunks */ + +void md_data_pinit(md_data_t *d, apr_size_t len, apr_pool_t *p) +{ + md_data_null(d); + d->data = apr_pcalloc(p, len); + d->len = len; +} + +md_data_t *md_data_pmake(apr_size_t len, apr_pool_t *p) +{ + md_data_t *d; + + d = apr_palloc(p, sizeof(*d)); + md_data_pinit(d, len, p); + return d; +} + +void md_data_init(md_data_t *d, const char *data, apr_size_t len) +{ + md_data_null(d); + d->len = len; + d->data = data; +} + +void md_data_init_str(md_data_t *d, const char *str) +{ + md_data_init(d, str, strlen(str)); +} + +void md_data_null(md_data_t *d) +{ + memset(d, 0, sizeof(*d)); +} + +void md_data_clear(md_data_t *d) +{ + if (d) { + if (d->data && d->free_data) d->free_data((void*)d->data); + memset(d, 0, sizeof(*d)); + } +} + +md_data_t *md_data_make_pcopy(apr_pool_t *p, const char *data, apr_size_t len) +{ + md_data_t *d; + + d = apr_palloc(p, sizeof(*d)); + d->len = len; + d->data = len? apr_pmemdup(p, data, len) : NULL; + return d; +} + +apr_status_t md_data_assign_copy(md_data_t *dest, const char *src, apr_size_t src_len) +{ + md_data_clear(dest); + if (src && src_len) { + dest->data = malloc(src_len); + if (!dest->data) return APR_ENOMEM; + memcpy((void*)dest->data, src, src_len); + dest->len = src_len; + dest->free_data = free; + } + return APR_SUCCESS; +} + +void md_data_assign_pcopy(md_data_t *dest, const char *src, apr_size_t src_len, apr_pool_t *p) +{ + md_data_clear(dest); + dest->data = (src && src_len)? apr_pmemdup(p, src, src_len) : NULL; + dest->len = dest->data? src_len : 0; +} + +static const char * const hex_const[] = { + "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f", + "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e", "1f", + "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2a", "2b", "2c", "2d", "2e", "2f", + "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e", "3f", + "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4a", "4b", "4c", "4d", "4e", "4f", + "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5a", "5b", "5c", "5d", "5e", "5f", + "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6a", "6b", "6c", "6d", "6e", "6f", + "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7a", "7b", "7c", "7d", "7e", "7f", + "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8a", "8b", "8c", "8d", "8e", "8f", + "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9a", "9b", "9c", "9d", "9e", "9f", + "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af", + "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf", + "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf", + "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "da", "db", "dc", "dd", "de", "df", + "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef", + "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff", +}; + +apr_status_t md_data_to_hex(const char **phex, char separator, + apr_pool_t *p, const md_data_t *data) +{ + char *hex, *cp; + const char * x; + unsigned int i; + + cp = hex = apr_pcalloc(p, ((separator? 3 : 2) * data->len) + 1); + if (!hex) { + *phex = NULL; + return APR_ENOMEM; + } + for (i = 0; i < data->len; ++i) { + x = hex_const[(unsigned char)data->data[i]]; + if (i && separator) *cp++ = separator; + *cp++ = x[0]; + *cp++ = x[1]; + } + *phex = hex; + return APR_SUCCESS; +} + +/**************************************************************************************************/ +/* generic arrays */ + +int md_array_remove_at(struct apr_array_header_t *a, int idx) +{ + char *ps, *pe; + + if (idx < 0 || idx >= a->nelts) return 0; + if (idx+1 == a->nelts) { + --a->nelts; + } + else { + ps = (a->elts + (idx * a->elt_size)); + pe = ps + a->elt_size; + memmove(ps, pe, (size_t)((a->nelts - (idx+1)) * a->elt_size)); + --a->nelts; + } + return 1; +} + +int md_array_remove(struct apr_array_header_t *a, void *elem) +{ + int i, n, m; + void **pe; + + assert(sizeof(void*) == a->elt_size); + n = i = 0; + while (i < a->nelts) { + pe = &APR_ARRAY_IDX(a, i, void*); + if (*pe == elem) { + m = a->nelts - (i+1); + if (m > 0) memmove(pe, pe+1, (unsigned)m*sizeof(void*)); + a->nelts--; + n++; + continue; + } + ++i; + } + return n; +} + +/**************************************************************************************************/ +/* string related */ + +int md_array_is_empty(const struct apr_array_header_t *array) +{ + return (array == NULL) || (array->nelts == 0); +} + +char *md_util_str_tolower(char *s) +{ + char *orig = s; + while (*s) { + *s = (char)apr_tolower(*s); + ++s; + } + return orig; +} + +int md_array_str_index(const apr_array_header_t *array, const char *s, + int start, int case_sensitive) +{ + if (start >= 0) { + int i; + + for (i = start; i < array->nelts; i++) { + const char *p = APR_ARRAY_IDX(array, i, const char *); + if ((case_sensitive && !strcmp(p, s)) + || (!case_sensitive && !apr_strnatcasecmp(p, s))) { + return i; + } + } + } + + return -1; +} + +int md_array_str_eq(const struct apr_array_header_t *a1, + const struct apr_array_header_t *a2, int case_sensitive) +{ + int i; + const char *s1, *s2; + + if (a1 == a2) return 1; + if (!a1 || !a2) return 0; + if (a1->nelts != a2->nelts) return 0; + for (i = 0; i < a1->nelts; ++i) { + s1 = APR_ARRAY_IDX(a1, i, const char *); + s2 = APR_ARRAY_IDX(a2, i, const char *); + if ((case_sensitive && strcmp(s1, s2)) + || (!case_sensitive && apr_strnatcasecmp(s1, s2))) { + return 0; + } + } + return 1; +} + +apr_array_header_t *md_array_str_clone(apr_pool_t *p, apr_array_header_t *src) +{ + apr_array_header_t *dest = apr_array_make(p, src->nelts, sizeof(const char*)); + if (dest) { + int i; + for (i = 0; i < src->nelts; i++) { + const char *s = APR_ARRAY_IDX(src, i, const char *); + APR_ARRAY_PUSH(dest, const char *) = apr_pstrdup(p, s); + } + } + return dest; +} + +struct apr_array_header_t *md_array_str_compact(apr_pool_t *p, struct apr_array_header_t *src, + int case_sensitive) +{ + apr_array_header_t *dest = apr_array_make(p, src->nelts, sizeof(const char*)); + if (dest) { + const char *s; + int i; + for (i = 0; i < src->nelts; ++i) { + s = APR_ARRAY_IDX(src, i, const char *); + if (md_array_str_index(dest, s, 0, case_sensitive) < 0) { + APR_ARRAY_PUSH(dest, char *) = md_util_str_tolower(apr_pstrdup(p, s)); + } + } + } + return dest; +} + +apr_array_header_t *md_array_str_remove(apr_pool_t *p, apr_array_header_t *src, + const char *exclude, int case_sensitive) +{ + apr_array_header_t *dest = apr_array_make(p, src->nelts, sizeof(const char*)); + if (dest) { + int i; + for (i = 0; i < src->nelts; i++) { + const char *s = APR_ARRAY_IDX(src, i, const char *); + if (!exclude + || (case_sensitive && strcmp(exclude, s)) + || (!case_sensitive && apr_strnatcasecmp(exclude, s))) { + APR_ARRAY_PUSH(dest, const char *) = apr_pstrdup(p, s); + } + } + } + return dest; +} + +int md_array_str_add_missing(apr_array_header_t *dest, apr_array_header_t *src, int case_sensitive) +{ + int i, added = 0; + for (i = 0; i < src->nelts; i++) { + const char *s = APR_ARRAY_IDX(src, i, const char *); + if (md_array_str_index(dest, s, 0, case_sensitive) < 0) { + APR_ARRAY_PUSH(dest, const char *) = s; + ++added; + } + } + return added; +} + +/**************************************************************************************************/ +/* file system related */ + +apr_status_t md_util_fopen(FILE **pf, const char *fn, const char *mode) +{ + *pf = fopen(fn, mode); + if (*pf == NULL) { + return errno; + } + + return APR_SUCCESS; +} + +apr_status_t md_util_fcreatex(apr_file_t **pf, const char *fn, + apr_fileperms_t perms, apr_pool_t *p) +{ + apr_status_t rv; + rv = apr_file_open(pf, fn, (APR_FOPEN_WRITE|APR_FOPEN_CREATE|APR_FOPEN_EXCL), + perms, p); + if (APR_SUCCESS == rv) { + /* See <https://github.com/icing/mod_md/issues/117> + * Some people set umask 007 to deny all world read/writability to files + * created by apache. While this is a noble effort, we need the store files + * to have the permissions as specified. */ + rv = apr_file_perms_set(fn, perms); + if (APR_STATUS_IS_ENOTIMPL(rv)) { + rv = APR_SUCCESS; + } + } + return rv; +} + +apr_status_t md_util_is_dir(const char *path, apr_pool_t *pool) +{ + apr_finfo_t info; + apr_status_t rv = apr_stat(&info, path, APR_FINFO_TYPE, pool); + if (rv == APR_SUCCESS) { + rv = (info.filetype == APR_DIR)? APR_SUCCESS : APR_EINVAL; + } + return rv; +} + +apr_status_t md_util_is_file(const char *path, apr_pool_t *pool) +{ + apr_finfo_t info; + apr_status_t rv = apr_stat(&info, path, APR_FINFO_TYPE, pool); + if (rv == APR_SUCCESS) { + rv = (info.filetype == APR_REG)? APR_SUCCESS : APR_EINVAL; + } + return rv; +} + +apr_status_t md_util_is_unix_socket(const char *path, apr_pool_t *pool) +{ + apr_finfo_t info; + apr_status_t rv = apr_stat(&info, path, APR_FINFO_TYPE, pool); + if (rv == APR_SUCCESS) { + rv = (info.filetype == APR_SOCK)? APR_SUCCESS : APR_EINVAL; + } + return rv; +} + +int md_file_exists(const char *fname, apr_pool_t *p) +{ + return (fname && *fname && APR_SUCCESS == md_util_is_file(fname, p)); +} + +apr_status_t md_util_path_merge(const char **ppath, apr_pool_t *p, ...) +{ + const char *segment, *path; + va_list ap; + apr_status_t rv = APR_SUCCESS; + + va_start(ap, p); + path = va_arg(ap, char *); + while (path && APR_SUCCESS == rv && (segment = va_arg(ap, char *))) { + rv = apr_filepath_merge((char **)&path, path, segment, APR_FILEPATH_SECUREROOT , p); + } + va_end(ap); + + *ppath = (APR_SUCCESS == rv)? (path? path : "") : NULL; + return rv; +} + +apr_status_t md_util_freplace(const char *fpath, apr_fileperms_t perms, apr_pool_t *p, + md_util_file_cb *write_cb, void *baton) +{ + apr_status_t rv = APR_EEXIST; + apr_file_t *f; + const char *tmp; + int i, max; + + tmp = apr_psprintf(p, "%s.tmp", fpath); + i = 0; max = 20; +creat: + while (i < max && APR_EEXIST == (rv = md_util_fcreatex(&f, tmp, perms, p))) { + ++i; + apr_sleep(apr_time_from_msec(50)); + } + if (APR_EEXIST == rv + && APR_SUCCESS == (rv = apr_file_remove(tmp, p)) + && max <= 20) { + max *= 2; + goto creat; + } + + if (APR_SUCCESS == rv) { + rv = write_cb(baton, f, p); + apr_file_close(f); + + if (APR_SUCCESS == rv) { + rv = apr_file_rename(tmp, fpath, p); + if (APR_SUCCESS != rv) { + apr_file_remove(tmp, p); + } + } + } + return rv; +} + +/**************************************************************************************************/ +/* text files */ + +apr_status_t md_text_fread8k(const char **ptext, apr_pool_t *p, const char *fpath) +{ + apr_status_t rv; + apr_file_t *f; + char buffer[8 * 1024]; + + *ptext = NULL; + if (APR_SUCCESS == (rv = apr_file_open(&f, fpath, APR_FOPEN_READ, 0, p))) { + apr_size_t blen = sizeof(buffer)/sizeof(buffer[0]) - 1; + rv = apr_file_read_full(f, buffer, blen, &blen); + if (APR_SUCCESS == rv || APR_STATUS_IS_EOF(rv)) { + *ptext = apr_pstrndup(p, buffer, blen); + rv = APR_SUCCESS; + } + apr_file_close(f); + } + return rv; +} + +static apr_status_t write_text(void *baton, struct apr_file_t *f, apr_pool_t *p) +{ + const char *text = baton; + apr_size_t len = strlen(text); + + (void)p; + return apr_file_write_full(f, text, len, &len); +} + +apr_status_t md_text_fcreatex(const char *fpath, apr_fileperms_t perms, + apr_pool_t *p, const char *text) +{ + apr_status_t rv; + apr_file_t *f; + + rv = md_util_fcreatex(&f, fpath, perms, p); + if (APR_SUCCESS == rv) { + rv = write_text((void*)text, f, p); + apr_file_close(f); + /* See <https://github.com/icing/mod_md/issues/117>: when a umask + * is set, files need to be assigned permissions explicitly. + * Otherwise, as in the issues reported, it will break our access model. */ + rv = apr_file_perms_set(fpath, perms); + if (APR_STATUS_IS_ENOTIMPL(rv)) { + rv = APR_SUCCESS; + } + } + return rv; +} + +apr_status_t md_text_freplace(const char *fpath, apr_fileperms_t perms, + apr_pool_t *p, const char *text) +{ + return md_util_freplace(fpath, perms, p, write_text, (void*)text); +} + +typedef struct { + const char *path; + apr_array_header_t *patterns; + int follow_links; + void *baton; + md_util_fdo_cb *cb; +} md_util_fwalk_t; + +static apr_status_t rm_recursive(const char *fpath, apr_pool_t *p, int max_level) +{ + apr_finfo_t info; + apr_status_t rv; + const char *npath; + + if (APR_SUCCESS != (rv = apr_stat(&info, fpath, (APR_FINFO_TYPE|APR_FINFO_LINK), p))) { + return rv; + } + + if (info.filetype == APR_DIR) { + if (max_level > 0) { + apr_dir_t *d; + + if (APR_SUCCESS == (rv = apr_dir_open(&d, fpath, p))) { + + while (APR_SUCCESS == rv && + APR_SUCCESS == (rv = apr_dir_read(&info, APR_FINFO_TYPE, d))) { + if (!strcmp(".", info.name) || !strcmp("..", info.name)) { + continue; + } + + rv = md_util_path_merge(&npath, p, fpath, info.name, NULL); + if (APR_SUCCESS == rv) { + rv = rm_recursive(npath, p, max_level - 1); + } + } + apr_dir_close(d); + if (APR_STATUS_IS_ENOENT(rv)) { + rv = APR_SUCCESS; + } + } + } + if (APR_SUCCESS == rv) { + rv = apr_dir_remove(fpath, p); + } + } + else { + rv = apr_file_remove(fpath, p); + } + return rv; +} + +static apr_status_t prm_recursive(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap) +{ + int max_level = va_arg(ap, int); + + (void)p; + return rm_recursive(baton, ptemp, max_level); +} + +apr_status_t md_util_rm_recursive(const char *fpath, apr_pool_t *p, int max_level) +{ + return md_util_pool_vdo(prm_recursive, (void*)fpath, p, max_level, NULL); +} + +static apr_status_t match_and_do(md_util_fwalk_t *ctx, const char *path, int depth, + apr_pool_t *p, apr_pool_t *ptemp) +{ + apr_status_t rv = APR_SUCCESS; + const char *pattern, *npath; + apr_dir_t *d; + apr_finfo_t finfo; + int ndepth = depth + 1; + apr_int32_t wanted = (APR_FINFO_TYPE); + + if (depth >= ctx->patterns->nelts) { + return APR_SUCCESS; + } + pattern = APR_ARRAY_IDX(ctx->patterns, depth, const char *); + + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do " + "path=%s depth=%d pattern=%s", path, depth, pattern); + rv = apr_dir_open(&d, path, ptemp); + if (APR_SUCCESS != rv) { + return rv; + } + + while (APR_SUCCESS == (rv = apr_dir_read(&finfo, wanted, d))) { + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do " + "candidate=%s", finfo.name); + if (!strcmp(".", finfo.name) || !strcmp("..", finfo.name)) { + continue; + } + if (APR_SUCCESS == apr_fnmatch(pattern, finfo.name, 0)) { + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do " + "candidate=%s matches pattern", finfo.name); + if (ndepth < ctx->patterns->nelts) { + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do " + "need to go deeper"); + if (APR_DIR == finfo.filetype) { + /* deeper and deeper, irgendwo in der tiefe leuchtet ein licht */ + rv = md_util_path_merge(&npath, ptemp, path, finfo.name, NULL); + if (APR_SUCCESS == rv) { + rv = match_and_do(ctx, npath, ndepth, p, ptemp); + } + } + } + else { + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do " + "invoking inspector on name=%s", finfo.name); + rv = ctx->cb(ctx->baton, p, ptemp, path, finfo.name, finfo.filetype); + } + } + if (APR_SUCCESS != rv) { + break; + } + } + + if (APR_STATUS_IS_ENOENT(rv)) { + rv = APR_SUCCESS; + } + + apr_dir_close(d); + return rv; +} + +static apr_status_t files_do_start(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap) +{ + md_util_fwalk_t *ctx = baton; + const char *segment; + + ctx->patterns = apr_array_make(ptemp, 5, sizeof(const char*)); + + segment = va_arg(ap, char *); + while (segment) { + APR_ARRAY_PUSH(ctx->patterns, const char *) = segment; + segment = va_arg(ap, char *); + } + + return match_and_do(ctx, ctx->path, 0, p, ptemp); +} + +apr_status_t md_util_files_do(md_util_fdo_cb *cb, void *baton, apr_pool_t *p, + const char *path, ...) +{ + apr_status_t rv; + va_list ap; + md_util_fwalk_t ctx; + + memset(&ctx, 0, sizeof(ctx)); + ctx.path = path; + ctx.follow_links = 1; + ctx.cb = cb; + ctx.baton = baton; + + va_start(ap, path); + rv = pool_vado(files_do_start, &ctx, p, ap); + va_end(ap); + + return rv; +} + +static apr_status_t tree_do(void *baton, apr_pool_t *p, apr_pool_t *ptemp, const char *path) +{ + md_util_fwalk_t *ctx = baton; + + apr_status_t rv = APR_SUCCESS; + const char *name, *fpath; + apr_filetype_e ftype; + apr_dir_t *d; + apr_int32_t wanted = APR_FINFO_TYPE; + apr_finfo_t finfo; + + if (APR_SUCCESS == (rv = apr_dir_open(&d, path, ptemp))) { + while (APR_SUCCESS == (rv = apr_dir_read(&finfo, wanted, d))) { + name = finfo.name; + if (!strcmp(".", name) || !strcmp("..", name)) { + continue; + } + + fpath = NULL; + ftype = finfo.filetype; + + if (APR_LNK == ftype && ctx->follow_links) { + rv = md_util_path_merge(&fpath, ptemp, path, name, NULL); + if (APR_SUCCESS == rv) { + rv = apr_stat(&finfo, ctx->path, wanted, ptemp); + } + } + + if (APR_DIR == finfo.filetype) { + if (!fpath) { + rv = md_util_path_merge(&fpath, ptemp, path, name, NULL); + } + if (APR_SUCCESS == rv) { + rv = tree_do(ctx, p, ptemp, fpath); + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, ptemp, "dir cb(%s/%s)", + path, name); + rv = ctx->cb(ctx->baton, p, ptemp, path, name, ftype); + } + } + else { + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, ptemp, "file cb(%s/%s)", + path, name); + rv = ctx->cb(ctx->baton, p, ptemp, path, name, finfo.filetype); + } + } + + apr_dir_close(d); + + if (APR_STATUS_IS_ENOENT(rv)) { + rv = APR_SUCCESS; + } + } + return rv; +} + +static apr_status_t tree_start_do(void *baton, apr_pool_t *p, apr_pool_t *ptemp) +{ + md_util_fwalk_t *ctx = baton; + apr_finfo_t info; + apr_status_t rv; + apr_int32_t wanted = ctx->follow_links? APR_FINFO_TYPE : (APR_FINFO_TYPE|APR_FINFO_LINK); + + rv = apr_stat(&info, ctx->path, wanted, ptemp); + if (rv == APR_SUCCESS) { + switch (info.filetype) { + case APR_DIR: + rv = tree_do(ctx, p, ptemp, ctx->path); + break; + default: + rv = APR_EINVAL; + } + } + return rv; +} + +apr_status_t md_util_tree_do(md_util_fdo_cb *cb, void *baton, apr_pool_t *p, + const char *path, int follow_links) +{ + apr_status_t rv; + md_util_fwalk_t ctx; + + memset(&ctx, 0, sizeof(ctx)); + ctx.path = path; + ctx.follow_links = follow_links; + ctx.cb = cb; + ctx.baton = baton; + + rv = md_util_pool_do(tree_start_do, &ctx, p); + + return rv; +} + +static apr_status_t rm_cb(void *baton, apr_pool_t *p, apr_pool_t *ptemp, + const char *path, const char *name, apr_filetype_e ftype) +{ + apr_status_t rv; + const char *fpath; + + (void)baton; + (void)p; + rv = md_util_path_merge(&fpath, ptemp, path, name, NULL); + if (APR_SUCCESS == rv) { + if (APR_DIR == ftype) { + rv = apr_dir_remove(fpath, ptemp); + } + else { + rv = apr_file_remove(fpath, ptemp); + } + } + return rv; +} + +apr_status_t md_util_ftree_remove(const char *path, apr_pool_t *p) +{ + apr_status_t rv = md_util_tree_do(rm_cb, NULL, p, path, 0); + if (APR_SUCCESS == rv) { + rv = apr_dir_remove(path, p); + } + return rv; +} + +/* DNS name checks ********************************************************************************/ + +int md_dns_is_name(apr_pool_t *p, const char *hostname, int need_fqdn) +{ + char c, last = 0; + const char *cp = hostname; + int dots = 0; + + /* Since we use the names in certificates, we need pure ASCII domain names + * and IDN need to be converted to unicode. */ + while ((c = *cp++)) { + switch (c) { + case '.': + if (last == '.') { + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "dns name with ..: %s", + hostname); + return 0; + } + ++dots; + break; + case '-': + break; + default: + if (!apr_isalnum(c)) { + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "dns invalid char %c: %s", + c, hostname); + return 0; + } + break; + } + last = c; + } + + if (last == '.') { /* DNS names may end with '.' */ + --dots; + } + if (need_fqdn && dots <= 0) { /* do not accept just top level domains */ + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "not a FQDN: %s", hostname); + return 0; + } + return 1; /* empty string not allowed */ +} + +int md_dns_is_wildcard(apr_pool_t *p, const char *domain) +{ + if (domain[0] != '*' || domain[1] != '.') return 0; + return md_dns_is_name(p, domain+2, 1); +} + +int md_dns_matches(const char *pattern, const char *domain) +{ + const char *s; + + if (!apr_strnatcasecmp(pattern, domain)) return 1; + if (pattern[0] == '*' && pattern[1] == '.') { + s = strchr(domain, '.'); + if (s && !apr_strnatcasecmp(pattern+1, s)) return 1; + } + return 0; +} + +apr_array_header_t *md_dns_make_minimal(apr_pool_t *p, apr_array_header_t *domains) +{ + apr_array_header_t *minimal; + const char *domain, *pattern; + int i, j, duplicate; + + minimal = apr_array_make(p, domains->nelts, sizeof(const char *)); + for (i = 0; i < domains->nelts; ++i) { + domain = APR_ARRAY_IDX(domains, i, const char*); + duplicate = 0; + /* is it matched in minimal already? */ + for (j = 0; j < minimal->nelts; ++j) { + pattern = APR_ARRAY_IDX(minimal, j, const char*); + if (md_dns_matches(pattern, domain)) { + duplicate = 1; + break; + } + } + if (!duplicate) { + if (!md_dns_is_wildcard(p, domain)) { + /* plain name, will we see a wildcard that replaces it? */ + for (j = i+1; j < domains->nelts; ++j) { + pattern = APR_ARRAY_IDX(domains, j, const char*); + if (md_dns_is_wildcard(p, pattern) && md_dns_matches(pattern, domain)) { + duplicate = 1; + break; + } + } + } + if (!duplicate) { + APR_ARRAY_PUSH(minimal, const char *) = domain; + } + } + } + return minimal; +} + +int md_dns_domains_match(const apr_array_header_t *domains, const char *name) +{ + const char *domain; + int i; + + for (i = 0; i < domains->nelts; ++i) { + domain = APR_ARRAY_IDX(domains, i, const char*); + if (md_dns_matches(domain, name)) return 1; + } + return 0; +} + +const char *md_util_schemify(apr_pool_t *p, const char *s, const char *def_scheme) +{ + const char *cp = s; + while (*cp) { + if (*cp == ':') { + /* could be an url scheme, leave unchanged */ + return s; + } + else if (!apr_isalnum(*cp)) { + break; + } + ++cp; + } + return apr_psprintf(p, "%s:%s", def_scheme, s); +} + +static apr_status_t uri_check(apr_uri_t *uri_parsed, apr_pool_t *p, + const char *uri, const char **perr) +{ + const char *s, *err = NULL; + apr_status_t rv; + + if (APR_SUCCESS != (rv = apr_uri_parse(p, uri, uri_parsed))) { + err = "not an uri"; + } + else if (uri_parsed->scheme) { + if (strlen(uri_parsed->scheme) + 1 >= strlen(uri)) { + err = "missing uri identifier"; + } + else if (!strncmp("http", uri_parsed->scheme, 4)) { + if (!uri_parsed->hostname) { + err = "missing hostname"; + } + else if (!md_dns_is_name(p, uri_parsed->hostname, 0)) { + err = "invalid hostname"; + } + if (uri_parsed->port_str + && (!apr_isdigit(uri_parsed->port_str[0]) + || uri_parsed->port == 0 + || uri_parsed->port > 65353)) { + err = "invalid port"; + } + } + else if (!strcmp("mailto", uri_parsed->scheme)) { + s = strchr(uri, '@'); + if (!s) { + err = "missing @"; + } + else if (strchr(s+1, '@')) { + err = "duplicate @"; + } + else if (s == uri + strlen(uri_parsed->scheme) + 1) { + err = "missing local part"; + } + else if (s == (uri + strlen(uri)-1)) { + err = "missing hostname"; + } + else if (strstr(uri, "..")) { + err = "double period"; + } + } + } + if (strchr(uri, ' ') || strchr(uri, '\t') ) { + err = "whitespace in uri"; + } + + if (err) { + rv = APR_EINVAL; + } + *perr = err; + return rv; +} + +apr_status_t md_util_abs_uri_check(apr_pool_t *p, const char *uri, const char **perr) +{ + apr_uri_t uri_parsed; + apr_status_t rv; + + if (APR_SUCCESS == (rv = uri_check(&uri_parsed, p, uri, perr))) { + if (!uri_parsed.scheme) { + *perr = "missing uri scheme"; + return APR_EINVAL; + } + } + return rv; +} + +apr_status_t md_util_abs_http_uri_check(apr_pool_t *p, const char *uri, const char **perr) +{ + apr_uri_t uri_parsed; + apr_status_t rv; + + if (APR_SUCCESS == (rv = uri_check(&uri_parsed, p, uri, perr))) { + if (!uri_parsed.scheme) { + *perr = "missing uri scheme"; + return APR_EINVAL; + } + if (apr_strnatcasecmp("http", uri_parsed.scheme) + && apr_strnatcasecmp("https", uri_parsed.scheme)) { + *perr = "uri scheme must be http or https"; + return APR_EINVAL; + } + } + return rv; +} + +/* try and retry for a while **********************************************************************/ + +apr_status_t md_util_try(md_util_try_fn *fn, void *baton, int ignore_errs, + apr_interval_time_t timeout, apr_interval_time_t start_delay, + apr_interval_time_t max_delay, int backoff) +{ + apr_status_t rv; + apr_time_t now = apr_time_now(); + apr_time_t giveup = now + timeout; + apr_interval_time_t nap_duration = start_delay? start_delay : apr_time_from_msec(100); + apr_interval_time_t nap_max = max_delay? max_delay : apr_time_from_sec(10); + apr_interval_time_t left; + int i = 0; + + while (1) { + if (APR_SUCCESS == (rv = fn(baton, i++))) { + break; + } + else if (!APR_STATUS_IS_EAGAIN(rv) && !ignore_errs) { + break; + } + + now = apr_time_now(); + if (now > giveup) { + rv = APR_TIMEUP; + break; + } + + left = giveup - now; + if (nap_duration > left) { + nap_duration = left; + } + if (nap_duration > nap_max) { + nap_duration = nap_max; + } + + apr_sleep(nap_duration); + if (backoff) { + nap_duration *= 2; + } + } + return rv; +} + +/* execute process ********************************************************************************/ + +apr_status_t md_util_exec(apr_pool_t *p, const char *cmd, const char * const *argv, + apr_array_header_t *env, int *exit_code) +{ + apr_status_t rv; + apr_procattr_t *procattr; + apr_proc_t *proc; + apr_exit_why_e ewhy; + const char * const *envp = NULL; + char buffer[1024]; + + *exit_code = 0; + if (!(proc = apr_pcalloc(p, sizeof(*proc)))) { + return APR_ENOMEM; + } + if (env && env->nelts > 0) { + apr_array_header_t *nenv; + + nenv = apr_array_copy(p, env); + APR_ARRAY_PUSH(nenv, const char *) = NULL; + envp = (const char * const *)nenv->elts; + } + if ( APR_SUCCESS == (rv = apr_procattr_create(&procattr, p)) + && APR_SUCCESS == (rv = apr_procattr_io_set(procattr, APR_NO_FILE, + APR_NO_PIPE, APR_FULL_BLOCK)) + && APR_SUCCESS == (rv = apr_procattr_cmdtype_set(procattr, APR_PROGRAM)) + && APR_SUCCESS == (rv = apr_proc_create(proc, cmd, argv, envp, procattr, p))) { + + /* read stderr and log on INFO for possible fault analysis. */ + while(APR_SUCCESS == (rv = apr_file_gets(buffer, sizeof(buffer)-1, proc->err))) { + md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p, "cmd(%s) stderr: %s", cmd, buffer); + } + if (!APR_STATUS_IS_EOF(rv)) goto out; + apr_file_close(proc->err); + + if (APR_CHILD_DONE == (rv = apr_proc_wait(proc, exit_code, &ewhy, APR_WAIT))) { + /* let's not dwell on exit stati, but core should signal something's bad */ + if (*exit_code > 127 || APR_PROC_SIGNAL_CORE == ewhy) { + return APR_EINCOMPLETE; + } + return APR_SUCCESS; + } + } +out: + return rv; +} + +/* base64 url encoding ****************************************************************************/ + +#define N6 (unsigned int)-1 + +static const unsigned int BASE64URL_UINT6[] = { +/* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 0 */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 1 */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, 62, N6, N6, /* 2 */ + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, N6, N6, N6, N6, N6, N6, /* 3 */ + N6, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 4 */ + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, N6, N6, N6, N6, 63, /* 5 */ + N6, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 6 */ + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, N6, N6, N6, N6, N6, /* 7 */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 8 */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 9 */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* a */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* b */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* c */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* d */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* e */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6 /* f */ +}; +static const unsigned char BASE64URL_CHARS[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', /* 0 - 9 */ + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', /* 10 - 19 */ + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', /* 20 - 29 */ + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', /* 30 - 39 */ + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', /* 40 - 49 */ + 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', /* 50 - 59 */ + '8', '9', '-', '_', ' ', ' ', ' ', ' ', ' ', ' ', /* 60 - 69 */ +}; + +#define BASE64URL_CHAR(x) BASE64URL_CHARS[ (unsigned int)(x) & 0x3fu ] + +apr_size_t md_util_base64url_decode(md_data_t *decoded, const char *encoded, + apr_pool_t *pool) +{ + const unsigned char *e = (const unsigned char *)encoded; + const unsigned char *p = e; + unsigned char *d; + unsigned int n; + long len, mlen, remain, i; + + while (*p && BASE64URL_UINT6[ *p ] != N6) { + ++p; + } + len = (int)(p - e); + mlen = (len/4)*4; + decoded->data = apr_pcalloc(pool, (apr_size_t)len + 1); + + i = 0; + d = (unsigned char*)decoded->data; + for (; i < mlen; i += 4) { + n = ((BASE64URL_UINT6[ e[i+0] ] << 18) + + (BASE64URL_UINT6[ e[i+1] ] << 12) + + (BASE64URL_UINT6[ e[i+2] ] << 6) + + (BASE64URL_UINT6[ e[i+3] ])); + *d++ = (unsigned char)(n >> 16); + *d++ = (unsigned char)(n >> 8 & 0xffu); + *d++ = (unsigned char)(n & 0xffu); + } + remain = len - mlen; + switch (remain) { + case 2: + n = ((BASE64URL_UINT6[ e[mlen+0] ] << 18) + + (BASE64URL_UINT6[ e[mlen+1] ] << 12)); + *d++ = (unsigned char)(n >> 16); + remain = 1; + break; + case 3: + n = ((BASE64URL_UINT6[ e[mlen+0] ] << 18) + + (BASE64URL_UINT6[ e[mlen+1] ] << 12) + + (BASE64URL_UINT6[ e[mlen+2] ] << 6)); + *d++ = (unsigned char)(n >> 16); + *d++ = (unsigned char)(n >> 8 & 0xffu); + remain = 2; + break; + default: /* do nothing */ + break; + } + decoded->len = (apr_size_t)(mlen/4*3 + remain); + return decoded->len; +} + +const char *md_util_base64url_encode(const md_data_t *data, apr_pool_t *pool) +{ + int i, len = (int)data->len; + apr_size_t slen = ((data->len+2)/3)*4 + 1; /* 0 terminated */ + const unsigned char *udata = (const unsigned char*)data->data; + unsigned char *enc, *p = apr_pcalloc(pool, slen); + + enc = p; + for (i = 0; i < len-2; i+= 3) { + *p++ = BASE64URL_CHAR( (udata[i] >> 2) ); + *p++ = BASE64URL_CHAR( (udata[i] << 4) + (udata[i+1] >> 4) ); + *p++ = BASE64URL_CHAR( (udata[i+1] << 2) + (udata[i+2] >> 6) ); + *p++ = BASE64URL_CHAR( (udata[i+2]) ); + } + + if (i < len) { + *p++ = BASE64URL_CHAR( (udata[i] >> 2) ); + if (i == (len - 1)) { + *p++ = BASE64URL_CHARS[ ((unsigned int)udata[i] << 4) & 0x3fu ]; + } + else { + *p++ = BASE64URL_CHAR( (udata[i] << 4) + (udata[i+1] >> 4) ); + *p++ = BASE64URL_CHAR( (udata[i+1] << 2) ); + } + } + *p++ = '\0'; + return (char *)enc; +} + +/******************************************************************************* + * link header handling + ******************************************************************************/ + +typedef struct { + const char *s; + apr_size_t slen; + apr_size_t i; + apr_size_t link_start; + apr_size_t link_len; + apr_size_t pn_start; + apr_size_t pn_len; + apr_size_t pv_start; + apr_size_t pv_len; +} link_ctx; + +static int attr_char(char c) +{ + switch (c) { + case '!': + case '#': + case '$': + case '&': + case '+': + case '-': + case '.': + case '^': + case '_': + case '`': + case '|': + case '~': + return 1; + default: + return apr_isalnum(c); + } +} + +static int ptoken_char(char c) +{ + switch (c) { + case '!': + case '#': + case '$': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case '-': + case '.': + case '/': + case ':': + case '<': + case '=': + case '>': + case '?': + case '@': + case '[': + case ']': + case '^': + case '_': + case '`': + case '{': + case '|': + case '}': + case '~': + return 1; + default: + return apr_isalnum(c); + } +} + +static int skip_ws(link_ctx *ctx) +{ + char c; + while (ctx->i < ctx->slen + && (((c = ctx->s[ctx->i]) == ' ') || (c == '\t'))) { + ++ctx->i; + } + return (ctx->i < ctx->slen); +} + +static int skip_nonws(link_ctx *ctx) +{ + char c; + while (ctx->i < ctx->slen + && (((c = ctx->s[ctx->i]) != ' ') && (c != '\t'))) { + ++ctx->i; + } + return (ctx->i < ctx->slen); +} + +static unsigned int find_chr(link_ctx *ctx, char c, apr_size_t *pidx) +{ + apr_size_t j; + for (j = ctx->i; j < ctx->slen; ++j) { + if (ctx->s[j] == c) { + *pidx = j; + return 1; + } + } + return 0; +} + +static int read_chr(link_ctx *ctx, char c) +{ + if (ctx->i < ctx->slen && ctx->s[ctx->i] == c) { + ++ctx->i; + return 1; + } + return 0; +} + +static int skip_qstring(link_ctx *ctx) +{ + if (skip_ws(ctx) && read_chr(ctx, '\"')) { + apr_size_t end; + if (find_chr(ctx, '\"', &end)) { + ctx->i = end + 1; + return 1; + } + } + return 0; +} + +static int skip_ptoken(link_ctx *ctx) +{ + if (skip_ws(ctx)) { + apr_size_t i; + for (i = ctx->i; i < ctx->slen && ptoken_char(ctx->s[i]); ++i) { + /* nop */ + } + if (i > ctx->i) { + ctx->i = i; + return 1; + } + } + return 0; +} + + +static int read_link(link_ctx *ctx) +{ + ctx->link_start = ctx->link_len = 0; + if (skip_ws(ctx) && read_chr(ctx, '<')) { + apr_size_t end; + if (find_chr(ctx, '>', &end)) { + ctx->link_start = ctx->i; + ctx->link_len = end - ctx->link_start; + ctx->i = end + 1; + return 1; + } + } + return 0; +} + +static int skip_pname(link_ctx *ctx) +{ + if (skip_ws(ctx)) { + apr_size_t i; + for (i = ctx->i; i < ctx->slen && attr_char(ctx->s[i]); ++i) { + /* nop */ + } + if (i > ctx->i) { + ctx->i = i; + return 1; + } + } + return 0; +} + +static int skip_pvalue(link_ctx *ctx) +{ + if (skip_ws(ctx) && read_chr(ctx, '=')) { + ctx->pv_start = ctx->i; + if (skip_qstring(ctx) || skip_ptoken(ctx)) { + ctx->pv_len = ctx->i - ctx->pv_start; + return 1; + } + } + return 0; +} + +static int skip_param(link_ctx *ctx) +{ + if (skip_ws(ctx) && read_chr(ctx, ';')) { + ctx->pn_start = ctx->i; + ctx->pn_len = 0; + if (skip_pname(ctx)) { + ctx->pn_len = ctx->i - ctx->pn_start; + ctx->pv_len = 0; + skip_pvalue(ctx); /* value is optional */ + return 1; + } + } + return 0; +} + +static int pv_contains(link_ctx *ctx, const char *s) +{ + apr_size_t pvstart = ctx->pv_start; + apr_size_t pvlen = ctx->pv_len; + + if (ctx->s[pvstart] == '\"' && pvlen > 1) { + ++pvstart; + pvlen -= 2; + } + if (pvlen > 0) { + apr_size_t slen = strlen(s); + link_ctx pvctx; + apr_size_t i; + + memset(&pvctx, 0, sizeof(pvctx)); + pvctx.s = ctx->s + pvstart; + pvctx.slen = pvlen; + + for (i = 0; i < pvctx.slen; i = pvctx.i) { + skip_nonws(&pvctx); + if ((pvctx.i - i) == slen && !strncmp(s, pvctx.s + i, slen)) { + return 1; + } + skip_ws(&pvctx); + } + } + return 0; +} + +/* RFC 5988 <https://tools.ietf.org/html/rfc5988#section-6.2.1> + Link = "Link" ":" #link-value + link-value = "<" URI-Reference ">" *( ";" link-param ) + link-param = ( ( "rel" "=" relation-types ) + | ( "anchor" "=" <"> URI-Reference <"> ) + | ( "rev" "=" relation-types ) + | ( "hreflang" "=" Language-Tag ) + | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) ) + | ( "title" "=" quoted-string ) + | ( "title*" "=" ext-value ) + | ( "type" "=" ( media-type | quoted-mt ) ) + | ( link-extension ) ) + link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] ) + | ( ext-name-star "=" ext-value ) + ext-name-star = parmname "*" ; reserved for RFC2231-profiled + ; extensions. Whitespace NOT + ; allowed in between. + ptoken = 1*ptokenchar + ptokenchar = "!" | "#" | "$" | "%" | "&" | "'" | "(" + | ")" | "*" | "+" | "-" | "." | "/" | DIGIT + | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA + | "[" | "]" | "^" | "_" | "`" | "{" | "|" + | "}" | "~" + media-type = type-name "/" subtype-name + quoted-mt = <"> media-type <"> + relation-types = relation-type + | <"> relation-type *( 1*SP relation-type ) <"> + relation-type = reg-rel-type | ext-rel-type + reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" ) + ext-rel-type = URI + + and from <https://tools.ietf.org/html/rfc5987> + parmname = 1*attr-char + attr-char = ALPHA / DIGIT + / "!" / "#" / "$" / "&" / "+" / "-" / "." + / "^" / "_" / "`" / "|" / "~" + */ + +typedef struct { + apr_pool_t *pool; + const char *relation; + const char *url; +} find_ctx; + +static int find_url(void *baton, const char *key, const char *value) +{ + find_ctx *outer = baton; + + if (!apr_strnatcasecmp("link", key)) { + link_ctx ctx; + + memset(&ctx, 0, sizeof(ctx)); + ctx.s = value; + ctx.slen = strlen(value); + + while (read_link(&ctx)) { + while (skip_param(&ctx)) { + if (ctx.pn_len == 3 && !strncmp("rel", ctx.s + ctx.pn_start, 3) + && pv_contains(&ctx, outer->relation)) { + /* this is the link relation we are looking for */ + outer->url = apr_pstrndup(outer->pool, ctx.s + ctx.link_start, ctx.link_len); + return 0; + } + } + } + } + return 1; +} + +const char *md_link_find_relation(const apr_table_t *headers, + apr_pool_t *pool, const char *relation) +{ + find_ctx ctx; + + memset(&ctx, 0, sizeof(ctx)); + ctx.pool = pool; + ctx.relation = relation; + + apr_table_do(find_url, &ctx, headers, NULL); + + return ctx.url; +} + +const char *md_util_parse_ct(apr_pool_t *pool, const char *cth) +{ + char *type; + const char *p; + apr_size_t hlen; + + if (!cth) return NULL; + + for( p = cth; *p && *p != ' ' && *p != ';'; ++p) + ; + hlen = (apr_size_t)(p - cth); + type = apr_pcalloc( pool, hlen + 1 ); + assert(type); + memcpy(type, cth, hlen); + type[hlen] = '\0'; + + return type; + /* Could parse and return parameters here, but we don't need any at present. + */ +} |