cookiejar 0.3.2 → 0.3.3
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.
- checksums.yaml +4 -4
- data/.gitignore +7 -0
- data/.rspec +2 -0
- data/.travis.yml +17 -0
- data/Gemfile +2 -0
- data/README.markdown +15 -1
- data/Rakefile +11 -12
- data/cookiejar.gemspec +27 -0
- data/lib/cookiejar.rb +2 -1
- data/lib/cookiejar/cookie.rb +57 -63
- data/lib/cookiejar/cookie_validation.rb +102 -99
- data/lib/cookiejar/jar.rb +50 -48
- data/lib/cookiejar/version.rb +4 -0
- data/spec/cookie_spec.rb +90 -90
- data/spec/cookie_validation_spec.rb +147 -155
- data/spec/jar_spec.rb +107 -110
- data/spec/spec_helper.rb +5 -0
- metadata +50 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5aba395a725f02d0c68ab3f4b306af071ed2546e
|
4
|
+
data.tar.gz: 138fc4cde85d20a2f820d94136aefec9465491f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b2866097c73852e924c2676f1fe71e5878832f282f9735b340789b9ee4fcf24f1031fe3cdce979be21e2f411dd268bc81fd7702d7ae75a17ea452579f7de08f
|
7
|
+
data.tar.gz: 3d8f17b5d837b3ce5e0f00726875509589a9628b66b240deec90e17a899cdbcb3bfcd08565d16c3ac953759bd973dfb7bc6c75b2588927466a691c43f1fd32c7
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.markdown
CHANGED
@@ -5,6 +5,8 @@ Ruby CookieJar
|
|
5
5
|
|
6
6
|
**Author**: David Waite
|
7
7
|
|
8
|
+
[](https://travis-ci.org/dwaite/cookiejar)
|
9
|
+
|
8
10
|
Synopsis
|
9
11
|
--------
|
10
12
|
|
@@ -12,6 +14,18 @@ The Ruby CookieJar is a library to help manage client-side cookies in pure Ruby.
|
|
12
14
|
|
13
15
|
Both Netscape/RFC 2109 cookies and RFC 2965 cookies are supported.
|
14
16
|
|
17
|
+
Roadmap
|
18
|
+
-------
|
19
|
+
|
20
|
+
For the Next major release, I would like to accomplish:
|
21
|
+
|
22
|
+
1. Check against [RFC 6265 - HTTP State Management Mechanism][rfc6265], the latest cookie spec which came out after the initial release of cookiejar
|
23
|
+
2. Determine better code structure to encourage alternate persistence mechanisms for cookie jars
|
24
|
+
|
25
|
+
[rfc6265]: http://tools.ietf.org/html/rfc6265
|
15
26
|
COPYRIGHT
|
16
27
|
---------
|
17
|
-
The Ruby CookieJar is Copyright © 2009 David Waite. Licensing terms are given within the LICENSE file.
|
28
|
+
The Ruby CookieJar is Copyright © 2009-2014 David Waite, with [additional contributions from various authors][contributions]. Licensing terms are given within the [LICENSE file][LICENSE].
|
29
|
+
|
30
|
+
[contributions]: ./contributors.json
|
31
|
+
[LICENSE]: ./LICENSE
|
data/Rakefile
CHANGED
@@ -1,19 +1,15 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
1
3
|
require 'rake'
|
2
4
|
|
3
5
|
require 'rake/clean'
|
4
|
-
require 'rake/packagetask'
|
5
6
|
require 'yard'
|
6
7
|
require 'yard/rake/yardoc_task'
|
7
8
|
|
8
|
-
require 'fileutils'
|
9
|
-
include FileUtils
|
10
|
-
|
11
|
-
# Default Rake task is to run all tests
|
12
|
-
task :default => :test
|
13
9
|
CLEAN << Rake::FileList['doc/**', '.yardoc']
|
14
|
-
#Yard
|
10
|
+
# Yard
|
15
11
|
YARD::Rake::YardocTask.new do |t|
|
16
|
-
t.files = ['lib/**/*.rb']
|
12
|
+
t.files = ['lib/**/*.rb'] # optional
|
17
13
|
t.options = ['--title', 'CookieJar, a HTTP Client Cookie Parsing Library',
|
18
14
|
'--main', 'README.markdown', '--files', 'LICENSE']
|
19
15
|
end
|
@@ -22,10 +18,13 @@ begin
|
|
22
18
|
require 'rspec/core/rake_task'
|
23
19
|
|
24
20
|
RSpec::Core::RakeTask.new do |t|
|
25
|
-
|
26
|
-
|
21
|
+
t.ruby_opts = %w(-w)
|
22
|
+
t.pattern = 'spec/**/*_spec.rb'
|
27
23
|
end
|
28
|
-
task :
|
24
|
+
task test: :spec
|
29
25
|
rescue LoadError
|
30
|
-
puts
|
26
|
+
puts 'Warning: unable to load rspec tasks'
|
31
27
|
end
|
28
|
+
|
29
|
+
# Default Rake task is to run all tests
|
30
|
+
task default: :test
|
data/cookiejar.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'cookiejar/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = 'cookiejar'
|
9
|
+
s.version = CookieJar::VERSION
|
10
|
+
s.authors = ['David Waite']
|
11
|
+
s.email = ['david@alkaline-solutions.com']
|
12
|
+
s.description = 'Allows for parsing and returning cookies in Ruby HTTP client code'
|
13
|
+
s.summary = 'Client-side HTTP Cookie library'
|
14
|
+
s.homepage = 'http://alkaline-solutions.com'
|
15
|
+
s.date = '2014-02-01'
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
18
|
+
s.test_files = s.files.grep(%r{^(spec)/})
|
19
|
+
s.rdoc_options = ['--title', 'CookieJar -- Client-side HTTP Cookies']
|
20
|
+
s.require_paths = ['lib']
|
21
|
+
|
22
|
+
s.add_development_dependency 'rake', '~> 10.0'
|
23
|
+
s.add_development_dependency 'rspec-collection_matchers', '~> 1.0'
|
24
|
+
s.add_development_dependency 'rspec', '~> 3.0'
|
25
|
+
s.add_development_dependency 'yard', '~> 0.8', '>= 0.8.7'
|
26
|
+
s.add_development_dependency 'bundler', '>= 0.9.3'
|
27
|
+
end
|
data/lib/cookiejar.rb
CHANGED
data/lib/cookiejar/cookie.rb
CHANGED
@@ -3,14 +3,12 @@ require 'uri'
|
|
3
3
|
require 'cookiejar/cookie_validation'
|
4
4
|
|
5
5
|
module CookieJar
|
6
|
-
|
7
6
|
# Cookie is an immutable object which defines the data model of a HTTP Cookie.
|
8
7
|
# The data values within the cookie may be different from the
|
9
8
|
# values described in the literal cookie declaration.
|
10
9
|
# Specifically, the 'domain' and 'path' values may be set to defaults
|
11
10
|
# based on the requested resource that resulted in the cookie being set.
|
12
11
|
class Cookie
|
13
|
-
|
14
12
|
# [String] The name of the cookie.
|
15
13
|
attr_reader :name
|
16
14
|
# [String] The value of the cookie, without any attempts at decoding.
|
@@ -68,15 +66,15 @@ module CookieJar
|
|
68
66
|
#
|
69
67
|
# @param [Time] time to compare against, or 'now' if omitted
|
70
68
|
# @return [Boolean]
|
71
|
-
def expired?
|
72
|
-
expires_at
|
69
|
+
def expired?(time = Time.now)
|
70
|
+
!expires_at.nil? && time > expires_at
|
73
71
|
end
|
74
72
|
|
75
73
|
# Indicates whether the cookie will be considered invalid after the end
|
76
74
|
# of the current user session
|
77
75
|
# @return [Boolean]
|
78
76
|
def session?
|
79
|
-
@expiry
|
77
|
+
@expiry.nil? || @discard
|
80
78
|
end
|
81
79
|
|
82
80
|
# Create a cookie based on an absolute URI and the string value of a
|
@@ -88,12 +86,12 @@ module CookieJar
|
|
88
86
|
# @param set_cookie_value [String] HTTP value for the Set-Cookie header.
|
89
87
|
# @return [Cookie] created from the header string and request URI
|
90
88
|
# @raise [InvalidCookieError] on validation failure(s)
|
91
|
-
def self.from_set_cookie
|
89
|
+
def self.from_set_cookie(request_uri, set_cookie_value)
|
92
90
|
args = CookieJar::CookieValidation.parse_set_cookie set_cookie_value
|
93
|
-
args[:domain] = CookieJar::CookieValidation
|
94
|
-
|
95
|
-
args[:path] = CookieJar::CookieValidation
|
96
|
-
|
91
|
+
args[:domain] = CookieJar::CookieValidation
|
92
|
+
.determine_cookie_domain request_uri, args[:domain]
|
93
|
+
args[:path] = CookieJar::CookieValidation
|
94
|
+
.determine_cookie_path request_uri, args[:path]
|
97
95
|
cookie = Cookie.new args
|
98
96
|
CookieJar::CookieValidation.validate_cookie request_uri, cookie
|
99
97
|
cookie
|
@@ -108,12 +106,12 @@ module CookieJar
|
|
108
106
|
# @param set_cookie_value [String] HTTP value for the Set-Cookie2 header.
|
109
107
|
# @return [Cookie] created from the header string and request URI
|
110
108
|
# @raise [InvalidCookieError] on validation failure(s)
|
111
|
-
def self.from_set_cookie2
|
109
|
+
def self.from_set_cookie2(request_uri, set_cookie_value)
|
112
110
|
args = CookieJar::CookieValidation.parse_set_cookie2 set_cookie_value
|
113
|
-
args[:domain] = CookieJar::CookieValidation
|
114
|
-
|
115
|
-
args[:path] = CookieJar::CookieValidation
|
116
|
-
|
111
|
+
args[:domain] = CookieJar::CookieValidation
|
112
|
+
.determine_cookie_domain request_uri, args[:domain]
|
113
|
+
args[:path] = CookieJar::CookieValidation
|
114
|
+
.determine_cookie_path request_uri, args[:path]
|
117
115
|
cookie = Cookie.new args
|
118
116
|
CookieJar::CookieValidation.validate_cookie request_uri, cookie
|
119
117
|
cookie
|
@@ -125,45 +123,37 @@ module CookieJar
|
|
125
123
|
# RFC2965-style.
|
126
124
|
# @param [Boolean] true prefix, for RFC2965, whether to prefix with
|
127
125
|
# "$Version=<version>;". Ignored for Netscape-style cookies
|
128
|
-
def to_s
|
129
|
-
|
130
|
-
when 0
|
131
|
-
"#{name}=#{value}"
|
132
|
-
when 1
|
133
|
-
# we do not need to encode path; the only characters required to be
|
134
|
-
# quoted must be escaped in URI
|
135
|
-
str = prefix ? "$Version=#{version};" : ""
|
136
|
-
str << "#{name}=#{value};$Path=\"#{path}\""
|
137
|
-
if domain.start_with? '.'
|
138
|
-
str << ";$Domain=#{domain}"
|
139
|
-
end
|
140
|
-
if ports
|
141
|
-
str << ";$Port=\"#{ports.join ','}\""
|
142
|
-
end
|
143
|
-
str
|
144
|
-
end
|
145
|
-
end
|
126
|
+
def to_s(ver = 0, prefix = true)
|
127
|
+
return "#{name}=#{value}" if ver == 0
|
146
128
|
|
129
|
+
# we do not need to encode path; the only characters required to be
|
130
|
+
# quoted must be escaped in URI
|
131
|
+
str = prefix ? "$Version=#{version};" : ''
|
132
|
+
str << "#{name}=#{value};$Path=\"#{path}\""
|
133
|
+
str << ";$Domain=#{domain}" if domain.start_with? '.'
|
134
|
+
str << ";$Port=\"#{ports.join ','}\"" if ports
|
135
|
+
str
|
136
|
+
end
|
147
137
|
|
148
138
|
# Return a hash representation of the cookie.
|
149
139
|
|
150
140
|
def to_hash
|
151
141
|
result = {
|
152
|
-
:
|
153
|
-
:
|
154
|
-
:
|
155
|
-
:
|
156
|
-
:
|
142
|
+
name: @name,
|
143
|
+
value: @value,
|
144
|
+
domain: @domain,
|
145
|
+
path: @path,
|
146
|
+
created_at: @created_at
|
157
147
|
}
|
158
148
|
{
|
159
|
-
:
|
160
|
-
:
|
161
|
-
:
|
162
|
-
:
|
163
|
-
:
|
164
|
-
:
|
165
|
-
:
|
166
|
-
:
|
149
|
+
expiry: @expiry,
|
150
|
+
secure: (true if @secure),
|
151
|
+
http_only: (true if @http_only),
|
152
|
+
version: (@version if version != 0),
|
153
|
+
comment: @comment,
|
154
|
+
comment_url: @comment_url,
|
155
|
+
discard: (true if @discard),
|
156
|
+
ports: @ports
|
167
157
|
}.each do |name, value|
|
168
158
|
result[name] = value if value
|
169
159
|
end
|
@@ -181,12 +171,17 @@ module CookieJar
|
|
181
171
|
# @param script [Boolean] indicates that cookies with the 'httponly'
|
182
172
|
# extension should be ignored
|
183
173
|
# @return [Boolean] whether this cookie should be sent to the server
|
184
|
-
def should_send?
|
174
|
+
def should_send?(request_uri, script)
|
185
175
|
uri = CookieJar::CookieValidation.to_uri request_uri
|
186
176
|
# cookie path must start with the uri, it must not be a secure cookie
|
187
177
|
# being sent over http, and it must not be a http_only cookie sent to
|
188
178
|
# a script
|
189
|
-
|
179
|
+
path = if uri.path == ''
|
180
|
+
'/'
|
181
|
+
else
|
182
|
+
uri.path
|
183
|
+
end
|
184
|
+
path_match = path.start_with? @path
|
190
185
|
secure_match = !(@secure && uri.scheme == 'http')
|
191
186
|
script_match = !(script && @http_only)
|
192
187
|
expiry_match = !expired?
|
@@ -195,7 +190,7 @@ module CookieJar
|
|
195
190
|
end
|
196
191
|
|
197
192
|
def decoded_value
|
198
|
-
CookieJar::CookieValidation
|
193
|
+
CookieJar::CookieValidation.decode_value value
|
199
194
|
end
|
200
195
|
|
201
196
|
# Return a JSON 'object' for the various data values. Allows for
|
@@ -204,8 +199,8 @@ module CookieJar
|
|
204
199
|
# @param [Array] a options controlling output JSON text
|
205
200
|
# (usually a State and a depth)
|
206
201
|
# @return [String] JSON representation of object data
|
207
|
-
def to_json
|
208
|
-
to_hash.merge(:
|
202
|
+
def to_json(*a)
|
203
|
+
to_hash.merge(json_class: self.class.name).to_json(*a)
|
209
204
|
end
|
210
205
|
|
211
206
|
# Given a Hash representation of a JSON document, create a local cookie
|
@@ -213,7 +208,7 @@ module CookieJar
|
|
213
208
|
#
|
214
209
|
# @param [Hash] o JSON object of array data
|
215
210
|
# @return [Cookie] cookie formed from JSON data
|
216
|
-
def self.json_create
|
211
|
+
def self.json_create(o)
|
217
212
|
params = o.inject({}) do |hash, (key, value)|
|
218
213
|
hash[key.to_sym] = value
|
219
214
|
hash
|
@@ -227,7 +222,7 @@ module CookieJar
|
|
227
222
|
end
|
228
223
|
params.delete :expiry
|
229
224
|
|
230
|
-
|
225
|
+
new params
|
231
226
|
end
|
232
227
|
|
233
228
|
# Compute the cookie search domains for a given request URI
|
@@ -236,28 +231,27 @@ module CookieJar
|
|
236
231
|
#
|
237
232
|
# @param request_uri [String, URI] address being requested
|
238
233
|
# @return [Array<String>] String domain matches
|
239
|
-
def self.compute_search_domains
|
234
|
+
def self.compute_search_domains(request_uri)
|
240
235
|
CookieValidation.compute_search_domains request_uri
|
241
236
|
end
|
242
|
-
protected
|
243
|
-
# Call {from_set_cookie} to create a new Cookie instance
|
244
|
-
def initialize args
|
245
237
|
|
238
|
+
protected
|
239
|
+
|
240
|
+
# Call {from_set_cookie} to create a new Cookie instance
|
241
|
+
def initialize(args)
|
246
242
|
@created_at, @name, @value, @domain, @path, @secure,
|
247
243
|
@http_only, @version, @comment, @comment_url, @discard, @ports \
|
248
244
|
= args.values_at \
|
249
|
-
|
250
|
-
|
245
|
+
:created_at, :name, :value, :domain, :path, :secure,
|
246
|
+
:http_only, :version, :comment, :comment_url, :discard, :ports
|
251
247
|
|
252
248
|
@created_at ||= Time.now
|
253
|
-
@expiry
|
249
|
+
@expiry = args[:max_age] || args[:expires_at]
|
254
250
|
@secure ||= false
|
255
251
|
@http_only ||= false
|
256
252
|
@discard ||= false
|
257
253
|
|
258
|
-
if @ports.is_a? Integer
|
259
|
-
@ports = [@ports]
|
260
|
-
end
|
254
|
+
@ports = [@ports] if @ports.is_a? Integer
|
261
255
|
end
|
262
256
|
end
|
263
257
|
end
|
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'cgi'
|
2
3
|
require 'uri'
|
4
|
+
|
3
5
|
module CookieJar
|
4
6
|
# Represents a set of cookie validation errors
|
5
7
|
class InvalidCookieError < StandardError
|
@@ -8,37 +10,37 @@ module CookieJar
|
|
8
10
|
|
9
11
|
# Create a new instance
|
10
12
|
# @param [String, Array<String>] the validation issue(s) encountered
|
11
|
-
def initialize
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
def initialize(message)
|
14
|
+
if message.is_a? Array
|
15
|
+
@messages = message
|
16
|
+
message = message.join ', '
|
17
|
+
else
|
18
|
+
@messages = [message]
|
19
|
+
end
|
20
|
+
super message
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
22
24
|
# Contains logic to parse and validate cookie headers
|
23
25
|
module CookieValidation
|
26
|
+
# REGEX cookie matching
|
24
27
|
module PATTERN
|
25
28
|
include URI::REGEXP::PATTERN
|
26
29
|
|
27
|
-
TOKEN = '[^(),\/<>@;:\\\"\[\]?={}\s]+'
|
28
|
-
VALUE1 =
|
29
|
-
IPADDR = "#{IPV4ADDR}|#{IPV6ADDR}"
|
30
|
-
BASE_HOSTNAME = "(?:#{DOMLABEL}\\.)(?:((?:(?:#{DOMLABEL}\\.)+(?:#{TOPLABEL}\\.?))|local))"
|
30
|
+
TOKEN = '[^(),\/<>@;:\\\"\[\]?={}\s]+'.freeze
|
31
|
+
VALUE1 = '([^;]*)'.freeze
|
32
|
+
IPADDR = "#{IPV4ADDR}|#{IPV6ADDR}".freeze
|
33
|
+
BASE_HOSTNAME = "(?:#{DOMLABEL}\\.)(?:((?:(?:#{DOMLABEL}\\.)+(?:#{TOPLABEL}\\.?))|local))".freeze
|
31
34
|
|
32
|
-
QUOTED_PAIR =
|
33
|
-
LWS =
|
35
|
+
QUOTED_PAIR = '\\\\[\\x00-\\x7F]'.freeze
|
36
|
+
LWS = '\\r\\n(?:[ \\t]+)'.freeze
|
34
37
|
# TEXT="[\\t\\x20-\\x7E\\x80-\\xFF]|(?:#{LWS})"
|
35
|
-
QDTEXT="[\\t\\x20-\\x21\\x23-\\x7E\\x80-\\xFF]|(?:#{LWS})"
|
36
|
-
QUOTED_TEXT = "\\\"(?:#{QDTEXT}|#{QUOTED_PAIR})*\\\""
|
37
|
-
VALUE2 = "#{TOKEN}|#{QUOTED_TEXT}"
|
38
|
-
|
38
|
+
QDTEXT = "[\\t\\x20-\\x21\\x23-\\x7E\\x80-\\xFF]|(?:#{LWS})".freeze
|
39
|
+
QUOTED_TEXT = "\\\"(?:#{QDTEXT}|#{QUOTED_PAIR})*\\\"".freeze
|
40
|
+
VALUE2 = "#{TOKEN}|#{QUOTED_TEXT}".freeze
|
39
41
|
end
|
40
42
|
BASE_HOSTNAME = /#{PATTERN::BASE_HOSTNAME}/
|
41
|
-
BASE_PATH =
|
43
|
+
BASE_PATH = %r{\A((?:[^/?#]*/)*)}
|
42
44
|
IPADDR = /\A#{PATTERN::IPV4ADDR}\Z|\A#{PATTERN::IPV6ADDR}\Z/
|
43
45
|
HDN = /\A#{PATTERN::HOSTNAME}\Z/
|
44
46
|
TOKEN = /\A#{PATTERN::TOKEN}\Z/
|
@@ -50,8 +52,8 @@ module CookieJar
|
|
50
52
|
#
|
51
53
|
# @param [String, URI] request_uri URI we are normalizing
|
52
54
|
# @param [URI] URI representation of input string, or original URI
|
53
|
-
def self.to_uri
|
54
|
-
(request_uri.is_a? URI)? request_uri : (URI.parse request_uri)
|
55
|
+
def self.to_uri(request_uri)
|
56
|
+
(request_uri.is_a? URI) ? request_uri : (URI.parse request_uri)
|
55
57
|
end
|
56
58
|
|
57
59
|
# Converts an input cookie or uri to a string representing the path.
|
@@ -59,7 +61,7 @@ module CookieJar
|
|
59
61
|
#
|
60
62
|
# @param [String, URI, Cookie] object containing the path
|
61
63
|
# @return [String] path information
|
62
|
-
def self.to_path
|
64
|
+
def self.to_path(uri_or_path)
|
63
65
|
if (uri_or_path.is_a? URI) || (uri_or_path.is_a? Cookie)
|
64
66
|
uri_or_path.path
|
65
67
|
else
|
@@ -72,7 +74,7 @@ module CookieJar
|
|
72
74
|
#
|
73
75
|
# @param [String, URI, Cookie] object containing the domain
|
74
76
|
# @return [String] domain information.
|
75
|
-
def self.to_domain
|
77
|
+
def self.to_domain(uri_or_domain)
|
76
78
|
if uri_or_domain.is_a? URI
|
77
79
|
uri_or_domain.host
|
78
80
|
elsif uri_or_domain.is_a? Cookie
|
@@ -88,10 +90,10 @@ module CookieJar
|
|
88
90
|
# @param [String] tested_domain domain to be tested against
|
89
91
|
# @param [String] base_domain new domain being tested
|
90
92
|
# @return [String,nil] matching domain on success, nil on failure
|
91
|
-
def self.domains_match
|
93
|
+
def self.domains_match(tested_domain, base_domain)
|
92
94
|
base = effective_host base_domain
|
93
95
|
search_domains = compute_search_domains_for_host base
|
94
|
-
|
96
|
+
search_domains.find do |domain|
|
95
97
|
domain == tested_domain
|
96
98
|
end
|
97
99
|
end
|
@@ -101,21 +103,19 @@ module CookieJar
|
|
101
103
|
#
|
102
104
|
# @param [String,URI,Cookie] hostname hostname, or object holding hostname
|
103
105
|
# @return [String,nil] next highest hostname, or nil if none
|
104
|
-
def self.hostname_reach
|
106
|
+
def self.hostname_reach(hostname)
|
105
107
|
host = to_domain hostname
|
106
108
|
host = host.downcase
|
107
109
|
match = BASE_HOSTNAME.match host
|
108
|
-
if match
|
109
|
-
match[1]
|
110
|
-
end
|
110
|
+
match[1] if match
|
111
111
|
end
|
112
112
|
|
113
113
|
# Compute the base of a path, for default cookie path assignment
|
114
114
|
#
|
115
115
|
# @param [String, URI, Cookie] path, or object holding path
|
116
116
|
# @return base path (all characters up to final '/')
|
117
|
-
def self.cookie_base_path
|
118
|
-
BASE_PATH.match(to_path
|
117
|
+
def self.cookie_base_path(path)
|
118
|
+
BASE_PATH.match(to_path(path))[1]
|
119
119
|
end
|
120
120
|
|
121
121
|
# Processes cookie path data using the following rules:
|
@@ -123,16 +123,16 @@ module CookieJar
|
|
123
123
|
# to the last '/' character. If no path is specified in the cookie, a path
|
124
124
|
# value will be taken from the request URI which was used for the site.
|
125
125
|
#
|
126
|
-
# Note that this will not attempt to detect a mismatch of the request uri
|
127
|
-
# and explicitly specified cookie path
|
126
|
+
# Note that this will not attempt to detect a mismatch of the request uri
|
127
|
+
# domain and explicitly specified cookie path
|
128
128
|
#
|
129
129
|
# @param [String,URI] request URI yielding this cookie
|
130
130
|
# @param [String] path on cookie
|
131
|
-
def self.determine_cookie_path
|
131
|
+
def self.determine_cookie_path(request_uri, cookie_path)
|
132
132
|
uri = to_uri request_uri
|
133
133
|
cookie_path = to_path cookie_path
|
134
134
|
|
135
|
-
if cookie_path
|
135
|
+
if cookie_path.nil? || cookie_path.empty?
|
136
136
|
cookie_path = cookie_base_path uri.path
|
137
137
|
end
|
138
138
|
cookie_path
|
@@ -145,8 +145,9 @@ module CookieJar
|
|
145
145
|
# @param [String, URI] request_uri requested uri
|
146
146
|
# @return [Array<String>] all cookie domain values which would match the
|
147
147
|
# requested uri
|
148
|
-
def self.compute_search_domains
|
148
|
+
def self.compute_search_domains(request_uri)
|
149
149
|
uri = to_uri request_uri
|
150
|
+
return nil unless uri.is_a? URI::HTTP
|
150
151
|
host = uri.host
|
151
152
|
compute_search_domains_for_host host
|
152
153
|
end
|
@@ -157,59 +158,55 @@ module CookieJar
|
|
157
158
|
# @param [String] host host being requested
|
158
159
|
# @return [Array<String>] all cookie domain values which would match the
|
159
160
|
# requested uri
|
160
|
-
def self.compute_search_domains_for_host
|
161
|
+
def self.compute_search_domains_for_host(host)
|
161
162
|
host = effective_host host
|
162
163
|
result = [host]
|
163
164
|
unless host =~ IPADDR
|
164
165
|
result << ".#{host}"
|
165
166
|
base = hostname_reach host
|
166
|
-
if base
|
167
|
-
result << ".#{base}"
|
168
|
-
end
|
167
|
+
result << ".#{base}" if base
|
169
168
|
end
|
170
169
|
result
|
171
170
|
end
|
172
171
|
|
173
172
|
# Processes cookie domain data using the following rules:
|
174
173
|
# Domains strings of the form .foo.com match 'foo.com' and all immediate
|
175
|
-
# subdomains of 'foo.com'. Domain strings specified of the form 'foo.com'
|
176
|
-
# modified to '.foo.com', and as such will still apply to subdomains.
|
174
|
+
# subdomains of 'foo.com'. Domain strings specified of the form 'foo.com'
|
175
|
+
# are modified to '.foo.com', and as such will still apply to subdomains.
|
177
176
|
#
|
178
|
-
# Cookies without an explicit domain will have their domain value taken
|
179
|
-
# from the URL, and will _NOT_ have any leading dot applied. For
|
180
|
-
# to http://foo.com/ will cause an entry for 'foo.com'
|
181
|
-
# to foo.com but no subdomain.
|
177
|
+
# Cookies without an explicit domain will have their domain value taken
|
178
|
+
# directly from the URL, and will _NOT_ have any leading dot applied. For
|
179
|
+
# example, a request to http://foo.com/ will cause an entry for 'foo.com'
|
180
|
+
# to be created - which applies to foo.com but no subdomain.
|
182
181
|
#
|
183
|
-
# Note that this will not attempt to detect a mismatch of the request uri
|
184
|
-
# and explicitly specified cookie domain
|
182
|
+
# Note that this will not attempt to detect a mismatch of the request uri
|
183
|
+
# domain and explicitly specified cookie domain
|
185
184
|
#
|
186
185
|
# @param [String, URI] request_uri originally requested URI
|
187
186
|
# @param [String] cookie domain value
|
188
187
|
# @return [String] effective host
|
189
|
-
def self.determine_cookie_domain
|
188
|
+
def self.determine_cookie_domain(request_uri, cookie_domain)
|
190
189
|
uri = to_uri request_uri
|
191
190
|
domain = to_domain cookie_domain
|
192
191
|
|
193
|
-
if domain
|
194
|
-
|
192
|
+
return effective_host(uri.host) if domain.nil? || domain.empty?
|
193
|
+
domain = domain.downcase
|
194
|
+
if domain =~ IPADDR || domain.start_with?('.')
|
195
|
+
domain
|
195
196
|
else
|
196
|
-
domain
|
197
|
-
if domain =~ IPADDR || domain.start_with?('.')
|
198
|
-
domain
|
199
|
-
else
|
200
|
-
".#{domain}"
|
201
|
-
end
|
197
|
+
".#{domain}"
|
202
198
|
end
|
203
199
|
end
|
204
200
|
|
205
201
|
# Compute the effective host (RFC 2965, section 1)
|
206
202
|
#
|
207
|
-
# Has the added additional logic of searching for interior dots
|
208
|
-
# matches colons to prevent .local being suffixed on
|
203
|
+
# Has the added additional logic of searching for interior dots
|
204
|
+
# specifically, and matches colons to prevent .local being suffixed on
|
205
|
+
# IPv6 addresses
|
209
206
|
#
|
210
207
|
# @param [String, URI] host_or_uridomain name, or absolute URI
|
211
208
|
# @return [String] effective host per RFC rules
|
212
|
-
def self.effective_host
|
209
|
+
def self.effective_host(host_or_uri)
|
213
210
|
hostname = to_domain host_or_uri
|
214
211
|
hostname = hostname.downcase
|
215
212
|
|
@@ -227,11 +224,9 @@ module CookieJar
|
|
227
224
|
# @param [Cookie] cookie object
|
228
225
|
# @param [true] will always return true on success
|
229
226
|
# @raise [InvalidCookieError] on failures, containing all validation errors
|
230
|
-
def self.validate_cookie
|
227
|
+
def self.validate_cookie(request_uri, cookie)
|
231
228
|
uri = to_uri request_uri
|
232
|
-
request_host = effective_host uri.host
|
233
229
|
request_path = uri.path
|
234
|
-
request_secure = (uri.scheme == 'https')
|
235
230
|
cookie_host = cookie.domain
|
236
231
|
cookie_path = cookie.path
|
237
232
|
|
@@ -242,19 +237,22 @@ module CookieJar
|
|
242
237
|
# A user agent rejects (SHALL NOT store its information) if the
|
243
238
|
# Version attribute is missing. Note that the legacy Set-Cookie
|
244
239
|
# directive will result in an implicit version 0.
|
245
|
-
unless cookie.version
|
246
|
-
errors << "Version missing"
|
247
|
-
end
|
240
|
+
errors << 'Version missing' unless cookie.version
|
248
241
|
|
249
242
|
# The value for the Path attribute is not a prefix of the request-URI
|
243
|
+
|
244
|
+
# If the initial request path is empty then this will always fail
|
245
|
+
# so check if it is empty and if so then set it to /
|
246
|
+
request_path = '/' if request_path == ''
|
247
|
+
|
250
248
|
unless request_path.start_with? cookie_path
|
251
|
-
errors <<
|
249
|
+
errors << 'Path is not a prefix of the request uri path'
|
252
250
|
end
|
253
251
|
|
254
|
-
unless cookie_host =~ IPADDR || #is an IPv4 or IPv6 address
|
255
|
-
|
256
|
-
|
257
|
-
errors <<
|
252
|
+
unless cookie_host =~ IPADDR || # is an IPv4 or IPv6 address
|
253
|
+
cookie_host =~ /.\../ || # contains an embedded dot
|
254
|
+
cookie_host == '.local' # is the domain cookie for local addresses
|
255
|
+
errors << 'Domain format is illegal'
|
258
256
|
end
|
259
257
|
|
260
258
|
# The effective host name that derives from the request-host does
|
@@ -264,18 +262,18 @@ module CookieJar
|
|
264
262
|
# where D is the value of the Domain attribute, and H is a string
|
265
263
|
# that contains one or more dots.
|
266
264
|
unless domains_match cookie_host, uri
|
267
|
-
errors <<
|
265
|
+
errors << 'Domain is inappropriate based on request URI hostname'
|
268
266
|
end
|
269
267
|
|
270
268
|
# The Port attribute has a "port-list", and the request-port was
|
271
269
|
# not in the list.
|
272
|
-
unless cookie.ports.nil? || cookie.ports.
|
270
|
+
unless cookie.ports.nil? || !cookie.ports.empty?
|
273
271
|
unless cookie.ports.find_index uri.port
|
274
|
-
errors <<
|
272
|
+
errors << 'Ports list does not contain request URI port'
|
275
273
|
end
|
276
274
|
end
|
277
275
|
|
278
|
-
|
276
|
+
fail InvalidCookieError, errors unless errors.empty?
|
279
277
|
|
280
278
|
# Note: 'secure' is not explicitly defined as an SSL channel, and no
|
281
279
|
# test is defined around validity and the 'secure' attribute
|
@@ -289,15 +287,16 @@ module CookieJar
|
|
289
287
|
# @param [String] set_cookie_value a Set-Cookie header formatted cookie
|
290
288
|
# definition
|
291
289
|
# @return [Hash] Contains the parsed values of the cookie
|
292
|
-
def self.parse_set_cookie
|
293
|
-
args = {
|
294
|
-
params=set_cookie_value.split(/;\s*/)
|
290
|
+
def self.parse_set_cookie(set_cookie_value)
|
291
|
+
args = {}
|
292
|
+
params = set_cookie_value.split(/;\s*/)
|
295
293
|
|
296
|
-
first=true
|
294
|
+
first = true
|
297
295
|
params.each do |param|
|
298
296
|
result = PARAM1.match param
|
299
|
-
|
300
|
-
|
297
|
+
unless result
|
298
|
+
fail InvalidCookieError,
|
299
|
+
"Invalid cookie parameter in cookie '#{set_cookie_value}'"
|
301
300
|
end
|
302
301
|
key = result[1].downcase.to_sym
|
303
302
|
keyvalue = result[2]
|
@@ -311,17 +310,19 @@ module CookieJar
|
|
311
310
|
begin
|
312
311
|
args[:expires_at] = Time.parse keyvalue
|
313
312
|
rescue ArgumentError
|
314
|
-
raise unless
|
313
|
+
raise unless $ERROR_INFO.message == 'time out of range'
|
315
314
|
args[:expires_at] = Time.at(0x7FFFFFFF)
|
316
315
|
end
|
317
|
-
when
|
316
|
+
when :"max-age"
|
317
|
+
args[:max_age] = keyvalue.to_i
|
318
|
+
when :domain, :path
|
318
319
|
args[key] = keyvalue
|
319
320
|
when :secure
|
320
321
|
args[:secure] = true
|
321
322
|
when :httponly
|
322
323
|
args[:http_only] = true
|
323
324
|
else
|
324
|
-
|
325
|
+
fail InvalidCookieError, "Unknown cookie parameter '#{key}'"
|
325
326
|
end
|
326
327
|
end
|
327
328
|
end
|
@@ -330,18 +331,18 @@ module CookieJar
|
|
330
331
|
end
|
331
332
|
|
332
333
|
# Parse a RFC 2965 value and convert to a literal string
|
333
|
-
def self.value_to_string
|
334
|
-
if /\A"(.*)"\Z
|
335
|
-
value =
|
336
|
-
value
|
334
|
+
def self.value_to_string(value)
|
335
|
+
if /\A"(.*)"\Z/ =~ value
|
336
|
+
value = Regexp.last_match(1)
|
337
|
+
value.gsub(/\\(.)/, '\1')
|
337
338
|
else
|
338
339
|
value
|
339
340
|
end
|
340
341
|
end
|
341
342
|
|
342
343
|
# Attempt to decipher a partially decoded version of text cookie values
|
343
|
-
def self.decode_value
|
344
|
-
if /\A"(.*)"\Z
|
344
|
+
def self.decode_value(value)
|
345
|
+
if /\A"(.*)"\Z/ =~ value
|
345
346
|
value_to_string value
|
346
347
|
else
|
347
348
|
CGI.unescape value
|
@@ -355,16 +356,17 @@ module CookieJar
|
|
355
356
|
# @param [String] set_cookie_value a Set-Cookie2 header formatted cookie
|
356
357
|
# definition
|
357
358
|
# @return [Hash] Contains the parsed values of the cookie
|
358
|
-
def self.parse_set_cookie2
|
359
|
-
args = {
|
359
|
+
def self.parse_set_cookie2(set_cookie_value)
|
360
|
+
args = {}
|
360
361
|
first = true
|
361
362
|
index = 0
|
362
363
|
begin
|
363
364
|
md = PARAM2.match set_cookie_value[index..-1]
|
364
365
|
if md.nil? || md.offset(0).first != 0
|
365
|
-
|
366
|
+
fail InvalidCookieError,
|
367
|
+
"Invalid Set-Cookie2 header '#{set_cookie_value}'"
|
366
368
|
end
|
367
|
-
index+=md.offset(0)[1]
|
369
|
+
index += md.offset(0)[1]
|
368
370
|
|
369
371
|
key = md[1].downcase.to_sym
|
370
372
|
keyvalue = md[2] || md[3]
|
@@ -375,9 +377,9 @@ module CookieJar
|
|
375
377
|
else
|
376
378
|
keyvalue = value_to_string keyvalue
|
377
379
|
case key
|
378
|
-
when
|
380
|
+
when :comment, :commenturl, :domain, :path
|
379
381
|
args[key] = keyvalue
|
380
|
-
when
|
382
|
+
when :discard, :secure
|
381
383
|
args[key] = true
|
382
384
|
when :httponly
|
383
385
|
args[:http_only] = true
|
@@ -388,15 +390,16 @@ module CookieJar
|
|
388
390
|
when :port
|
389
391
|
# must be in format '"port,port"'
|
390
392
|
ports = keyvalue.split(/,\s*/)
|
391
|
-
args[:ports] = ports.map
|
393
|
+
args[:ports] = ports.map(&:to_i)
|
392
394
|
else
|
393
|
-
|
395
|
+
fail InvalidCookieError, "Unknown cookie parameter '#{key}'"
|
394
396
|
end
|
395
397
|
end
|
396
398
|
end until md.post_match.empty?
|
397
399
|
# if our last match in the scan failed
|
398
400
|
if args[:version] != 1
|
399
|
-
|
401
|
+
fail InvalidCookieError,
|
402
|
+
'Set-Cookie2 declares a non RFC2965 version cookie'
|
400
403
|
end
|
401
404
|
|
402
405
|
args
|