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
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) 2013 Akinori MUSHA
|
2
|
+
Copyright (c) 2011-2012 Akinori MUSHA, Eric Hodel
|
3
|
+
Copyright (c) 2006-2011 Aaron Patterson, Mike Dalessio
|
4
|
+
|
5
|
+
MIT License
|
6
|
+
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
8
|
+
a copy of this software and associated documentation files (the
|
9
|
+
"Software"), to deal in the Software without restriction, including
|
10
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
11
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
12
|
+
permit persons to whom the Software is furnished to do so, subject to
|
13
|
+
the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be
|
16
|
+
included in all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
20
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
22
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
23
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
24
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
# HTTP::Cookie
|
2
|
+
|
3
|
+
`HTTP::Cookie` is a ruby library to handle HTTP cookies in a way both
|
4
|
+
compliant with RFCs and compatible with today's major browsers.
|
5
|
+
|
6
|
+
It was originally a part of the
|
7
|
+
[Mechanize](https://github.com/sparklemotion/mechanize) library,
|
8
|
+
separated as an independent library in the hope of serving as a common
|
9
|
+
component that is reusable from any HTTP related piece of software.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's `Gemfile`:
|
14
|
+
|
15
|
+
gem 'http-cookie'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install http-cookie
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
########################
|
28
|
+
# Client side example
|
29
|
+
########################
|
30
|
+
|
31
|
+
# Initialize a cookie jar
|
32
|
+
jar = HTTP::CookieJar.new
|
33
|
+
|
34
|
+
# Load from a file
|
35
|
+
jar.load(filename) if File.exist?(filename)
|
36
|
+
|
37
|
+
# Store received cookies
|
38
|
+
HTTP::Cookie.parse(set_cookie_header_value, origin: uri) { |cookie|
|
39
|
+
jar << cookie
|
40
|
+
}
|
41
|
+
|
42
|
+
# Get the value for the Cookie field of a request header
|
43
|
+
cookie_header_value = jar.cookies(uri).join(', ')
|
44
|
+
|
45
|
+
# Save to a file
|
46
|
+
jar.save(filename)
|
47
|
+
|
48
|
+
|
49
|
+
########################
|
50
|
+
# Server side example
|
51
|
+
########################
|
52
|
+
|
53
|
+
# Generate a cookie
|
54
|
+
cookies = HTTP::Cookie.new("uid", "a12345", domain: 'example.org',
|
55
|
+
for_domain: true,
|
56
|
+
path: '/',
|
57
|
+
max_age: 7*86400)
|
58
|
+
|
59
|
+
# Get the value for the Set-Cookie field of a response header
|
60
|
+
set_cookie_header_value = cookies.set_cookie_value(my_url)
|
61
|
+
|
62
|
+
|
63
|
+
## Incompatibilities with `Mechanize::Cookie`/`CookieJar`
|
64
|
+
|
65
|
+
There are several incompatibilities between
|
66
|
+
`Mechanize::Cookie`/`CookieJar` and `HTTP::Cookie`/`CookieJar`. Below
|
67
|
+
is how to rewrite existing code written for `Mechanize::Cookie` with
|
68
|
+
equivalent using `HTTP::Cookie`:
|
69
|
+
|
70
|
+
- `Mechanize::Cookie.parse`
|
71
|
+
|
72
|
+
# before
|
73
|
+
cookie1 = Mechanize::Cookie.parse(uri, set_cookie1)
|
74
|
+
cookie2 = Mechanize::Cookie.parse(uri, set_cookie2, log)
|
75
|
+
|
76
|
+
# after
|
77
|
+
cookie1 = HTTP::Cookie.parse(set_cookie1, :origin => uri)
|
78
|
+
cookie2 = HTTP::Cookie.parse(set_cookie2, :origin => uri, :logger => log)
|
79
|
+
|
80
|
+
- `Mechanize::Cookie#set_domain`
|
81
|
+
|
82
|
+
# before
|
83
|
+
cookie.set_domain(domain)
|
84
|
+
|
85
|
+
# after
|
86
|
+
cookie.domain = domain
|
87
|
+
|
88
|
+
- `Mechanize::CookieJar#add`, `#add!`
|
89
|
+
|
90
|
+
# before
|
91
|
+
jar.add!(cookie1)
|
92
|
+
jar.add(uri, cookie2)
|
93
|
+
|
94
|
+
# after
|
95
|
+
jar.add(cookie1)
|
96
|
+
cookie2.origin = uri; jar.add(cookie2) # or specify origin in parse() or new()
|
97
|
+
|
98
|
+
- `Mechanize::CookieJar#clear!`
|
99
|
+
|
100
|
+
# before
|
101
|
+
jar.clear!
|
102
|
+
|
103
|
+
# after
|
104
|
+
jar.clear
|
105
|
+
|
106
|
+
- `Mechanize::CookieJar#save_as`
|
107
|
+
|
108
|
+
# before
|
109
|
+
jar.save_as(file)
|
110
|
+
|
111
|
+
# after
|
112
|
+
jar.save(file)
|
113
|
+
|
114
|
+
`HTTP::Cookie`/`CookieJar` raise runtime errors to help migration, so
|
115
|
+
after replacing the class names, try running your test code once to
|
116
|
+
find out how to fix your code base.
|
117
|
+
|
118
|
+
### File formats
|
119
|
+
|
120
|
+
The YAML serialization format has changed, and `HTTP::CookieJar#load`
|
121
|
+
cannot import what is written in a YAML file saved by
|
122
|
+
`Mechanize::CookieJar#save_as`. `HTTP::CookieJar#load` will not raise
|
123
|
+
an exception if an incompatible YAML file is given, but the content is
|
124
|
+
silently ignored.
|
125
|
+
|
126
|
+
Note that there is (obviously) no forward compatibillity with this.
|
127
|
+
Trying to load a YAML file saved by `HTTP::CookieJar` with
|
128
|
+
`Mechanize::CookieJar` will fail in runtime error.
|
129
|
+
|
130
|
+
On the other hand, there has been (and will ever be) no change in the
|
131
|
+
cookies.txt format, so use it instead if compatibility is required.
|
132
|
+
|
133
|
+
## Contributing
|
134
|
+
|
135
|
+
1. Fork it
|
136
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
137
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
138
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
139
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
if RUBY_VERSION >= '1.9.0'
|
4
|
+
require 'rake/testtask'
|
5
|
+
Rake::TestTask
|
6
|
+
else
|
7
|
+
require 'rcov/rcovtask'
|
8
|
+
Rcov::RcovTask
|
9
|
+
end.new(:test) do |test|
|
10
|
+
test.libs << 'lib' << 'test'
|
11
|
+
test.ruby_opts << '-r./test/simplecov_start.rb' if !defined?(Rcov)
|
12
|
+
test.pattern = 'test/**/test_*.rb'
|
13
|
+
test.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
task :default => :test
|
17
|
+
|
18
|
+
require 'rdoc/task'
|
19
|
+
Rake::RDocTask.new do |rdoc|
|
20
|
+
version = HTTP::Cookie::VERSION
|
21
|
+
|
22
|
+
rdoc.rdoc_dir = 'rdoc'
|
23
|
+
rdoc.title = "http-cookie #{version}"
|
24
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
25
|
+
rdoc.rdoc_files.include(Bundler::GemHelper.gemspec.extra_rdoc_files)
|
26
|
+
end
|
data/http-cookie.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'http/cookie/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "http-cookie"
|
8
|
+
gem.version = HTTP::Cookie::VERSION
|
9
|
+
gem.authors, gem.email = {
|
10
|
+
'Akinori MUSHA' => 'knu@idaemons.org',
|
11
|
+
'Aaron Patterson' => 'aaronp@rubyforge.org',
|
12
|
+
'Eric Hodel' => 'drbrain@segment7.net',
|
13
|
+
'Mike Dalessio' => 'mike.dalessio@gmail.com',
|
14
|
+
}.instance_eval { [keys, values] }
|
15
|
+
|
16
|
+
gem.description = %q{A Ruby library to handle HTTP Cookies}
|
17
|
+
gem.summary = %q{A Ruby library to handle HTTP Cookies}
|
18
|
+
gem.homepage = "https://github.com/sparklemotion/http-cookie"
|
19
|
+
|
20
|
+
gem.files = `git ls-files`.split($/)
|
21
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
22
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
23
|
+
gem.require_paths = ["lib"]
|
24
|
+
|
25
|
+
gem.add_runtime_dependency("domain_name", ["~> 0.5"])
|
26
|
+
gem.add_development_dependency("bundler", [">= 1.2.0"])
|
27
|
+
gem.add_development_dependency("test-unit", [">= 2.4.3"])
|
28
|
+
gem.add_development_dependency("simplecov", [">= 0"])
|
29
|
+
end
|
data/lib/http-cookie.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'http/cookie'
|
data/lib/http/cookie.rb
ADDED
@@ -0,0 +1,447 @@
|
|
1
|
+
require 'http/cookie/version'
|
2
|
+
require 'time'
|
3
|
+
require 'webrick/httputils'
|
4
|
+
require 'domain_name'
|
5
|
+
|
6
|
+
module HTTP
|
7
|
+
autoload :CookieJar, 'http/cookie_jar'
|
8
|
+
end
|
9
|
+
|
10
|
+
# This class is used to represent an HTTP Cookie.
|
11
|
+
class HTTP::Cookie
|
12
|
+
# Maximum number of bytes per cookie (RFC 6265 6.1 requires 4096 at least)
|
13
|
+
MAX_LENGTH = 4096
|
14
|
+
# Maximum number of cookies per domain (RFC 6265 6.1 requires 50 at least)
|
15
|
+
MAX_COOKIES_PER_DOMAIN = 50
|
16
|
+
# Maximum number of cookies total (RFC 6265 6.1 requires 3000 at least)
|
17
|
+
MAX_COOKIES_TOTAL = 3000
|
18
|
+
|
19
|
+
UNIX_EPOCH = Time.at(0)
|
20
|
+
|
21
|
+
PERSISTENT_PROPERTIES = %w[
|
22
|
+
name value
|
23
|
+
domain for_domain path
|
24
|
+
secure httponly
|
25
|
+
expires created_at accessed_at
|
26
|
+
]
|
27
|
+
|
28
|
+
# In Ruby < 1.9.3 URI() does not accept an URI object.
|
29
|
+
if RUBY_VERSION < "1.9.3"
|
30
|
+
module URIFix
|
31
|
+
def URI(url)
|
32
|
+
url.is_a?(URI) ? url : Kernel::URI(url)
|
33
|
+
end
|
34
|
+
private :URI
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
if String.respond_to?(:try_convert)
|
39
|
+
def check_string_type(object)
|
40
|
+
String.try_convert(object)
|
41
|
+
end
|
42
|
+
private :check_string_type
|
43
|
+
else
|
44
|
+
def check_string_type(object)
|
45
|
+
if object.is_a?(String) ||
|
46
|
+
(object.respond_to?(:to_str) && (object = object.to_str).is_a?(String))
|
47
|
+
object
|
48
|
+
else
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
private :check_string_type
|
53
|
+
end
|
54
|
+
|
55
|
+
include URIFix if defined?(URIFix)
|
56
|
+
|
57
|
+
attr_reader :name, :domain, :path, :origin
|
58
|
+
attr_accessor :secure, :httponly, :value, :version
|
59
|
+
attr_reader :domain_name, :expires
|
60
|
+
attr_accessor :comment, :max_age
|
61
|
+
|
62
|
+
attr_accessor :session
|
63
|
+
|
64
|
+
attr_accessor :created_at
|
65
|
+
attr_accessor :accessed_at
|
66
|
+
|
67
|
+
# :call-seq:
|
68
|
+
# new(name, value)
|
69
|
+
# new(name, value, attr_hash)
|
70
|
+
# new(attr_hash)
|
71
|
+
#
|
72
|
+
# Creates a cookie object. For each key of +attr_hash+, the setter
|
73
|
+
# is called if defined. Each key can be either a symbol or a
|
74
|
+
# string, downcased or not.
|
75
|
+
#
|
76
|
+
# e.g.
|
77
|
+
# new("uid", "a12345")
|
78
|
+
# new("uid", "a12345", :domain => 'example.org',
|
79
|
+
# :for_domain => true, :expired => Time.now + 7*86400)
|
80
|
+
# new("name" => "uid", "value" => "a12345", "Domain" => 'www.example.org')
|
81
|
+
#
|
82
|
+
def initialize(*args)
|
83
|
+
@version = 0 # Netscape Cookie
|
84
|
+
|
85
|
+
@origin = @domain = @path =
|
86
|
+
@secure = @httponly =
|
87
|
+
@expires = @max_age =
|
88
|
+
@comment = nil
|
89
|
+
|
90
|
+
@created_at = @accessed_at = Time.now
|
91
|
+
case args.size
|
92
|
+
when 2
|
93
|
+
self.name, self.value = *args
|
94
|
+
@for_domain = false
|
95
|
+
return
|
96
|
+
when 3
|
97
|
+
self.name, self.value, attr_hash = *args
|
98
|
+
when 1
|
99
|
+
attr_hash = args.first
|
100
|
+
else
|
101
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for 1-3)"
|
102
|
+
end
|
103
|
+
for_domain = false
|
104
|
+
origin = nil
|
105
|
+
attr_hash.each_pair { |key, val|
|
106
|
+
skey = key.to_s.downcase
|
107
|
+
if skey.sub!(/\?\z/, '')
|
108
|
+
val = val ? true : false
|
109
|
+
end
|
110
|
+
case skey
|
111
|
+
when 'for_domain'
|
112
|
+
for_domain = !!val
|
113
|
+
when 'origin'
|
114
|
+
origin = val
|
115
|
+
else
|
116
|
+
setter = :"#{skey}="
|
117
|
+
send(setter, val) if respond_to?(setter)
|
118
|
+
end
|
119
|
+
}
|
120
|
+
if @name.nil? || @value.nil?
|
121
|
+
raise ArgumentError, "at least name and value must be specified"
|
122
|
+
end
|
123
|
+
@for_domain = for_domain
|
124
|
+
if origin
|
125
|
+
self.origin = origin
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# If this flag is true, this cookie will be sent to any host in the
|
130
|
+
# +domain+. If it is false, this cookie will be sent only to the
|
131
|
+
# host indicated by the +domain+.
|
132
|
+
attr_accessor :for_domain
|
133
|
+
alias for_domain? for_domain
|
134
|
+
|
135
|
+
class << self
|
136
|
+
include URIFix if defined?(URIFix)
|
137
|
+
|
138
|
+
# Normalizes a given path. If it is empty, the root path '/' is
|
139
|
+
# returned. If a URI object is given, returns a new URI object
|
140
|
+
# with the path part normalized.
|
141
|
+
def normalize_path(uri)
|
142
|
+
# Currently does not replace // to /
|
143
|
+
case uri
|
144
|
+
when URI
|
145
|
+
uri.path.empty? ? uri + '/' : uri
|
146
|
+
else
|
147
|
+
uri.empty? ? '/' : uri
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Parses a Set-Cookie header value +set_cookie+ into an array of
|
152
|
+
# Cookie objects. Parts (separated by commas) that are malformed
|
153
|
+
# are ignored.
|
154
|
+
#
|
155
|
+
# If a block is given, each cookie object is passed to the block.
|
156
|
+
#
|
157
|
+
# Available option keywords are below:
|
158
|
+
#
|
159
|
+
# * +origin+
|
160
|
+
# The cookie's origin URI/URL
|
161
|
+
# * +date+
|
162
|
+
# The base date used for interpreting Max-Age attribute values
|
163
|
+
# instead of the current time
|
164
|
+
# * +logger+
|
165
|
+
# Logger object useful for debugging
|
166
|
+
def parse(set_cookie, options = nil, *_, &block)
|
167
|
+
_.empty? && !options.is_a?(String) or
|
168
|
+
raise ArgumentError, 'HTTP::Cookie equivalent for Mechanize::Cookie.parse(uri, set_cookie[, log]) is HTTP::Cookie.parse(set_cookie, :origin => uri[, :logger => log]).'
|
169
|
+
|
170
|
+
if options
|
171
|
+
logger = options[:logger]
|
172
|
+
origin = options[:origin] and origin = URI(origin)
|
173
|
+
date = options[:date]
|
174
|
+
end
|
175
|
+
date ||= Time.now
|
176
|
+
|
177
|
+
[].tap { |cookies|
|
178
|
+
set_cookie.split(/,(?=[^;,]*=)|,$/).each { |c|
|
179
|
+
if c.bytesize > MAX_LENGTH
|
180
|
+
logger.warn("Cookie definition too long: #{c}") if logger
|
181
|
+
next
|
182
|
+
end
|
183
|
+
|
184
|
+
cookie_elem = c.split(/;+/)
|
185
|
+
first_elem = cookie_elem.shift
|
186
|
+
first_elem.strip!
|
187
|
+
key, value = first_elem.split(/\=/, 2)
|
188
|
+
|
189
|
+
begin
|
190
|
+
cookie = new(key, value.dup)
|
191
|
+
rescue
|
192
|
+
logger.warn("Couldn't parse key/value: #{first_elem}") if logger
|
193
|
+
next
|
194
|
+
end
|
195
|
+
|
196
|
+
cookie_elem.each do |pair|
|
197
|
+
pair.strip!
|
198
|
+
key, value = pair.split(/=/, 2) #/)
|
199
|
+
next unless key
|
200
|
+
value = WEBrick::HTTPUtils.dequote(value.strip) if value
|
201
|
+
|
202
|
+
case key.downcase
|
203
|
+
when 'domain'
|
204
|
+
next unless value && !value.empty?
|
205
|
+
begin
|
206
|
+
cookie.domain = value
|
207
|
+
cookie.for_domain = true
|
208
|
+
rescue
|
209
|
+
logger.warn("Couldn't parse domain: #{value}") if logger
|
210
|
+
end
|
211
|
+
when 'path'
|
212
|
+
next unless value && !value.empty?
|
213
|
+
cookie.path = value
|
214
|
+
when 'expires'
|
215
|
+
next unless value && !value.empty?
|
216
|
+
begin
|
217
|
+
cookie.expires = Time.parse(value)
|
218
|
+
rescue
|
219
|
+
logger.warn("Couldn't parse expires: #{value}") if logger
|
220
|
+
end
|
221
|
+
when 'max-age'
|
222
|
+
next unless value && !value.empty?
|
223
|
+
begin
|
224
|
+
cookie.max_age = Integer(value)
|
225
|
+
rescue
|
226
|
+
logger.warn("Couldn't parse max age '#{value}'") if logger
|
227
|
+
end
|
228
|
+
when 'comment'
|
229
|
+
next unless value
|
230
|
+
cookie.comment = value
|
231
|
+
when 'version'
|
232
|
+
next unless value
|
233
|
+
begin
|
234
|
+
cookie.version = Integer(value)
|
235
|
+
rescue
|
236
|
+
logger.warn("Couldn't parse version '#{value}'") if logger
|
237
|
+
cookie.version = nil
|
238
|
+
end
|
239
|
+
when 'secure'
|
240
|
+
cookie.secure = true
|
241
|
+
when 'httponly'
|
242
|
+
cookie.httponly = true
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
cookie.secure ||= false
|
247
|
+
cookie.httponly ||= false
|
248
|
+
|
249
|
+
# RFC 6265 4.1.2.2
|
250
|
+
cookie.expires = date + cookie.max_age if cookie.max_age
|
251
|
+
cookie.session = !cookie.expires
|
252
|
+
|
253
|
+
if origin
|
254
|
+
begin
|
255
|
+
cookie.origin = origin
|
256
|
+
rescue => e
|
257
|
+
logger.warn("Invalid cookie for the origin: #{origin} (#{e})") if logger
|
258
|
+
next
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
yield cookie if block_given?
|
263
|
+
|
264
|
+
cookies << cookie
|
265
|
+
}
|
266
|
+
}
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def name=(name)
|
271
|
+
if name.nil? || name.empty?
|
272
|
+
raise ArgumentError, "cookie name cannot be empty"
|
273
|
+
elsif name.match(/[\x00-\x1F=\x7F]/)
|
274
|
+
raise ArgumentError, "cookie name cannot contain a control character or an equal sign"
|
275
|
+
end
|
276
|
+
@name = name
|
277
|
+
end
|
278
|
+
|
279
|
+
# Sets the domain attribute. A leading dot in +domain+ implies
|
280
|
+
# turning the +for_domain?+ flag on.
|
281
|
+
def domain=(domain)
|
282
|
+
if DomainName === domain
|
283
|
+
@domain_name = domain
|
284
|
+
else
|
285
|
+
domain = check_string_type(domain) or
|
286
|
+
raise TypeError, "#{domain.class} is not a String"
|
287
|
+
if domain.start_with?('.')
|
288
|
+
@for_domain = true
|
289
|
+
domain = domain[1..-1]
|
290
|
+
end
|
291
|
+
# Do we really need to support this?
|
292
|
+
if domain.match(/\A([^:]+):[0-9]+\z/)
|
293
|
+
domain = $1
|
294
|
+
end
|
295
|
+
@domain_name = DomainName.new(domain)
|
296
|
+
end
|
297
|
+
@domain = @domain_name.hostname
|
298
|
+
end
|
299
|
+
|
300
|
+
# Used to exist in Mechanize::CookieJar. Use #domain=().
|
301
|
+
def set_domain(domain)
|
302
|
+
raise NoMethodError, 'HTTP::Cookie equivalent for Mechanize::CookieJar#set_domain() is #domain=().'
|
303
|
+
end
|
304
|
+
|
305
|
+
def path=(path)
|
306
|
+
@path = HTTP::Cookie.normalize_path(path)
|
307
|
+
end
|
308
|
+
|
309
|
+
def origin=(origin)
|
310
|
+
@origin.nil? or
|
311
|
+
raise ArgumentError, "origin cannot be changed once it is set"
|
312
|
+
origin = URI(origin)
|
313
|
+
self.domain ||= origin.host
|
314
|
+
self.path ||= (HTTP::Cookie.normalize_path(origin) + './').path
|
315
|
+
acceptable_from_uri?(origin) or
|
316
|
+
raise ArgumentError, "unacceptable cookie sent from URI #{origin}"
|
317
|
+
@origin = origin
|
318
|
+
end
|
319
|
+
|
320
|
+
def expires=(t)
|
321
|
+
case t
|
322
|
+
when nil, Time
|
323
|
+
@expires = t
|
324
|
+
else
|
325
|
+
@expires = Time.parse(t)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def expired?(time = Time.now)
|
330
|
+
return false unless @expires
|
331
|
+
time > @expires
|
332
|
+
end
|
333
|
+
|
334
|
+
def expire
|
335
|
+
@expires = UNIX_EPOCH
|
336
|
+
self
|
337
|
+
end
|
338
|
+
|
339
|
+
alias secure? secure
|
340
|
+
alias httponly? httponly
|
341
|
+
alias session? session
|
342
|
+
|
343
|
+
def acceptable_from_uri?(uri)
|
344
|
+
uri = URI(uri)
|
345
|
+
host = DomainName.new(uri.host)
|
346
|
+
|
347
|
+
# RFC 6265 5.3
|
348
|
+
# When the user agent "receives a cookie":
|
349
|
+
return @domain.nil? || host.hostname == @domain unless @for_domain
|
350
|
+
|
351
|
+
if host.cookie_domain?(@domain_name)
|
352
|
+
true
|
353
|
+
elsif host.hostname == @domain
|
354
|
+
@for_domain = false
|
355
|
+
true
|
356
|
+
else
|
357
|
+
false
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def valid_for_uri?(uri)
|
362
|
+
uri = URI(uri)
|
363
|
+
if @domain.nil?
|
364
|
+
raise "cannot tell if this cookie is valid because the domain is unknown"
|
365
|
+
end
|
366
|
+
return false if secure? && uri.scheme != 'https'
|
367
|
+
acceptable_from_uri?(uri) && HTTP::Cookie.normalize_path(uri.path).start_with?(@path)
|
368
|
+
end
|
369
|
+
|
370
|
+
# Returns a string for use in a Cookie header value,
|
371
|
+
# i.e. "name=value".
|
372
|
+
def cookie_value
|
373
|
+
"#{@name}=#{@value}"
|
374
|
+
end
|
375
|
+
alias to_s cookie_value
|
376
|
+
|
377
|
+
# Returns a string for use in a Set-Cookie header value. If the
|
378
|
+
# cookie does not have an origin set, one must be given from the
|
379
|
+
# argument.
|
380
|
+
#
|
381
|
+
# This method does not check if this cookie will be accepted from
|
382
|
+
# the origin.
|
383
|
+
def set_cookie_value(origin = nil)
|
384
|
+
origin = origin ? URI(origin) : @origin or
|
385
|
+
raise "origin must be specified to produce a value for Set-Cookie"
|
386
|
+
|
387
|
+
string = cookie_value
|
388
|
+
if @for_domain || @domain != DomainName.new(origin.host).hostname
|
389
|
+
string << "; domain=#{@domain}"
|
390
|
+
end
|
391
|
+
if (HTTP::Cookie.normalize_path(origin) + './').path != @path
|
392
|
+
string << "; path=#{@path}"
|
393
|
+
end
|
394
|
+
if expires = @expires
|
395
|
+
string << "; expires=#{@expires.httpdate}"
|
396
|
+
end
|
397
|
+
if comment = @comment
|
398
|
+
string << "; comment=#{@comment}"
|
399
|
+
end
|
400
|
+
if httponly?
|
401
|
+
string << "; HttpOnly"
|
402
|
+
end
|
403
|
+
if secure?
|
404
|
+
string << "; secure"
|
405
|
+
end
|
406
|
+
string
|
407
|
+
end
|
408
|
+
|
409
|
+
# Compares the cookie with another. When there are many cookies with
|
410
|
+
# the same name for a URL, the value of the smallest must be used.
|
411
|
+
def <=>(other)
|
412
|
+
# RFC 6265 5.4
|
413
|
+
# Precedence: 1. longer path 2. older creation
|
414
|
+
(@name <=> other.name).nonzero? ||
|
415
|
+
(other.path.length <=> @path.length).nonzero? ||
|
416
|
+
(@created_at <=> other.created_at).nonzero? ||
|
417
|
+
@value <=> other.value
|
418
|
+
end
|
419
|
+
include Comparable
|
420
|
+
|
421
|
+
# YAML serialization helper for Syck.
|
422
|
+
def to_yaml_properties
|
423
|
+
PERSISTENT_PROPERTIES.map { |name| "@#{name}" }
|
424
|
+
end
|
425
|
+
|
426
|
+
# YAML serialization helper for Psych.
|
427
|
+
def encode_with(coder)
|
428
|
+
PERSISTENT_PROPERTIES.each { |key|
|
429
|
+
coder[key.to_s] = instance_variable_get(:"@#{key}")
|
430
|
+
}
|
431
|
+
end
|
432
|
+
|
433
|
+
# YAML deserialization helper for Syck.
|
434
|
+
def init_with(coder)
|
435
|
+
yaml_initialize(coder.tag, coder.map)
|
436
|
+
end
|
437
|
+
|
438
|
+
# YAML deserialization helper for Psych.
|
439
|
+
def yaml_initialize(tag, map)
|
440
|
+
map.each { |key, value|
|
441
|
+
case key
|
442
|
+
when *PERSISTENT_PROPERTIES
|
443
|
+
send(:"#{key}=", value)
|
444
|
+
end
|
445
|
+
}
|
446
|
+
end
|
447
|
+
end
|