cookie_store 0.1.0 → 0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 03f6d983ab27d0ae52b8af6468da9a131e815e4c
4
- data.tar.gz: b235fd0ecfba2b57d10d121cca4bace173e56b08
2
+ SHA256:
3
+ metadata.gz: ed1b21def909112649960bb17b6e9f15291b27a45e1121111ba7fac8bc6944ca
4
+ data.tar.gz: 2c8066581ac182115e759ddf0421ce9e2a3ecc8c3bc6feab292efa9a2c9a3c16
5
5
  SHA512:
6
- metadata.gz: c74560238d8e3ec19ccf3d987bea2c8dcb1f516fa45ec58eaeddacb43553557736f54e2c92f6e2e87daf0234d1ec3acee2346b291e63f583718432d7bf735774
7
- data.tar.gz: 3a4f46c5276009ca3203ea6bdf1b6597fa872f1e3ad4ab731cae18e719fc15bb03f721439ec5f3a177c065f65b7b10a93c7cc3ab74f7a1c2dc7b17f05264c13f
6
+ metadata.gz: 8db6b08273b5324d8866709e52af11eefe89538c891cf5d551160a51df4386a612defb0e8f560e55d406fcbbf15add61b415b56ffb2c933877d66e7ee557589d
7
+ data.tar.gz: 1049c26606be9fe1585377c983357958416bd842a4e36c41cac7107f8f58841fadd003e29cf2e98cef5ff6f1d67078f0af9461b58783a7f0f0e15632b4d6c63e
@@ -0,0 +1,23 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ stream_parser:
11
+ name: CookieStore Test
12
+ runs-on: ubuntu-20.04
13
+
14
+ steps:
15
+ - uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: 3.0
18
+
19
+ - uses: actions/checkout@v2
20
+
21
+ - run: bundle
22
+
23
+ - run: bundle exec rake test
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .DS_Store
2
+ *.gem
3
+ .byebug_history
4
+ Gemfile.lock
5
+ coverage/
data/.tm_properties ADDED
@@ -0,0 +1 @@
1
+ exclude = '{$exclude,log,tmp,.tm_properties,coverage}'
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in cookie_store.gemspec
4
+ gemspec
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- cookie_store
1
+ cookie_store [![Circle CI](https://circleci.com/gh/malomalo/cookie_store.svg?style=svg)](https://circleci.com/gh/malomalo/cookie_store)
2
2
  ============
3
3
 
4
4
  A Ruby library to handle client-side HTTP cookies
data/cookie_store.gemspec CHANGED
@@ -1,25 +1,30 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
1
+ require_relative 'lib/cookie_store/version'
3
2
 
4
3
  Gem::Specification.new do |s|
5
4
  s.name = "cookie_store"
6
- s.version = '0.1.0'
7
- s.licenses = ['MIT']
5
+ s.version = CookieStore::VERSION
8
6
  s.authors = ["Jon Bracy"]
9
7
  s.email = ["jonbracy@gmail.com"]
10
8
  s.homepage = "https://github.com/malomalo/cookie_store"
11
9
  s.summary = %q{A Ruby library to handle client-side HTTP cookies}
12
- s.description = %q{A Ruby library to handle and store client-side HTTP cookies}
10
+ s.description = %q{A Ruby library to handle client-side HTTP cookies}
13
11
 
14
12
  s.files = `git ls-files`.split("\n")
15
13
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
14
  s.require_paths = ["lib"]
17
15
 
16
+ s.add_runtime_dependency 'stream_parser', '>= 0.2'
17
+
18
18
  # Developoment
19
19
  s.add_development_dependency 'rake'
20
+ s.add_development_dependency 'byebug'
20
21
  # s.add_development_dependency 'rdoc'
21
22
  # s.add_development_dependency 'sdoc'
22
23
  s.add_development_dependency 'minitest'
23
24
  s.add_development_dependency 'minitest-reporters'
24
25
  s.add_development_dependency 'mocha'
26
+ s.add_development_dependency 'faker'
27
+ s.add_development_dependency 'webmock'
28
+ s.add_development_dependency 'activesupport'
29
+ s.add_development_dependency 'simplecov'
25
30
  end
data/lib/cookie_store.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'uri'
2
2
 
3
3
  class CookieStore
4
+
5
+ autoload :CookieParser, 'cookie_store/cookie_parser'
4
6
 
5
7
  # Maximum number of bytes per cookie (RFC 6265 6.1 requires at least 4096)
6
8
  MAX_COOKIE_LENGTH = 4096
@@ -14,15 +16,16 @@ class CookieStore
14
16
  # Read and set the cookie from the Set-Cookie header
15
17
  def set_cookie(request_uri, set_cookie_value)
16
18
  request_uri = URI.parse(request_uri)
17
- cookie = CookieStore::Cookie.parse(request_uri, set_cookie_value)
18
-
19
- # reject as per RFC2965 Section 3.3.2
20
- return if !cookie.request_match?(request_uri) || !(cookie.domain =~ /.+\..+/)
21
19
 
22
- # reject cookies over the max-bytes
23
- return if cookie.to_s.size > MAX_COOKIE_LENGTH
20
+ CookieStore::Cookie.parse_cookies(request_uri, set_cookie_value) do |cookie|
21
+ # reject as per RFC2965 Section 3.3.2
22
+ return if !cookie.request_match?(request_uri) || !(cookie.domain =~ /.+\..+/ || cookie.domain == 'localhost')
24
23
 
25
- add(cookie)
24
+ # reject cookies over the max-bytes
25
+ return if cookie.to_s.size > MAX_COOKIE_LENGTH
26
+
27
+ add(cookie)
28
+ end
26
29
  end
27
30
 
28
31
  def cookie_header_for(request_uri)
@@ -1,29 +1,7 @@
1
1
  class CookieStore::Cookie
2
2
 
3
- QUOTED_PAIR = "\\\\[\\x00-\\x7F]"
4
- LWS = "\\r\\n(?:[ \\t]+)"
5
- QDTEXT = "[\\t\\x20-\\x21\\x23-\\x7E\\x80-\\xFF]|(?:#{LWS})"
6
- QUOTED_TEXT = "(?:#{QUOTED_PAIR}|#{QDTEXT})*"
7
- IPADDR = /\A#{URI::REGEXP::PATTERN::IPV4ADDR}\Z|\A#{URI::REGEXP::PATTERN::IPV6ADDR}\Z/
8
-
9
- TOKEN = '[^(),\/<>@;:\\\"\[\]?={}\s]+'
10
- VALUE = "(?:#{TOKEN}|#{IPADDR}|#{QUOTED_TEXT})"
11
- EXPIRES_AT_VALUE = '[A-Za-z]{3},\ \d{2}[-\ ][A-Za-z]{3}[-\ ]\d{4}\ \d{2}:\d{2}:\d{2}\ [A-Z]{3}'
12
-
13
- COOKIE = /(?<name>#{TOKEN})=(?:"(?<quoted_value>#{QUOTED_TEXT})"|(?<value>#{VALUE}))(?<attributes>.*)/n
14
- COOKIE_AV = %r{
15
- ;\s+
16
- (?<key>#{TOKEN})
17
- (?:
18
- =
19
- (?:
20
- "(?<quoted_value>#{QUOTED_TEXT})"
21
- |
22
- (?<value>#{EXPIRES_AT_VALUE}|#{VALUE})
23
- )
24
- ){0,1}
25
- }nx
26
-
3
+ IPADDR = /\A#{URI::REGEXP::PATTERN::IPV4ADDR}\Z|\A#{URI::REGEXP::PATTERN::IPV6ADDR}\Z/
4
+
27
5
  # [String] The name of the cookie.
28
6
  attr_reader :name
29
7
 
@@ -79,7 +57,7 @@ class CookieStore::Cookie
79
57
  # [Time] Time when this cookie was first evaluated and created.
80
58
  attr_reader :created_at
81
59
 
82
- def initialize(name, value, options={})
60
+ def initialize(name, value, attributes={})
83
61
  @name = name
84
62
  @value = value
85
63
  @secure = false
@@ -88,8 +66,12 @@ class CookieStore::Cookie
88
66
  @discard = false
89
67
  @created_at = Time.now
90
68
 
91
- options.each do |attr_name, attr_value|
92
- self.instance_variable_set(:"@#{attr_name}", attr_value)
69
+ @attributes = attributes
70
+
71
+ %i{name value domain path secure http_only version comment comment_url discard ports expires max_age created_at}.each do |attr_name|
72
+ if attributes.has_key?(attr_name)
73
+ self.instance_variable_set(:"@#{attr_name}", attributes[attr_name])
74
+ end
93
75
  end
94
76
  end
95
77
 
@@ -179,60 +161,27 @@ class CookieStore::Cookie
179
161
  end
180
162
 
181
163
  def self.parse(request_uri, set_cookie_value)
164
+ parse_cookies(request_uri, set_cookie_value).first
165
+ end
166
+
167
+ def self.parse_cookies(request_uri, set_cookie_value)
182
168
  uri = request_uri.is_a?(URI) ? request_uri : URI.parse(request_uri)
183
- data = COOKIE.match(set_cookie_value)
184
- options = {}
185
-
186
- if !data
187
- raise Net::HTTPHeaderSyntaxError.new("Invalid Set-Cookie header format")
188
- end
189
169
 
190
- if data[:attributes]
191
- data[:attributes].scan(COOKIE_AV) do |key, quoted_value, value|
192
- value = quoted_value.gsub(/\\(.)/, '\1') if !value && quoted_value
170
+ cookies = []
171
+ CookieStore::CookieParser.parse(set_cookie_value) do |parsed|
172
+ parsed[:attributes][:domain] ||= uri.host.downcase
173
+ parsed[:attributes][:path] ||= uri.path
193
174
 
194
- # RFC 2109 4.1, Attributes (names) are case-insensitive
195
- case key.downcase
196
- when 'comment'
197
- options[:comment] = value
198
- when 'commenturl'
199
- options[:comment_url] = value
200
- when 'discard'
201
- options[:discard] = true
202
- when 'domain'
203
- if value =~ IPADDR
204
- options[:domain] = value
205
- else
206
- # As per RFC2965 if a host name contains no dots, the effective host name is
207
- # that name with the string .local appended to it.
208
- value = "#{value}.local" if !value.include?('.')
209
- options[:domain] = (value.start_with?('.') ? value : ".#{value}").downcase
210
- end
211
- when 'expires'
212
- if value.include?('-')
213
- options[:expires] = DateTime.strptime(value, '%a, %d-%b-%Y %H:%M:%S %Z')
214
- else
215
- options[:expires] = DateTime.strptime(value, '%a, %d %b %Y %H:%M:%S %Z')
216
- end
217
- when 'max-age'
218
- options[:max_age] = value.to_i
219
- when 'path'
220
- options[:path] = value
221
- when 'port'
222
- options[:ports] = value.split(',').map(&:to_i)
223
- when 'secure'
224
- options[:secure] = true
225
- when 'httponly'
226
- options[:http_only] = true
227
- when 'version'
228
- options[:version] = value.to_i
229
- end
175
+ cookie = CookieStore::Cookie.new(parsed[:key], parsed[:value], parsed[:attributes])
176
+
177
+ cookies << if block_given?
178
+ yield(cookie)
179
+ else
180
+ cookie
230
181
  end
231
182
  end
232
- options[:domain] ||= uri.host.downcase
233
- options[:path] ||= uri.path
234
-
235
- CookieStore::Cookie.new(data[:name], data[:value] || data[:quoted_value].gsub(/\\(.)/, '\1'), options)
183
+
184
+ cookies
236
185
  end
237
186
 
238
187
  end
@@ -0,0 +1,110 @@
1
+ require 'stream_parser'
2
+
3
+ class CookieStore::CookieParser
4
+
5
+ include StreamParser
6
+
7
+ NUMERICAL_TIMEZONE = /[-+]\d{4}\Z/
8
+
9
+ def parse
10
+ cookie = {attributes: {}}
11
+
12
+ @stack = [:cookie_name]
13
+ while !eos?
14
+ case @stack.last
15
+ when :cookie_name
16
+ scan_until(/\s*=\s*/)
17
+ if match == '='
18
+ @stack << :cookie_value
19
+ cookie[:key] = pre_match
20
+ end
21
+ when :cookie_value
22
+ scan_until(/\s*(['";]\s*|\Z)/)
23
+ if match.strip == '"' || match.strip == "'"
24
+ cookie[:value] = quoted_value(match.strip)
25
+ @stack.pop
26
+ @stack << :cookie_attributes
27
+ elsif match
28
+ cookie[:value] = pre_match
29
+ @stack.pop
30
+ @stack << :cookie_attributes
31
+ end
32
+ when :cookie_attributes
33
+ # RFC 2109 4.1, Attributes (names) are case-insensitive
34
+ scan_until(/[,=;]\s*/)
35
+ if match&.start_with?('=')
36
+ key = normalize_key(pre_match)
37
+ scan_until(key == :expires ? /\s*((?<!\w{3}),|['";])\s*/ : /\s*(['";,]\s*|\Z)/)
38
+ if match =~ /["']\s*\Z/
39
+ cookie[:attributes][key] = normalize_attribute_value(key, quoted_value(match.strip))
40
+ elsif match =~ /,\s*\Z/
41
+ cookie[:attributes][key] = normalize_attribute_value(key, pre_match)
42
+ yield(cookie)
43
+ cookie = {attributes: {}}
44
+ @stack.pop
45
+ else
46
+ cookie[:attributes][key] = normalize_attribute_value(key, pre_match)
47
+ end
48
+ elsif match&.start_with?(',')
49
+ yield(cookie)
50
+ cookie = {attributes: {}}
51
+ @stack.pop
52
+ else
53
+ cookie[:attributes][normalize_key(pre_match)] = true
54
+ end
55
+ end
56
+ end
57
+
58
+ yield(cookie)
59
+ end
60
+
61
+ def normalize_key(key)
62
+ key = key.downcase.gsub('-','_')
63
+ if key == 'port'
64
+ :ports
65
+ elsif key == 'httponly'
66
+ :http_only
67
+ elsif key == 'commenturl'
68
+ :comment_url
69
+ else
70
+ key.to_sym
71
+ end
72
+ end
73
+
74
+ def normalize_attribute_value(key, value)
75
+ case key
76
+ when :domain
77
+ if value =~ CookieStore::Cookie::IPADDR
78
+ value
79
+ else
80
+ # As per RFC2965 if a host name contains no dots, the effective host
81
+ # name is that name with the string .local appended to it.
82
+ value = "#{value}.local" if !value.include?('.')
83
+ (value.start_with?('.') ? value : ".#{value}").downcase
84
+ end
85
+ when :expires
86
+ byebug if $debug
87
+ case value
88
+ when /\w{3}, \d{2}-\w{3}-\d{2} /
89
+ DateTime.strptime(value, '%a, %d-%b-%y %H:%M:%S %Z')
90
+ when /\w{3}, \d{2}-\w{3}-\d{4} /
91
+ DateTime.strptime(value, '%a, %d-%b-%Y %H:%M:%S %Z')
92
+ when /\w{3}, \d{2} \w{3} \d{2} /
93
+ DateTime.strptime(value, '%a, %d %b %y %H:%M:%S %Z')
94
+ when /\w{3}, \d{2} \w{3} \d{4} /
95
+ DateTime.strptime(value, '%a, %d %b %Y %H:%M:%S %Z')
96
+ else
97
+ DateTime.parse(value)
98
+ end
99
+ when :max_age
100
+ value&.to_i
101
+ when :ports
102
+ value.split(',').map(&:to_i)
103
+ when :version
104
+ value.to_i
105
+ else
106
+ value
107
+ end
108
+ end
109
+
110
+ end
@@ -0,0 +1,3 @@
1
+ class CookieStore
2
+ VERSION = '0.2'
3
+ end
@@ -13,7 +13,7 @@ class CookieStore::CookieTest < Minitest::Test
13
13
 
14
14
  test "::new(name, value, options)" do
15
15
  #TODO: test all options are set
16
- cookie = CookieStore::Cookie.new('foo', 'bar', :domain => 'test.com')
16
+ cookie = CookieStore::Cookie.new('foo', 'bar', domain: 'test.com')
17
17
 
18
18
  assert_equal 'test.com', cookie.domain
19
19
  end
@@ -27,7 +27,7 @@ class CookieStore::CookieTest < Minitest::Test
27
27
  '123.456.57.21' => '123.456.57.21'
28
28
  #TODO: not sure how ipv6 works '[E3D7::51F4:9BC8:C0A8:6420]' => '[E3D7::51F4:9BC8:C0A8:6420]'
29
29
  }.each do |host, cookie_host|
30
- cookie = CookieStore::Cookie.new('key', 'value', :domain => cookie_host)
30
+ cookie = CookieStore::Cookie.new('key', 'value', domain: cookie_host)
31
31
  assert_equal true, cookie.domain_match(host)
32
32
  end
33
33
 
@@ -43,7 +43,7 @@ class CookieStore::CookieTest < Minitest::Test
43
43
  '123.456.57.21' => '.123.456.57.21'
44
44
  #TODO: not sure how ipv6 works '[E3D7::51F4:9BC8:C0A8:6420]' => '[E3D7::51F4:9BC8:C0A8:6421]'
45
45
  }.each do |host, cookie_host|
46
- cookie = CookieStore::Cookie.new('key', 'value', :domain => cookie_host)
46
+ cookie = CookieStore::Cookie.new('key', 'value', domain: cookie_host)
47
47
  assert_equal false, cookie.domain_match(host)
48
48
  end
49
49
  end
@@ -55,7 +55,7 @@ class CookieStore::CookieTest < Minitest::Test
55
55
  '/test' => '/',
56
56
  '/this/is/my/url' => '/this/is'
57
57
  }.each do |path, cookie_path|
58
- cookie = CookieStore::Cookie.new('key', 'value', :path => cookie_path)
58
+ cookie = CookieStore::Cookie.new('key', 'value', path: cookie_path)
59
59
  assert_equal true, cookie.path_match(path)
60
60
  end
61
61
 
@@ -63,7 +63,7 @@ class CookieStore::CookieTest < Minitest::Test
63
63
  '/test' => '/rest',
64
64
  '/' => '/test'
65
65
  }.each do |path, cookie_path|
66
- cookie = CookieStore::Cookie.new('key', 'value', :path => cookie_path)
66
+ cookie = CookieStore::Cookie.new('key', 'value', path: cookie_path)
67
67
  assert_equal false, cookie.path_match(path)
68
68
  end
69
69
  end
@@ -76,7 +76,7 @@ class CookieStore::CookieTest < Minitest::Test
76
76
  end
77
77
 
78
78
  test "::port_match(request_port) with ports attribute set" do
79
- cookie = CookieStore::Cookie.new('key', 'value', :ports => [80, 8700])
79
+ cookie = CookieStore::Cookie.new('key', 'value', ports: [80, 8700])
80
80
  assert_equal true, cookie.port_match(8700)
81
81
  assert_equal false, cookie.port_match(87)
82
82
  end
@@ -97,14 +97,19 @@ class CookieStore::CookieTest < Minitest::Test
97
97
 
98
98
  test "#expires_at perfers max-age to expires" do
99
99
  travel_to Time.new(2013, 12, 13, 8, 26, 12, 0) do
100
- cookie = CookieStore::Cookie.parse('http://google.com/test/this', 'foo=bar; Max-Age=3600 Expires="Wed, 13 Jan 2021 22:23:01 GMT"')
100
+ cookie = CookieStore::Cookie.parse('http://google.com/test/this', 'foo=bar; Max-Age=3600; Expires="Wed, 13 Jan 2021 22:23:01 GMT"')
101
101
  assert_equal Time.new(2013, 12, 13, 9, 26, 12, 0), cookie.expires_at
102
102
  end
103
103
  end
104
104
 
105
105
  test "#expires_at returns nil if no max-age or expires attribute" do
106
106
  cookie = CookieStore::Cookie.parse('http://google.com/test/this', 'foo=bar')
107
- assert_equal nil, cookie.expires_at
107
+ assert_nil cookie.expires_at
108
+ end
109
+
110
+ test "date formats" do
111
+ cookie = CookieStore::Cookie.parse('http://google.com/test/this', 'foor=bar; expires=Fri, 04-Jun-21 14:13:58 GMT; path=/;')
112
+ assert_equal DateTime.new(2021, 6, 04, 14, 13, 58, 0), cookie.expires_at
108
113
  end
109
114
 
110
115
  # CookieStore::Cookie.expired? =========================================================
@@ -188,13 +193,31 @@ class CookieStore::CookieTest < Minitest::Test
188
193
  assert_equal '/test', cookie.path
189
194
  assert_equal false, cookie.secure
190
195
  assert_equal false, cookie.http_only
191
- assert_equal nil, cookie.comment
192
- assert_equal nil, cookie.comment_url
196
+ assert_nil cookie.comment
197
+ assert_nil cookie.comment_url
198
+ assert_equal 1, cookie.version
199
+ assert_equal false, cookie.discard
200
+ assert_nil cookie.ports
201
+ assert_nil cookie.expires
202
+ assert_nil cookie.max_age
203
+ end
204
+
205
+ test "::parse a cookie with options" do
206
+ cookie = CookieStore::Cookie.parse('http://google.com/test', "foo=bar; path=/; HttpOnly")
207
+
208
+ assert_equal 'foo', cookie.name
209
+ assert_equal 'bar', cookie.value
210
+ assert_equal 'google.com', cookie.domain
211
+ assert_equal '/', cookie.path
212
+ assert_equal false, cookie.secure
213
+ assert_equal true, cookie.http_only
214
+ assert_nil cookie.comment
215
+ assert_nil cookie.comment_url
193
216
  assert_equal 1, cookie.version
194
217
  assert_equal false, cookie.discard
195
- assert_equal nil, cookie.ports
196
- assert_equal nil, cookie.expires
197
- assert_equal nil, cookie.max_age
218
+ assert_nil cookie.ports
219
+ assert_nil cookie.expires
220
+ assert_nil cookie.max_age
198
221
  end
199
222
 
200
223
  test "::parse normalizes the request domain" do
@@ -216,9 +239,9 @@ class CookieStore::CookieTest < Minitest::Test
216
239
  test "::parse a simple quoted cookie" do
217
240
  cookie = CookieStore::Cookie.parse('http://google.com/test', 'foo="b\"ar"')
218
241
 
219
- assert_equal 'google.com', cookie.domain
220
- assert_equal 'foo', cookie.name
221
- assert_equal 'b"ar', cookie.value
242
+ assert_equal 'google.com', cookie.domain
243
+ assert_equal 'foo', cookie.name
244
+ assert_equal 'b"ar', cookie.value
222
245
  end
223
246
 
224
247
  test "::parse domain attribute without leading ." do
@@ -294,20 +317,96 @@ class CookieStore::CookieTest < Minitest::Test
294
317
  cookie = CookieStore::Cookie.parse('http://google.com/test/this', 'foo=bar; Expires="Wed, 13 Jan 2021 22:23:01 GMT"')
295
318
  assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookie.expires
296
319
 
320
+ cookie = CookieStore::Cookie.parse('http://google.com/test/this', "foo=bar; Expires='Wed, 13 Jan 2021 22:23:01 GMT'")
321
+ assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookie.expires
322
+
323
+ # Wed, 13 Jan 21 22:23:01 GMT format
324
+ cookie = CookieStore::Cookie.parse('http://google.com/test/this', 'foo=bar; Expires=Wed, 13 Jan 21 22:23:01 GMT')
325
+ assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookie.expires
326
+
327
+ cookie = CookieStore::Cookie.parse('http://google.com/test/this', 'foo=bar; Expires="Wed, 13 Jan 21 22:23:01 GMT"')
328
+ assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookie.expires
329
+
330
+ cookie = CookieStore::Cookie.parse('http://google.com/test/this', "foo=bar; Expires='Wed, 13 Jan 21 22:23:01 GMT'")
331
+ assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookie.expires
332
+
297
333
  # Wed, 13-Jan-2021 22:23:01 GMT format
298
334
  cookie = CookieStore::Cookie.parse('http://google.com/test/this', 'foo=bar; Expires=Wed, 13-Jan-2021 22:23:01 GMT')
299
335
  assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookie.expires
300
336
 
301
337
  cookie = CookieStore::Cookie.parse('http://google.com/test/this', 'foo=bar; Expires="Wed, 13-Jan-2021 22:23:01 GMT"')
302
338
  assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookie.expires
339
+
340
+ cookie = CookieStore::Cookie.parse('http://google.com/test/this', "foo=bar; Expires='Wed, 13-Jan-2021 22:23:01 GMT'")
341
+ assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookie.expires
342
+
343
+ # Wed, 13-Jan-21 22:23:01 GMT format
344
+ cookie = CookieStore::Cookie.parse('http://google.com/test/this', 'foo=bar; Expires=Wed, 13-Jan-21 22:23:01 GMT')
345
+ assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookie.expires
346
+
347
+ cookie = CookieStore::Cookie.parse('http://google.com/test/this', 'foo=bar; Expires="Wed, 13-Jan-21 22:23:01 GMT"')
348
+ assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookie.expires
349
+
350
+ cookie = CookieStore::Cookie.parse('http://google.com/test/this', "foo=bar; Expires='Wed, 13-Jan-21 22:23:01 GMT'")
351
+ assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookie.expires
352
+
353
+ # Wed, 13 Jan 2021 22:23:01 -0000 format
354
+ cookie = CookieStore::Cookie.parse('http://google.com/test/this', 'foo=bar; Expires=Wed, 13 Jan 2021 22:23:01 -0000')
355
+ assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookie.expires
356
+
357
+ cookie = CookieStore::Cookie.parse('http://google.com/test/this', 'foo=bar; Expires="Wed, 13 Jan 2021 22:23:01 +0000"')
358
+ assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookie.expires
359
+
360
+ cookie = CookieStore::Cookie.parse('http://google.com/test/this', "foo=bar; Expires='Wed, 13 Jan 2021 22:23:01 +0000'")
361
+ assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookie.expires
362
+
363
+ # Wed, 13 Jan 21 22:23:01 -0000 format
364
+ cookie = CookieStore::Cookie.parse('http://google.com/test/this', 'foo=bar; Expires=Wed, 13 Jan 21 22:23:01 -0000')
365
+ assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookie.expires
366
+
367
+ cookie = CookieStore::Cookie.parse('http://google.com/test/this', 'foo=bar; Expires="Wed, 13 Jan 21 22:23:01 +0000"')
368
+ assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookie.expires
369
+
370
+ cookie = CookieStore::Cookie.parse('http://google.com/test/this', "foo=bar; Expires='Wed, 13 Jan 21 22:23:01 +0000'")
371
+ assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookie.expires
303
372
  end
304
373
 
305
374
  test "::parse max_age attribute" do
306
375
  cookie = CookieStore::Cookie.parse('http://google.com/test/this', 'foo=bar; Max-Age=3660')
307
376
  assert_equal 3660, cookie.max_age
308
377
  end
378
+
379
+ test "::parse_cookies with one cookie" do
380
+ cookies = CookieStore::Cookie.parse_cookies("http://google.com/test/this", "current_account_id=2449; path=/")
381
+
382
+ assert_equal "current_account_id", cookies.first.name
383
+ assert_equal "2449", cookies.first.value
384
+ assert_equal "/", cookies.first.path
385
+ end
386
+
387
+ test "::parse_cookies with multiple cookies" do
388
+ cookies = CookieStore::Cookie.parse_cookies("http://google.com/test/this", "current_account_id=2449; path=/, _session=QUZwVE5jNjB; path=/test; expires=Wed, 13-Jan-2021 22:23:01 GMT; HttpOnly")
389
+
390
+ assert_equal "current_account_id", cookies.first.name
391
+ assert_equal "2449", cookies.first.value
392
+ assert_equal "/", cookies.first.path
393
+
394
+ assert_equal "_session", cookies[1].name
395
+ assert_equal "QUZwVE5jNjB", cookies[1].value
396
+ assert_equal "/test", cookies[1].path
397
+ assert_equal DateTime.new(2021, 1, 13, 22, 23, 1, 0), cookies[1].expires
398
+ assert_equal true, cookies[1].http_only
399
+ end
309
400
 
310
- # TODO: test expires_at, based on expires attribute
311
- # TODO: test expires_at, based on max-age attribute
401
+ # CookieStore::Cookie parse invalid cookies =========================================================
402
+ test "unclosed quotes" do
403
+ assert_raises Net::HTTPHeaderSyntaxError do
404
+ CookieStore::Cookie.parse('http://google.com/test/this', 'foo=bar; Max-Age="3660')
405
+ end
406
+
407
+ assert_raises Net::HTTPHeaderSyntaxError do
408
+ CookieStore::Cookie.parse('http://google.com/test/this', "foo=bar; Max-Age='3660")
409
+ end
410
+ end
312
411
 
313
412
  end
@@ -6,9 +6,18 @@ class CookieStoreTest < Minitest::Test
6
6
 
7
7
  test "#set_cookie" do
8
8
  store = CookieStore.new
9
- store.expects(:add)
9
+ store.expects(:add).times(3)
10
10
 
11
11
  store.set_cookie('http://google.com/test/this', 'foo=bar; Max-Age=3600')
12
+ store.set_cookie('http://localhost/test/this', 'foo=bar; Max-Age=3600')
13
+ store.set_cookie('http://127.0.0.1/test/this', 'foo=bar; Max-Age=3600')
14
+ end
15
+
16
+ test "#set_cookie contains multiple cookies" do
17
+ store = CookieStore.new
18
+ store.expects(:add).times(2)
19
+
20
+ store.set_cookie("http://google.com/test/this", "current_account_id=2449; path=/, _session=QUZwVE5jNjB; path=/test; expires=Wed, 13-Jan-2021 22:23:01 GMT; HttpOnly")
12
21
  end
13
22
 
14
23
  test "#set_cookie rejects cookies where the value for the Domain attribute contains no embedded dots" do
data/test/test_helper.rb CHANGED
@@ -6,6 +6,9 @@ lib = File.expand_path(File.join(root, 'lib'))
6
6
 
7
7
  $LOAD_PATH << lib
8
8
 
9
+ require 'simplecov'
10
+ SimpleCov.start
11
+
9
12
  require 'cookie_store'
10
13
  require "minitest/autorun"
11
14
  require 'minitest/unit'
@@ -13,11 +16,18 @@ require 'minitest/reporters'
13
16
  require 'faker'
14
17
  require 'webmock/minitest'
15
18
  require "mocha"
16
- require "mocha/mini_test"
19
+ require "mocha/minitest"
20
+ require 'active_support/time'
17
21
  require 'active_support/testing/time_helpers'
22
+ require "byebug"
18
23
 
19
24
  Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
20
25
 
26
+
27
+
28
+
29
+ $debug = false
30
+
21
31
  # File 'lib/active_support/testing/declarative.rb', somewhere in rails....
22
32
  class Minitest::Test
23
33
  include ActiveSupport::Testing::TimeHelpers
@@ -35,4 +45,12 @@ class Minitest::Test
35
45
  end
36
46
  end
37
47
 
48
+ def debug
49
+ $debug = true
50
+ puts '=========================='
51
+ yield
52
+ ensure
53
+ puts '=========================='
54
+ $debug = false
55
+ end
38
56
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cookie_store
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: '0.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Bracy
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-09 00:00:00.000000000 Z
11
+ date: 2021-05-07 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: stream_parser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0.2'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rake
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -24,6 +38,20 @@ dependencies:
24
38
  - - ">="
25
39
  - !ruby/object:Gem::Version
26
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: minitest
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -66,29 +94,90 @@ dependencies:
66
94
  - - ">="
67
95
  - !ruby/object:Gem::Version
68
96
  version: '0'
69
- description: A Ruby library to handle and store client-side HTTP cookies
97
+ - !ruby/object:Gem::Dependency
98
+ name: faker
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webmock
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: activesupport
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: simplecov
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description: A Ruby library to handle client-side HTTP cookies
70
154
  email:
71
155
  - jonbracy@gmail.com
72
156
  executables: []
73
157
  extensions: []
74
158
  extra_rdoc_files: []
75
159
  files:
160
+ - ".github/workflows/ci.yml"
161
+ - ".gitignore"
162
+ - ".tm_properties"
163
+ - Gemfile
76
164
  - LICENSE
77
165
  - README.md
78
166
  - Rakefile
79
167
  - cookie_store.gemspec
80
168
  - lib/cookie_store.rb
81
169
  - lib/cookie_store/cookie.rb
170
+ - lib/cookie_store/cookie_parser.rb
82
171
  - lib/cookie_store/hash_store.rb
172
+ - lib/cookie_store/version.rb
83
173
  - test/cookie_store/cookie_test.rb
84
174
  - test/cookie_store/hash_store_test.rb
85
175
  - test/cookie_store_test.rb
86
176
  - test/test_helper.rb
87
177
  homepage: https://github.com/malomalo/cookie_store
88
- licenses:
89
- - MIT
178
+ licenses: []
90
179
  metadata: {}
91
- post_install_message:
180
+ post_install_message:
92
181
  rdoc_options: []
93
182
  require_paths:
94
183
  - lib
@@ -103,9 +192,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
192
  - !ruby/object:Gem::Version
104
193
  version: '0'
105
194
  requirements: []
106
- rubyforge_project:
107
- rubygems_version: 2.2.2
108
- signing_key:
195
+ rubygems_version: 3.2.3
196
+ signing_key:
109
197
  specification_version: 4
110
198
  summary: A Ruby library to handle client-side HTTP cookies
111
199
  test_files:
@@ -113,4 +201,3 @@ test_files:
113
201
  - test/cookie_store/hash_store_test.rb
114
202
  - test/cookie_store_test.rb
115
203
  - test/test_helper.rb
116
- has_rdoc: