From d737610959b0f437ca8d71827f86b17ee51cbf7b Mon Sep 17 00:00:00 2001 From: Mridankan Mandal Date: Mon, 15 Jun 2026 12:19:30 +0530 Subject: [PATCH] fix(request): prefer If-None-Match Signed-off-by: Mridankan Mandal --- CHANGELOG.md | 1 + lib/rage/request.rb | 18 ++++++++------- spec/controller/api/conditional_get_spec.rb | 25 ++++++++++++++++----- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b57a11eb..40e1c890 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Fixed +- [API] Ignore `If-Modified-Since` when `If-None-Match` is present. - [Request] Treat IPv6 literals as non-domain hosts. - [Router] Fall back to `SERVER_NAME` when deriving exact host constraints. - [Cookies] Use request host fallback when resolving cookie domains. diff --git a/lib/rage/request.rb b/lib/rage/request.rb index 953b45d9..ac0d2bc1 100644 --- a/lib/rage/request.rb +++ b/lib/rage/request.rb @@ -169,16 +169,18 @@ def headers # request.fresh?(last_modified: Time.utc(2023, 12, 15)) # request.fresh?(etag: "123") def fresh?(etag:, last_modified:) + request_if_none_match = if_none_match + request_not_modified_since = if_not_modified_since + # Always render response when no freshness information # is provided in the request. - return false unless if_none_match || if_not_modified_since - - etag_matches?( - requested_etags: if_none_match, response_etag: etag - ) && not_modified?( - request_not_modified_since: if_not_modified_since, - response_last_modified: last_modified - ) + return false unless request_if_none_match || request_not_modified_since + + if request_if_none_match + etag_matches?(requested_etags: request_if_none_match, response_etag: etag) + else + not_modified?(request_not_modified_since: request_not_modified_since, response_last_modified: last_modified) + end end # Get the domain part of the request. diff --git a/spec/controller/api/conditional_get_spec.rb b/spec/controller/api/conditional_get_spec.rb index 93c379d7..1bc82029 100644 --- a/spec/controller/api/conditional_get_spec.rb +++ b/spec/controller/api/conditional_get_spec.rb @@ -248,6 +248,21 @@ def last_modified_set_raises_error end end + context "and If-None-Match matches but If-Modified-Since is stale" do + let(:env) do + { + "HTTP_IF_MODIFIED_SINCE" => Time.utc(2023, 11, 15).httpdate, + "HTTP_IF_NONE_MATCH" => %(W/"#{Digest::SHA1.hexdigest("123")}") + } + end + + it "returns NOT MODIFIED" do + expect(run_action(klass, :stale_last_modified_and_etag_test, env:)).to match( + [304, a_hash_including(Rage::Response::ETAG_HEADER => expected_etag, Rage::Response::LAST_MODIFIED_HEADER => expected_last_modified), []] + ) + end + end + context "and request is stale" do let(:env) do { @@ -299,17 +314,17 @@ def last_modified_set_raises_error context "and last_modified is not set in the action" do let(:env) do { - "HTTP_IF_MODIFIED_SINCE" => Time.utc(2023, 12, 15).httpdate, - "HTTP_IF_NONE_MATCH" => "123" + "HTTP_IF_MODIFIED_SINCE" => Time.utc(2023, 11, 15).httpdate, + "HTTP_IF_NONE_MATCH" => %(W/"#{Digest::SHA1.hexdigest("123")}") } end - it "renders the requested resource" do + it "returns NOT MODIFIED" do expect(run_action(klass, :stale_etag_test, env:)).to match( - [200, a_hash_including( + [304, a_hash_including( Rage::Response::ETAG_HEADER => expected_etag, Rage::Response::LAST_MODIFIED_HEADER => nil - ), ["test_etag"]] + ), []] ) end end