Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

- [API] Ignore `If-Modified-Since` when `If-None-Match` is present.
- [API] Use weak comparison for `If-None-Match` validation.
- [Request] Normalize malformed `SERVER_NAME` fallback hosts that already include a port.
- [Deferred] Ignore missing temp files during async disk storage cleanup.
- [Request] Treat IPv6 literals as non-domain hosts.
- [Router] Fall back to `SERVER_NAME` when deriving exact host constraints.
Expand Down
2 changes: 1 addition & 1 deletion lib/rage/cookies.rb
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ def []=(key, value)
end

if (domain = value[:domain])
host = Rack::Request.new(@env).host
host = Rage::Request.new(@env).host

processed_domain = if domain.is_a?(String)
domain
Expand Down
23 changes: 22 additions & 1 deletion lib/rage/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ class Rage::Request
# Set data structure of all RFC defined HTTP headers
KNOWN_HTTP_METHODS = (RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789).to_set

# Extract the host from a host:port authority while leaving bare IPv6 literals unchanged.
# @param authority [String, nil]
# @return [String, nil]
def self.extract_host(authority)
if authority&.start_with?("[")
authority.sub(/\]:\d+\z/, "]")
elsif authority&.count(":") == 1
authority.sub(/:\d+\z/, "")
else
authority
end
end

# @private
# @param env [Hash] Rack env
# @param controller [RageController::API]
Expand Down Expand Up @@ -226,7 +239,15 @@ def route_uri_pattern
private

def rack_request
@rack_request ||= Rack::Request.new(@env)
@rack_request ||= begin
request_env = @env

if !request_env["HTTP_HOST"] && (server_name = self.class.extract_host(request_env["SERVER_NAME"])) != request_env["SERVER_NAME"]
request_env = request_env.merge("SERVER_NAME" => server_name)
end

Rack::Request.new(request_env)
end
end

def check_method(name)
Expand Down
2 changes: 1 addition & 1 deletion lib/rage/router/constrainer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def __build_derive_constraints
# Optimization: inline the derivation for the common built in constraints
if !strategy.custom?
if key == :host
lines << " host: env['HTTP_HOST'.freeze]&.sub(/:\\d+\\z/, ''.freeze) || env['SERVER_NAME'.freeze],"
lines << " host: env['HTTP_HOST'.freeze]&.sub(/:\\d+\\z/, ''.freeze) || Rage::Request.extract_host(env['SERVER_NAME'.freeze]),"
else
raise ArgumentError, "unknown non-custom strategy for compiling constraint derivation function"
end
Expand Down
36 changes: 36 additions & 0 deletions spec/controller/api/cookies_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,24 @@
expect(response_cookies[:user_id]).to eq("120; domain=cookie.test.com")
end
end

context "when request uses malformed SERVER_NAME fallback" do
let(:request_env) do
{
"SERVER_NAME" => "cookie.test.com:3000",
"SERVER_PORT" => "3000"
}
end

it "correctly sets domain value" do
subject.cookies[:user_id] = {
domain: %w(api.test.com cookie.test.com),
value: 120
}

expect(response_cookies[:user_id]).to eq("120; domain=cookie.test.com")
end
end
end

context "with :all domain" do
Expand Down Expand Up @@ -382,6 +400,24 @@
expect(response_cookies[:user_id]).to eq("120; domain=test.com")
end
end

context "when request uses malformed SERVER_NAME fallback" do
let(:request_env) do
{
"SERVER_NAME" => "cookie.test.com:3000",
"SERVER_PORT" => "3000"
}
end

it "correctly sets domain value" do
subject.cookies[:user_id] = {
domain: :all,
value: 120
}

expect(response_cookies[:user_id]).to eq("120; domain=test.com")
end
end
end

context "with permanent cookies" do
Expand Down
32 changes: 32 additions & 0 deletions spec/rage/request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@
expect(request.url).to eq("http://localhost:3000/users?show_archived=true")
end

context "when HTTP_HOST is missing and SERVER_NAME contains a port" do
before do
env.delete("HTTP_HOST")
env["SERVER_NAME"] = "api.foo.bar.com:3000"
env["SERVER_PORT"] = "3000"
end

it "normalizes the URL" do
expect(request.url).to eq("http://api.foo.bar.com:3000/users?show_archived=true")
end
end

it "returns the path" do
expect(request.path).to eq("/users")
end
Expand Down Expand Up @@ -137,6 +149,14 @@
it "falls back to SERVER_NAME" do
expect(subject).to eq("fallback.example")
end

context "when SERVER_NAME contains a port" do
before { env["SERVER_NAME"] = "api.foo.bar.com:3000" }

it "falls back to the normalized SERVER_NAME" do
expect(subject).to eq("api.foo.bar.com")
end
end
end
end

Expand Down Expand Up @@ -179,6 +199,18 @@
expect(request.domain).to be_nil
end
end

context "without HTTP_HOST and with SERVER_NAME containing a port" do
before do
env.delete("HTTP_HOST")
env["SERVER_NAME"] = "api.foo.bar.com:3000"
env["SERVER_PORT"] = "3000"
end

it "returns the correct domain" do
expect(request.domain).to eq("bar.com")
end
end
end

describe "HTTP method handling" do
Expand Down
17 changes: 17 additions & 0 deletions spec/router/constraints_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,23 @@
expect(handler[:handler].call(env, handler[:params])).to eq("get photos")
end

it "correctly processes a constrained url when malformed SERVER_NAME contains a port" do
router.on("GET", "/photos", ->(_) { "get photos" }, constraints: { host: "google.com" })

env = {
"REQUEST_METHOD" => "GET",
"PATH_INFO" => "/photos",
"SERVER_NAME" => "google.com:3000",
"SERVER_PORT" => "3000",
"rack.input" => StringIO.new
}

handler = router.lookup(env)

expect(handler).not_to be_nil
expect(handler[:handler].call(env, handler[:params])).to eq("get photos")
end

it "correctly processes urls with multiple constraints" do
router.on("GET", "/photos", ->(_) { "US photos" }, constraints: { host: "google.com" })
router.on("GET", "/photos", ->(_) { "CA photos" }, constraints: { host: "google.ca" })
Expand Down