From e26ebc985b882c38da50fb0104791a5f2c0f8522 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 8 Apr 2009 17:42:23 -0700 Subject: http11: handle "X-Forwarded-Proto: https" Pass "https" to "rack.url_scheme" if the X-Forwarded-Proto header matches "https". X-Forwarded-Proto is a semi-standard header that Ruby frameworks seem to respect; so we use that. We won't support ENV['HTTPS'] since that can only be set at start time and some app servers supporting https also support http. Currently, "rack.url_scheme" only allows "http" and "https", so we won't set anything else to avoid breaking Rack::Lint. --- ext/unicorn/http11/http11.c | 16 ++++++++++++++++ lib/unicorn/http_request.rb | 1 - test/unit/test_request.rb | 21 +++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/ext/unicorn/http11/http11.c b/ext/unicorn/http11/http11.c index 995bf2a..a262d18 100644 --- a/ext/unicorn/http11/http11.c +++ b/ext/unicorn/http11/http11.c @@ -24,6 +24,7 @@ static VALUE sym_http_body; #define HTTP_PREFIX "HTTP_" #define HTTP_PREFIX_LEN (sizeof(HTTP_PREFIX) - 1) +static VALUE global_rack_url_scheme; static VALUE global_request_method; static VALUE global_request_uri; static VALUE global_fragment; @@ -37,6 +38,7 @@ static VALUE global_server_port; static VALUE global_server_protocol; static VALUE global_server_protocol_value; static VALUE global_http_host; +static VALUE global_http_x_forwarded_proto; static VALUE global_port_80; static VALUE global_localhost; @@ -106,6 +108,7 @@ static struct common_field common_http_fields[] = { f("USER_AGENT"), f("VIA"), f("X_FORWARDED_FOR"), /* common for proxies */ + f("X_FORWARDED_PROTO"), /* common for proxies */ f("X_REAL_IP"), /* common for proxies */ f("WARNING") # undef f @@ -248,6 +251,17 @@ static void header_done(void *data, const char *at, size_t length) VALUE temp = Qnil; char *colon = NULL; + /* set rack.url_scheme to "https" or "http" */ + if ((temp = rb_hash_aref(req, global_http_x_forwarded_proto)) != Qnil) { + if (strcmp("https", RSTRING_PTR(temp))) + temp = rb_str_new("http", 4); + /* we leave temp alone if it's "https" */ + } else { + temp = rb_str_new("http", 4); + } + rb_hash_aset(req, global_rack_url_scheme, temp); + + /* set the SERVER_NAME and SERVER_PORT variables */ if((temp = rb_hash_aref(req, global_http_host)) != Qnil) { colon = memchr(RSTRING_PTR(temp), ':', RSTRING_LEN(temp)); if(colon != NULL) { @@ -372,6 +386,7 @@ void Init_http11() mUnicorn = rb_define_module("Unicorn"); + DEF_GLOBAL(rack_url_scheme, "rack.url_scheme"); DEF_GLOBAL(request_method, "REQUEST_METHOD"); DEF_GLOBAL(request_uri, "REQUEST_URI"); DEF_GLOBAL(fragment, "FRAGMENT"); @@ -385,6 +400,7 @@ void Init_http11() DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL"); DEF_GLOBAL(server_protocol_value, "HTTP/1.1"); DEF_GLOBAL(http_host, "HTTP_HOST"); + DEF_GLOBAL(http_x_forwarded_proto, "HTTP_X_FORWARDED_PROTO"); DEF_GLOBAL(port_80, "80"); DEF_GLOBAL(localhost, "localhost"); diff --git a/lib/unicorn/http_request.rb b/lib/unicorn/http_request.rb index 750deea..a3a1d4d 100644 --- a/lib/unicorn/http_request.rb +++ b/lib/unicorn/http_request.rb @@ -19,7 +19,6 @@ module Unicorn "rack.multiprocess" => true, "rack.multithread" => false, "rack.run_once" => false, - "rack.url_scheme" => "http", "rack.version" => [0, 1], "SCRIPT_NAME" => "", diff --git a/test/unit/test_request.rb b/test/unit/test_request.rb index dacbac0..5109c7b 100644 --- a/test/unit/test_request.rb +++ b/test/unit/test_request.rb @@ -55,10 +55,31 @@ class RequestTest < Test::Unit::TestCase assert_nothing_raised { res = @lint.call(env) } end + def test_x_forwarded_proto + res = env = nil + client = MockRequest.new("GET / HTTP/1.1\r\n" \ + "X-Forwarded-Proto: https\r\n" \ + "Host: foo\r\n\r\n") + assert_nothing_raised { env = @request.read(client) } + assert_equal "https", env['rack.url_scheme'] + assert_nothing_raised { res = @lint.call(env) } + end + + def test_x_forwarded_proto_invalid + res = env = nil + client = MockRequest.new("GET / HTTP/1.1\r\n" \ + "X-Forwarded-Proto: ftp\r\n" \ + "Host: foo\r\n\r\n") + assert_nothing_raised { env = @request.read(client) } + assert_equal "http", env['rack.url_scheme'] + assert_nothing_raised { res = @lint.call(env) } + end + def test_rack_lint_get client = MockRequest.new("GET / HTTP/1.1\r\nHost: foo\r\n\r\n") res = env = nil assert_nothing_raised { env = @request.read(client) } + assert_equal "http", env['rack.url_scheme'] assert_equal '666.666.666.666', env['REMOTE_ADDR'] assert_nothing_raised { res = @lint.call(env) } end -- cgit v1.2.3-24-ge0c7