http-cookie/lib/http/cookie_jar.rb
2012-10-14 18:52:52 +09:00

221 lines
4.8 KiB
Ruby

module HTTP
autoload :Cookie, 'http/cookie'
end
##
# This class is used to manage the Cookies that have been returned from
# any particular website.
class HTTP::CookieJar
include Enumerable
# add_cookie wants something resembling a URI.
attr_reader :jar
def initialize
@jar = {}
end
def initialize_copy other # :nodoc:
@jar = Marshal.load Marshal.dump other.jar
end
# Add a +cookie+ to the jar if it is considered acceptable from
# +uri+. Return nil if the cookie was not added, otherwise return
# the cookie added.
def add(uri, cookie)
return nil unless cookie.acceptable_from_uri?(uri)
add!(cookie)
cookie
end
# Add a +cookie+ to the jar and return self.
def add!(cookie)
normal_domain = cookie.domain.downcase
@jar[normal_domain] ||= {} unless @jar.has_key?(normal_domain)
@jar[normal_domain][cookie.path] ||= {}
@jar[normal_domain][cookie.path][cookie.name] = cookie
self
end
alias << add!
# Fetch the cookies that should be used for the URI object passed in.
def cookies(url)
cleanup
url.path = '/' if url.path.empty?
now = Time.now
select { |cookie|
!cookie.expired? && cookie.valid_for_uri?(url) && (cookie.accessed_at = now)
}.sort_by { |cookie|
# RFC 6265 5.4
# Precedence: 1. longer path 2. older creation
[-cookie.path.length, cookie.created_at]
}
end
def empty?(url)
cookies(url).length > 0 ? false : true
end
def each
block_given? or return enum_for(__method__)
cleanup
@jar.each { |domain, paths|
paths.each { |path, hash|
hash.each_value { |cookie|
yield cookie
}
}
}
end
# call-seq:
# jar.save_as(file, format = :yaml)
# jar.save_as(file, options)
#
# Save the cookie jar to a file in the format specified and return
# self.
#
# Available option keywords are below:
#
# * +format+
# [<tt>:yaml</tt>]
# YAML structure (default)
# [<tt>:cookiestxt</tt>]
# Mozilla's cookies.txt format
# * +session+
# [+true+]
# Save session cookies as well.
# [+false+]
# Do not save session cookies. (default)
def save_as(file, options = nil)
if Symbol === options
format = options
session = false
else
options ||= {}
format = options[:format] || :yaml
session = !!options[:session]
end
jar = dup
jar.cleanup !session
open(file, 'w') { |f|
case format
when :yaml then
load_yaml
YAML.dump(jar.jar, f)
when :cookiestxt then
jar.dump_cookiestxt(f)
else
raise ArgumentError, "Unknown cookie jar file format"
end
}
self
end
# Load cookie jar from a file in the format specified.
#
# Available formats:
# :yaml <- YAML structure.
# :cookiestxt <- Mozilla's cookies.txt format
def load(file, format = :yaml)
@jar = open(file) { |f|
case format
when :yaml then
load_yaml
YAML.load(f)
when :cookiestxt then
load_cookiestxt(f)
else
raise ArgumentError, "Unknown cookie jar file format"
end
}
cleanup
self
end
def load_yaml # :nodoc:
begin
require 'psych'
rescue LoadError
end
require 'yaml'
end
# Clear the cookie jar
def clear!
@jar = {}
end
# Read cookies from Mozilla cookies.txt-style IO stream
def load_cookiestxt(io)
now = Time.now
io.each_line do |line|
line.chomp!
line.gsub!(/#.+/, '')
fields = line.split("\t")
next if fields.length != 7
expires_seconds = fields[4].to_i
expires = (expires_seconds == 0) ? nil : Time.at(expires_seconds)
next if expires and (expires < now)
c = HTTP::Cookie.new(fields[5], fields[6])
c.domain = fields[0]
c.for_domain = (fields[1] == "TRUE") # Whether this cookie is for domain
c.path = fields[2] # Path for which the cookie is relevant
c.secure = (fields[3] == "TRUE") # Requires a secure connection
c.expires = expires # Time the cookie expires.
c.version = 0 # Conforms to Netscape cookie spec.
add!(c)
end
@jar
end
# Write cookies to Mozilla cookies.txt-style IO stream
def dump_cookiestxt(io)
to_a.each do |cookie|
io.puts([
cookie.domain,
cookie.for_domain? ? "TRUE" : "FALSE",
cookie.path,
cookie.secure ? "TRUE" : "FALSE",
cookie.expires.to_i.to_s,
cookie.name,
cookie.value
].join("\t"))
end
end
protected
# Remove expired cookies
def cleanup session = false
@jar.each do |domain, paths|
paths.each do |path, names|
names.each do |cookie_name, cookie|
paths[path].delete(cookie_name) if
cookie.expired? or (session and cookie.session)
end
end
end
end
end