module Rack module Cache # Parses a cache-control header and exposes the directives as a Hash. # Directives that do not have values are set to +true+. class CacheControl < Hash def initialize(value=nil) parse(value) end # Indicates that the response MAY be cached by any cache, even if it # would normally be non-cacheable or cacheable only within a non- # shared cache. # # A response may be considered public without this directive if the # private directive is not set and the request does not include an # Authorization header. def public? self['public'] end # Indicates that all or part of the response message is intended for # a single user and MUST NOT be cached by a shared cache. This # allows an origin server to state that the specified parts of the # response are intended for only one user and are not a valid # response for requests by other users. A private (non-shared) cache # MAY cache the response. # # Note: This usage of the word private only controls where the # response may be cached, and cannot ensure the privacy of the # message content. def private? self['private'] end # When set in a response, a cache MUST NOT use the response to satisfy a # subsequent request without successful revalidation with the origin # server. This allows an origin server to prevent caching even by caches # that have been configured to return stale responses to client requests. # # Note that this does not necessary imply that the response may not be # stored by the cache, only that the cache cannot serve it without first # making a conditional GET request with the origin server. # # When set in a request, the server MUST NOT use a cached copy for its # response. This has quite different semantics compared to the no-cache # directive on responses. When the client specifies no-cache, it causes # an end-to-end reload, forcing each cache to update their cached copies. def no_cache? self['no-cache'] end # Indicates that the response MUST NOT be stored under any circumstances. # # The purpose of the no-store directive is to prevent the # inadvertent release or retention of sensitive information (for # example, on backup tapes). The no-store directive applies to the # entire message, and MAY be sent either in a response or in a # request. If sent in a request, a cache MUST NOT store any part of # either this request or any response to it. If sent in a response, # a cache MUST NOT store any part of either this response or the # request that elicited it. This directive applies to both non- # shared and shared caches. "MUST NOT store" in this context means # that the cache MUST NOT intentionally store the information in # non-volatile storage, and MUST make a best-effort attempt to # remove the information from volatile storage as promptly as # possible after forwarding it. # # The purpose of this directive is to meet the stated requirements # of certain users and service authors who are concerned about # accidental releases of information via unanticipated accesses to # cache data structures. While the use of this directive might # improve privacy in some cases, we caution that it is NOT in any # way a reliable or sufficient mechanism for ensuring privacy. In # particular, malicious or compromised caches might not recognize or # obey this directive, and communications networks might be # vulnerable to eavesdropping. def no_store? self['no-store'] end # The expiration time of an entity MAY be specified by the origin # server using the expires header (see section 14.21). Alternatively, # it MAY be specified using the max-age directive in a response. When # the max-age cache-control directive is present in a cached response, # the response is stale if its current age is greater than the age # value given (in seconds) at the time of a new request for that # resource. The max-age directive on a response implies that the # response is cacheable (i.e., "public") unless some other, more # restrictive cache directive is also present. # # If a response includes both an expires header and a max-age # directive, the max-age directive overrides the expires header, even # if the expires header is more restrictive. This rule allows an origin # server to provide, for a given response, a longer expiration time to # an HTTP/1.1 (or later) cache than to an HTTP/1.0 cache. This might be # useful if certain HTTP/1.0 caches improperly calculate ages or # expiration times, perhaps due to desynchronized clocks. # # Many HTTP/1.0 cache implementations will treat an expires value that # is less than or equal to the response Date value as being equivalent # to the cache-control response directive "no-cache". If an HTTP/1.1 # cache receives such a response, and the response does not include a # cache-control header field, it SHOULD consider the response to be # non-cacheable in order to retain compatibility with HTTP/1.0 servers. # # When the max-age directive is included in the request, it indicates # that the client is willing to accept a response whose age is no # greater than the specified time in seconds. def max_age self['max-age'].to_i if key?('max-age') end # If a response includes an s-maxage directive, then for a shared # cache (but not for a private cache), the maximum age specified by # this directive overrides the maximum age specified by either the # max-age directive or the expires header. The s-maxage directive # also implies the semantics of the proxy-revalidate directive. i.e., # that the shared cache must not use the entry after it becomes stale # to respond to a subsequent request without first revalidating it with # the origin server. The s-maxage directive is always ignored by a # private cache. def shared_max_age self['s-maxage'].to_i if key?('s-maxage') end alias_method :s_maxage, :shared_max_age # If a response includes a r-maxage directive, then for a reverse cache # (but not for a private or proxy cache), the maximum age specified by # this directive overrides the maximum age specified by either the max-age # directive, the s-maxage directive, or the expires header. The r-maxage # directive also implies the semantics of the proxy-revalidate directive. # i.e., that the reverse cache must not use the entry after it becomes # stale to respond to a subsequent request without first revalidating it # with the origin server. The r-maxage directive is always ignored by # private and proxy caches. def reverse_max_age self['r-maxage'].to_i if key?('r-maxage') end alias_method :r_maxage, :reverse_max_age # Because a cache MAY be configured to ignore a server's specified # expiration time, and because a client request MAY include a max- # stale directive (which has a similar effect), the protocol also # includes a mechanism for the origin server to require revalidation # of a cache entry on any subsequent use. When the must-revalidate # directive is present in a response received by a cache, that cache # MUST NOT use the entry after it becomes stale to respond to a # subsequent request without first revalidating it with the origin # server. (I.e., the cache MUST do an end-to-end revalidation every # time, if, based solely on the origin server's expires or max-age # value, the cached response is stale.) # # The must-revalidate directive is necessary to support reliable # operation for certain protocol features. In all circumstances an # HTTP/1.1 cache MUST obey the must-revalidate directive; in # particular, if the cache cannot reach the origin server for any # reason, it MUST generate a 504 (Gateway Timeout) response. # # Servers SHOULD send the must-revalidate directive if and only if # failure to revalidate a request on the entity could result in # incorrect operation, such as a silently unexecuted financial # transaction. Recipients MUST NOT take any automated action that # violates this directive, and MUST NOT automatically provide an # unvalidated copy of the entity if revalidation fails. def must_revalidate? self['must-revalidate'] end # The proxy-revalidate directive has the same meaning as the must- # revalidate directive, except that it does not apply to non-shared # user agent caches. It can be used on a response to an # authenticated request to permit the user's cache to store and # later return the response without needing to revalidate it (since # it has already been authenticated once by that user), while still # requiring proxies that service many users to revalidate each time # (in order to make sure that each user has been authenticated). # Note that such authenticated responses also need the public cache # control directive in order to allow them to be cached at all. def proxy_revalidate? self['proxy-revalidate'] end def to_s bools, vals = [], [] each do |key,value| if value == true bools << key elsif value vals << "#{key}=#{value}" end end (bools.sort + vals.sort).join(', ') end private def parse(value) return if value.nil? || value.empty? value.delete(' ').split(',').each do |part| next if part.empty? name, value = part.split('=', 2) self[name.downcase] = (value || true) unless name.empty? end self end end end end