mirror of
https://github.com/samsonjs/http-cookie.git
synced 2026-03-25 08:55:53 +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
|
||||
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
|
||||
# Cookie objects. Parts (separated by commas) that are malformed
|
||||
# are ignored.
|
||||
|
|
@ -277,20 +290,8 @@ class HTTP::Cookie
|
|||
@domain = @domain_name.hostname
|
||||
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)
|
||||
@path = normalize_path(path)
|
||||
@path = HTTP::Cookie.normalize_path(path)
|
||||
end
|
||||
|
||||
def origin=(origin)
|
||||
|
|
@ -298,7 +299,7 @@ class HTTP::Cookie
|
|||
raise ArgumentError, "origin cannot be changed once it is set"
|
||||
origin = URI(origin)
|
||||
self.domain ||= origin.host
|
||||
self.path ||= (normalize_uri_path(origin) + './').path
|
||||
self.path ||= (HTTP::Cookie.normalize_path(origin) + './').path
|
||||
acceptable_from_uri?(origin) or
|
||||
raise ArgumentError, "unacceptable cookie sent from URI #{origin}"
|
||||
@origin = origin
|
||||
|
|
@ -351,7 +352,7 @@ class HTTP::Cookie
|
|||
raise "cannot tell if this cookie is valid because the domain is unknown"
|
||||
end
|
||||
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
|
||||
|
||||
# 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
|
||||
string << "; domain=#{@domain}"
|
||||
end
|
||||
if (normalize_uri_path(origin) + './').path != @path
|
||||
if (HTTP::Cookie.normalize_path(origin) + './').path != @path
|
||||
string << "; path=#{@path}"
|
||||
end
|
||||
if expires = @expires
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
module HTTP
|
||||
autoload :Cookie, 'http/cookie'
|
||||
end
|
||||
require 'http/cookie'
|
||||
|
||||
##
|
||||
# This class is used to manage the Cookies that have been returned from
|
||||
|
|
@ -8,15 +6,25 @@ end
|
|||
|
||||
class HTTP::CookieJar
|
||||
autoload :AbstractSaver, 'http/cookie_jar/abstract_saver'
|
||||
autoload :AbstractStore, 'http/cookie_jar/abstract_store'
|
||||
|
||||
attr_reader :jar
|
||||
attr_reader :store
|
||||
|
||||
def initialize
|
||||
@jar = {}
|
||||
def initialize(store = :hash, options = nil)
|
||||
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
|
||||
|
||||
def initialize_copy other # :nodoc:
|
||||
@jar = Marshal.load Marshal.dump other.jar
|
||||
def initialize_copy(other)
|
||||
@store = other.instance_eval { @store.dup }
|
||||
end
|
||||
|
||||
# Add a +cookie+ to the jar and return self.
|
||||
|
|
@ -24,16 +32,8 @@ class HTTP::CookieJar
|
|||
if cookie.domain.nil? || cookie.path.nil?
|
||||
raise ArgumentError, "a cookie with unknown domain or path cannot be added"
|
||||
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
|
||||
end
|
||||
alias << add
|
||||
|
|
@ -53,12 +53,23 @@ class HTTP::CookieJar
|
|||
each(url) { return false }
|
||||
return true
|
||||
else
|
||||
@jar.empty?
|
||||
@store.empty?
|
||||
end
|
||||
end
|
||||
|
||||
# Iterate over cookies. If +uri+ is given, cookies not for the
|
||||
# URL/URI are excluded.
|
||||
# Iterates over all cookies that are not expired.
|
||||
#
|
||||
# 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)
|
||||
block_given? or return enum_for(__method__, uri)
|
||||
|
||||
|
|
@ -68,11 +79,7 @@ class HTTP::CookieJar
|
|||
}
|
||||
end
|
||||
|
||||
@jar.each { |domain, paths|
|
||||
paths.each { |path, hash|
|
||||
hash.each_value(&block)
|
||||
}
|
||||
}
|
||||
@store.each(uri, &block)
|
||||
self
|
||||
end
|
||||
include Enumerable
|
||||
|
|
@ -202,22 +209,13 @@ class HTTP::CookieJar
|
|||
|
||||
# Clear the cookie jar and return self.
|
||||
def clear
|
||||
@jar.clear
|
||||
@store.clear
|
||||
self
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Remove expired cookies and return self.
|
||||
def cleanup session = false
|
||||
@jar.each do |domain, paths|
|
||||
paths.each do |path, hash|
|
||||
hash.delete_if { |cookie_name, cookie|
|
||||
cookie.expired? || (session && cookie.session?)
|
||||
}
|
||||
end
|
||||
end
|
||||
def cleanup(session = false)
|
||||
@store.cleanup session
|
||||
self
|
||||
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