lib/rack/conditional_get.rb in rack-2.1.4.4 vs lib/rack/conditional_get.rb in rack-2.2.0

- old
+ new

@@ -1,9 +1,7 @@ # frozen_string_literal: true -require 'rack/utils' - module Rack # Middleware that enables conditional GET using If-None-Match and # If-Modified-Since. The application should set either or both of the # Last-Modified or Etag response headers according to RFC 2616. When @@ -19,15 +17,17 @@ class ConditionalGet def initialize(app) @app = app end + # Return empty 304 response if the response has not been + # modified since the last request. def call(env) case env[REQUEST_METHOD] when "GET", "HEAD" status, headers, body = @app.call(env) - headers = Utils::HeaderHash.new(headers) + headers = Utils::HeaderHash[headers] if status == 200 && fresh?(env, headers) status = 304 headers.delete(CONTENT_TYPE) headers.delete(CONTENT_LENGTH) original_body = body @@ -41,41 +41,43 @@ end end private + # Return whether the response has not been modified since the + # last request. def fresh?(env, headers) - modified_since = env['HTTP_IF_MODIFIED_SINCE'] - none_match = env['HTTP_IF_NONE_MATCH'] - - return false unless modified_since || none_match - - success = true - success &&= modified_since?(to_rfc2822(modified_since), headers) if modified_since - success &&= etag_matches?(none_match, headers) if none_match - success + # If-None-Match has priority over If-Modified-Since per RFC 7232 + if none_match = env['HTTP_IF_NONE_MATCH'] + etag_matches?(none_match, headers) + elsif (modified_since = env['HTTP_IF_MODIFIED_SINCE']) && (modified_since = to_rfc2822(modified_since)) + modified_since?(modified_since, headers) + end end + # Whether the ETag response header matches the If-None-Match request header. + # If so, the request has not been modified. def etag_matches?(none_match, headers) - etag = headers['ETag'] and etag == none_match + headers['ETag'] == none_match end + # Whether the Last-Modified response header matches the If-Modified-Since + # request header. If so, the request has not been modified. def modified_since?(modified_since, headers) last_modified = to_rfc2822(headers['Last-Modified']) and - modified_since and modified_since >= last_modified end + # Return a Time object for the given string (which should be in RFC2822 + # format), or nil if the string cannot be parsed. def to_rfc2822(since) # shortest possible valid date is the obsolete: 1 Nov 97 09:55 A # anything shorter is invalid, this avoids exceptions for common cases # most common being the empty string if since && since.length >= 16 # NOTE: there is no trivial way to write this in a non exception way # _rfc2822 returns a hash but is not that usable Time.rfc2822(since) rescue nil - else - nil end end end end