mirror of
https://github.com/samsonjs/http-cookie.git
synced 2026-04-27 14:57:46 +00:00
Introduce an abstraction layer for the cookie store.
CookieJar#jar is removed and #store is added instead.
This commit is contained in:
parent
1f5eb6bc7f
commit
d004408296
4 changed files with 236 additions and 53 deletions
|
|
@ -131,6 +131,19 @@ class HTTP::Cookie
|
||||||
class << self
|
class << self
|
||||||
include URIFix if defined?(URIFix)
|
include URIFix if defined?(URIFix)
|
||||||
|
|
||||||
|
# Normalizes a given path. If it is empty, the root path '/' is
|
||||||
|
# returned. If a URI object is given, returns a new URI object
|
||||||
|
# with the path part normalized.
|
||||||
|
def normalize_path(uri)
|
||||||
|
# Currently does not replace // to /
|
||||||
|
case uri
|
||||||
|
when URI
|
||||||
|
uri.path.empty? ? uri + '/' : uri
|
||||||
|
else
|
||||||
|
uri.empty? ? '/' : uri
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Parses a Set-Cookie header value +set_cookie+ into an array of
|
# Parses a Set-Cookie header value +set_cookie+ into an array of
|
||||||
# Cookie objects. Parts (separated by commas) that are malformed
|
# Cookie objects. Parts (separated by commas) that are malformed
|
||||||
# are ignored.
|
# are ignored.
|
||||||
|
|
@ -277,20 +290,8 @@ class HTTP::Cookie
|
||||||
@domain = @domain_name.hostname
|
@domain = @domain_name.hostname
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize_uri_path(uri)
|
|
||||||
# Currently does not replace // to /
|
|
||||||
uri.path.empty? ? uri + '/' : uri
|
|
||||||
end
|
|
||||||
private :normalize_uri_path
|
|
||||||
|
|
||||||
def normalize_path(path)
|
|
||||||
# Currently does not replace // to /
|
|
||||||
path.empty? ? '/' : path
|
|
||||||
end
|
|
||||||
private :normalize_path
|
|
||||||
|
|
||||||
def path=(path)
|
def path=(path)
|
||||||
@path = normalize_path(path)
|
@path = HTTP::Cookie.normalize_path(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
def origin=(origin)
|
def origin=(origin)
|
||||||
|
|
@ -298,7 +299,7 @@ class HTTP::Cookie
|
||||||
raise ArgumentError, "origin cannot be changed once it is set"
|
raise ArgumentError, "origin cannot be changed once it is set"
|
||||||
origin = URI(origin)
|
origin = URI(origin)
|
||||||
self.domain ||= origin.host
|
self.domain ||= origin.host
|
||||||
self.path ||= (normalize_uri_path(origin) + './').path
|
self.path ||= (HTTP::Cookie.normalize_path(origin) + './').path
|
||||||
acceptable_from_uri?(origin) or
|
acceptable_from_uri?(origin) or
|
||||||
raise ArgumentError, "unacceptable cookie sent from URI #{origin}"
|
raise ArgumentError, "unacceptable cookie sent from URI #{origin}"
|
||||||
@origin = origin
|
@origin = origin
|
||||||
|
|
@ -351,7 +352,7 @@ class HTTP::Cookie
|
||||||
raise "cannot tell if this cookie is valid because the domain is unknown"
|
raise "cannot tell if this cookie is valid because the domain is unknown"
|
||||||
end
|
end
|
||||||
return false if secure? && uri.scheme != 'https'
|
return false if secure? && uri.scheme != 'https'
|
||||||
acceptable_from_uri?(uri) && normalize_path(uri.path).start_with?(@path)
|
acceptable_from_uri?(uri) && HTTP::Cookie.normalize_path(uri.path).start_with?(@path)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a string for use in a Cookie header value,
|
# Returns a string for use in a Cookie header value,
|
||||||
|
|
@ -375,7 +376,7 @@ class HTTP::Cookie
|
||||||
if @for_domain || @domain != DomainName.new(origin.host).hostname
|
if @for_domain || @domain != DomainName.new(origin.host).hostname
|
||||||
string << "; domain=#{@domain}"
|
string << "; domain=#{@domain}"
|
||||||
end
|
end
|
||||||
if (normalize_uri_path(origin) + './').path != @path
|
if (HTTP::Cookie.normalize_path(origin) + './').path != @path
|
||||||
string << "; path=#{@path}"
|
string << "; path=#{@path}"
|
||||||
end
|
end
|
||||||
if expires = @expires
|
if expires = @expires
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
module HTTP
|
require 'http/cookie'
|
||||||
autoload :Cookie, 'http/cookie'
|
|
||||||
end
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# This class is used to manage the Cookies that have been returned from
|
# This class is used to manage the Cookies that have been returned from
|
||||||
|
|
@ -8,15 +6,25 @@ end
|
||||||
|
|
||||||
class HTTP::CookieJar
|
class HTTP::CookieJar
|
||||||
autoload :AbstractSaver, 'http/cookie_jar/abstract_saver'
|
autoload :AbstractSaver, 'http/cookie_jar/abstract_saver'
|
||||||
|
autoload :AbstractStore, 'http/cookie_jar/abstract_store'
|
||||||
|
|
||||||
attr_reader :jar
|
attr_reader :store
|
||||||
|
|
||||||
def initialize
|
def initialize(store = :hash, options = nil)
|
||||||
@jar = {}
|
case store
|
||||||
|
when Symbol
|
||||||
|
@store = AbstractStore.implementation(store).new(options)
|
||||||
|
when AbstractStore
|
||||||
|
options.empty? or
|
||||||
|
raise ArgumentError, 'wrong number of arguments (%d for 1)' % (1 + options.size)
|
||||||
|
@store = store
|
||||||
|
else
|
||||||
|
raise TypeError, 'wrong object given as cookie store: %s' % store.inspect
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize_copy other # :nodoc:
|
def initialize_copy(other)
|
||||||
@jar = Marshal.load Marshal.dump other.jar
|
@store = other.instance_eval { @store.dup }
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add a +cookie+ to the jar and return self.
|
# Add a +cookie+ to the jar and return self.
|
||||||
|
|
@ -24,16 +32,8 @@ class HTTP::CookieJar
|
||||||
if cookie.domain.nil? || cookie.path.nil?
|
if cookie.domain.nil? || cookie.path.nil?
|
||||||
raise ArgumentError, "a cookie with unknown domain or path cannot be added"
|
raise ArgumentError, "a cookie with unknown domain or path cannot be added"
|
||||||
end
|
end
|
||||||
normal_domain = cookie.domain_name.hostname
|
|
||||||
|
|
||||||
path_cookies = ((@jar[normal_domain] ||= {})[cookie.path] ||= {})
|
|
||||||
|
|
||||||
if cookie.expired?
|
|
||||||
path_cookies.delete(cookie.name)
|
|
||||||
else
|
|
||||||
path_cookies[cookie.name] = cookie
|
|
||||||
end
|
|
||||||
|
|
||||||
|
@store.add(cookie)
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
alias << add
|
alias << add
|
||||||
|
|
@ -53,12 +53,23 @@ class HTTP::CookieJar
|
||||||
each(url) { return false }
|
each(url) { return false }
|
||||||
return true
|
return true
|
||||||
else
|
else
|
||||||
@jar.empty?
|
@store.empty?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Iterate over cookies. If +uri+ is given, cookies not for the
|
# Iterates over all cookies that are not expired.
|
||||||
# URL/URI are excluded.
|
#
|
||||||
|
# Available option keywords are below:
|
||||||
|
#
|
||||||
|
# * +uri+
|
||||||
|
#
|
||||||
|
# Specify a URI/URL indicating the destination of the cookies
|
||||||
|
# being selected. Every cookie yielded should be good to send to
|
||||||
|
# the given URI, i.e. cookie.valid_for_uri?(uri) evaluates to
|
||||||
|
# true.
|
||||||
|
#
|
||||||
|
# If (and only if) this option is given, last access time of each
|
||||||
|
# cookie is updated to the current time.
|
||||||
def each(uri = nil, &block)
|
def each(uri = nil, &block)
|
||||||
block_given? or return enum_for(__method__, uri)
|
block_given? or return enum_for(__method__, uri)
|
||||||
|
|
||||||
|
|
@ -68,11 +79,7 @@ class HTTP::CookieJar
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@jar.each { |domain, paths|
|
@store.each(uri, &block)
|
||||||
paths.each { |path, hash|
|
|
||||||
hash.each_value(&block)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
include Enumerable
|
include Enumerable
|
||||||
|
|
@ -202,22 +209,13 @@ class HTTP::CookieJar
|
||||||
|
|
||||||
# Clear the cookie jar and return self.
|
# Clear the cookie jar and return self.
|
||||||
def clear
|
def clear
|
||||||
@jar.clear
|
@store.clear
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# Remove expired cookies and return self.
|
# Remove expired cookies and return self.
|
||||||
def cleanup session = false
|
def cleanup(session = false)
|
||||||
@jar.each do |domain, paths|
|
@store.cleanup session
|
||||||
paths.each do |path, hash|
|
|
||||||
hash.delete_if { |cookie_name, cookie|
|
|
||||||
cookie.expired? || (session && cookie.session?)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
90
lib/http/cookie_jar/abstract_store.rb
Normal file
90
lib/http/cookie_jar/abstract_store.rb
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
require 'http/cookie_jar'
|
||||||
|
|
||||||
|
class HTTP::CookieJar::AbstractStore
|
||||||
|
class << self
|
||||||
|
@@class_map = {}
|
||||||
|
|
||||||
|
# Gets an implementation class by the name, optionally trying to
|
||||||
|
# load "http/cookie_jar/*_store" if not found. If loading fails,
|
||||||
|
# KeyError is raised.
|
||||||
|
def implementation(symbol)
|
||||||
|
@@class_map.fetch(symbol)
|
||||||
|
rescue KeyError
|
||||||
|
begin
|
||||||
|
require 'http/cookie_jar/%s_store' % symbol
|
||||||
|
@@class_map.fetch(symbol)
|
||||||
|
rescue LoadError, KeyError => e
|
||||||
|
raise KeyError, 'cookie store unavailable: %s' % symbol.inspect
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def inherited(subclass)
|
||||||
|
@@class_map[class_to_symbol(subclass)] = subclass
|
||||||
|
end
|
||||||
|
|
||||||
|
def class_to_symbol(klass)
|
||||||
|
klass.name[/[^:]+?(?=Store$|$)/].downcase.to_sym
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_options
|
||||||
|
{}
|
||||||
|
end
|
||||||
|
private :default_options
|
||||||
|
|
||||||
|
def initialize(options = nil)
|
||||||
|
options ||= {}
|
||||||
|
@logger = options[:logger]
|
||||||
|
# Initializes each instance variable of the same name as option
|
||||||
|
# keyword.
|
||||||
|
default_options.each_pair { |key, default|
|
||||||
|
instance_variable_set("@#{key}", options.key?(key) ? options[key] : default)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize_copy(other)
|
||||||
|
raise
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def add(cookie)
|
||||||
|
raise
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
# Iterates over all cookies that are not expired.
|
||||||
|
#
|
||||||
|
# Available option keywords are below:
|
||||||
|
#
|
||||||
|
# * +uri+
|
||||||
|
#
|
||||||
|
# Specify a URI object indicating the destination of the cookies
|
||||||
|
# being selected. Every cookie yielded should be good to send to
|
||||||
|
# the given URI, i.e. cookie.valid_for_uri?(uri) evaluates to
|
||||||
|
# true.
|
||||||
|
#
|
||||||
|
# If (and only if) this option is given, last access time of each
|
||||||
|
# cookie is updated to the current time.
|
||||||
|
def each(options = nil, &block)
|
||||||
|
raise
|
||||||
|
self
|
||||||
|
end
|
||||||
|
include Enumerable
|
||||||
|
|
||||||
|
def clear
|
||||||
|
raise
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def cleanup(session = false)
|
||||||
|
if session
|
||||||
|
select { |cookie| cookie.session? || cookie.expired? }
|
||||||
|
else
|
||||||
|
select(&:expired?)
|
||||||
|
end.each { |cookie|
|
||||||
|
add(cookie.expire)
|
||||||
|
}
|
||||||
|
# subclasses can optionally remove over-the-limit cookies.
|
||||||
|
self
|
||||||
|
end
|
||||||
|
end
|
||||||
94
lib/http/cookie_jar/hash_store.rb
Normal file
94
lib/http/cookie_jar/hash_store.rb
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
require 'http/cookie_jar'
|
||||||
|
|
||||||
|
class Array
|
||||||
|
def sort_by!(&block)
|
||||||
|
replace(sort_by(&block))
|
||||||
|
end unless method_defined?(:sort_by!)
|
||||||
|
end
|
||||||
|
|
||||||
|
class HTTP::CookieJar
|
||||||
|
class HashStore < AbstractStore
|
||||||
|
def default_options
|
||||||
|
{}
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(options = nil)
|
||||||
|
super
|
||||||
|
|
||||||
|
@jar = {}
|
||||||
|
# {
|
||||||
|
# hostname => {
|
||||||
|
# path => {
|
||||||
|
# name => cookie,
|
||||||
|
# ...
|
||||||
|
# },
|
||||||
|
# ...
|
||||||
|
# },
|
||||||
|
# ...
|
||||||
|
# }
|
||||||
|
|
||||||
|
@gc_index = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize_copy(other)
|
||||||
|
@jar = Marshal.load(Marshal.dump(other.instance_variable_get(:@jar)))
|
||||||
|
end
|
||||||
|
|
||||||
|
def add(cookie)
|
||||||
|
path_cookies = ((@jar[cookie.domain_name.hostname] ||= {})[cookie.path] ||= {})
|
||||||
|
|
||||||
|
if cookie.expired?
|
||||||
|
path_cookies.delete(cookie.name)
|
||||||
|
else
|
||||||
|
path_cookies[cookie.name] = cookie
|
||||||
|
end
|
||||||
|
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def each(uri = nil)
|
||||||
|
if uri
|
||||||
|
uri = URI(uri)
|
||||||
|
thost = DomainName.new(uri.host)
|
||||||
|
tpath = HTTP::Cookie.normalize_path(uri.path)
|
||||||
|
@jar.each { |domain, paths|
|
||||||
|
next unless thost.cookie_domain?(domain)
|
||||||
|
paths.each { |path, hash|
|
||||||
|
next unless tpath.start_with?(path)
|
||||||
|
hash.delete_if { |name, cookie|
|
||||||
|
if cookie.expired?
|
||||||
|
true
|
||||||
|
else
|
||||||
|
cookie.accessed_at = Time.now
|
||||||
|
yield cookie
|
||||||
|
false
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
@jar.each { |domain, paths|
|
||||||
|
paths.each { |path, hash|
|
||||||
|
hash.delete_if { |name, cookie|
|
||||||
|
if cookie.expired?
|
||||||
|
true
|
||||||
|
else
|
||||||
|
yield cookie
|
||||||
|
false
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear
|
||||||
|
@jar.clear
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def empty?
|
||||||
|
@jar.empty?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue