[PATCH v2] Make ngx_http_parse_unsafe_uri() to be able to unescape uri

Raman Shishniou rommer at activecloud.com
Wed Dec 11 05:48:54 UTC 2013


# HG changeset patch
# User Raman Shishniou <rommer at activecloud.com>
# Date 1386740673 -10800
# Node ID 353840924c0ffd1203c8d0894f2128a2548f80c5
# Parent  a279d2a33dbfbad511f4415f833c35a60e46bb76
Make ngx_http_parse_unsafe_uri() to be able to unescape uri

It makes possible to use an escaped uri with "X-Accel-Redirect" header in
http_upstream module and "Destination" header http_dav module. No need
to unescape uri in http_ssi_filter module any more.

diff --git a/src/http/modules/ngx_http_ssi_filter_module.c b/src/http/modules/ngx_http_ssi_filter_module.c
--- a/src/http/modules/ngx_http_ssi_filter_module.c
+++ b/src/http/modules/ngx_http_ssi_filter_module.c
@@ -1982,8 +1982,6 @@
 ngx_http_ssi_include(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
     ngx_str_t **params)
 {
-    u_char                      *dst, *src;
-    size_t                       len;
     ngx_int_t                    rc, key;
     ngx_str_t                   *uri, *file, *wait, *set, *stub, args;
     ngx_buf_t                   *b;
@@ -2054,18 +2052,6 @@
         return rc;
     }
 
-    dst = uri->data;
-    src = uri->data;
-
-    ngx_unescape_uri(&dst, &src, uri->len, NGX_UNESCAPE_URI);
-
-    len = (uri->data + uri->len) - src;
-    if (len) {
-        dst = ngx_movemem(dst, src, len);
-    }
-
-    uri->len = dst - uri->data;
-
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                    "ssi include: \"%V\"", uri);
 
diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c
--- a/src/http/ngx_http_parse.c
+++ b/src/http/ngx_http_parse.c
@@ -9,6 +9,8 @@
 #include <ngx_core.h>
 #include <ngx_http.h>
 
+static ngx_inline ngx_int_t
+ngx_http_parse_test_doubledot(const u_char *p, const u_char *begin);
 
 static uint32_t  usual[] = {
     0xffffdbfe, /* 1111 1111 1111 1111  1101 1011 1111 1110 */
@@ -1776,35 +1778,75 @@
 }
 
 
+static ngx_inline ngx_int_t
+ngx_http_parse_test_doubledot(const u_char *p, const u_char *begin)
+{
+    /* assume *p is the path separator or
+       p points to the next byte after the end */
+
+    if (p - 2 > begin &&
+        *(p - 1) == '.' && *(p - 2) == '.' &&
+        ngx_path_separator(*(p - 3))) {
+        return 1;
+    }
+
+    if (p - 2 == begin &&
+        begin[0] == '.' && begin[1] == '.') {
+        return 1;
+    }
+
+    return 0;
+}
+
+
 ngx_int_t
 ngx_http_parse_unsafe_uri(ngx_http_request_t *r, ngx_str_t *uri,
     ngx_str_t *args, ngx_uint_t *flags)
 {
-    u_char  ch, *p;
-    size_t  len;
-
+    u_char   *src, *dst, *newuri, ch, c, decoded;
+    ngx_int_t unescape;
+    size_t    len;
+    enum {
+        sw_usual = 0,
+        sw_quoted,
+        sw_quoted_second
+    } state;
+
+    src = uri->data;
     len = uri->len;
-    p = uri->data;
-
-    if (len == 0 || p[0] == '?') {
+    unescape = 0;
+
+    if (len == 0 || src[0] == '?') {
         goto unsafe;
     }
 
-    if (p[0] == '.' && len == 3 && p[1] == '.' && (ngx_path_separator(p[2]))) {
-        goto unsafe;
-    }
-
     for ( /* void */ ; len; len--) {
 
-        ch = *p++;
+        ch = *src++;
 
         if (usual[ch >> 5] & (1 << (ch & 0x1f))) {
             continue;
         }
 
+        if (ch == '%') {
+            unescape++;
+            continue;
+        }
+
         if (ch == '?') {
             args->len = len - 1;
-            args->data = p;
+            args->data = src;
+
+            if (unescape) {
+                src--;
+                goto unescape;
+            }
+
+            /* detect "/.." at the end or whole uri is ".." */
+            if (ngx_http_parse_test_doubledot(src - 1, uri->data)) {
+                goto unsafe;
+            }
+
             uri->len -= len;
 
             return NGX_OK;
@@ -1814,16 +1856,151 @@
             goto unsafe;
         }
 
-        if (ngx_path_separator(ch) && len > 2) {
-
-            /* detect "/../" */
-
-            if (p[0] == '.' && p[1] == '.' && ngx_path_separator(p[2])) {
+        /* detect "../" at the beginning or "/../" in the middle */
+        if (ngx_path_separator(ch) &&
+            ngx_http_parse_test_doubledot(src - 1, uri->data)) {
+            goto unsafe;
+        }
+    }
+
+    /* detect "/.." at the end or whole uri is ".." */
+    if (ngx_http_parse_test_doubledot(src, uri->data)) {
+        goto unsafe;
+    }
+
+    if (!unescape) {
+        return NGX_OK;
+    }
+
+unescape:
+
+    len = src - uri->data;
+    newuri = ngx_pnalloc(r->pool, len);
+
+    if (newuri == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(newuri, uri->data, len);
+
+    src = uri->data;
+    dst = newuri;
+    decoded = 0;
+    state = 0;
+
+    for ( /* void */ ; len; len--) {
+
+        ch = *src++;
+
+        switch (state) {
+
+        case sw_usual:
+            if (usual[ch >> 5] & (1 << (ch & 0x1f))) {
+                *dst++ = ch;
+                break;
+            }
+
+            if (ch == '%') {
+                state = sw_quoted;
+                break;
+            }
+
+            /* detect "../" at the beginning or "/../" in the middle */
+            if (ngx_path_separator(ch) &&
+                ngx_http_parse_test_doubledot(dst, newuri)) {
                 goto unsafe;
             }
+
+            *dst++ = ch;
+            break;
+
+        case sw_quoted:
+            if (ch >= '0' && ch <= '9') {
+                decoded = (u_char) (ch - '0');
+                state = sw_quoted_second;
+                break;
+            }
+
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'f') {
+                decoded = (u_char) (c - 'a' + 10);
+                state = sw_quoted_second;
+                break;
+            }
+
+            if (ch == '%') {
+                *dst++ = '%';
+                break;
+            }
+
+            *dst++ = '%';
+            *dst++ = ch;
+            state = sw_usual;
+            break;
+
+        case sw_quoted_second:
+
+            state = sw_usual;
+
+            if (ch >= '0' && ch <= '9') {
+                ch = (u_char) ((decoded << 4) + ch - '0');
+
+                if (ch == '\0') {
+                    goto unsafe;
+                }
+
+                *dst++ = ch;
+                break;
+            }
+
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'f') {
+                ch = (u_char) ((decoded << 4) + c - 'a' + 10);
+
+                /* detect "../" at the beginning or "/../" in the middle */
+                if (ngx_path_separator(ch) &&
+                    ngx_http_parse_test_doubledot(dst, newuri)) {
+                    goto unsafe;
+                }
+
+                *dst++ = ch;
+                break;
+            }
+
+            if (ch == '%') {
+                *dst++ = '%';
+                *dst++ = *(src - 2);
+                state = sw_quoted;
+                break;
+            }
+
+            *dst++ = '%';
+            *dst++ = *(src - 2);
+            *dst++ = ch;
+            break;
         }
     }
 
+    switch (state) {
+    case sw_usual:
+        break;
+    case sw_quoted:
+        *dst++ = '%';
+        break;
+    case sw_quoted_second:
+        *dst++ = '%';
+        *dst++ = *(src - 1);
+        break;
+    }
+
+    /* detect "/.." at the end or whole uri is ".." */
+    if (ngx_http_parse_test_doubledot(dst, newuri)) {
+        goto unsafe;
+    }
+
+    uri->len = dst - newuri;
+    uri->data = newuri;
+
     return NGX_OK;
 
 unsafe:



More information about the nginx-devel mailing list