#include "ap_config.h"
#include "apr_base64.h"
+#include "apr_fnmatch.h"
#include "httpd.h"
#include "http_main.h"
#include "http_log.h"
* char in here and get it to work, because if char is signed then it
* will first be sign extended.
*/
-#define TEST_CHAR(c, f) (test_char_table[(unsigned)(c)] & (f))
+#define TEST_CHAR(c, f) (test_char_table[(unsigned char)(c)] & (f))
/* Win32/NetWare/OS2 need to check for both forward and back slashes
* in ap_getparents() and ap_escape_url.
#undef APLOG_MODULE_INDEX
#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX
+/* maximum nesting level for config directories */
+#ifndef AP_MAX_FNMATCH_DIR_DEPTH
+#define AP_MAX_FNMATCH_DIR_DEPTH (128)
+#endif
+
/*
* Examine a field value (such as a media-/content-type) string and return
* it sans any parameters; e.g., strip off any ';charset=foo' and the like.
int x, y;
for (x = 0, y = 0; expected[y]; ++y, ++x) {
- if ((!str[x]) && (expected[y] != '*'))
- return -1;
if (expected[y] == '*') {
while (expected[++y] == '*');
if (!expected[y])
}
return -1;
}
+ else if (!str[x])
+ return -1;
else if ((expected[y] != '?') && (str[x] != expected[y]))
return 1;
}
* MODIFIED FOR HAVE_DRIVE_LETTERS and NETWARE environments,
* so that if n == 0, "/" is returned in d with n == 1
* and s == "e:/test.html", "e:/" is returned in d
- * *** See also directory_walk in modules/http/http_request.c
-
+ * *** See also ap_directory_walk in server/request.c
+ *
* examples:
* /a/b, 0 ==> / (true for all platforms)
* /a/b, 1 ==> /
break;
if (*strend == '{')
++count;
- if (*strend == '\\' && strend[1] && strend[1] == '\\') {
+ if (*strend == '\\' && strend[1] == '\\') {
++strend;
}
++strend;
while (!string_end) {
const unsigned char c = (unsigned char)*cur;
- if (!TEST_CHAR(c, T_HTTP_TOKEN_STOP) && c != '\0') {
+ if (!TEST_CHAR(c, T_HTTP_TOKEN_STOP)) {
/* Non-separator character; we are finished with leading
* whitespace. We must never have encountered any trailing
* whitespace before the delimiter (comma) */
return NULL;
}
+/* Scan a string for HTTP VCHAR/obs-text characters including HT and SP
+ * (as used in header values, for example, in RFC 7230 section 3.2)
+ * returning the pointer to the first non-HT ASCII ctrl character.
+ */
+AP_DECLARE(const char *) ap_scan_http_field_content(const char *ptr)
+{
+ for ( ; !TEST_CHAR(*ptr, T_HTTP_CTRLS); ++ptr) ;
+
+ return ptr;
+}
+
+/* Scan a string for HTTP token characters, returning the pointer to
+ * the first non-token character.
+ */
+AP_DECLARE(const char *) ap_scan_http_token(const char *ptr)
+{
+ for ( ; !TEST_CHAR(*ptr, T_HTTP_TOKEN_STOP); ++ptr) ;
+
+ return ptr;
+}
+
+/* Scan a string for visible ASCII (0x21-0x7E) or obstext (0x80+)
+ * and return a pointer to the first ctrl/space character encountered.
+ */
+AP_DECLARE(const char *) ap_scan_vchar_obstext(const char *ptr)
+{
+ for ( ; TEST_CHAR(*ptr, T_VCHAR_OBSTEXT); ++ptr) ;
+
+ return ptr;
+}
+
/* Retrieve a token, spacing over it and returning a pointer to
* the first non-white byte afterwards. Note that these tokens
* are delimited by semis and commas; and can also be delimited
s = (const unsigned char *)line;
for (;;) {
- /* find start of token, skip all stop characters, note NUL
- * isn't a token stop, so we don't need to test for it
- */
- while (TEST_CHAR(*s, T_HTTP_TOKEN_STOP)) {
+ /* find start of token, skip all stop characters */
+ while (*s && TEST_CHAR(*s, T_HTTP_TOKEN_STOP)) {
++s;
}
if (!*s) {
badesc = 0;
badpath = 0;
+
+ if (url == NULL) {
+ return OK;
+ }
/* Initial scan for first '%'. Don't bother writing values before
* seeing a '%' */
y = strchr(url, '%');
*dest = '\0';
}
-AP_DECLARE(int) ap_has_cntrl(const char *str)
-{
- while (*str) {
- if (apr_iscntrl(*str))
- return 1;
- str++;
- }
- return 0;
-}
-
AP_DECLARE(int) ap_is_directory(apr_pool_t *p, const char *path)
{
apr_finfo_t finfo;
return decoded;
}
+/* a stringent version of ap_pbase64decode() */
+AP_DECLARE(apr_status_t) ap_pbase64decode_strict(apr_pool_t *p,
+ const char *encoded,
+ char **decoded,
+ apr_size_t *len)
+{
+ apr_size_t end_index;
+ int last_group_len;
+ const char *end;
+
+ /* Sanity check.
+ * TODO: this would be a lot more efficient if we had access to the lookup
+ * table used by APR. If that gets pulled in at any point, make use of it.
+ */
+ end_index = strspn(encoded, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/");
+
+ last_group_len = end_index % 4;
+ end = encoded + end_index;
+
+ /* The only non-alphabet character allowed is the padding character '=' at
+ * the end of the string. There are two allowable padding cases for the last
+ * group of four: "xY==" or "xyZ=". We require the final (non-padding)
+ * character to have been zero-padded during encoding, which limits the
+ * character choices.
+ */
+ if (last_group_len == 1) {
+ /* This isn't ever valid. */
+ return APR_EINVAL;
+ }
+ else if (last_group_len == 2) {
+ /* ...xY== */
+ if (*end != '=' || end[1] != '=') {
+ return APR_EINVAL;
+ }
+ else if (!ap_strchr("AQgw", end[-1])) {
+ /* Correctly zero-padded input data will result in a final character
+ * that is one of the four above. */
+ return APR_EINVAL;
+ }
+
+ end += 2;
+ }
+ else if (last_group_len == 3) {
+ /* ...xyZ= */
+ if (*end != '=') {
+ return APR_EINVAL;
+ }
+ else if (!ap_strchr("AEIMQUYcgkosw048", end[-1])) {
+ /* Correctly zero-padded input data will result in a final character
+ * that is one of the sixteen above. */
+ return APR_EINVAL;
+ }
+
+ end++;
+ }
+
+ /* At this point, if the encoded buffer is correct, we should be at the end
+ * of the string. */
+ if (*end) {
+ return APR_EINVAL;
+ }
+
+ *decoded = apr_palloc(p, apr_base64_decode_len(encoded));
+ *len = apr_base64_decode(*decoded, encoded);
+
+ return APR_SUCCESS;
+}
+
AP_DECLARE(char *) ap_pbase64encode(apr_pool_t *p, char *string)
{
char *encoded;
/**
* Parse a given timeout parameter string into an apr_interval_time_t value.
* The unit of the time interval is given as postfix string to the numeric
- * string. Currently the following units are understood:
+ * string. Currently the following units are understood (case insensitive):
*
* ms : milliseconds
* s : seconds
switch (*time_str) {
/* Time is in seconds */
case 's':
+ case 'S':
*timeout = (apr_interval_time_t) apr_time_from_sec(tout);
break;
case 'h':
+ case 'H':
/* Time is in hours */
*timeout = (apr_interval_time_t) apr_time_from_sec(tout * 3600);
break;
case 'm':
+ case 'M':
switch (*(++time_str)) {
/* Time is in milliseconds */
case 's':
+ case 'S':
*timeout = (apr_interval_time_t) tout * 1000;
break;
/* Time is in minutes */
case 'i':
+ case 'I':
*timeout = (apr_interval_time_t) apr_time_from_sec(tout * 60);
break;
default:
return has_body;
}
+/**
+ * Check whether a request is tainted by exposure to something
+ * potentially untrusted.
+ *
+ */
+AP_DECLARE(int) ap_request_tainted(request_rec *r, int flags)
+{
+ /** Potential future: a hook or callback here could serve modules
+ * like mod_security and ironbee with more complex needs.
+ */
+ return r && ((r->taint&flags)
+ || ap_request_tainted(r->main, flags)
+ || ap_request_tainted(r->prev, flags));
+}
+
AP_DECLARE_NONSTD(apr_status_t) ap_pool_cleanup_set_null(void *data_)
{
void **ptr = (void **)data_;
ap_form_type_t state = FORM_NAME, percent = FORM_NORMAL;
ap_form_pair_t *pair = NULL;
apr_array_header_t *pairs = apr_array_make(r->pool, 4, sizeof(ap_form_pair_t));
-
- char hi = 0;
- char low = 0;
+ char escaped_char[2] = { 0 };
*ptr = pairs;
continue;
}
if (FORM_PERCENTA == percent) {
- if (c >= 'a') {
- hi = c - 'a' + 10;
- }
- else if (c >= 'A') {
- hi = c - 'A' + 10;
- }
- else if (c >= '0') {
- hi = c - '0';
- }
- hi = hi << 4;
+ escaped_char[0] = c;
percent = FORM_PERCENTB;
continue;
}
if (FORM_PERCENTB == percent) {
- if (c >= 'a') {
- low = c - 'a' + 10;
- }
- else if (c >= 'A') {
- low = c - 'A' + 10;
- }
- else if (c >= '0') {
- low = c - '0';
- }
- c = low | hi;
+ escaped_char[1] = c;
+ c = x2c(escaped_char);
percent = FORM_NORMAL;
}
switch (state) {
case FORM_NAME:
if (offset < HUGE_STRING_LEN) {
if ('=' == c) {
- buffer[offset] = 0;
- offset = 0;
pair = (ap_form_pair_t *) apr_array_push(pairs);
- pair->name = apr_pstrdup(r->pool, buffer);
+ pair->name = apr_pstrmemdup(r->pool, buffer, offset);
pair->value = apr_brigade_create(r->pool, r->connection->bucket_alloc);
state = FORM_VALUE;
+ offset = 0;
}
else {
buffer[offset++] = c;
};
#endif
-AP_DECLARE(int) ap_cstr_casecmpn(const char *s1, const char *s2)
+AP_DECLARE(int) ap_cstr_casecmp(const char *s1, const char *s2)
{
const unsigned char *str1 = (const unsigned char *)s1;
const unsigned char *str2 = (const unsigned char *)s2;
return 0;
}
+typedef struct {
+ const char *fname;
+} fnames;
+
+static int fname_alphasort(const void *fn1, const void *fn2)
+{
+ const fnames *f1 = fn1;
+ const fnames *f2 = fn2;
+
+ return strcmp(f1->fname, f2->fname);
+}
+
+AP_DECLARE(ap_dir_match_t *)ap_dir_cfgmatch(cmd_parms *cmd, int flags,
+ const char *(*cb)(ap_dir_match_t *w, const char *fname), void *ctx)
+{
+ ap_dir_match_t *w = apr_palloc(cmd->temp_pool, sizeof(*w));
+
+ w->prefix = apr_pstrcat(cmd->pool, cmd->cmd->name, ": ", NULL);
+ w->p = cmd->pool;
+ w->ptemp = cmd->temp_pool;
+ w->flags = flags;
+ w->cb = cb;
+ w->ctx = ctx;
+ w->depth = 0;
+
+ return w;
+}
+
+AP_DECLARE(const char *)ap_dir_nofnmatch(ap_dir_match_t *w, const char *fname)
+{
+ const char *error;
+ apr_status_t rv;
+
+ if ((w->flags & AP_DIR_FLAG_RECURSIVE) && ap_is_directory(w->ptemp, fname)) {
+ apr_dir_t *dirp;
+ apr_finfo_t dirent;
+ int current;
+ apr_array_header_t *candidates = NULL;
+ fnames *fnew;
+ char *path = apr_pstrdup(w->ptemp, fname);
+
+ if (++w->depth > AP_MAX_FNMATCH_DIR_DEPTH) {
+ return apr_psprintf(w->p, "%sDirectory '%s' exceeds the maximum include "
+ "directory nesting level of %u. You have "
+ "probably a recursion somewhere.", w->prefix ? w->prefix : "", path,
+ AP_MAX_FNMATCH_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, w->ptemp);
+ if (rv != APR_SUCCESS) {
+ return apr_psprintf(w->p, "%sCould not open directory %s: %pm",
+ w->prefix ? w->prefix : "", path, &rv);
+ }
+
+ candidates = apr_array_make(w->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 = ap_make_full_path(w->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 = ap_dir_nofnmatch(w, fnew->fname);
+ if (error) {
+ return error;
+ }
+ }
+ }
+
+ w->depth--;
+
+ return NULL;
+ }
+ else if (w->flags & AP_DIR_FLAG_OPTIONAL) {
+ /* If the optional flag is set (like for IncludeOptional) we can
+ * tolerate that no file or directory is present and bail out.
+ */
+ apr_finfo_t finfo;
+ if (apr_stat(&finfo, fname, APR_FINFO_TYPE, w->ptemp) != APR_SUCCESS
+ || finfo.filetype == APR_NOFILE)
+ return NULL;
+ }
+
+ return w->cb(w, fname);
+}
+
+AP_DECLARE(const char *)ap_dir_fnmatch(ap_dir_match_t *w, const char *path,
+ const char *fname)
+{
+ 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_pstrmemdup(w->ptemp, fname, rest - fname);
+ rest++;
+ }
+
+ /* optimisation - if the filename isn't a wildcard, process it directly */
+ if (!apr_fnmatch_test(fname)) {
+ path = path ? ap_make_full_path(w->ptemp, path, fname) : fname;
+ if (!rest) {
+ return ap_dir_nofnmatch(w, path);
+ }
+ else {
+ return ap_dir_fnmatch(w, path, rest);
+ }
+ }
+
+ /*
+ * 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, w->ptemp);
+ if (rv != APR_SUCCESS) {
+ /* If the directory doesn't exist and the optional flag is set
+ * there is no need to return an error.
+ */
+ if (rv == APR_ENOENT && (w->flags & AP_DIR_FLAG_OPTIONAL)) {
+ return NULL;
+ }
+ return apr_psprintf(w->p, "%sCould not open directory %s: %pm",
+ w->prefix ? w->prefix : "", path, &rv);
+ }
+
+ candidates = apr_array_make(w->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 = ap_make_full_path(w->ptemp, path, dirent.name);
+ /* If matching internal to path, and we happen to match something
+ * other than a directory, skip it
+ */
+ if (rest && (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 = ap_dir_nofnmatch(w, fnew->fname);
+ }
+ else {
+ error = ap_dir_fnmatch(w, fnew->fname, rest);
+ }
+ if (error) {
+ return error;
+ }
+ }
+ }
+ else {
+
+ if (!(w->flags & AP_DIR_FLAG_OPTIONAL)) {
+ return apr_psprintf(w->p, "%sNo matches for the wildcard '%s' in '%s', failing",
+ w->prefix ? w->prefix : "", fname, path);
+ }
+ }
+
+ return NULL;
+}