changeset 4313:e7db97bfac25

Added support for IP-literal in the Host header and request line (ticket #1). Additional parsing logic added to correctly handle RFC 3986 compliant IPv6 and IPvFuture characters enclosed in square brackets. The host validation was completely rewritten. The behavior for non IP literals was changed in a more proper and safer way: - Host part is now delimited either by the first colon or by the end of string if there's no colon. Previously the last colon was used as delimiter which allowed substitution of a port number in the $host variable. (e.g. Host: 127.0.0.1:9000:80) - Fixed stripping of the ending dot in the Host header when the host was also followed by a port number. (e.g. Host: nginx.com.:80) - Fixed upper case characters detection. Previously it was broken which led to wasting memory and CPU.
author Valentin Bartenev <vbart@nginx.com>
date Mon, 28 Nov 2011 09:15:33 +0000
parents 0a8e51a16484
children 4a07bad0e2c5
files src/http/ngx_http_parse.c src/http/ngx_http_request.c
diffstat 2 files changed, 121 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- a/src/http/ngx_http_parse.c
+++ b/src/http/ngx_http_parse.c
@@ -110,7 +110,10 @@ ngx_http_parse_request_line(ngx_http_req
         sw_schema,
         sw_schema_slash,
         sw_schema_slash_slash,
+        sw_host_start,
         sw_host,
+        sw_host_end,
+        sw_host_ip_literal,
         sw_port,
         sw_host_http_09,
         sw_after_slash_in_uri,
@@ -323,14 +326,26 @@ ngx_http_parse_request_line(ngx_http_req
         case sw_schema_slash_slash:
             switch (ch) {
             case '/':
-                r->host_start = p + 1;
-                state = sw_host;
+                state = sw_host_start;
                 break;
             default:
                 return NGX_HTTP_PARSE_INVALID_REQUEST;
             }
             break;
 
+        case sw_host_start:
+
+            r->host_start = p;
+
+            if (ch == '[') {
+                state = sw_host_ip_literal;
+                break;
+            }
+
+            state = sw_host;
+
+            /* fall through */
+
         case sw_host:
 
             c = (u_char) (ch | 0x20);
@@ -342,6 +357,10 @@ ngx_http_parse_request_line(ngx_http_req
                 break;
             }
 
+            /* fall through */
+
+        case sw_host_end:
+
             r->host_end = p;
 
             switch (ch) {
@@ -366,6 +385,47 @@ ngx_http_parse_request_line(ngx_http_req
             }
             break;
 
+        case sw_host_ip_literal:
+
+            if (ch >= '0' && ch <= '9') {
+                break;
+            }
+
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'z') {
+                break;
+            }
+
+            switch (ch) {
+            case ':':
+                break;
+            case ']':
+                state = sw_host_end;
+                break;
+            case '-':
+            case '.':
+            case '_':
+            case '~':
+                /* unreserved */
+                break;
+            case '!':
+            case '$':
+            case '&':
+            case '\'':
+            case '(':
+            case ')':
+            case '*':
+            case '+':
+            case ',':
+            case ';':
+            case '=':
+                /* sub-delims */
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
         case sw_port:
             if (ch >= '0' && ch <= '9') {
                 break;
--- a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -1674,56 +1674,85 @@ static ssize_t
 ngx_http_validate_host(ngx_http_request_t *r, u_char **host, size_t len,
     ngx_uint_t alloc)
 {
-    u_char      *h, ch;
-    size_t       i, last;
-    ngx_uint_t   dot;
-
-    last = len;
+    u_char  *h, ch;
+    size_t   i, dot_pos, host_len;
+
+    enum {
+        sw_usual = 0,
+        sw_literal,
+        sw_rest
+    } state;
+
+    dot_pos = len;
+    host_len = len;
+
     h = *host;
-    dot = 0;
+
+    state = sw_usual;
 
     for (i = 0; i < len; i++) {
         ch = h[i];
 
-        if (ch == '.') {
-            if (dot) {
+        switch (ch) {
+
+        case '.':
+            if (dot_pos == i - 1) {
                 return 0;
             }
-
-            dot = 1;
-            continue;
-        }
-
-        dot = 0;
-
-        if (ch == ':') {
-            last = i;
-            continue;
-        }
-
-        if (ngx_path_separator(ch) || ch == '\0') {
+            dot_pos = i;
+            break;
+
+        case ':':
+            if (state == sw_usual) {
+                host_len = i;
+                state = sw_rest;
+            }
+            break;
+
+        case '[':
+            if (i == 0) {
+                state = sw_literal;
+            }
+            break;
+
+        case ']':
+            if (state == sw_literal) {
+                host_len = i + 1;
+                state = sw_rest;
+            }
+            break;
+
+        case '\0':
             return 0;
-        }
-
-        if (ch >= 'A' || ch < 'Z') {
-            alloc = 1;
+
+        default:
+
+            if (ngx_path_separator(ch)) {
+                return 0;
+            }
+
+            if (ch >= 'A' && ch <= 'Z') {
+                alloc = 1;
+            }
+
+            break;
         }
     }
 
-    if (dot) {
-        last--;
+    if (dot_pos == host_len - 1) {
+        host_len--;
     }
 
     if (alloc) {
-        *host = ngx_pnalloc(r->pool, last) ;
+        *host = ngx_pnalloc(r->pool, host_len);
         if (*host == NULL) {
             return -1;
         }
 
-        ngx_strlow(*host, h, last);
+        ngx_strlow(*host, h, host_len);
     }
 
-    return last;
+    return host_len;
 }