Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use rack 2.2x headers implementation #249

Merged
merged 2 commits into from
Jun 13, 2024
Merged
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
249 changes: 53 additions & 196 deletions lib/httpi/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,236 +3,93 @@
# library for it.
module HTTPI
module Utils
# HTTPI::Utils::Headers is a Hash subclass that downcases all keys.
# A case-insensitive Hash that preserves the original case of a
# header when set.
#
class Headers < Hash
KNOWN_HEADERS = {}
%w(
Accept-CH
Accept-Patch
Accept-Ranges
Access-Control-Allow-Credentials
Access-Control-Allow-Headers
Access-Control-Allow-Methods
Access-Control-Allow-Origin
Access-Control-Expose-Headers
Access-Control-Max-Age
Age
Allow
Alt-Svc
Cache-Control
Connection
Content-Disposition
Content-Encoding
Content-Language
Content-Length
Content-Location
Content-MD5
Content-Range
Content-Security-Policy
Content-Security-Policy-Report-Only
Content-Type
Date
Delta-Base
ETag
Expect-CT
Expires
Feature-Policy
IM
Last-Modified
Link
Location
NEL
P3P
Permissions-Policy
Pragma
Preference-Applied
Proxy-Authenticate
Public-Key-Pins
Referrer-Policy
Refresh
Report-To
Retry-After
Server
Set-Cookie
Status
Strict-Transport-Security
Timing-Allow-Origin
Tk
Trailer
Transfer-Encoding
Upgrade
Vary
Via
WWW-Authenticate
Warning
X-Cascade
X-Content-Duration
X-Content-Security-Policy
X-Content-Type-Options
X-Correlation-ID
X-Correlation-Id
X-Download-Options
X-Frame-Options
X-Permitted-Cross-Domain-Policies
X-Powered-By
X-Redirect-By
X-Request-ID
X-Request-Id
X-Runtime
X-UA-Compatible
X-WebKit-CS
X-XSS-Protection
).each do |str|
downcased = str.downcase.freeze
KNOWN_HEADERS[str] = KNOWN_HEADERS[downcased] = downcased
end

def self.[](*items)
if items.length % 2 != 0
if items.length == 1 && items.first.is_a?(Hash)
new.merge!(items.first)
else
raise ArgumentError, "odd number of arguments for Utils::Headers"
end
def self.[](headers)
if headers.is_a?(Headers) && !headers.frozen?
return headers
else
hash = new
loop do
break if items.length == 0
key = items.shift
value = items.shift
hash[key] = value
end
hash
return self.new(headers)
end
end

def [](key)
super(downcase_key(key))
end

def []=(key, value)
super(KNOWN_HEADERS[key] || key.downcase.freeze, value)
end
alias store []=

def assoc(key)
super(downcase_key(key))
end

def compare_by_identity
raise TypeError, "Utils::Headers cannot compare by identity, use regular Hash"
end

def delete(key)
super(downcase_key(key))
end

def dig(key, *a)
super(downcase_key(key), *a)
def initialize(hash = {})
super()
@names = {}
hash.each { |k, v| self[k] = v }
end

def fetch(key, *default, &block)
key = downcase_key(key)
# on dup/clone, we need to duplicate @names hash
def initialize_copy(other)
super
@names = other.names.dup
end

def fetch_values(*a)
super(*a.map!{|key| downcase_key(key)})
# on clear, we need to clear @names hash
def clear
super
@names.clear
end

def has_key?(key)
super(downcase_key(key))
def each
super do |k, v|
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
end
end
alias include? has_key?
alias key? has_key?
alias member? has_key?

def invert
hash = self.class.new
each{|key, value| hash[value] = key}
def to_hash
hash = {}
each { |k, v| hash[k] = v }
hash
end

def merge(hash, &block)
dup.merge!(hash, &block)
def [](k)
super(k) || super(@names[k.downcase])
end

def reject(&block)
hash = dup
hash.reject!(&block)
hash
def []=(k, v)
canonical = k.downcase.freeze
delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
@names[canonical] = k
super k, v
end

def replace(hash)
clear
update(hash)
def delete(k)
canonical = k.downcase
result = super @names.delete(canonical)
result
end

def select(&block)
hash = dup
hash.select!(&block)
hash
def include?(k)
super || @names.include?(k.downcase)
end

def to_proc
lambda{|x| self[x]}
end
alias_method :has_key?, :include?
alias_method :member?, :include?
alias_method :key?, :include?

def transform_values(&block)
dup.transform_values!(&block)
end

def update(hash, &block)
hash.each do |key, value|
self[key] = if block_given? && include?(key)
block.call(key, self[key], value)
else
value
end
end
def merge!(other)
other.each { |k, v| self[k] = v }
self
end
alias merge! update

def values_at(*keys)
keys.map{|key| self[key]}
def merge(other)
hash = dup
hash.merge! other
end

# :nocov:
if RUBY_VERSION >= '2.5'
# :nocov:
def slice(*a)
h = self.class.new
a.each{|k| h[k] = self[k] if has_key?(k)}
h
end

def transform_keys(&block)
dup.transform_keys!(&block)
end

def transform_keys!
hash = self.class.new
each do |k, v|
hash[yield k] = v
end
replace(hash)
end
def replace(other)
clear
other.each { |k, v| self[k] = v }
self
end

# :nocov:
if RUBY_VERSION >= '3.0'
# :nocov:
def except(*a)
super(*a.map!{|key| downcase_key(key)})
protected
def names
@names
end
end

private

def downcase_key(key)
key.is_a?(String) ? KNOWN_HEADERS[key] || key.downcase : key
end
end
end
end
4 changes: 4 additions & 0 deletions spec/httpi/response_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@
it "returns the HTTP response headers" do
expect(response.headers).to eq HTTPI::Utils::Headers.new.merge({ "Content-Type" => "application/dime" })
end

it "preserves casing" do
pcai marked this conversation as resolved.
Show resolved Hide resolved
expect(response.headers.keys).to eq ["Content-Type"]
end
end

describe "#body" do
Expand Down