From ec73be7808c76bf58fed3b4f55446b2a4f2ab7b4 Mon Sep 17 00:00:00 2001 From: Benoist Claassen Date: Sat, 10 Oct 2015 19:57:56 +0200 Subject: [PATCH 1/3] HTTP::Request, added remote ip and peer_addr --- spec/std/http/request_spec.cr | 65 +++++++++++++++++++++++++++++++++++ src/http/request.cr | 38 ++++++++++++++++++++ src/http/server/server.cr | 2 ++ 3 files changed, 105 insertions(+) diff --git a/spec/std/http/request_spec.cr b/spec/std/http/request_spec.cr index 6c169b7e3b3e..4fea587b55cc 100644 --- a/spec/std/http/request_spec.cr +++ b/spec/std/http/request_spec.cr @@ -1,5 +1,6 @@ require "spec" require "http/request" +require "socket" module HTTP describe Request do @@ -203,5 +204,69 @@ module HTTP io.to_s.should eq("GET /api/v3/some/resource?q=isearchforsomething&locale=de HTTP/1.1\r\n\r\n") end end + + describe "#peer_addr=" do + it "sets the peer_addr" do + request = Request.from_io(StringIO.new("GET / HTTP/1.1\r\nHost: host.example.org\r\n\r\n")).not_nil! + addr = Socket::Addr.new("AF_INET", "12345", "127.0.0.1") + request.peer_addr = addr + request.peer_addr.should eq addr + end + end + + describe "#peer_addr" do + it "returns the peer_addr" do + request = Request.from_io(StringIO.new("GET / HTTP/1.1\r\nHost: host.example.org\r\n\r\n")).not_nil! + addr = Socket::Addr.new("AF_INET", "12345", "127.0.0.1") + request.peer_addr = addr + request.peer_addr.should eq addr + end + + it "raises if peer_addr is nil" do + request = Request.from_io(StringIO.new("GET / HTTP/1.1\r\nHost: host.example.org\r\n\r\n")).not_nil! + + expect_raises do + request.peer_addr + end + end + end + + describe "#remote_ip" do + context "trusting headers" do + it "returns the remote ip from the Client-Ip" do + request = Request.from_io(StringIO.new("GET / HTTP/1.1\r\nHost: host.example.org\r\nClient-Ip: 8.8.8.8\r\n\r\n")).not_nil! + addr = Socket::Addr.new("AF_INET", "12345", "127.0.0.1") + request.peer_addr = addr + + request.remote_ip.should eq "8.8.8.8" + end + + it "returns the remote ip from the X-Forwarded-For header" do + request = Request.from_io(StringIO.new("GET / HTTP/1.1\r\nHost: host.example.org\r\nX-Forwarded-For: 4.4.4.4, 10.0.0.1\r\n\r\n")).not_nil! + addr = Socket::Addr.new("AF_INET", "12345", "127.0.0.1") + request.peer_addr = addr + + request.remote_ip.should eq "4.4.4.4" + end + + it "returns the peer addr if headers are not set" do + request = Request.from_io(StringIO.new("GET / HTTP/1.1\r\nHost: host.example.org\r\n\r\n")).not_nil! + addr = Socket::Addr.new("AF_INET", "12345", "127.0.0.1") + request.peer_addr = addr + + request.remote_ip.should eq "127.0.0.1" + end + end + + context "without trusting headers" do + it "returns the peer_addr ip address" do + request = Request.from_io(StringIO.new("GET / HTTP/1.1\r\nHost: host.example.org\r\n\r\n")).not_nil! + addr = Socket::Addr.new("AF_INET", "12345", "127.0.0.1") + request.peer_addr = addr + + request.remote_ip(false).should eq "127.0.0.1" + end + end + end end end diff --git a/src/http/request.cr b/src/http/request.cr index ec1b8d15f135..b4d1cf77ee99 100644 --- a/src/http/request.cr +++ b/src/http/request.cr @@ -33,6 +33,27 @@ class HTTP::Request @method == "HEAD" end + def peer_addr=(addr) + @peer_addr = addr + end + + def peer_addr + @peer_addr.not_nil! + end + + def remote_ip(trust_headers=true) + if trust_headers + client_ips = ips_from("Client-Ip").reverse + forwarded_ips = ips_from("X-Forwarded-For").reverse + + ips = [forwarded_ips, client_ips, peer_addr.ip_address].flatten.compact + + first_remote_ip_address(ips) || peer_addr.ip_address + else + peer_addr.ip_address + end + end + def to_io(io) io << @method << " " << resource << " " << @version << "\r\n" cookies = @cookies @@ -68,4 +89,21 @@ class HTTP::Request private def uri (@uri ||= URI.parse(@resource)).not_nil! end + + private def ips_from(header) + if ips = headers[header]? || headers["Http-#{header.tr("_", "-")}"]? + ips.strip.split(/[,\s]+/) + else + [] of String + end + end + + private def first_remote_ip_address(ip_addresses) + ip_addresses.find { |ip| !trusted_proxy?(ip) } + end + + private def trusted_proxy?(ip) + ip =~ /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i + end + end diff --git a/src/http/server/server.cr b/src/http/server/server.cr index 77903cec1121..0f44a8581aee 100644 --- a/src/http/server/server.cr +++ b/src/http/server/server.cr @@ -139,6 +139,8 @@ class HTTP::Server return end break unless request + request.peer_addr = sock.peeraddr + response = @handler.call(request) response.headers["Connection"] = "keep-alive" if request.keep_alive? response.to_io io From dd3bf8c71f33a8b008464172f42b045e31ebb150 Mon Sep 17 00:00:00 2001 From: Benoist Claassen Date: Sat, 10 Oct 2015 20:01:53 +0200 Subject: [PATCH 2/3] use macro to define peer_addr propterty --- src/http/request.cr | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/http/request.cr b/src/http/request.cr index b4d1cf77ee99..655a0128a5cb 100644 --- a/src/http/request.cr +++ b/src/http/request.cr @@ -6,6 +6,7 @@ class HTTP::Request getter headers getter body getter version + property! peer_addr def initialize(@method : String, @resource, @headers = Headers.new : Headers, @body = nil, @version = "HTTP/1.1") if body = @body @@ -33,14 +34,6 @@ class HTTP::Request @method == "HEAD" end - def peer_addr=(addr) - @peer_addr = addr - end - - def peer_addr - @peer_addr.not_nil! - end - def remote_ip(trust_headers=true) if trust_headers client_ips = ips_from("Client-Ip").reverse From 535d79f453cc224cfa1cbdcafb1c9e468ab73929 Mon Sep 17 00:00:00 2001 From: Benoist Claassen Date: Sat, 10 Oct 2015 20:08:23 +0200 Subject: [PATCH 3/3] remove redundant underscore to dash conversion --- src/http/request.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/request.cr b/src/http/request.cr index 655a0128a5cb..41acc5223f7b 100644 --- a/src/http/request.cr +++ b/src/http/request.cr @@ -84,7 +84,7 @@ class HTTP::Request end private def ips_from(header) - if ips = headers[header]? || headers["Http-#{header.tr("_", "-")}"]? + if ips = headers[header]? || headers["Http-#{header}"]? ips.strip.split(/[,\s]+/) else [] of String