Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP::Request, added remote ip and peer_addr #1722

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions spec/std/http/request_spec.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require "spec"
require "http/request"
require "socket"

module HTTP
describe Request do
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the point of having two identical specs?


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
38 changes: 38 additions & 0 deletions src/http/request.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is property! peer_addr

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to know :)


def remote_ip(trust_headers=true)
if trust_headers
client_ips = ips_from("Client-Ip").reverse
forwarded_ips = ips_from("X-Forwarded-For").reverse
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As said I've also seen "X-Real-Ip" in configs and X-Fowarded, Forwarded and "X-Cluster-Client-Ip" appear to be used values too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


ips = [forwarded_ips, client_ips, peer_addr.ip_address].flatten.compact
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mhh, Array#+ perhaps? Or Array#concat even?


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
Expand Down Expand Up @@ -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("_", "-")}"]?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HTTP::Headers should have independent lookup by now, https://github.com/manastech/crystal/blob/master/spec/std/http/headers_spec.cr

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it can't lookup HTTP_X_FORWARDED_FOR and X-Forwarded-For right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So only the tr("_", "-") is redundant right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh, missed the comment, sorry. But yes, you're right and looks good now.

ips.strip.split(/[,\s]+/)
else
[] of String
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mhh, Tuple.new should work too here, no?

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also wonder given this whether we shouldn't have remote_ip(trust_headers=true, trust_all_clients=false)? Then docs on that would be great elaborating on what the parameters mean.

end

end
2 changes: 2 additions & 0 deletions src/http/server/server.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down