http-cookie 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +24 -0
- data/README.md +139 -0
- data/Rakefile +26 -0
- data/http-cookie.gemspec +29 -0
- data/lib/http-cookie.rb +1 -0
- data/lib/http/cookie.rb +447 -0
- data/lib/http/cookie/version.rb +5 -0
- data/lib/http/cookie_jar.rb +233 -0
- data/lib/http/cookie_jar/abstract_saver.rb +53 -0
- data/lib/http/cookie_jar/abstract_store.rb +90 -0
- data/lib/http/cookie_jar/cookiestxt_saver.rb +74 -0
- data/lib/http/cookie_jar/hash_store.rb +141 -0
- data/lib/http/cookie_jar/yaml_saver.rb +37 -0
- data/test/helper.rb +33 -0
- data/test/simplecov_start.rb +2 -0
- data/test/test_http_cookie.rb +624 -0
- data/test/test_http_cookie_jar.rb +585 -0
- metadata +138 -0
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'http/cookie'
|
2
|
+
|
3
|
+
##
|
4
|
+
# This class is used to manage the Cookies that have been returned from
|
5
|
+
# any particular website.
|
6
|
+
|
7
|
+
class HTTP::CookieJar
|
8
|
+
autoload :AbstractSaver, 'http/cookie_jar/abstract_saver'
|
9
|
+
autoload :AbstractStore, 'http/cookie_jar/abstract_store'
|
10
|
+
|
11
|
+
attr_reader :store
|
12
|
+
|
13
|
+
def initialize(store = :hash, options = nil)
|
14
|
+
case store
|
15
|
+
when Symbol
|
16
|
+
@store = AbstractStore.implementation(store).new(options)
|
17
|
+
when AbstractStore
|
18
|
+
options.empty? or
|
19
|
+
raise ArgumentError, 'wrong number of arguments (%d for 1)' % (1 + options.size)
|
20
|
+
@store = store
|
21
|
+
else
|
22
|
+
raise TypeError, 'wrong object given as cookie store: %s' % store.inspect
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize_copy(other)
|
27
|
+
@store = other.instance_eval { @store.dup }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Add a +cookie+ to the jar and return self.
|
31
|
+
def add(cookie, *_)
|
32
|
+
_.empty? or
|
33
|
+
raise ArgumentError, 'HTTP::Cookie equivalent for Mechanize::CookieJar#add(uri, cookie) is #add(cookie) after setting cookie.origin = uri.'
|
34
|
+
|
35
|
+
if cookie.domain.nil? || cookie.path.nil?
|
36
|
+
raise ArgumentError, "a cookie with unknown domain or path cannot be added"
|
37
|
+
end
|
38
|
+
|
39
|
+
@store.add(cookie)
|
40
|
+
self
|
41
|
+
end
|
42
|
+
alias << add
|
43
|
+
|
44
|
+
# Used to exist in Mechanize::CookieJar. Use #add().
|
45
|
+
def add!(cookie)
|
46
|
+
raise NoMethodError, 'HTTP::Cookie equivalent for Mechanize::CookieJar#add!() is #add().'
|
47
|
+
end
|
48
|
+
|
49
|
+
# Fetch the cookies that should be used for the URL/URI.
|
50
|
+
def cookies(url)
|
51
|
+
now = Time.now
|
52
|
+
each(url).select { |cookie|
|
53
|
+
!cookie.expired? && (cookie.accessed_at = now)
|
54
|
+
}.sort
|
55
|
+
end
|
56
|
+
|
57
|
+
# Tests if the jar is empty. If url is given, tests if there is no
|
58
|
+
# cookie for the URL.
|
59
|
+
def empty?(url = nil)
|
60
|
+
if url
|
61
|
+
each(url) { return false }
|
62
|
+
return true
|
63
|
+
else
|
64
|
+
@store.empty?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Iterates over all cookies that are not expired.
|
69
|
+
#
|
70
|
+
# Available option keywords are below:
|
71
|
+
#
|
72
|
+
# * +uri+
|
73
|
+
#
|
74
|
+
# Specify a URI/URL indicating the destination of the cookies
|
75
|
+
# being selected. Every cookie yielded should be good to send to
|
76
|
+
# the given URI, i.e. cookie.valid_for_uri?(uri) evaluates to
|
77
|
+
# true.
|
78
|
+
#
|
79
|
+
# If (and only if) this option is given, last access time of each
|
80
|
+
# cookie is updated to the current time.
|
81
|
+
def each(uri = nil, &block)
|
82
|
+
block_given? or return enum_for(__method__, uri)
|
83
|
+
|
84
|
+
if uri
|
85
|
+
block = proc { |cookie|
|
86
|
+
yield cookie if cookie.valid_for_uri?(uri)
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
@store.each(uri, &block)
|
91
|
+
self
|
92
|
+
end
|
93
|
+
include Enumerable
|
94
|
+
|
95
|
+
# call-seq:
|
96
|
+
# jar.save(filename_or_io, **options)
|
97
|
+
# jar.save(filename_or_io, format = :yaml, **options)
|
98
|
+
#
|
99
|
+
# Save the cookie jar into a file or an IO in the format specified
|
100
|
+
# and return self. If the given object responds to #write it is
|
101
|
+
# taken as an IO, or taken as a filename otherwise.
|
102
|
+
#
|
103
|
+
# Available option keywords are below:
|
104
|
+
#
|
105
|
+
# * +format+
|
106
|
+
# [<tt>:yaml</tt>]
|
107
|
+
# YAML structure (default)
|
108
|
+
# [<tt>:cookiestxt</tt>]
|
109
|
+
# Mozilla's cookies.txt format
|
110
|
+
# * +session+
|
111
|
+
# [+true+]
|
112
|
+
# Save session cookies as well.
|
113
|
+
# [+false+]
|
114
|
+
# Do not save session cookies. (default)
|
115
|
+
#
|
116
|
+
# All options given are passed through to the underlying cookie
|
117
|
+
# saver module.
|
118
|
+
def save(writable, *options)
|
119
|
+
opthash = {
|
120
|
+
:format => :yaml,
|
121
|
+
:session => false,
|
122
|
+
}
|
123
|
+
case options.size
|
124
|
+
when 0
|
125
|
+
when 1
|
126
|
+
case options = options.first
|
127
|
+
when Symbol
|
128
|
+
opthash[:format] = options
|
129
|
+
else
|
130
|
+
opthash.update(options) if options
|
131
|
+
end
|
132
|
+
when 2
|
133
|
+
opthash[:format], options = options
|
134
|
+
opthash.update(options) if options
|
135
|
+
else
|
136
|
+
raise ArgumentError, 'wrong number of arguments (%d for 1-3)' % (1 + options.size)
|
137
|
+
end
|
138
|
+
|
139
|
+
begin
|
140
|
+
saver = AbstractSaver.implementation(opthash[:format]).new(opthash)
|
141
|
+
rescue KeyError => e
|
142
|
+
raise ArgumentError, e.message
|
143
|
+
end
|
144
|
+
|
145
|
+
if writable.respond_to?(:write)
|
146
|
+
saver.save(writable, self)
|
147
|
+
else
|
148
|
+
File.open(writable, 'w') { |io|
|
149
|
+
saver.save(io, self)
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
self
|
154
|
+
end
|
155
|
+
|
156
|
+
# Used to exist in Mechanize::CookieJar. Use #save().
|
157
|
+
def save_as(*args)
|
158
|
+
raise NoMethodError, 'HTTP::Cookie equivalent for Mechanize::CookieJar#save_as() is #save().'
|
159
|
+
end
|
160
|
+
|
161
|
+
# call-seq:
|
162
|
+
# jar.load(filename_or_io, **options)
|
163
|
+
# jar.load(filename_or_io, format = :yaml, **options)
|
164
|
+
#
|
165
|
+
# Load cookies recorded in a file or an IO in the format specified
|
166
|
+
# into the jar and return self. If the given object responds to
|
167
|
+
# #read it is taken as an IO, or taken as a filename otherwise.
|
168
|
+
#
|
169
|
+
# Available option keywords are below:
|
170
|
+
#
|
171
|
+
# * +format+
|
172
|
+
# [<tt>:yaml</tt>]
|
173
|
+
# YAML structure (default)
|
174
|
+
# [<tt>:cookiestxt</tt>]
|
175
|
+
# Mozilla's cookies.txt format
|
176
|
+
#
|
177
|
+
# All options given are passed through to the underlying cookie
|
178
|
+
# saver module.
|
179
|
+
def load(readable, *options)
|
180
|
+
opthash = {
|
181
|
+
:format => :yaml,
|
182
|
+
:session => false,
|
183
|
+
}
|
184
|
+
case options.size
|
185
|
+
when 0
|
186
|
+
when 1
|
187
|
+
case options = options.first
|
188
|
+
when Symbol
|
189
|
+
opthash[:format] = options
|
190
|
+
else
|
191
|
+
opthash.update(options) if options
|
192
|
+
end
|
193
|
+
when 2
|
194
|
+
opthash[:format], options = options
|
195
|
+
opthash.update(options) if options
|
196
|
+
else
|
197
|
+
raise ArgumentError, 'wrong number of arguments (%d for 1-3)' % (1 + options.size)
|
198
|
+
end
|
199
|
+
|
200
|
+
begin
|
201
|
+
saver = AbstractSaver.implementation(opthash[:format]).new(opthash)
|
202
|
+
rescue KeyError => e
|
203
|
+
raise ArgumentError, e.message
|
204
|
+
end
|
205
|
+
|
206
|
+
if readable.respond_to?(:write)
|
207
|
+
saver.load(readable, self)
|
208
|
+
else
|
209
|
+
File.open(readable, 'r') { |io|
|
210
|
+
saver.load(io, self)
|
211
|
+
}
|
212
|
+
end
|
213
|
+
|
214
|
+
self
|
215
|
+
end
|
216
|
+
|
217
|
+
# Clear the cookie jar and return self.
|
218
|
+
def clear
|
219
|
+
@store.clear
|
220
|
+
self
|
221
|
+
end
|
222
|
+
|
223
|
+
# Used to exist in Mechanize::CookieJar. Use #clear().
|
224
|
+
def clear!(*args)
|
225
|
+
raise NoMethodError, 'HTTP::Cookie equivalent for Mechanize::CookieJar#clear!() is #clear().'
|
226
|
+
end
|
227
|
+
|
228
|
+
# Remove expired cookies and return self.
|
229
|
+
def cleanup(session = false)
|
230
|
+
@store.cleanup session
|
231
|
+
self
|
232
|
+
end
|
233
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'http/cookie_jar'
|
2
|
+
|
3
|
+
class HTTP::CookieJar::AbstractSaver
|
4
|
+
class << self
|
5
|
+
@@class_map = {}
|
6
|
+
|
7
|
+
# Gets an implementation class by the name, optionally trying to
|
8
|
+
# load "http/cookie_jar/*_saver" if not found. If loading fails,
|
9
|
+
# KeyError is raised.
|
10
|
+
def implementation(symbol)
|
11
|
+
@@class_map.fetch(symbol)
|
12
|
+
rescue KeyError
|
13
|
+
begin
|
14
|
+
require 'http/cookie_jar/%s_saver' % symbol
|
15
|
+
@@class_map.fetch(symbol)
|
16
|
+
rescue LoadError, KeyError => e
|
17
|
+
raise KeyError, 'cookie saver unavailable: %s' % symbol.inspect
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def inherited(subclass)
|
22
|
+
@@class_map[class_to_symbol(subclass)] = subclass
|
23
|
+
end
|
24
|
+
|
25
|
+
def class_to_symbol(klass)
|
26
|
+
klass.name[/[^:]+?(?=Saver$|$)/].downcase.to_sym
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def default_options
|
31
|
+
{}
|
32
|
+
end
|
33
|
+
private :default_options
|
34
|
+
|
35
|
+
def initialize(options = nil)
|
36
|
+
options ||= {}
|
37
|
+
@logger = options[:logger]
|
38
|
+
@session = options[:session]
|
39
|
+
# Initializes each instance variable of the same name as option
|
40
|
+
# keyword.
|
41
|
+
default_options.each_pair { |key, default|
|
42
|
+
instance_variable_set("@#{key}", options.key?(key) ? options[key] : default)
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def save(io, jar)
|
47
|
+
raise
|
48
|
+
end
|
49
|
+
|
50
|
+
def load(io, jar)
|
51
|
+
raise
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'http/cookie_jar'
|
2
|
+
|
3
|
+
class HTTP::CookieJar::AbstractStore
|
4
|
+
class << self
|
5
|
+
@@class_map = {}
|
6
|
+
|
7
|
+
# Gets an implementation class by the name, optionally trying to
|
8
|
+
# load "http/cookie_jar/*_store" if not found. If loading fails,
|
9
|
+
# KeyError is raised.
|
10
|
+
def implementation(symbol)
|
11
|
+
@@class_map.fetch(symbol)
|
12
|
+
rescue KeyError
|
13
|
+
begin
|
14
|
+
require 'http/cookie_jar/%s_store' % symbol
|
15
|
+
@@class_map.fetch(symbol)
|
16
|
+
rescue LoadError, KeyError => e
|
17
|
+
raise KeyError, 'cookie store unavailable: %s' % symbol.inspect
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def inherited(subclass)
|
22
|
+
@@class_map[class_to_symbol(subclass)] = subclass
|
23
|
+
end
|
24
|
+
|
25
|
+
def class_to_symbol(klass)
|
26
|
+
klass.name[/[^:]+?(?=Store$|$)/].downcase.to_sym
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def default_options
|
31
|
+
{}
|
32
|
+
end
|
33
|
+
private :default_options
|
34
|
+
|
35
|
+
def initialize(options = nil)
|
36
|
+
options ||= {}
|
37
|
+
@logger = options[:logger]
|
38
|
+
# Initializes each instance variable of the same name as option
|
39
|
+
# keyword.
|
40
|
+
default_options.each_pair { |key, default|
|
41
|
+
instance_variable_set("@#{key}", options.key?(key) ? options[key] : default)
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize_copy(other)
|
46
|
+
raise
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def add(cookie)
|
51
|
+
raise
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
# Iterates over all cookies that are not expired.
|
56
|
+
#
|
57
|
+
# Available option keywords are below:
|
58
|
+
#
|
59
|
+
# * +uri+
|
60
|
+
#
|
61
|
+
# Specify a URI object indicating the destination of the cookies
|
62
|
+
# being selected. Every cookie yielded should be good to send to
|
63
|
+
# the given URI, i.e. cookie.valid_for_uri?(uri) evaluates to
|
64
|
+
# true.
|
65
|
+
#
|
66
|
+
# If (and only if) this option is given, last access time of each
|
67
|
+
# cookie is updated to the current time.
|
68
|
+
def each(options = nil, &block)
|
69
|
+
raise
|
70
|
+
self
|
71
|
+
end
|
72
|
+
include Enumerable
|
73
|
+
|
74
|
+
def clear
|
75
|
+
raise
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
def cleanup(session = false)
|
80
|
+
if session
|
81
|
+
select { |cookie| cookie.session? || cookie.expired? }
|
82
|
+
else
|
83
|
+
select(&:expired?)
|
84
|
+
end.each { |cookie|
|
85
|
+
add(cookie.expire)
|
86
|
+
}
|
87
|
+
# subclasses can optionally remove over-the-limit cookies.
|
88
|
+
self
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'http/cookie_jar'
|
2
|
+
|
3
|
+
# CookiestxtSaver saves and loads cookies in the cookies.txt format.
|
4
|
+
class HTTP::CookieJar::CookiestxtSaver < HTTP::CookieJar::AbstractSaver
|
5
|
+
True = "TRUE"
|
6
|
+
False = "FALSE"
|
7
|
+
|
8
|
+
def save(io, jar)
|
9
|
+
io.puts @header if @header
|
10
|
+
jar.each { |cookie|
|
11
|
+
next if !@session && cookie.session?
|
12
|
+
io.print cookie_to_record(cookie)
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def load(io, jar)
|
17
|
+
io.each_line { |line|
|
18
|
+
cookie = parse_record(line) and jar.add(cookie)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def default_options
|
25
|
+
{
|
26
|
+
header: "# HTTP Cookie File",
|
27
|
+
linefeed: "\n",
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Serializes the cookie into a cookies.txt line.
|
32
|
+
def cookie_to_record(cookie)
|
33
|
+
cookie.instance_eval {
|
34
|
+
[
|
35
|
+
@domain,
|
36
|
+
@for_domain ? True : False,
|
37
|
+
@path,
|
38
|
+
@secure ? True : False,
|
39
|
+
@expires.to_i,
|
40
|
+
@name,
|
41
|
+
@value
|
42
|
+
]
|
43
|
+
}.join("\t") << @linefeed
|
44
|
+
end
|
45
|
+
|
46
|
+
# Parses a line from cookies.txt and returns a cookie object if the
|
47
|
+
# line represents a cookie record or returns nil otherwise.
|
48
|
+
def parse_record(line)
|
49
|
+
return nil if line.match(/^#/)
|
50
|
+
|
51
|
+
domain,
|
52
|
+
s_for_domain, # Whether this cookie is for domain
|
53
|
+
path, # Path for which the cookie is relevant
|
54
|
+
s_secure, # Requires a secure connection
|
55
|
+
s_expires, # Time the cookie expires (Unix epoch time)
|
56
|
+
name, value = line.split("\t", 7)
|
57
|
+
return nil if value.nil?
|
58
|
+
|
59
|
+
value.chomp!
|
60
|
+
|
61
|
+
if (expires_seconds = s_expires.to_i).nonzero?
|
62
|
+
expires = Time.at(expires_seconds)
|
63
|
+
return nil if expires < Time.now
|
64
|
+
end
|
65
|
+
|
66
|
+
HTTP::Cookie.new(name, value,
|
67
|
+
:domain => domain,
|
68
|
+
:for_domain => s_for_domain == True,
|
69
|
+
:path => path,
|
70
|
+
:secure => s_secure == True,
|
71
|
+
:expires => expires,
|
72
|
+
:version => 0)
|
73
|
+
end
|
74
|
+
end
|