about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2010-12-20 19:40:57 +0000
committerEric Wong <normalperson@yhbt.net>2010-12-20 20:45:29 +0000
commitb740269f121167c4f93e3a0e155e05422f6e80ff (patch)
tree853bbc2717ed511aab04deb2e86fcb18f51f9ef1
parent7ad59e0c48e12febae2a2fe86b76116c05977c6f (diff)
downloadunicorn-b740269f121167c4f93e3a0e155e05422f6e80ff.tar.gz
The first value of X-Forwarded-Proto in rack.url_scheme should
be used as it can be chained.  This header can be set multiple
times via different proxies in the chain, but consider the first
one to be valid.

Additionally, respect X-Forwarded-SSL as it may be passed with
the "on" flag instead of X-Forwarded-Proto.

ref: rack commit 85ca454e6143a3081d90e4546ccad602a4c3ad2e
     and 35bb5ba6746b5d346de9202c004cc926039650c7
-rw-r--r--ext/unicorn_http/global_variables.h4
-rw-r--r--ext/unicorn_http/unicorn_http.rl28
-rw-r--r--test/unit/test_http_parser.rb55
3 files changed, 82 insertions, 5 deletions
diff --git a/ext/unicorn_http/global_variables.h b/ext/unicorn_http/global_variables.h
index 274f456..cdbc42d 100644
--- a/ext/unicorn_http/global_variables.h
+++ b/ext/unicorn_http/global_variables.h
@@ -15,6 +15,7 @@ static VALUE g_server_port;
 static VALUE g_server_protocol;
 static VALUE g_http_host;
 static VALUE g_http_x_forwarded_proto;
+static VALUE g_http_x_forwarded_ssl;
 static VALUE g_http_transfer_encoding;
 static VALUE g_content_length;
 static VALUE g_http_trailer;
@@ -23,6 +24,7 @@ static VALUE g_port_80;
 static VALUE g_port_443;
 static VALUE g_localhost;
 static VALUE g_http;
+static VALUE g_https;
 static VALUE g_http_09;
 static VALUE g_http_10;
 static VALUE g_http_11;
@@ -73,10 +75,12 @@ static void init_globals(void)
   DEF_GLOBAL(server_port, "SERVER_PORT");
   DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
   DEF_GLOBAL(http_x_forwarded_proto, "HTTP_X_FORWARDED_PROTO");
+  DEF_GLOBAL(http_x_forwarded_ssl, "HTTP_X_FORWARDED_SSL");
   DEF_GLOBAL(port_80, "80");
   DEF_GLOBAL(port_443, "443");
   DEF_GLOBAL(localhost, "localhost");
   DEF_GLOBAL(http, "http");
+  DEF_GLOBAL(https, "https");
   DEF_GLOBAL(http_11, "HTTP/1.1");
   DEF_GLOBAL(http_10, "HTTP/1.0");
   DEF_GLOBAL(http_09, "HTTP/0.9");
diff --git a/ext/unicorn_http/unicorn_http.rl b/ext/unicorn_http/unicorn_http.rl
index ba7ecb3..05e88bc 100644
--- a/ext/unicorn_http/unicorn_http.rl
+++ b/ext/unicorn_http/unicorn_http.rl
@@ -422,13 +422,31 @@ static void finalize_header(struct http_parser *hp)
   VALUE server_name = g_localhost;
   VALUE server_port = g_port_80;
 
-  /* set rack.url_scheme to "https" or "http", no others are allowed by Rack */
+  /*
+   * set rack.url_scheme to "https" or "http", no others are allowed by Rack
+   * this resembles the Rack::Request#scheme method as of rack commit
+   * 35bb5ba6746b5d346de9202c004cc926039650c7
+   */
   if (NIL_P(temp)) {
-    temp = rb_hash_aref(hp->env, g_http_x_forwarded_proto);
-    if (!NIL_P(temp) && STR_CSTR_EQ(temp, "https"))
+    temp = rb_hash_aref(hp->env, g_http_x_forwarded_ssl);
+    if (!NIL_P(temp) && STR_CSTR_EQ(temp, "on")) {
       server_port = g_port_443;
-    else
-      temp = g_http;
+      temp = g_https;
+    } else {
+      temp = rb_hash_aref(hp->env, g_http_x_forwarded_proto);
+      if (NIL_P(temp)) {
+        temp = g_http;
+      } else {
+        long len = RSTRING_LEN(temp);
+        if (len >= 5 && !memcmp(RSTRING_PTR(temp), "https", 5)) {
+          if (len != 5)
+            temp = g_https;
+          server_port = g_port_443;
+        } else {
+          temp = g_http;
+        }
+      }
+    }
     rb_hash_aset(hp->env, g_rack_url_scheme, temp);
   } else if (STR_CSTR_EQ(temp, "https")) {
     server_port = g_port_443;
diff --git a/test/unit/test_http_parser.rb b/test/unit/test_http_parser.rb
index 31cb2cb..a11740d 100644
--- a/test/unit/test_http_parser.rb
+++ b/test/unit/test_http_parser.rb
@@ -147,6 +147,61 @@ class HttpParserTest < Test::Unit::TestCase
     assert parser.keepalive?
   end
 
+  def test_parse_xfp_https_chained
+    parser = HttpParser.new
+    req = {}
+    tmp = "GET / HTTP/1.0\r\n" \
+          "X-Forwarded-Proto: https,http\r\n\r\n"
+    assert_equal req, parser.headers(req, tmp)
+    assert_equal '443', req['SERVER_PORT'], req.inspect
+    assert_equal 'https', req['rack.url_scheme'], req.inspect
+    assert_equal '', tmp
+  end
+
+  def test_parse_xfp_https_chained_backwards
+    parser = HttpParser.new
+    req = {}
+    tmp = "GET / HTTP/1.0\r\n" \
+          "X-Forwarded-Proto: http,https\r\n\r\n"
+    assert_equal req, parser.headers(req, tmp)
+    assert_equal '80', req['SERVER_PORT'], req.inspect
+    assert_equal 'http', req['rack.url_scheme'], req.inspect
+    assert_equal '', tmp
+  end
+
+  def test_parse_xfp_gopher_is_ignored
+    parser = HttpParser.new
+    req = {}
+    tmp = "GET / HTTP/1.0\r\n" \
+          "X-Forwarded-Proto: gopher\r\n\r\n"
+    assert_equal req, parser.headers(req, tmp)
+    assert_equal '80', req['SERVER_PORT'], req.inspect
+    assert_equal 'http', req['rack.url_scheme'], req.inspect
+    assert_equal '', tmp
+  end
+
+  def test_parse_x_forwarded_ssl_on
+    parser = HttpParser.new
+    req = {}
+    tmp = "GET / HTTP/1.0\r\n" \
+          "X-Forwarded-Ssl: on\r\n\r\n"
+    assert_equal req, parser.headers(req, tmp)
+    assert_equal '443', req['SERVER_PORT'], req.inspect
+    assert_equal 'https', req['rack.url_scheme'], req.inspect
+    assert_equal '', tmp
+  end
+
+  def test_parse_x_forwarded_ssl_off
+    parser = HttpParser.new
+    req = {}
+    tmp = "GET / HTTP/1.0\r\n" \
+          "X-Forwarded-Ssl: off\r\n\r\n"
+    assert_equal req, parser.headers(req, tmp)
+    assert_equal '80', req['SERVER_PORT'], req.inspect
+    assert_equal 'http', req['rack.url_scheme'], req.inspect
+    assert_equal '', tmp
+  end
+
   def test_parse_strange_headers
     parser = HttpParser.new
     req = {}