Skip to content

Commit

Permalink
use rack 2.2x headers implementation (#249)
Browse files Browse the repository at this point in the history
  • Loading branch information
pcai authored Jun 13, 2024
1 parent ec2a0ad commit 6ff5867
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 196 deletions.
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
expect(response.headers.keys).to eq ["Content-Type"]
end
end

describe "#body" do
Expand Down

0 comments on commit 6ff5867

Please sign in to comment.