| /* |
| * Mod Defender for HAProxy |
| * |
| * Support for the Mod Defender code on non-Apache platforms. |
| * |
| * Copyright 2017 HAProxy Technologies, Dragan Dosen <ddosen@haproxy.com> |
| * |
| * Parts of code based on Apache HTTP Server source |
| * Copyright 2015 The Apache Software Foundation (http://www.apache.org/) |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version |
| * 3 of the License, or (at your option) any later version. |
| * |
| */ |
| #include <limits.h> |
| |
| #include <http_core.h> |
| #include <http_main.h> |
| #include <http_log.h> |
| |
| #include <apr_lib.h> |
| #include <apr_strings.h> |
| #include <apr_fnmatch.h> |
| |
| #include "standalone.h" |
| |
| #define MAX_ARGC 64 |
| #define MAX_INCLUDE_DIR_DEPTH 128 |
| |
| #define SLASHES "/" |
| |
| #define FILTER_POOL apr_hook_global_pool |
| #define TRIE_INITIAL_SIZE 4 |
| |
| typedef struct filter_trie_node filter_trie_node; |
| |
| typedef struct { |
| int c; |
| filter_trie_node *child; |
| } filter_trie_child_ptr; |
| |
| struct filter_trie_node { |
| ap_filter_rec_t *frec; |
| filter_trie_child_ptr *children; |
| int nchildren; |
| int size; |
| }; |
| |
| typedef struct { |
| const char *fname; |
| } fnames; |
| |
| AP_DECLARE_DATA const char *ap_server_root = "/"; |
| |
| void (*logger)(int level, char *str) = NULL; |
| |
| static void str_tolower(char *str) |
| { |
| while (*str) { |
| *str = apr_tolower(*str); |
| ++str; |
| } |
| } |
| |
| static char x2c(const char *what) |
| { |
| char digit; |
| |
| #if !APR_CHARSET_EBCDIC |
| digit = ((what[0] >= 'A') ? ((what[0] & 0xdf) - 'A') + 10 |
| : (what[0] - '0')); |
| digit *= 16; |
| digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 |
| : (what[1] - '0')); |
| #else /*APR_CHARSET_EBCDIC*/ |
| char xstr[5]; |
| xstr[0]='0'; |
| xstr[1]='x'; |
| xstr[2]=what[0]; |
| xstr[3]=what[1]; |
| xstr[4]='\0'; |
| digit = apr_xlate_conv_byte(ap_hdrs_from_ascii, |
| 0xFF & strtol(xstr, NULL, 16)); |
| #endif /*APR_CHARSET_EBCDIC*/ |
| return (digit); |
| } |
| |
| static int unescape_url(char *url, const char *forbid, const char *reserved) |
| { |
| int badesc, badpath; |
| char *x, *y; |
| |
| badesc = 0; |
| badpath = 0; |
| /* Initial scan for first '%'. Don't bother writing values before |
| * seeing a '%' */ |
| y = strchr(url, '%'); |
| if (y == NULL) { |
| return OK; |
| } |
| for (x = y; *y; ++x, ++y) { |
| if (*y != '%') { |
| *x = *y; |
| } |
| else { |
| if (!apr_isxdigit(*(y + 1)) || !apr_isxdigit(*(y + 2))) { |
| badesc = 1; |
| *x = '%'; |
| } |
| else { |
| char decoded; |
| decoded = x2c(y + 1); |
| if ((decoded == '\0') |
| || (forbid && ap_strchr_c(forbid, decoded))) { |
| badpath = 1; |
| *x = decoded; |
| y += 2; |
| } |
| else if (reserved && ap_strchr_c(reserved, decoded)) { |
| *x++ = *y++; |
| *x++ = *y++; |
| *x = *y; |
| } |
| else { |
| *x = decoded; |
| y += 2; |
| } |
| } |
| } |
| } |
| *x = '\0'; |
| if (badesc) { |
| return HTTP_BAD_REQUEST; |
| } |
| else if (badpath) { |
| return HTTP_NOT_FOUND; |
| } |
| else { |
| return OK; |
| } |
| } |
| |
| AP_DECLARE(int) ap_unescape_url(char *url) |
| { |
| /* Traditional */ |
| return unescape_url(url, SLASHES, NULL); |
| } |
| |
| AP_DECLARE(void) ap_get_server_revision(ap_version_t *version) |
| { |
| version->major = AP_SERVER_MAJORVERSION_NUMBER; |
| version->minor = AP_SERVER_MINORVERSION_NUMBER; |
| version->patch = AP_SERVER_PATCHLEVEL_NUMBER; |
| version->add_string = AP_SERVER_ADD_STRING; |
| } |
| |
| static void log_error_core(const char *file, int line, int module_index, |
| int level, |
| apr_status_t status, const server_rec *s, |
| const conn_rec *c, |
| const request_rec *r, apr_pool_t *pool, |
| const char *fmt, va_list args) |
| { |
| char errstr[MAX_STRING_LEN]; |
| |
| apr_vsnprintf(errstr, MAX_STRING_LEN, fmt, args); |
| |
| if (logger != NULL) |
| logger(level, errstr); |
| } |
| |
| AP_DECLARE(void) ap_log_error_(const char *file, int line, int module_index, |
| int level, apr_status_t status, |
| const server_rec *s, const char *fmt, ...) |
| { |
| va_list args; |
| |
| va_start(args, fmt); |
| log_error_core(file, line, module_index, level, status, s, NULL, NULL, |
| NULL, fmt, args); |
| va_end(args); |
| } |
| |
| AP_DECLARE(void) ap_log_rerror_(const char *file, int line, int module_index, |
| int level, apr_status_t status, |
| const request_rec *r, const char *fmt, ...) |
| { |
| va_list args; |
| |
| va_start(args, fmt); |
| log_error_core(file, line, module_index, level, status, r->server, NULL, r, |
| NULL, fmt, args); |
| va_end(args); |
| } |
| |
| AP_DECLARE(void) ap_log_cerror_(const char *file, int line, int module_index, |
| int level, apr_status_t status, |
| const conn_rec *c, const char *fmt, ...) |
| { |
| va_list args; |
| |
| va_start(args, fmt); |
| log_error_core(file, line, module_index, level, status, c->base_server, c, |
| NULL, NULL, fmt, args); |
| va_end(args); |
| } |
| |
| AP_DECLARE(piped_log *) ap_open_piped_log(apr_pool_t *p, const char *program) |
| { |
| return NULL; |
| } |
| |
| AP_DECLARE(apr_file_t *) ap_piped_log_write_fd(piped_log *pl) |
| { |
| return NULL; |
| } |
| |
| static cmd_parms default_parms = |
| {NULL, 0, 0, NULL, -1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; |
| |
| AP_DECLARE(char *) ap_server_root_relative(apr_pool_t *p, const char *file) |
| { |
| char *newpath = NULL; |
| apr_status_t rv; |
| rv = apr_filepath_merge(&newpath, ap_server_root, file, |
| APR_FILEPATH_TRUENAME, p); |
| if (newpath && (rv == APR_SUCCESS || APR_STATUS_IS_EPATHWILD(rv) |
| || APR_STATUS_IS_ENOENT(rv) |
| || APR_STATUS_IS_ENOTDIR(rv))) { |
| return newpath; |
| } |
| else { |
| return NULL; |
| } |
| } |
| |
| AP_DECLARE(apr_status_t) ap_get_brigade(ap_filter_t *next, |
| apr_bucket_brigade *bb, |
| ap_input_mode_t mode, |
| apr_read_type_e block, |
| apr_off_t readbytes) |
| { |
| if (next) { |
| return next->frec->filter_func.in_func(next, bb, mode, block, |
| readbytes); |
| } |
| return AP_NOBODY_READ; |
| } |
| |
| static void |
| argstr_to_table(char *str, apr_table_t *parms) |
| { |
| char *key; |
| char *value; |
| char *strtok_state; |
| |
| if (str == NULL) { |
| return; |
| } |
| |
| key = apr_strtok(str, "&", &strtok_state); |
| while (key) { |
| value = strchr(key, '='); |
| if (value) { |
| *value = '\0'; /* Split the string in two */ |
| value++; /* Skip passed the = */ |
| } |
| else { |
| value = "1"; |
| } |
| ap_unescape_url(key); |
| ap_unescape_url(value); |
| apr_table_set(parms, key, value); |
| key = apr_strtok(NULL, "&", &strtok_state); |
| } |
| } |
| |
| AP_DECLARE(void) ap_args_to_table(request_rec *r, apr_table_t **table) |
| { |
| apr_table_t *t = apr_table_make(r->pool, 10); |
| argstr_to_table(apr_pstrdup(r->pool, r->args), t); |
| *table = t; |
| } |
| |
| /* Link a trie node to its parent |
| */ |
| static void trie_node_link(apr_pool_t *p, filter_trie_node *parent, |
| filter_trie_node *child, int c) |
| { |
| int i, j; |
| |
| if (parent->nchildren == parent->size) { |
| filter_trie_child_ptr *new; |
| parent->size *= 2; |
| new = (filter_trie_child_ptr *)apr_palloc(p, parent->size * |
| sizeof(filter_trie_child_ptr)); |
| memcpy(new, parent->children, parent->nchildren * |
| sizeof(filter_trie_child_ptr)); |
| parent->children = new; |
| } |
| |
| for (i = 0; i < parent->nchildren; i++) { |
| if (c == parent->children[i].c) { |
| return; |
| } |
| else if (c < parent->children[i].c) { |
| break; |
| } |
| } |
| for (j = parent->nchildren; j > i; j--) { |
| parent->children[j].c = parent->children[j - 1].c; |
| parent->children[j].child = parent->children[j - 1].child; |
| } |
| parent->children[i].c = c; |
| parent->children[i].child = child; |
| |
| parent->nchildren++; |
| } |
| |
| /* Allocate a new node for a trie. |
| * If parent is non-NULL, link the new node under the parent node with |
| * key 'c' (or, if an existing child node matches, return that one) |
| */ |
| static filter_trie_node *trie_node_alloc(apr_pool_t *p, |
| filter_trie_node *parent, char c) |
| { |
| filter_trie_node *new_node; |
| if (parent) { |
| int i; |
| for (i = 0; i < parent->nchildren; i++) { |
| if (c == parent->children[i].c) { |
| return parent->children[i].child; |
| } |
| else if (c < parent->children[i].c) { |
| break; |
| } |
| } |
| new_node = (filter_trie_node *)apr_palloc(p, sizeof(filter_trie_node)); |
| trie_node_link(p, parent, new_node, c); |
| } |
| else { /* No parent node */ |
| new_node = (filter_trie_node *)apr_palloc(p, |
| sizeof(filter_trie_node)); |
| } |
| |
| new_node->frec = NULL; |
| new_node->nchildren = 0; |
| new_node->size = TRIE_INITIAL_SIZE; |
| new_node->children = (filter_trie_child_ptr *)apr_palloc(p, |
| new_node->size * sizeof(filter_trie_child_ptr)); |
| return new_node; |
| } |
| |
| static filter_trie_node *registered_output_filters = NULL; |
| static filter_trie_node *registered_input_filters = NULL; |
| |
| |
| static apr_status_t filter_cleanup(void *ctx) |
| { |
| registered_output_filters = NULL; |
| registered_input_filters = NULL; |
| return APR_SUCCESS; |
| } |
| |
| static ap_filter_rec_t *register_filter(const char *name, |
| ap_filter_func filter_func, |
| ap_init_filter_func filter_init, |
| ap_filter_type ftype, |
| filter_trie_node **reg_filter_set) |
| { |
| ap_filter_rec_t *frec; |
| char *normalized_name; |
| const char *n; |
| filter_trie_node *node; |
| |
| if (!*reg_filter_set) { |
| *reg_filter_set = trie_node_alloc(FILTER_POOL, NULL, 0); |
| } |
| |
| normalized_name = apr_pstrdup(FILTER_POOL, name); |
| str_tolower(normalized_name); |
| |
| node = *reg_filter_set; |
| for (n = normalized_name; *n; n++) { |
| filter_trie_node *child = trie_node_alloc(FILTER_POOL, node, *n); |
| if (apr_isalpha(*n)) { |
| trie_node_link(FILTER_POOL, node, child, apr_toupper(*n)); |
| } |
| node = child; |
| } |
| if (node->frec) { |
| frec = node->frec; |
| } |
| else { |
| frec = apr_pcalloc(FILTER_POOL, sizeof(*frec)); |
| node->frec = frec; |
| frec->name = normalized_name; |
| } |
| frec->filter_func = filter_func; |
| frec->filter_init_func = filter_init; |
| frec->ftype = ftype; |
| |
| apr_pool_cleanup_register(FILTER_POOL, NULL, filter_cleanup, |
| apr_pool_cleanup_null); |
| return frec; |
| } |
| |
| AP_DECLARE(ap_filter_rec_t *) ap_register_input_filter(const char *name, |
| ap_in_filter_func filter_func, |
| ap_init_filter_func filter_init, |
| ap_filter_type ftype) |
| { |
| ap_filter_func f; |
| f.in_func = filter_func; |
| return register_filter(name, f, filter_init, ftype, |
| ®istered_input_filters); |
| } |
| |
| static ap_filter_t *add_any_filter_handle(ap_filter_rec_t *frec, void *ctx, |
| request_rec *r, conn_rec *c, |
| ap_filter_t **r_filters, |
| ap_filter_t **p_filters, |
| ap_filter_t **c_filters) |
| { |
| apr_pool_t *p = frec->ftype < AP_FTYPE_CONNECTION && r ? r->pool : c->pool; |
| ap_filter_t *f = apr_palloc(p, sizeof(*f)); |
| ap_filter_t **outf; |
| |
| if (frec->ftype < AP_FTYPE_PROTOCOL) { |
| if (r) { |
| outf = r_filters; |
| } |
| else { |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(00080) |
| "a content filter was added without a request: %s", frec->name); |
| return NULL; |
| } |
| } |
| else if (frec->ftype < AP_FTYPE_CONNECTION) { |
| if (r) { |
| outf = p_filters; |
| } |
| else { |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(00081) |
| "a protocol filter was added without a request: %s", frec->name); |
| return NULL; |
| } |
| } |
| else { |
| outf = c_filters; |
| } |
| |
| f->frec = frec; |
| f->ctx = ctx; |
| /* f->r must always be NULL for connection filters */ |
| f->r = frec->ftype < AP_FTYPE_CONNECTION ? r : NULL; |
| f->c = c; |
| f->next = NULL; |
| |
| if (INSERT_BEFORE(f, *outf)) { |
| f->next = *outf; |
| |
| if (*outf) { |
| ap_filter_t *first = NULL; |
| |
| if (r) { |
| /* If we are adding our first non-connection filter, |
| * Then don't try to find the right location, it is |
| * automatically first. |
| */ |
| if (*r_filters != *c_filters) { |
| first = *r_filters; |
| while (first && (first->next != (*outf))) { |
| first = first->next; |
| } |
| } |
| } |
| if (first && first != (*outf)) { |
| first->next = f; |
| } |
| } |
| *outf = f; |
| } |
| else { |
| ap_filter_t *fscan = *outf; |
| while (!INSERT_BEFORE(f, fscan->next)) |
| fscan = fscan->next; |
| |
| f->next = fscan->next; |
| fscan->next = f; |
| } |
| |
| if (frec->ftype < AP_FTYPE_CONNECTION && (*r_filters == *c_filters)) { |
| *r_filters = *p_filters; |
| } |
| return f; |
| } |
| |
| static ap_filter_t *add_any_filter(const char *name, void *ctx, |
| request_rec *r, conn_rec *c, |
| const filter_trie_node *reg_filter_set, |
| ap_filter_t **r_filters, |
| ap_filter_t **p_filters, |
| ap_filter_t **c_filters) |
| { |
| if (reg_filter_set) { |
| const char *n; |
| const filter_trie_node *node; |
| |
| node = reg_filter_set; |
| for (n = name; *n; n++) { |
| int start, end; |
| start = 0; |
| end = node->nchildren - 1; |
| while (end >= start) { |
| int middle = (end + start) / 2; |
| char ch = node->children[middle].c; |
| if (*n == ch) { |
| node = node->children[middle].child; |
| break; |
| } |
| else if (*n < ch) { |
| end = middle - 1; |
| } |
| else { |
| start = middle + 1; |
| } |
| } |
| if (end < start) { |
| node = NULL; |
| break; |
| } |
| } |
| |
| if (node && node->frec) { |
| return add_any_filter_handle(node->frec, ctx, r, c, r_filters, |
| p_filters, c_filters); |
| } |
| } |
| |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, r ? r->connection : c, APLOGNO(00082) |
| "an unknown filter was not added: %s", name); |
| return NULL; |
| } |
| |
| AP_DECLARE(ap_filter_t *) ap_add_input_filter(const char *name, void *ctx, |
| request_rec *r, conn_rec *c) |
| { |
| return add_any_filter(name, ctx, r, c, registered_input_filters, |
| r ? &r->input_filters : NULL, |
| r ? &r->proto_input_filters : NULL, |
| &c->input_filters); |
| } |
| |
| static void remove_any_filter(ap_filter_t *f, ap_filter_t **r_filt, ap_filter_t **p_filt, |
| ap_filter_t **c_filt) |
| { |
| ap_filter_t **curr = r_filt ? r_filt : c_filt; |
| ap_filter_t *fscan = *curr; |
| |
| if (p_filt && *p_filt == f) |
| *p_filt = (*p_filt)->next; |
| |
| if (*curr == f) { |
| *curr = (*curr)->next; |
| return; |
| } |
| |
| while (fscan->next != f) { |
| if (!(fscan = fscan->next)) { |
| return; |
| } |
| } |
| |
| fscan->next = f->next; |
| } |
| |
| AP_DECLARE(void) ap_remove_input_filter(ap_filter_t *f) |
| { |
| remove_any_filter(f, f->r ? &f->r->input_filters : NULL, |
| f->r ? &f->r->proto_input_filters : NULL, |
| &f->c->input_filters); |
| } |
| |
| static int cfg_closefile(ap_configfile_t *cfp) |
| { |
| #ifdef DEBUG |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, |
| "Done with config file %s", cfp->name); |
| #endif |
| return (cfp->close == NULL) ? 0 : cfp->close(cfp->param); |
| } |
| |
| /* we can't use apr_file_* directly because of linking issues on Windows */ |
| static apr_status_t cfg_close(void *param) |
| { |
| return apr_file_close(param); |
| } |
| |
| static apr_status_t cfg_getch(char *ch, void *param) |
| { |
| return apr_file_getc(ch, param); |
| } |
| |
| static apr_status_t cfg_getstr(void *buf, apr_size_t bufsiz, void *param) |
| { |
| return apr_file_gets(buf, bufsiz, param); |
| } |
| |
| /* Read one line from open ap_configfile_t, strip LF, increase line number */ |
| /* If custom handler does not define a getstr() function, read char by char */ |
| static apr_status_t cfg_getline_core(char *buf, apr_size_t bufsize, |
| apr_size_t offset, ap_configfile_t *cfp) |
| { |
| apr_status_t rc; |
| /* If a "get string" function is defined, use it */ |
| if (cfp->getstr != NULL) { |
| char *cp; |
| char *cbuf = buf + offset; |
| apr_size_t cbufsize = bufsize - offset; |
| |
| while (1) { |
| ++cfp->line_number; |
| rc = cfp->getstr(cbuf, cbufsize, cfp->param); |
| if (rc == APR_EOF) { |
| if (cbuf != buf + offset) { |
| *cbuf = '\0'; |
| break; |
| } |
| else { |
| return APR_EOF; |
| } |
| } |
| if (rc != APR_SUCCESS) { |
| return rc; |
| } |
| |
| /* |
| * check for line continuation, |
| * i.e. match [^\\]\\[\r]\n only |
| */ |
| cp = cbuf; |
| cp += strlen(cp); |
| if (cp > buf && cp[-1] == LF) { |
| cp--; |
| if (cp > buf && cp[-1] == CR) |
| cp--; |
| if (cp > buf && cp[-1] == '\\') { |
| cp--; |
| /* |
| * line continuation requested - |
| * then remove backslash and continue |
| */ |
| cbufsize -= (cp-cbuf); |
| cbuf = cp; |
| continue; |
| } |
| } |
| else if (cp - buf >= bufsize - 1) { |
| return APR_ENOSPC; |
| } |
| break; |
| } |
| } else { |
| /* No "get string" function defined; read character by character */ |
| apr_size_t i = offset; |
| |
| if (bufsize < 2) { |
| /* too small, assume caller is crazy */ |
| return APR_EINVAL; |
| } |
| buf[offset] = '\0'; |
| |
| while (1) { |
| char c; |
| rc = cfp->getch(&c, cfp->param); |
| if (rc == APR_EOF) { |
| if (i > offset) |
| break; |
| else |
| return APR_EOF; |
| } |
| if (rc != APR_SUCCESS) |
| return rc; |
| if (c == LF) { |
| ++cfp->line_number; |
| /* check for line continuation */ |
| if (i > 0 && buf[i-1] == '\\') { |
| i--; |
| continue; |
| } |
| else { |
| break; |
| } |
| } |
| buf[i] = c; |
| ++i; |
| if (i >= bufsize - 1) { |
| return APR_ENOSPC; |
| } |
| } |
| buf[i] = '\0'; |
| } |
| return APR_SUCCESS; |
| } |
| |
| static int cfg_trim_line(char *buf) |
| { |
| char *start, *end; |
| /* |
| * Leading and trailing white space is eliminated completely |
| */ |
| start = buf; |
| while (apr_isspace(*start)) |
| ++start; |
| /* blast trailing whitespace */ |
| end = &start[strlen(start)]; |
| while (--end >= start && apr_isspace(*end)) |
| *end = '\0'; |
| /* Zap leading whitespace by shifting */ |
| if (start != buf) |
| memmove(buf, start, end - start + 2); |
| #ifdef DEBUG_CFG_LINES |
| ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, APLOGNO(00555) "Read config: '%s'", buf); |
| #endif |
| return end - start + 1; |
| } |
| |
| /* Read one line from open ap_configfile_t, strip LF, increase line number */ |
| /* If custom handler does not define a getstr() function, read char by char */ |
| static apr_status_t cfg_getline(char *buf, apr_size_t bufsize, |
| ap_configfile_t *cfp) |
| { |
| apr_status_t rc = cfg_getline_core(buf, bufsize, 0, cfp); |
| if (rc == APR_SUCCESS) |
| cfg_trim_line(buf); |
| return rc; |
| } |
| |
| static char *substring_conf(apr_pool_t *p, const char *start, int len, |
| char quote) |
| { |
| char *result = apr_palloc(p, len + 1); |
| char *resp = result; |
| int i; |
| |
| for (i = 0; i < len; ++i) { |
| if (start[i] == '\\' && (start[i + 1] == '\\' |
| || (quote && start[i + 1] == quote))) |
| *resp++ = start[++i]; |
| else |
| *resp++ = start[i]; |
| } |
| |
| *resp++ = '\0'; |
| #if RESOLVE_ENV_PER_TOKEN |
| return (char *)ap_resolve_env(p,result); |
| #else |
| return result; |
| #endif |
| } |
| |
| static char *getword_conf(apr_pool_t *p, const char **line) |
| { |
| const char *str = *line, *strend; |
| char *res; |
| char quote; |
| |
| while (apr_isspace(*str)) |
| ++str; |
| |
| if (!*str) { |
| *line = str; |
| return ""; |
| } |
| |
| if ((quote = *str) == '"' || quote == '\'') { |
| strend = str + 1; |
| while (*strend && *strend != quote) { |
| if (*strend == '\\' && strend[1] && |
| (strend[1] == quote || strend[1] == '\\')) { |
| strend += 2; |
| } |
| else { |
| ++strend; |
| } |
| } |
| res = substring_conf(p, str + 1, strend - str - 1, quote); |
| |
| if (*strend == quote) |
| ++strend; |
| } |
| else { |
| strend = str; |
| while (*strend && !apr_isspace(*strend)) |
| ++strend; |
| |
| res = substring_conf(p, str, strend - str, 0); |
| } |
| |
| while (apr_isspace(*strend)) |
| ++strend; |
| *line = strend; |
| return res; |
| } |
| |
| /* Open a ap_configfile_t as FILE, return open ap_configfile_t struct pointer */ |
| static apr_status_t pcfg_openfile(ap_configfile_t **ret_cfg, |
| apr_pool_t *p, const char *name) |
| { |
| ap_configfile_t *new_cfg; |
| apr_file_t *file = NULL; |
| apr_finfo_t finfo; |
| apr_status_t status; |
| #ifdef DEBUG |
| char buf[120]; |
| #endif |
| |
| if (name == NULL) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00552) |
| "Internal error: pcfg_openfile() called with NULL filename"); |
| return APR_EBADF; |
| } |
| |
| status = apr_file_open(&file, name, APR_READ | APR_BUFFERED, |
| APR_OS_DEFAULT, p); |
| #ifdef DEBUG |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, APLOGNO(00553) |
| "Opening config file %s (%s)", |
| name, (status != APR_SUCCESS) ? |
| apr_strerror(status, buf, sizeof(buf)) : "successful"); |
| #endif |
| if (status != APR_SUCCESS) |
| return status; |
| |
| status = apr_file_info_get(&finfo, APR_FINFO_TYPE, file); |
| if (status != APR_SUCCESS) |
| return status; |
| |
| if (finfo.filetype != APR_REG && |
| strcmp(name, "/dev/null") != 0) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00554) |
| "Access to file %s denied by server: not a regular file", |
| name); |
| apr_file_close(file); |
| return APR_EBADF; |
| } |
| |
| new_cfg = apr_palloc(p, sizeof(*new_cfg)); |
| new_cfg->param = file; |
| new_cfg->name = apr_pstrdup(p, name); |
| new_cfg->getch = cfg_getch; |
| new_cfg->getstr = cfg_getstr; |
| new_cfg->close = cfg_close; |
| new_cfg->line_number = 0; |
| *ret_cfg = new_cfg; |
| return APR_SUCCESS; |
| } |
| |
| static const command_rec *find_command(const char *name, |
| const command_rec *cmds) |
| { |
| while (cmds->name) { |
| if (!strcasecmp(name, cmds->name)) |
| return cmds; |
| ++cmds; |
| } |
| |
| return NULL; |
| } |
| |
| static const char *invoke_cmd(const command_rec *cmd, cmd_parms *parms, |
| void *mconfig, const char *args) |
| { |
| int override_list_ok = 0; |
| char *w, *w2, *w3; |
| const char *errmsg = NULL; |
| |
| /** Have we been provided a list of acceptable directives? */ |
| if (parms->override_list != NULL) { |
| if (apr_table_get(parms->override_list, cmd->name) != NULL) { |
| override_list_ok = 1; |
| } |
| } |
| |
| if ((parms->override & cmd->req_override) == 0 && !override_list_ok) { |
| return apr_pstrcat(parms->pool, cmd->name, |
| " not allowed here", NULL); |
| } |
| |
| parms->info = cmd->cmd_data; |
| parms->cmd = cmd; |
| |
| switch (cmd->args_how) { |
| case RAW_ARGS: |
| #ifdef RESOLVE_ENV_PER_TOKEN |
| args = ap_resolve_env(parms->pool,args); |
| #endif |
| return cmd->AP_RAW_ARGS(parms, mconfig, args); |
| |
| case TAKE_ARGV: |
| { |
| char *argv[MAX_ARGC]; |
| int argc = 0; |
| |
| do { |
| w = getword_conf(parms->pool, &args); |
| if (*w == '\0' && *args == '\0') { |
| break; |
| } |
| argv[argc] = w; |
| argc++; |
| } while (argc < MAX_ARGC && *args != '\0'); |
| |
| return cmd->AP_TAKE_ARGV(parms, mconfig, argc, argv); |
| } |
| |
| case NO_ARGS: |
| if (*args != 0) |
| return apr_pstrcat(parms->pool, cmd->name, " takes no arguments", |
| NULL); |
| |
| return cmd->AP_NO_ARGS(parms, mconfig); |
| |
| case TAKE1: |
| w = getword_conf(parms->pool, &args); |
| |
| if (*w == '\0' || *args != 0) |
| return apr_pstrcat(parms->pool, cmd->name, " takes one argument", |
| cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); |
| |
| return cmd->AP_TAKE1(parms, mconfig, w); |
| |
| case TAKE2: |
| w = getword_conf(parms->pool, &args); |
| w2 = getword_conf(parms->pool, &args); |
| |
| if (*w == '\0' || *w2 == '\0' || *args != 0) |
| return apr_pstrcat(parms->pool, cmd->name, " takes two arguments", |
| cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); |
| |
| return cmd->AP_TAKE2(parms, mconfig, w, w2); |
| |
| case TAKE12: |
| w = getword_conf(parms->pool, &args); |
| w2 = getword_conf(parms->pool, &args); |
| |
| if (*w == '\0' || *args != 0) |
| return apr_pstrcat(parms->pool, cmd->name, " takes 1-2 arguments", |
| cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); |
| |
| return cmd->AP_TAKE2(parms, mconfig, w, *w2 ? w2 : NULL); |
| |
| case TAKE3: |
| w = getword_conf(parms->pool, &args); |
| w2 = getword_conf(parms->pool, &args); |
| w3 = getword_conf(parms->pool, &args); |
| |
| if (*w == '\0' || *w2 == '\0' || *w3 == '\0' || *args != 0) |
| return apr_pstrcat(parms->pool, cmd->name, " takes three arguments", |
| cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); |
| |
| return cmd->AP_TAKE3(parms, mconfig, w, w2, w3); |
| |
| case TAKE23: |
| w = getword_conf(parms->pool, &args); |
| w2 = getword_conf(parms->pool, &args); |
| w3 = *args ? getword_conf(parms->pool, &args) : NULL; |
| |
| if (*w == '\0' || *w2 == '\0' || *args != 0) |
| return apr_pstrcat(parms->pool, cmd->name, |
| " takes two or three arguments", |
| cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); |
| |
| return cmd->AP_TAKE3(parms, mconfig, w, w2, w3); |
| |
| case TAKE123: |
| w = getword_conf(parms->pool, &args); |
| w2 = *args ? getword_conf(parms->pool, &args) : NULL; |
| w3 = *args ? getword_conf(parms->pool, &args) : NULL; |
| |
| if (*w == '\0' || *args != 0) |
| return apr_pstrcat(parms->pool, cmd->name, |
| " takes one, two or three arguments", |
| cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); |
| |
| return cmd->AP_TAKE3(parms, mconfig, w, w2, w3); |
| |
| case TAKE13: |
| w = getword_conf(parms->pool, &args); |
| w2 = *args ? getword_conf(parms->pool, &args) : NULL; |
| w3 = *args ? getword_conf(parms->pool, &args) : NULL; |
| |
| if (*w == '\0' || (w2 && *w2 && !w3) || *args != 0) |
| return apr_pstrcat(parms->pool, cmd->name, |
| " takes one or three arguments", |
| cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); |
| |
| return cmd->AP_TAKE3(parms, mconfig, w, w2, w3); |
| |
| case ITERATE: |
| w = getword_conf(parms->pool, &args); |
| |
| if (*w == '\0') |
| return apr_pstrcat(parms->pool, cmd->name, |
| " requires at least one argument", |
| cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); |
| |
| while (*w != '\0') { |
| errmsg = cmd->AP_TAKE1(parms, mconfig, w); |
| |
| if (errmsg && strcmp(errmsg, DECLINE_CMD) != 0) |
| return errmsg; |
| |
| w = getword_conf(parms->pool, &args); |
| } |
| |
| return errmsg; |
| |
| case ITERATE2: |
| w = getword_conf(parms->pool, &args); |
| |
| if (*w == '\0' || *args == 0) |
| return apr_pstrcat(parms->pool, cmd->name, |
| " requires at least two arguments", |
| cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); |
| |
| while (*(w2 = getword_conf(parms->pool, &args)) != '\0') { |
| |
| errmsg = cmd->AP_TAKE2(parms, mconfig, w, w2); |
| |
| if (errmsg && strcmp(errmsg, DECLINE_CMD) != 0) |
| return errmsg; |
| } |
| |
| return errmsg; |
| |
| case FLAG: |
| /* |
| * This is safe to use temp_pool here, because the 'flag' itself is not |
| * forwarded as-is |
| */ |
| w = getword_conf(parms->temp_pool, &args); |
| |
| if (*w == '\0' || (strcasecmp(w, "on") && strcasecmp(w, "off"))) |
| return apr_pstrcat(parms->pool, cmd->name, " must be On or Off", |
| NULL); |
| |
| return cmd->AP_FLAG(parms, mconfig, strcasecmp(w, "off") != 0); |
| |
| default: |
| return apr_pstrcat(parms->pool, cmd->name, |
| " is improperly configured internally (server bug)", |
| NULL); |
| } |
| } |
| |
| static int is_directory(apr_pool_t *p, const char *path) |
| { |
| apr_finfo_t finfo; |
| |
| if (apr_stat(&finfo, path, APR_FINFO_TYPE, p) != APR_SUCCESS) |
| return 0; /* in error condition, just return no */ |
| |
| return (finfo.filetype == APR_DIR); |
| } |
| |
| static char *make_full_path(apr_pool_t *a, const char *src1, |
| const char *src2) |
| { |
| apr_size_t len1, len2; |
| char *path; |
| |
| len1 = strlen(src1); |
| len2 = strlen(src2); |
| /* allocate +3 for '/' delimiter, trailing NULL and overallocate |
| * one extra byte to allow the caller to add a trailing '/' |
| */ |
| path = (char *)apr_palloc(a, len1 + len2 + 3); |
| if (len1 == 0) { |
| *path = '/'; |
| memcpy(path + 1, src2, len2 + 1); |
| } |
| else { |
| char *next; |
| memcpy(path, src1, len1); |
| next = path + len1; |
| if (next[-1] != '/') { |
| *next++ = '/'; |
| } |
| memcpy(next, src2, len2 + 1); |
| } |
| return path; |
| } |
| |
| static int fname_alphasort(const void *fn1, const void *fn2) |
| { |
| const fnames *f1 = fn1; |
| const fnames *f2 = fn2; |
| |
| return strcmp(f1->fname,f2->fname); |
| } |
| |
| static const char *process_resource_config(const char *fname, |
| apr_array_header_t *ari, |
| apr_pool_t *p, |
| apr_pool_t *ptemp) |
| { |
| *(char **)apr_array_push(ari) = (char *)fname; |
| return NULL; |
| } |
| |
| static const char *process_resource_config_nofnmatch(const char *fname, |
| apr_array_header_t *ari, |
| apr_pool_t *p, |
| apr_pool_t *ptemp, |
| unsigned depth, |
| int optional) |
| { |
| const char *error; |
| apr_status_t rv; |
| |
| if (is_directory(ptemp, fname)) { |
| apr_dir_t *dirp; |
| apr_finfo_t dirent; |
| int current; |
| apr_array_header_t *candidates = NULL; |
| fnames *fnew; |
| char *path = apr_pstrdup(ptemp, fname); |
| |
| if (++depth > MAX_INCLUDE_DIR_DEPTH) { |
| return apr_psprintf(p, "Directory %s exceeds the maximum include " |
| "directory nesting level of %u. You have " |
| "probably a recursion somewhere.", path, |
| MAX_INCLUDE_DIR_DEPTH); |
| } |
| |
| /* |
| * first course of business is to grok all the directory |
| * entries here and store 'em away. Recall we need full pathnames |
| * for this. |
| */ |
| rv = apr_dir_open(&dirp, path, ptemp); |
| if (rv != APR_SUCCESS) { |
| return apr_psprintf(p, "Could not open config directory %s: %pm", |
| path, &rv); |
| } |
| |
| candidates = apr_array_make(ptemp, 1, sizeof(fnames)); |
| while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) { |
| /* strip out '.' and '..' */ |
| if (strcmp(dirent.name, ".") |
| && strcmp(dirent.name, "..")) { |
| fnew = (fnames *) apr_array_push(candidates); |
| fnew->fname = make_full_path(ptemp, path, dirent.name); |
| } |
| } |
| |
| apr_dir_close(dirp); |
| if (candidates->nelts != 0) { |
| qsort((void *) candidates->elts, candidates->nelts, |
| sizeof(fnames), fname_alphasort); |
| |
| /* |
| * Now recurse these... we handle errors and subdirectories |
| * via the recursion, which is nice |
| */ |
| for (current = 0; current < candidates->nelts; ++current) { |
| fnew = &((fnames *) candidates->elts)[current]; |
| error = process_resource_config_nofnmatch(fnew->fname, |
| ari, p, ptemp, |
| depth, optional); |
| if (error) { |
| return error; |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| return process_resource_config(fname, ari, p, ptemp); |
| } |
| |
| static const char *process_resource_config_fnmatch(const char *path, |
| const char *fname, |
| apr_array_header_t *ari, |
| apr_pool_t *p, |
| apr_pool_t *ptemp, |
| unsigned depth, |
| int optional) |
| { |
| const char *rest; |
| apr_status_t rv; |
| apr_dir_t *dirp; |
| apr_finfo_t dirent; |
| apr_array_header_t *candidates = NULL; |
| fnames *fnew; |
| int current; |
| |
| /* find the first part of the filename */ |
| rest = ap_strchr_c(fname, '/'); |
| if (rest) { |
| fname = apr_pstrndup(ptemp, fname, rest - fname); |
| rest++; |
| } |
| |
| /* optimisation - if the filename isn't a wildcard, process it directly */ |
| if (!apr_fnmatch_test(fname)) { |
| path = make_full_path(ptemp, path, fname); |
| if (!rest) { |
| return process_resource_config_nofnmatch(path, |
| ari, p, |
| ptemp, 0, optional); |
| } |
| else { |
| return process_resource_config_fnmatch(path, rest, |
| ari, p, |
| ptemp, 0, optional); |
| } |
| } |
| |
| /* |
| * first course of business is to grok all the directory |
| * entries here and store 'em away. Recall we need full pathnames |
| * for this. |
| */ |
| rv = apr_dir_open(&dirp, path, ptemp); |
| if (rv != APR_SUCCESS) { |
| return apr_psprintf(p, "Could not open config directory %s: %pm", |
| path, &rv); |
| } |
| |
| candidates = apr_array_make(ptemp, 1, sizeof(fnames)); |
| while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp) == APR_SUCCESS) { |
| /* strip out '.' and '..' */ |
| if (strcmp(dirent.name, ".") |
| && strcmp(dirent.name, "..") |
| && (apr_fnmatch(fname, dirent.name, |
| APR_FNM_PERIOD) == APR_SUCCESS)) { |
| const char *full_path = make_full_path(ptemp, path, dirent.name); |
| /* If matching internal to path, and we happen to match something |
| * other than a directory, skip it |
| */ |
| if (rest && (rv == APR_SUCCESS) && (dirent.filetype != APR_DIR)) { |
| continue; |
| } |
| fnew = (fnames *) apr_array_push(candidates); |
| fnew->fname = full_path; |
| } |
| } |
| |
| apr_dir_close(dirp); |
| if (candidates->nelts != 0) { |
| const char *error; |
| |
| qsort((void *) candidates->elts, candidates->nelts, |
| sizeof(fnames), fname_alphasort); |
| |
| /* |
| * Now recurse these... we handle errors and subdirectories |
| * via the recursion, which is nice |
| */ |
| for (current = 0; current < candidates->nelts; ++current) { |
| fnew = &((fnames *) candidates->elts)[current]; |
| if (!rest) { |
| error = process_resource_config_nofnmatch(fnew->fname, |
| ari, p, |
| ptemp, 0, optional); |
| } |
| else { |
| error = process_resource_config_fnmatch(fnew->fname, rest, |
| ari, p, |
| ptemp, 0, optional); |
| } |
| if (error) { |
| return error; |
| } |
| } |
| } |
| else { |
| |
| if (!optional) { |
| return apr_psprintf(p, "No matches for the wildcard '%s' in '%s', failing " |
| "(use IncludeOptional if required)", fname, path); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static const char *process_fnmatch_configs(const char *fname, |
| apr_array_header_t *ari, |
| apr_pool_t *p, |
| apr_pool_t *ptemp, |
| int optional) |
| { |
| if (!apr_fnmatch_test(fname)) { |
| return process_resource_config_nofnmatch(fname, ari, p, ptemp, 0, optional); |
| } |
| else { |
| apr_status_t status; |
| const char *rootpath, *filepath = fname; |
| |
| /* locate the start of the directories proper */ |
| status = apr_filepath_root(&rootpath, &filepath, APR_FILEPATH_TRUENAME, ptemp); |
| |
| /* we allow APR_SUCCESS and APR_EINCOMPLETE */ |
| if (APR_ERELATIVE == status) { |
| return apr_pstrcat(p, "Include must have an absolute path, ", fname, NULL); |
| } |
| else if (APR_EBADPATH == status) { |
| return apr_pstrcat(p, "Include has a bad path, ", fname, NULL); |
| } |
| |
| /* walk the filepath */ |
| return process_resource_config_fnmatch(rootpath, filepath, ari, p, ptemp, |
| 0, optional); |
| } |
| } |
| |
| const char *read_module_config(server_rec *s, void *mconfig, |
| const command_rec *cmds, |
| apr_pool_t *p, apr_pool_t *ptemp, |
| const char *filename) |
| { |
| apr_array_header_t *ari, *arr; |
| ap_directive_t *newdir; |
| cmd_parms *parms; |
| |
| char line[MAX_STRING_LEN]; |
| const char *errmsg; |
| const char *err = NULL; |
| |
| ari = apr_array_make(p, 1, sizeof(char *)); |
| arr = apr_array_make(p, 1, sizeof(cmd_parms)); |
| |
| errmsg = process_fnmatch_configs(filename, ari, p, ptemp, 0); |
| |
| if (errmsg != NULL) |
| goto out; |
| |
| while (ari->nelts || arr->nelts) { |
| |
| /* similar to process_command_config() */ |
| if (ari->nelts) { |
| char *inc = *(char **)apr_array_pop(ari); |
| |
| parms = (cmd_parms *)apr_array_push(arr); |
| *parms = default_parms; |
| parms->pool = p; |
| parms->temp_pool = ptemp; |
| parms->server = s; |
| parms->override = (RSRC_CONF | ACCESS_CONF); |
| parms->override_opts = OPT_ALL | OPT_SYM_OWNER | OPT_MULTI; |
| |
| if (pcfg_openfile(&parms->config_file, p, inc) != APR_SUCCESS) { |
| apr_array_pop(arr); |
| errmsg = apr_pstrcat(p, "Cannot open file: ", inc, NULL); |
| goto out; |
| } |
| } |
| |
| if (arr->nelts > MAX_INCLUDE_DIR_DEPTH) { |
| errmsg = apr_psprintf(p, "Exceeded the maximum include " |
| "directory nesting level of %u. You have " |
| "probably a recursion somewhere.", |
| MAX_INCLUDE_DIR_DEPTH); |
| goto out; |
| } |
| |
| if (!(parms = (cmd_parms *)apr_array_pop(arr))) |
| break; |
| |
| while (!(cfg_getline(line, MAX_STRING_LEN, parms->config_file))) { |
| |
| const command_rec *cmd; |
| char *cmd_name; |
| const char *args = line; |
| int optional = 0; |
| |
| if (*line == '#' || *line == '\0') |
| continue; |
| |
| if (!(cmd_name = getword_conf(p, &args))) |
| continue; |
| |
| /* similar to invoke_cmd() */ |
| if (!strcasecmp(cmd_name, "IncludeOptional") || |
| !strcasecmp(cmd_name, "Include")) |
| { |
| char *w, *fullname; |
| |
| if (!strcasecmp(cmd_name, "IncludeOptional")) |
| optional = 1; |
| |
| w = getword_conf(parms->pool, &args); |
| |
| if (*w == '\0' || *args != 0) { |
| errmsg = apr_pstrcat(parms->pool, cmd_name, " takes one argument", NULL); |
| goto out; |
| } |
| |
| fullname = ap_server_root_relative(ptemp, w); |
| errmsg = process_fnmatch_configs(fullname, ari, p, ptemp, optional); |
| |
| *(cmd_parms *)apr_array_push(arr) = *parms; |
| |
| if(errmsg != NULL) |
| goto out; |
| |
| parms = NULL; |
| break; |
| } |
| |
| if (!(cmd = find_command(cmd_name, cmds))) { |
| errmsg = apr_pstrcat(parms->pool, "Invalid command '", |
| cmd_name, "'", NULL); |
| goto out; |
| } |
| |
| newdir = apr_pcalloc(p, sizeof(ap_directive_t)); |
| newdir->filename = parms->config_file->name; |
| newdir->line_num = parms->config_file->line_number; |
| newdir->directive = cmd_name; |
| newdir->args = apr_pstrdup(p, args); |
| |
| parms->directive = newdir; |
| |
| if ((errmsg = invoke_cmd(cmd, parms, mconfig, args)) != NULL) |
| break; |
| } |
| |
| if (parms != NULL) |
| cfg_closefile(parms->config_file); |
| |
| if (errmsg != NULL) |
| break; |
| } |
| |
| if (errmsg) { |
| if (parms) { |
| err = apr_psprintf(p, "Syntax error on line %d of %s: %s", |
| parms->config_file->line_number, |
| parms->config_file->name, |
| errmsg); |
| errmsg = err; |
| } |
| } |
| |
| out: |
| |
| while ((parms = (cmd_parms *)apr_array_pop(arr)) != NULL) |
| cfg_closefile(parms->config_file); |
| |
| return errmsg; |
| } |
| |
| int lookup_builtin_method(const char *method, apr_size_t len) |
| { |
| /* Note: from Apache 2 HTTP Server source. */ |
| |
| /* Note: the following code was generated by the "shilka" tool from |
| the "cocom" parsing/compilation toolkit. It is an optimized lookup |
| based on analysis of the input keywords. Postprocessing was done |
| on the shilka output, but the basic structure and analysis is |
| from there. Should new HTTP methods be added, then manual insertion |
| into this code is fine, or simply re-running the shilka tool on |
| the appropriate input. */ |
| |
| /* Note: it is also quite reasonable to just use our method_registry, |
| but I'm assuming (probably incorrectly) we want more speed here |
| (based on the optimizations the previous code was doing). */ |
| |
| switch (len) |
| { |
| case 3: |
| switch (method[0]) |
| { |
| case 'P': |
| return (method[1] == 'U' |
| && method[2] == 'T' |
| ? M_PUT : UNKNOWN_METHOD); |
| case 'G': |
| return (method[1] == 'E' |
| && method[2] == 'T' |
| ? M_GET : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 4: |
| switch (method[0]) |
| { |
| case 'H': |
| return (method[1] == 'E' |
| && method[2] == 'A' |
| && method[3] == 'D' |
| ? M_GET : UNKNOWN_METHOD); |
| case 'P': |
| return (method[1] == 'O' |
| && method[2] == 'S' |
| && method[3] == 'T' |
| ? M_POST : UNKNOWN_METHOD); |
| case 'M': |
| return (method[1] == 'O' |
| && method[2] == 'V' |
| && method[3] == 'E' |
| ? M_MOVE : UNKNOWN_METHOD); |
| case 'L': |
| return (method[1] == 'O' |
| && method[2] == 'C' |
| && method[3] == 'K' |
| ? M_LOCK : UNKNOWN_METHOD); |
| case 'C': |
| return (method[1] == 'O' |
| && method[2] == 'P' |
| && method[3] == 'Y' |
| ? M_COPY : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 5: |
| switch (method[2]) |
| { |
| case 'T': |
| return (memcmp(method, "PATCH", 5) == 0 |
| ? M_PATCH : UNKNOWN_METHOD); |
| case 'R': |
| return (memcmp(method, "MERGE", 5) == 0 |
| ? M_MERGE : UNKNOWN_METHOD); |
| case 'C': |
| return (memcmp(method, "MKCOL", 5) == 0 |
| ? M_MKCOL : UNKNOWN_METHOD); |
| case 'B': |
| return (memcmp(method, "LABEL", 5) == 0 |
| ? M_LABEL : UNKNOWN_METHOD); |
| case 'A': |
| return (memcmp(method, "TRACE", 5) == 0 |
| ? M_TRACE : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 6: |
| switch (method[0]) |
| { |
| case 'U': |
| switch (method[5]) |
| { |
| case 'K': |
| return (memcmp(method, "UNLOCK", 6) == 0 |
| ? M_UNLOCK : UNKNOWN_METHOD); |
| case 'E': |
| return (memcmp(method, "UPDATE", 6) == 0 |
| ? M_UPDATE : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| case 'R': |
| return (memcmp(method, "REPORT", 6) == 0 |
| ? M_REPORT : UNKNOWN_METHOD); |
| case 'D': |
| return (memcmp(method, "DELETE", 6) == 0 |
| ? M_DELETE : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 7: |
| switch (method[1]) |
| { |
| case 'P': |
| return (memcmp(method, "OPTIONS", 7) == 0 |
| ? M_OPTIONS : UNKNOWN_METHOD); |
| case 'O': |
| return (memcmp(method, "CONNECT", 7) == 0 |
| ? M_CONNECT : UNKNOWN_METHOD); |
| case 'H': |
| return (memcmp(method, "CHECKIN", 7) == 0 |
| ? M_CHECKIN : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 8: |
| switch (method[0]) |
| { |
| case 'P': |
| return (memcmp(method, "PROPFIND", 8) == 0 |
| ? M_PROPFIND : UNKNOWN_METHOD); |
| case 'C': |
| return (memcmp(method, "CHECKOUT", 8) == 0 |
| ? M_CHECKOUT : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 9: |
| return (memcmp(method, "PROPPATCH", 9) == 0 |
| ? M_PROPPATCH : UNKNOWN_METHOD); |
| |
| case 10: |
| switch (method[0]) |
| { |
| case 'U': |
| return (memcmp(method, "UNCHECKOUT", 10) == 0 |
| ? M_UNCHECKOUT : UNKNOWN_METHOD); |
| case 'M': |
| return (memcmp(method, "MKACTIVITY", 10) == 0 |
| ? M_MKACTIVITY : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 11: |
| return (memcmp(method, "MKWORKSPACE", 11) == 0 |
| ? M_MKWORKSPACE : UNKNOWN_METHOD); |
| |
| case 15: |
| return (memcmp(method, "VERSION-CONTROL", 15) == 0 |
| ? M_VERSION_CONTROL : UNKNOWN_METHOD); |
| |
| case 16: |
| return (memcmp(method, "BASELINE-CONTROL", 16) == 0 |
| ? M_BASELINE_CONTROL : UNKNOWN_METHOD); |
| |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| /* NOTREACHED */ |
| } |