* 20200420.1 (2.5.1-dev) Add ap_filter_adopt_brigade()
* 20200420.2 (2.5.1-dev) Add ap_proxy_worker_can_upgrade()
* 20200420.3 (2.5.1-dev) Add ap_parse_strict_length()
+ * 20200420.4 (2.5.1-dev) Add ap_normalize_path()
*/
#define MODULE_MAGIC_COOKIE 0x41503235UL /* "AP25" */
#ifndef MODULE_MAGIC_NUMBER_MAJOR
#define MODULE_MAGIC_NUMBER_MAJOR 20200420
#endif
-#define MODULE_MAGIC_NUMBER_MINOR 3 /* 0...n */
+#define MODULE_MAGIC_NUMBER_MINOR 4 /* 0...n */
/**
* Determine if the server's current MODULE_MAGIC_NUMBER is at least a
AP_DECLARE(void) ap_no2slash_ex(char *name, int is_fs_path)
AP_FN_ATTR_NONNULL_ALL;
+#define AP_NORMALIZE_ALLOW_RELATIVE (1u << 0)
+#define AP_NORMALIZE_NOT_ABOVE_ROOT (1u << 1)
+#define AP_NORMALIZE_DECODE_UNRESERVED (1u << 2)
+#define AP_NORMALIZE_MERGE_SLASHES (1u << 3)
+#define AP_NORMALIZE_DROP_PARAMETERS (1u << 4)
+
+/**
+ * Remove all ////, /./ and /xx/../ substrings from a path, and more
+ * depending on passed in flags.
+ * @param path The path to normalize
+ * @param flags bitmask of AP_NORMALIZE_* flags
+ * @return non-zero on success
+ */
+AP_DECLARE(int) ap_normalize_path(char *path, unsigned int flags)
+ AP_FN_ATTR_NONNULL((1));
/**
* Remove all ./ and xx/../ substrings from a file name. Also remove
/* note that parsed_uri.path is allocated; we can trash it */
/* clean up the URI a bit */
- ap_getparents(parsed_uri.path);
+ if (!ap_normalize_path(parsed_uri.path,
+ AP_NORMALIZE_DECODE_UNRESERVED)) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_TAGGED, rv,
+ "Invalid URI path tagged If-header.");
+ }
/* the resources we will compare to have unencoded paths */
if (ap_unescape_url(parsed_uri.path) != OK) {
#include "test_char.h"
/* Win32/NetWare/OS2 need to check for both forward and back slashes
- * in ap_getparents() and ap_escape_url.
+ * in ap_normalize_path() and ap_escape_url().
*/
#ifdef CASE_BLIND_FILESYSTEM
#define IS_SLASH(s) ((s == '/') || (s == '\\'))
return rc;
}
+/* Forward declare */
+static char x2c(const char *what);
+
+#define IS_SLASH_OR_NUL(s) (s == '\0' || IS_SLASH(s))
+
+/*
+ * Inspired by mod_jk's jk_servlet_normalize().
+ */
+AP_DECLARE(int) ap_normalize_path(char *path, unsigned int flags)
+{
+ int ret = 1;
+ apr_size_t l = 1, w = 1;
+
+ if (!IS_SLASH(path[0])) {
+ /* Besides "OPTIONS *", a request-target should start with '/'
+ * per RFC 7230 section 5.3, so anything else is invalid.
+ */
+ if (path[0] == '*' && path[1] == '\0') {
+ return 1;
+ }
+ /* However, AP_NORMALIZE_ALLOW_RELATIVE can be used to bypass
+ * this restriction (e.g. for subrequest file lookups).
+ */
+ if (!(flags & AP_NORMALIZE_ALLOW_RELATIVE) || path[0] == '\0') {
+ return 0;
+ }
+
+ l = w = 0;
+ }
+
+ while (path[l] != '\0') {
+ /* RFC-3986 section 2.3:
+ * For consistency, percent-encoded octets in the ranges of
+ * ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D),
+ * period (%2E), underscore (%5F), or tilde (%7E) should [...]
+ * be decoded to their corresponding unreserved characters by
+ * URI normalizers.
+ */
+ if ((flags & AP_NORMALIZE_DECODE_UNRESERVED)
+ && path[l] == '%' && apr_isxdigit(path[l + 1])
+ && apr_isxdigit(path[l + 2])) {
+ const char c = x2c(&path[l + 1]);
+ if (apr_isalnum(c) || (c && strchr("-._~", c))) {
+ /* Replace last char and fall through as the current
+ * read position */
+ l += 2;
+ path[l] = c;
+ }
+ }
+
+ if ((flags & AP_NORMALIZE_DROP_PARAMETERS) && path[l] == ';') {
+ do {
+ l++;
+ } while (!IS_SLASH_OR_NUL(path[l]));
+ continue;
+ }
+
+ if (w == 0 || IS_SLASH(path[w - 1])) {
+ /* Collapse ///// sequences to / */
+ if ((flags & AP_NORMALIZE_MERGE_SLASHES) && IS_SLASH(path[l])) {
+ do {
+ l++;
+ } while (IS_SLASH(path[l]));
+ continue;
+ }
+
+ if (path[l] == '.') {
+ /* Remove /./ segments */
+ if (IS_SLASH_OR_NUL(path[l + 1])) {
+ l++;
+ if (path[l]) {
+ l++;
+ }
+ continue;
+ }
+
+ /* Remove /xx/../ segments */
+ if (path[l + 1] == '.' && IS_SLASH_OR_NUL(path[l + 2])) {
+ /* Wind w back to remove the previous segment */
+ if (w > 1) {
+ do {
+ w--;
+ } while (w && !IS_SLASH(path[w - 1]));
+ }
+ else {
+ /* Already at root, ignore and return a failure
+ * if asked to.
+ */
+ if (flags & AP_NORMALIZE_NOT_ABOVE_ROOT) {
+ ret = 0;
+ }
+ }
+
+ /* Move l forward to the next segment */
+ l += 2;
+ if (path[l]) {
+ l++;
+ }
+ continue;
+ }
+ }
+ }
+
+ path[w++] = path[l++];
+ }
+ path[w] = '\0';
+
+ return ret;
+}
+
/*
* Parse .. so we don't compromise security
*/