mirror of
https://github.com/samsonjs/http-cookie.git
synced 2026-04-27 14:57:46 +00:00
Introduce an abstraction layer for saving (serializing) CookieJar.
CookieJar#save is the new name for the now obsolete #save_as. CookieJar#save and #load now accept IO-like instead of a filename. Change the YAML file format, and make #load discard incompatible data.
This commit is contained in:
parent
fd7450717a
commit
1f5eb6bc7f
7 changed files with 256 additions and 120 deletions
|
|
@ -63,8 +63,6 @@ Or install it yourself as:
|
||||||
|
|
||||||
- Print kind error messages to make migration from Mechanize::Cookie easier
|
- Print kind error messages to make migration from Mechanize::Cookie easier
|
||||||
|
|
||||||
- Make serializers pluggable/autoloadable and prepare a binary friendly API
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
1. Fork it
|
1. Fork it
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,6 @@ class HTTP::Cookie
|
||||||
secure httponly
|
secure httponly
|
||||||
expires created_at accessed_at
|
expires created_at accessed_at
|
||||||
]
|
]
|
||||||
True = "TRUE"
|
|
||||||
False = "FALSE"
|
|
||||||
|
|
||||||
# In Ruby < 1.9.3 URI() does not accept an URI object.
|
# In Ruby < 1.9.3 URI() does not accept an URI object.
|
||||||
if RUBY_VERSION < "1.9.3"
|
if RUBY_VERSION < "1.9.3"
|
||||||
|
|
@ -247,35 +245,6 @@ class HTTP::Cookie
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Parses a line from cookies.txt and returns a cookie object if
|
|
||||||
# the line represents a cookie record or returns nil otherwise.
|
|
||||||
def parse_cookiestxt_line(line)
|
|
||||||
return nil if line.match(/^#/)
|
|
||||||
|
|
||||||
domain,
|
|
||||||
s_for_domain, # Whether this cookie is for domain
|
|
||||||
path, # Path for which the cookie is relevant
|
|
||||||
s_secure, # Requires a secure connection
|
|
||||||
s_expires, # Time the cookie expires (Unix epoch time)
|
|
||||||
name, value = line.split("\t", 7)
|
|
||||||
return nil if value.nil?
|
|
||||||
|
|
||||||
value.chomp!
|
|
||||||
|
|
||||||
if (expires_seconds = s_expires.to_i).nonzero?
|
|
||||||
expires = Time.at(expires_seconds)
|
|
||||||
return nil if expires < Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
HTTP::Cookie.new(name, value,
|
|
||||||
:domain => domain,
|
|
||||||
:for_domain => s_for_domain == True,
|
|
||||||
:path => path,
|
|
||||||
:secure => s_secure == True,
|
|
||||||
:expires => expires,
|
|
||||||
:version => 0)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def name=(name)
|
def name=(name)
|
||||||
|
|
@ -392,19 +361,6 @@ class HTTP::Cookie
|
||||||
end
|
end
|
||||||
alias to_s cookie_value
|
alias to_s cookie_value
|
||||||
|
|
||||||
# Serializes the cookie into a cookies.txt line.
|
|
||||||
def to_cookiestxt_line(linefeed = "\n")
|
|
||||||
[
|
|
||||||
@domain,
|
|
||||||
@for_domain ? True : False,
|
|
||||||
@path,
|
|
||||||
@secure ? True : False,
|
|
||||||
@expires.to_i,
|
|
||||||
@name,
|
|
||||||
@value
|
|
||||||
].join("\t") << linefeed
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a string for use in a Set-Cookie header value. If the
|
# Returns a string for use in a Set-Cookie header value. If the
|
||||||
# cookie does not have an origin set, one must be given from the
|
# cookie does not have an origin set, one must be given from the
|
||||||
# argument.
|
# argument.
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ end
|
||||||
# any particular website.
|
# any particular website.
|
||||||
|
|
||||||
class HTTP::CookieJar
|
class HTTP::CookieJar
|
||||||
include Enumerable
|
autoload :AbstractSaver, 'http/cookie_jar/abstract_saver'
|
||||||
|
|
||||||
attr_reader :jar
|
attr_reader :jar
|
||||||
|
|
||||||
|
|
@ -75,13 +75,15 @@ class HTTP::CookieJar
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
include Enumerable
|
||||||
|
|
||||||
# call-seq:
|
# call-seq:
|
||||||
# jar.save_as(file, format = :yaml)
|
# jar.save(filename_or_io, **options)
|
||||||
# jar.save_as(file, options)
|
# jar.save(filename_or_io, format = :yaml, **options)
|
||||||
#
|
#
|
||||||
# Save the cookie jar to a file in the format specified and return
|
# Save the cookie jar into a file or an IO in the format specified
|
||||||
# self.
|
# and return self. If the given object responds to #write it is
|
||||||
|
# taken as an IO, or taken as a filename otherwise.
|
||||||
#
|
#
|
||||||
# Available option keywords are below:
|
# Available option keywords are below:
|
||||||
#
|
#
|
||||||
|
|
@ -95,65 +97,108 @@ class HTTP::CookieJar
|
||||||
# Save session cookies as well.
|
# Save session cookies as well.
|
||||||
# [+false+]
|
# [+false+]
|
||||||
# Do not save session cookies. (default)
|
# Do not save session cookies. (default)
|
||||||
def save_as(file, options = nil)
|
#
|
||||||
if Symbol === options
|
# All options given are passed through to the underlying cookie
|
||||||
format = options
|
# saver module.
|
||||||
session = false
|
def save(writable, *options)
|
||||||
else
|
opthash = {
|
||||||
options ||= {}
|
:format => :yaml,
|
||||||
format = options[:format] || :yaml
|
:session => false,
|
||||||
session = !!options[:session]
|
|
||||||
end
|
|
||||||
|
|
||||||
jar = dup
|
|
||||||
jar.cleanup !session
|
|
||||||
|
|
||||||
open(file, 'w') { |f|
|
|
||||||
case format
|
|
||||||
when :yaml then
|
|
||||||
require_yaml
|
|
||||||
|
|
||||||
YAML.dump(jar.jar, f)
|
|
||||||
when :cookiestxt then
|
|
||||||
jar.dump_cookiestxt(f)
|
|
||||||
else
|
|
||||||
raise ArgumentError, "Unknown cookie jar file format"
|
|
||||||
end
|
|
||||||
}
|
}
|
||||||
|
case options.size
|
||||||
|
when 0
|
||||||
|
when 1
|
||||||
|
case options = options.first
|
||||||
|
when Symbol
|
||||||
|
opthash[:format] = options
|
||||||
|
else
|
||||||
|
opthash.update(options) if options
|
||||||
|
end
|
||||||
|
when 2
|
||||||
|
opthash[:format], options = options
|
||||||
|
opthash.update(options) if options
|
||||||
|
else
|
||||||
|
raise ArgumentError, 'wrong number of arguments (%d for 1-3)' % (1 + options.size)
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
saver = AbstractSaver.implementation(opthash[:format]).new(opthash)
|
||||||
|
rescue KeyError => e
|
||||||
|
raise ArgumentError, e.message
|
||||||
|
end
|
||||||
|
|
||||||
|
if writable.respond_to?(:write)
|
||||||
|
saver.save(writable, self)
|
||||||
|
else
|
||||||
|
File.open(writable, 'w') { |io|
|
||||||
|
saver.save(io, self)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
# Load cookie jar from a file in the format specified.
|
# An obsolete name for save().
|
||||||
|
def save_as(*args)
|
||||||
|
warn "%s() is obsolete; use save()." % __method__
|
||||||
|
save(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# jar.load(filename_or_io, **options)
|
||||||
|
# jar.load(filename_or_io, format = :yaml, **options)
|
||||||
#
|
#
|
||||||
# Available formats:
|
# Load cookies recorded in a file or an IO in the format specified
|
||||||
# :yaml <- YAML structure.
|
# into the jar and return self. If the given object responds to
|
||||||
# :cookiestxt <- Mozilla's cookies.txt format
|
# #read it is taken as an IO, or taken as a filename otherwise.
|
||||||
def load(file, format = :yaml)
|
#
|
||||||
File.open(file) { |f|
|
# Available option keywords are below:
|
||||||
case format
|
#
|
||||||
when :yaml then
|
# * +format+
|
||||||
require_yaml
|
# [<tt>:yaml</tt>]
|
||||||
@jar = YAML.load(f)
|
# YAML structure (default)
|
||||||
when :cookiestxt then
|
# [<tt>:cookiestxt</tt>]
|
||||||
load_cookiestxt(f)
|
# Mozilla's cookies.txt format
|
||||||
else
|
#
|
||||||
raise ArgumentError, "Unknown cookie jar file format"
|
# All options given are passed through to the underlying cookie
|
||||||
end
|
# saver module.
|
||||||
|
def load(readable, *options)
|
||||||
|
opthash = {
|
||||||
|
:format => :yaml,
|
||||||
|
:session => false,
|
||||||
}
|
}
|
||||||
|
case options.size
|
||||||
cleanup
|
when 0
|
||||||
|
when 1
|
||||||
|
case options = options.first
|
||||||
|
when Symbol
|
||||||
|
opthash[:format] = options
|
||||||
|
else
|
||||||
|
opthash.update(options) if options
|
||||||
|
end
|
||||||
|
when 2
|
||||||
|
opthash[:format], options = options
|
||||||
|
opthash.update(options) if options
|
||||||
|
else
|
||||||
|
raise ArgumentError, 'wrong number of arguments (%d for 1-3)' % (1 + options.size)
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_yaml # :nodoc:
|
|
||||||
begin
|
begin
|
||||||
require 'psych'
|
saver = AbstractSaver.implementation(opthash[:format]).new(opthash)
|
||||||
rescue LoadError
|
rescue KeyError => e
|
||||||
|
raise ArgumentError, e.message
|
||||||
end
|
end
|
||||||
|
|
||||||
require 'yaml'
|
if readable.respond_to?(:write)
|
||||||
|
saver.load(readable, self)
|
||||||
|
else
|
||||||
|
File.open(readable, 'r') { |io|
|
||||||
|
saver.load(io, self)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
self
|
||||||
end
|
end
|
||||||
private :require_yaml
|
|
||||||
|
|
||||||
# Clear the cookie jar and return self.
|
# Clear the cookie jar and return self.
|
||||||
def clear
|
def clear
|
||||||
|
|
@ -161,26 +206,6 @@ class HTTP::CookieJar
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
# Read cookies from Mozilla cookies.txt-style IO stream and return
|
|
||||||
# self.
|
|
||||||
def load_cookiestxt(io)
|
|
||||||
io.each_line do |line|
|
|
||||||
c = HTTP::Cookie.parse_cookiestxt_line(line) and add(c)
|
|
||||||
end
|
|
||||||
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
# Write cookies to Mozilla cookies.txt-style IO stream and return
|
|
||||||
# self.
|
|
||||||
def dump_cookiestxt(io)
|
|
||||||
io.puts "# HTTP Cookie File"
|
|
||||||
to_a.each do |cookie|
|
|
||||||
io.print cookie.to_cookiestxt_line
|
|
||||||
end
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
# Remove expired cookies and return self.
|
# Remove expired cookies and return self.
|
||||||
|
|
|
||||||
53
lib/http/cookie_jar/abstract_saver.rb
Normal file
53
lib/http/cookie_jar/abstract_saver.rb
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
require 'http/cookie_jar'
|
||||||
|
|
||||||
|
class HTTP::CookieJar::AbstractSaver
|
||||||
|
class << self
|
||||||
|
@@class_map = {}
|
||||||
|
|
||||||
|
# Gets an implementation class by the name, optionally trying to
|
||||||
|
# load "http/cookie_jar/*_saver" if not found. If loading fails,
|
||||||
|
# KeyError is raised.
|
||||||
|
def implementation(symbol)
|
||||||
|
@@class_map.fetch(symbol)
|
||||||
|
rescue KeyError
|
||||||
|
begin
|
||||||
|
require 'http/cookie_jar/%s_saver' % symbol
|
||||||
|
@@class_map.fetch(symbol)
|
||||||
|
rescue LoadError, KeyError => e
|
||||||
|
raise KeyError, 'cookie saver unavailable: %s' % symbol.inspect
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def inherited(subclass)
|
||||||
|
@@class_map[class_to_symbol(subclass)] = subclass
|
||||||
|
end
|
||||||
|
|
||||||
|
def class_to_symbol(klass)
|
||||||
|
klass.name[/[^:]+?(?=Saver$|$)/].downcase.to_sym
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_options
|
||||||
|
{}
|
||||||
|
end
|
||||||
|
private :default_options
|
||||||
|
|
||||||
|
def initialize(options = nil)
|
||||||
|
options ||= {}
|
||||||
|
@logger = options[:logger]
|
||||||
|
@session = options[:session]
|
||||||
|
# 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 save(io, jar)
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
|
||||||
|
def load(io, jar)
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
end
|
||||||
74
lib/http/cookie_jar/cookiestxt_saver.rb
Normal file
74
lib/http/cookie_jar/cookiestxt_saver.rb
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
require 'http/cookie_jar'
|
||||||
|
|
||||||
|
# CookiestxtSaver saves and loads cookies in the cookies.txt format.
|
||||||
|
class HTTP::CookieJar::CookiestxtSaver < HTTP::CookieJar::AbstractSaver
|
||||||
|
True = "TRUE"
|
||||||
|
False = "FALSE"
|
||||||
|
|
||||||
|
def save(io, jar)
|
||||||
|
io.puts @header if @header
|
||||||
|
jar.each { |cookie|
|
||||||
|
next if !@session && cookie.session?
|
||||||
|
io.print cookie_to_record(cookie)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def load(io, jar)
|
||||||
|
io.each_line { |line|
|
||||||
|
cookie = parse_record(line) and jar.add(cookie)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def default_options
|
||||||
|
{
|
||||||
|
header: "# HTTP Cookie File",
|
||||||
|
linefeed: "\n",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Serializes the cookie into a cookies.txt line.
|
||||||
|
def cookie_to_record(cookie)
|
||||||
|
cookie.instance_eval {
|
||||||
|
[
|
||||||
|
@domain,
|
||||||
|
@for_domain ? True : False,
|
||||||
|
@path,
|
||||||
|
@secure ? True : False,
|
||||||
|
@expires.to_i,
|
||||||
|
@name,
|
||||||
|
@value
|
||||||
|
]
|
||||||
|
}.join("\t") << @linefeed
|
||||||
|
end
|
||||||
|
|
||||||
|
# Parses a line from cookies.txt and returns a cookie object if the
|
||||||
|
# line represents a cookie record or returns nil otherwise.
|
||||||
|
def parse_record(line)
|
||||||
|
return nil if line.match(/^#/)
|
||||||
|
|
||||||
|
domain,
|
||||||
|
s_for_domain, # Whether this cookie is for domain
|
||||||
|
path, # Path for which the cookie is relevant
|
||||||
|
s_secure, # Requires a secure connection
|
||||||
|
s_expires, # Time the cookie expires (Unix epoch time)
|
||||||
|
name, value = line.split("\t", 7)
|
||||||
|
return nil if value.nil?
|
||||||
|
|
||||||
|
value.chomp!
|
||||||
|
|
||||||
|
if (expires_seconds = s_expires.to_i).nonzero?
|
||||||
|
expires = Time.at(expires_seconds)
|
||||||
|
return nil if expires < Time.now
|
||||||
|
end
|
||||||
|
|
||||||
|
HTTP::Cookie.new(name, value,
|
||||||
|
:domain => domain,
|
||||||
|
:for_domain => s_for_domain == True,
|
||||||
|
:path => path,
|
||||||
|
:secure => s_secure == True,
|
||||||
|
:expires => expires,
|
||||||
|
:version => 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
30
lib/http/cookie_jar/yaml_saver.rb
Normal file
30
lib/http/cookie_jar/yaml_saver.rb
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
require 'http/cookie_jar'
|
||||||
|
begin
|
||||||
|
require 'psych'
|
||||||
|
rescue LoadError
|
||||||
|
end
|
||||||
|
require 'yaml'
|
||||||
|
|
||||||
|
# YAMLSaver saves and loads cookies in the YAML format.
|
||||||
|
class HTTP::CookieJar::YAMLSaver < HTTP::CookieJar::AbstractSaver
|
||||||
|
def save(io, jar)
|
||||||
|
YAML.dump(@session ? jar.to_a : jar.reject(&:session?), io)
|
||||||
|
end
|
||||||
|
|
||||||
|
def load(io, jar)
|
||||||
|
begin
|
||||||
|
YAML.load(io)
|
||||||
|
rescue ArgumentError
|
||||||
|
@logger.warn "incompatible YAML cookie data discarded" if @logger
|
||||||
|
return
|
||||||
|
end.each { |cookie|
|
||||||
|
jar.add(cookie)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def default_options
|
||||||
|
{}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -301,7 +301,7 @@ class TestHTTPCookieJar < Test::Unit::TestCase
|
||||||
assert_equal(3, @jar.cookies(url).length)
|
assert_equal(3, @jar.cookies(url).length)
|
||||||
|
|
||||||
in_tmpdir do
|
in_tmpdir do
|
||||||
value = @jar.save_as("cookies.yml")
|
value = @jar.save("cookies.yml")
|
||||||
assert_same @jar, value
|
assert_same @jar, value
|
||||||
|
|
||||||
jar = HTTP::CookieJar.new
|
jar = HTTP::CookieJar.new
|
||||||
|
|
@ -333,7 +333,7 @@ class TestHTTPCookieJar < Test::Unit::TestCase
|
||||||
assert_equal(3, @jar.cookies(url).length)
|
assert_equal(3, @jar.cookies(url).length)
|
||||||
|
|
||||||
in_tmpdir do
|
in_tmpdir do
|
||||||
@jar.save_as("cookies.yml", :format => :yaml, :session => true)
|
@jar.save("cookies.yml", :format => :yaml, :session => true)
|
||||||
|
|
||||||
jar = HTTP::CookieJar.new
|
jar = HTTP::CookieJar.new
|
||||||
jar.load("cookies.yml")
|
jar.load("cookies.yml")
|
||||||
|
|
@ -365,7 +365,7 @@ class TestHTTPCookieJar < Test::Unit::TestCase
|
||||||
assert_equal(3, @jar.cookies(url).length)
|
assert_equal(3, @jar.cookies(url).length)
|
||||||
|
|
||||||
in_tmpdir do
|
in_tmpdir do
|
||||||
@jar.save_as("cookies.txt", :cookiestxt)
|
@jar.save("cookies.txt", :cookiestxt)
|
||||||
|
|
||||||
jar = HTTP::CookieJar.new
|
jar = HTTP::CookieJar.new
|
||||||
jar.load("cookies.txt", :cookiestxt) # HACK test the format
|
jar.load("cookies.txt", :cookiestxt) # HACK test the format
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue