lib/protocol/http/header/etags.rb
# frozen_string_literal: true # Released under the MIT License. # Copyright, 2020-2025, by Samuel Williams. # Copyright, 2023, by Thomas Morgan. require_relative "split" module Protocol module HTTP module Header # The `etags` header represents a list of entity tags (ETags) for resources. # # The `etags` header is used for conditional requests to compare the current version of a resource with previously stored versions. It supports both strong and weak validators, as well as the wildcard character (`*`) to indicate a match for any resource. class ETags < Split # Checks if the `etags` header contains the wildcard (`*`) character. # # The wildcard character matches any resource version, regardless of its actual value. # # @returns [Boolean] whether the wildcard is present. def wildcard? self.include?("*") end # Checks if the specified ETag matches the `etags` header. # # This method returns `true` if the wildcard is present or if the exact ETag is found in the list. Note that this implementation is not strictly compliant with the RFC-specified format. # # @parameter etag [String] the ETag to compare against the `etags` header. # @returns [Boolean] whether the specified ETag matches. def match?(etag) wildcard? || self.include?(etag) end # Checks for a strong match with the specified ETag, useful with the `if-match` header. # # A strong match requires that the ETag in the header list matches the specified ETag and that neither is a weak validator. # # @parameter etag [String] the ETag to compare against the `etags` header. # @returns [Boolean] whether a strong match is found. def strong_match?(etag) wildcard? || (!weak_tag?(etag) && self.include?(etag)) end # Checks for a weak match with the specified ETag, useful with the `if-none-match` header. # # A weak match allows for semantically equivalent content, including weak validators and their strong counterparts. # # @parameter etag [String] the ETag to compare against the `etags` header. # @returns [Boolean] whether a weak match is found. def weak_match?(etag) wildcard? || self.include?(etag) || self.include?(opposite_tag(etag)) end private # Converts a weak tag to its strong counterpart or vice versa. # # @parameter etag [String] the ETag to convert. # @returns [String] the opposite form of the provided ETag. def opposite_tag(etag) weak_tag?(etag) ? etag[2..-1] : "W/#{etag}" end # Checks if the given ETag is a weak validator. # # @parameter tag [String] the ETag to check. # @returns [Boolean] whether the tag is weak. def weak_tag?(tag) tag&.start_with? "W/" end end end end end