http-cookie 1.0.0.pre10 → 1.0.0.pre11

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f409469763d06151a3094c2246c64c71f8f0016a
4
- data.tar.gz: 4c6433fa901c541aa2bf16461fbbe817fca00f4e
3
+ metadata.gz: 74a442b5c8222376568f11b2ed2f3ff948cb297a
4
+ data.tar.gz: 27b3d634fdd2e75bff0302f89463b5ed777e936f
5
5
  SHA512:
6
- metadata.gz: ec86eccfc64ce5e1672b0ed786722deda4c0386deb564c7eec89ba965199717b603e517a8acdd0cbd560380585e8ed96e66ff9d8cf227ca716ab9c9b4dfabec1
7
- data.tar.gz: ad8af975e1ec653bd2e978f21a1c8199160598c16a8bad6851a9d6b6a2ee3e069409858b31a7f076e37c59f697eeeec6f43b9309b6eff51f5acbc0767d9ac4e4
6
+ metadata.gz: 48ee26913f44add403e4d620e75b6f64156efaf23b37ab30e7c10543bd765a12adaeb25ff7f6c666410b82d291457f049d718ae4d802906be53db5a28638b89c
7
+ data.tar.gz: 7703dd086e0802411ab9e7ee9c12278a3321a41a0fcecdb203a6de3e2848c5c73e6e5a4141ccb01f65e165e1f80a3c8b3ea1a60be3505bb5ea1bcfa2bf55fc59
data/README.md CHANGED
@@ -25,7 +25,7 @@ Or install it yourself as:
25
25
  ## Usage
26
26
 
27
27
  ########################
28
- # Client side example
28
+ # Client side example 1
29
29
  ########################
30
30
 
31
31
  # Initialize a cookie jar
@@ -34,30 +34,60 @@ Or install it yourself as:
34
34
  # Load from a file
35
35
  jar.load(filename) if File.exist?(filename)
36
36
 
37
- # Store received cookies
38
- HTTP::Cookie.parse(set_cookie_header_value, origin: uri) { |cookie|
39
- jar << cookie
37
+ # Store received cookies, where uri is the origin of this header
38
+ header["Set-Cookie"].each { |value|
39
+ jar.parse(value, uri)
40
40
  }
41
41
 
42
- # Get the value for the Cookie field of a request header
43
- cookie_header_value = jar.cookies(uri).join(', ')
42
+ # ...
43
+
44
+ # Set the Cookie header value, where uri is the destination URI
45
+ header["Cookie"] = HTTP::Cookie.cookie_value(jar.cookies(uri))
44
46
 
45
47
  # Save to a file
46
48
  jar.save(filename)
47
49
 
48
50
 
51
+ ########################
52
+ # Client side example 2
53
+ ########################
54
+
55
+ # Initialize a cookie jar using a Mozilla compatible SQLite3 backend
56
+ jar = HTTP::CookieJar.new(store: :mozilla, filename: 'cookies.sqlite')
57
+
58
+ # There is no need for load & save in this backend.
59
+
60
+ # Store received cookies, where uri is the origin of this header
61
+ header["Set-Cookie"].each { |value|
62
+ jar.parse(value, uri)
63
+ }
64
+
65
+ # ...
66
+
67
+ # Set the Cookie header value, where uri is the destination URI
68
+ header["Cookie"] = HTTP::Cookie.cookie_value(jar.cookies(uri))
69
+
70
+
49
71
  ########################
50
72
  # Server side example
51
73
  ########################
52
74
 
53
- # Generate a cookie
54
- cookies = HTTP::Cookie.new("uid", "a12345", domain: 'example.org',
75
+ # Generate a domain cookie
76
+ cookie1 = HTTP::Cookie.new("uid", "u12345", domain: 'example.org',
55
77
  for_domain: true,
56
78
  path: '/',
57
79
  max_age: 7*86400)
58
80
 
59
- # Get the value for the Set-Cookie field of a response header
60
- set_cookie_header_value = cookies.set_cookie_value(my_url)
81
+ # Add it to the Set-Cookie response header
82
+ header['Set-Cookie'] = cookie1.set_cookie_value
83
+
84
+ # Generate a host-only cookie
85
+ cookie2 = HTTP::Cookie.new("aid", "a12345", origin: my_url,
86
+ path: '/',
87
+ max_age: 7*86400)
88
+
89
+ # Add it to the Set-Cookie response header
90
+ header['Set-Cookie'] = cookie2.set_cookie_value
61
91
 
62
92
 
63
93
  ## Incompatibilities with Mechanize::Cookie/CookieJar
@@ -77,14 +107,17 @@ equivalent using HTTP::Cookie:
77
107
 
78
108
  # after
79
109
  cookies1 = HTTP::Cookie.parse(set_cookie1, uri_or_url)
80
- cookies2 = HTTP::Cookie.parse(set_cookie2, uri_or_url, :logger => log)
110
+ cookies2 = HTTP::Cookie.parse(set_cookie2, uri_or_url, logger: log)
111
+ # or you can directly store parsed cookies in your jar
112
+ jar.parse(set_cookie1, uri_or_url)
113
+ jar.parse(set_cookie1, uri_or_url, logger: log)
81
114
 
82
115
  - Mechanize::Cookie#version, #version=
83
116
 
84
- There is no longer a sense of version in HTTP cookie. The only
85
- version number that has ever been defined was zero, and there will
86
- be no other version since the version attribute has been removed
87
- in RFC 6265.
117
+ There is no longer a sense of version in the HTTP cookie
118
+ specification. The only version number ever defined was zero, and
119
+ there will be no other version defined since the version attribute
120
+ has been removed in RFC 6265.
88
121
 
89
122
  - Mechanize::Cookie#comment, #comment=
90
123
 
@@ -3,29 +3,12 @@ require 'http/cookie/version'
3
3
  require 'time'
4
4
  require 'uri'
5
5
  require 'domain_name'
6
+ require 'http/cookie/ruby_compat'
6
7
 
7
8
  module HTTP
8
9
  autoload :CookieJar, 'http/cookie_jar'
9
10
  end
10
11
 
11
- # In Ruby < 1.9.3 URI() does not accept a URI object.
12
- if RUBY_VERSION < "1.9.3"
13
- begin
14
- URI(URI(''))
15
- rescue
16
- def URI(url) # :nodoc:
17
- case url
18
- when URI
19
- url
20
- when String
21
- URI.parse(url)
22
- else
23
- raise ArgumentError, 'bad argument (expected URI object or URI string)'
24
- end
25
- end
26
- end
27
- end
28
-
29
12
  # This class is used to represent an HTTP Cookie.
30
13
  class HTTP::Cookie
31
14
  # Maximum number of bytes per cookie (RFC 6265 6.1 requires 4096 at
@@ -48,31 +31,13 @@ class HTTP::Cookie
48
31
  expires max_age
49
32
  created_at accessed_at
50
33
  ]
51
-
52
- if String.respond_to?(:try_convert)
53
- def check_string_type(object)
54
- String.try_convert(object)
55
- end
56
- private :check_string_type
57
- else
58
- def check_string_type(object)
59
- if object.is_a?(String) ||
60
- (object.respond_to?(:to_str) && (object = object.to_str).is_a?(String))
61
- object
62
- else
63
- nil
64
- end
65
- end
66
- private :check_string_type
67
- end
68
34
  # :startdoc:
69
35
 
70
36
  # The cookie name. It may not be nil or empty.
71
37
  #
72
- # Trying to set a value with the normal setter method will raise
73
- # ArgumentError only when it contains any of these characters:
74
- # control characters (\x00-\x1F and \x7F), space and separators
75
- # `,;\"=`.
38
+ # Assign a string containing any of the following characters will
39
+ # raise ArgumentError: control characters (`\x00-\x1F` and `\x7F`),
40
+ # space and separators `,;\"=`.
76
41
  #
77
42
  # Note that RFC 6265 4.1.1 lists more characters disallowed for use
78
43
  # in a cookie name, which are these: `<>@:/[]?{}`. Using these
@@ -82,9 +47,12 @@ class HTTP::Cookie
82
47
 
83
48
  # The cookie value.
84
49
  #
85
- # Trying to set a value with the normal setter method will raise an
86
- # ArgumentError only when it contains any of these characters:
87
- # control characters (\x00-\x1F and \x7F).
50
+ # Assign a string containing a control character (`\x00-\x1F` and
51
+ # `\x7F`) will raise ArgumentError.
52
+ #
53
+ # Assigning nil sets the value to an empty string and the expiration
54
+ # date to the Unix epoch. This is a handy way to make a cookie for
55
+ # expiration.
88
56
  #
89
57
  # Note that RFC 6265 4.1.1 lists more characters disallowed for use
90
58
  # in a cookie value, which are these: ` ",;\`. Using these
@@ -138,18 +106,21 @@ class HTTP::Cookie
138
106
  # :attr_accessor: max_age
139
107
 
140
108
  # :call-seq:
141
- # new(name, value)
142
- # new(name, value, attr_hash)
143
- # new(attr_hash)
109
+ # new(name, value = nil)
110
+ # new(name, value = nil, **attr_hash)
111
+ # new(**attr_hash)
144
112
  #
145
113
  # Creates a cookie object. For each key of `attr_hash`, the setter
146
114
  # is called if defined. Each key can be either a symbol or a
147
- # string, downcased or not.
115
+ # string of downcased attribute names.
148
116
  #
149
117
  # This methods accepts any attribute name for which a setter method
150
118
  # is defined. Beware, however, any error (typically ArgumentError)
151
119
  # a setter method raises will be passed through.
152
120
  #
121
+ # If `value` is omitted or it is nil, an expiration cookie is
122
+ # created unless `max_age` or `expires` (`expires_at`) is given.
123
+ #
153
124
  # e.g.
154
125
  #
155
126
  # new("uid", "a12345")
@@ -160,51 +131,82 @@ class HTTP::Cookie
160
131
  def initialize(*args)
161
132
  @origin = @domain = @path =
162
133
  @expires = @max_age = nil
163
- @secure = @httponly = false
134
+ @for_domain = @secure = @httponly = false
164
135
  @session = true
165
136
  @created_at = @accessed_at = Time.now
166
-
167
- case args.size
168
- when 2
169
- self.name, self.value = *args
170
- @for_domain = false
171
- return
172
- when 3
173
- self.name, self.value, attr_hash = *args
137
+ case argc = args.size
174
138
  when 1
175
- attr_hash = args.first
139
+ if attr_hash = Hash.try_convert(args.last)
140
+ args.pop
141
+ else
142
+ self.name, self.value = args # value is set to nil
143
+ return
144
+ end
145
+ when 2..3
146
+ if attr_hash = Hash.try_convert(args.last)
147
+ args.pop
148
+ self.name, value = args
149
+ else
150
+ argc == 2 or
151
+ raise ArgumentError, "wrong number of arguments (#{argc} for 1-3)"
152
+ self.name, self.value = args
153
+ return
154
+ end
176
155
  else
177
- raise ArgumentError, "wrong number of arguments (#{args.size} for 1-3)"
156
+ raise ArgumentError, "wrong number of arguments (#{argc} for 1-3)"
178
157
  end
179
158
  for_domain = false
180
159
  domain = max_age = origin = nil
181
160
  attr_hash.each_pair { |key, val|
182
- skey = key.to_s.downcase
183
- if skey.sub!(/\?\z/, '')
184
- val = val ? true : false
185
- end
186
- case skey
187
- when 'for_domain'
188
- for_domain = !!val
189
- when 'domain'
161
+ case key.to_sym
162
+ when :name
163
+ self.name = val
164
+ when :value
165
+ value = val
166
+ when :domain
190
167
  domain = val
191
- when 'origin'
168
+ when :path
169
+ self.path = val
170
+ when :origin
192
171
  origin = val
193
- when 'max_age'
172
+ when :for_domain, :for_domain?
173
+ for_domain = val
174
+ when :max_age
194
175
  # Let max_age take precedence over expires
195
176
  max_age = val
177
+ when :expires, :expires_at
178
+ self.expires = val
179
+ when :httponly, :httponly?
180
+ @httponly = val
181
+ when :secure, :secure?
182
+ @secure = val
183
+ when /[A-Z]/
184
+ warn "keyword should be downcased: #{key}" if $VERBOSE
185
+ key = key.downcase
186
+ redo
187
+ when Symbol
188
+ setter = :"#{key}="
189
+ if respond_to?(setter)
190
+ __send__(setter, val)
191
+ else
192
+ warn "unknown keyword: #{key}" if $VERBOSE
193
+ end
194
+ when String
195
+ key = key.to_sym
196
+ redo
196
197
  else
197
- setter = :"#{skey}="
198
- __send__(setter, val) if respond_to?(setter)
198
+ key = key.to_s
199
+ redo
199
200
  end
200
201
  }
201
- if @name.nil? || @value.nil?
202
- raise ArgumentError, "at least name and value must be specified"
202
+ if @name.nil?
203
+ raise ArgumentError, "name must be specified"
203
204
  end
204
205
  @for_domain = for_domain
205
206
  self.domain = domain if domain
206
207
  self.origin = origin if origin
207
208
  self.max_age = max_age if max_age
209
+ self.value = value.nil? && (@expires || @max_age) ? '' : value
208
210
  end
209
211
 
210
212
  autoload :Scanner, 'http/cookie/scanner'
@@ -238,11 +240,10 @@ class HTTP::Cookie
238
240
  target_path[bsize] == ?/
239
241
  end
240
242
 
241
- # Parses a Set-Cookie header value `set_cookie` into an array of
242
- # Cookie objects taking `origin` as the source URI/URL. Parts
243
- # (separated by commas) that are malformed or invalid are silently
244
- # ignored. For example, cookies that a given origin is not
245
- # allowed to issue are excluded from the resulted array.
243
+ # Parses a Set-Cookie header value `set_cookie` assuming that it
244
+ # is sent from a source URI/URL `origin`, and returns an array of
245
+ # Cookie objects. Parts (separated by commas) that are malformed
246
+ # or considered unacceptable are silently ignored.
246
247
  #
247
248
  # If a block is given, each cookie object is passed to the block.
248
249
  #
@@ -280,9 +281,7 @@ class HTTP::Cookie
280
281
  origin = URI(origin)
281
282
 
282
283
  [].tap { |cookies|
283
- s = Scanner.new(set_cookie, logger)
284
- until s.eos?
285
- name, value, attrs = s.scan_cookie
284
+ Scanner.new(set_cookie, logger).scan_set_cookie { |name, value, attrs|
286
285
  break if name.nil? || name.empty?
287
286
 
288
287
  cookie = new(name, value)
@@ -319,7 +318,24 @@ class HTTP::Cookie
319
318
  yield cookie if block_given?
320
319
 
321
320
  cookies << cookie
322
- end
321
+ }
322
+ }
323
+ end
324
+
325
+ # Takes an array of cookies and returns a string for use in the
326
+ # Cookie header, like "name1=value2; name2=value2".
327
+ def cookie_value(cookies)
328
+ cookies.join('; ')
329
+ end
330
+
331
+ # Parses a Cookie header value into a hash of name-value string
332
+ # pairs. The first appearance takes precedence if multiple pairs
333
+ # with the same name occur.
334
+ def cookie_value_to_hash(cookie_value)
335
+ {}.tap { |hash|
336
+ Scanner.new(cookie_value).scan_cookie { |name, value|
337
+ hash[name] ||= value
338
+ }
323
339
  }
324
340
  end
325
341
  end
@@ -328,7 +344,7 @@ class HTTP::Cookie
328
344
 
329
345
  # See #name.
330
346
  def name=(name)
331
- name = check_string_type(name) or
347
+ name = String.try_convert(name) or
332
348
  raise TypeError, "#{name.class} is not a String"
333
349
  if name.empty?
334
350
  raise ArgumentError, "cookie name cannot be empty"
@@ -345,7 +361,11 @@ class HTTP::Cookie
345
361
 
346
362
  # See #value.
347
363
  def value=(value)
348
- value = check_string_type(value) or
364
+ if value.nil?
365
+ self.expires = UNIX_EPOCH
366
+ return @value = ''
367
+ end
368
+ value = String.try_convert(value) or
349
369
  raise TypeError, "#{value.class} is not a String"
350
370
  if value.match(/[\x00-\x1F\x7F]/)
351
371
  raise ArgumentError, "invalid cookie value"
@@ -373,7 +393,7 @@ class HTTP::Cookie
373
393
  when DomainName
374
394
  @domain_name = domain
375
395
  else
376
- domain = check_string_type(domain) or
396
+ domain = String.try_convert(domain) or
377
397
  raise TypeError, "#{domain.class} is not a String"
378
398
  if domain.start_with?('.')
379
399
  for_domain = true
@@ -418,7 +438,7 @@ class HTTP::Cookie
418
438
 
419
439
  # See #path.
420
440
  def path=(path)
421
- path = check_string_type(path) or
441
+ path = String.try_convert(path) or
422
442
  raise TypeError, "#{path.class} is not a String"
423
443
  @path = path.start_with?('/') ? path : '/'
424
444
  end
@@ -484,7 +504,7 @@ class HTTP::Cookie
484
504
  case sec
485
505
  when Integer, nil
486
506
  else
487
- str = check_string_type(sec) or
507
+ str = String.try_convert(sec) or
488
508
  raise TypeError, "#{sec.class} is not an Integer or String"
489
509
  /\A-?\d+\z/.match(str) or
490
510
  raise ArgumentError, "invalid Max-Age: #{sec.inspect}"
@@ -566,29 +586,32 @@ class HTTP::Cookie
566
586
  acceptable_from_uri?(uri) && HTTP::Cookie.path_match?(@path, uri.path)
567
587
  end
568
588
 
569
- # Returns a string for use in a Cookie header value,
570
- # i.e. "name=value".
589
+ # Returns a string for use in the Cookie header, i.e. `name=value`
590
+ # or `name="value"`.
571
591
  def cookie_value
572
592
  "#{@name}=#{Scanner.quote(@value)}"
573
593
  end
574
594
  alias to_s cookie_value
575
595
 
576
- # Returns a string for use in a Set-Cookie header value. If the
577
- # cookie does not have an origin set, one must be given from the
578
- # argument.
579
- #
580
- # This method does not check if this cookie will be accepted from
581
- # the origin.
582
- def set_cookie_value(origin = nil)
583
- origin = origin ? URI(origin) : @origin or
584
- raise "origin must be specified to produce a value for Set-Cookie"
585
-
596
+ # Returns a string for use in the Set-Cookie header. If necessary
597
+ # information like a path or domain (when `for_domain` is set) is
598
+ # missing, RuntimeError is raised. It is always the best to set an
599
+ # origin before calling this method.
600
+ def set_cookie_value
586
601
  string = cookie_value
587
602
  if @for_domain
588
- string << "; Domain=#{@domain}"
603
+ if @domain
604
+ string << "; Domain=#{@domain}"
605
+ else
606
+ raise "for_domain is specified but domain is known"
607
+ end
589
608
  end
590
- if (origin + './').path != @path
591
- string << "; Path=#{@path}"
609
+ if @path
610
+ if !@origin || (@origin + './').path != @path
611
+ string << "; Path=#{@path}"
612
+ end
613
+ else
614
+ raise "path is known"
592
615
  end
593
616
  if @max_age
594
617
  string << "; Max-Age=#{@max_age}"
@@ -608,7 +631,6 @@ class HTTP::Cookie
608
631
  '#<%s:' % self.class << PERSISTENT_PROPERTIES.map { |key|
609
632
  '%s=%s' % [key, instance_variable_get(:"@#{key}").inspect]
610
633
  }.join(', ') << ' origin=%s>' % [@origin ? @origin.to_s : 'nil']
611
-
612
634
  end
613
635
 
614
636
  # Compares the cookie with another. When there are many cookies with
@@ -643,6 +665,7 @@ class HTTP::Cookie
643
665
  # YAML deserialization helper for Psych.
644
666
  def yaml_initialize(tag, map)
645
667
  expires = nil
668
+ @origin = nil
646
669
  map.each { |key, value|
647
670
  case key
648
671
  when 'expires'
@@ -0,0 +1,59 @@
1
+ class Array
2
+ def select!
3
+ i = 0
4
+ each_with_index { |x, j|
5
+ yield x or next
6
+ self[i] = x if i != j
7
+ i += 1
8
+ }
9
+ return nil if i == size
10
+ self[i..-1] = []
11
+ self
12
+ end unless method_defined?(:select!)
13
+ end
14
+
15
+ class Hash
16
+ class << self
17
+ def try_convert(object)
18
+ if object.is_a?(Hash) ||
19
+ (object.respond_to?(:to_hash) && (object = object.to_hash).is_a?(Hash))
20
+ object
21
+ else
22
+ nil
23
+ end
24
+ end unless method_defined?(:try_convert)
25
+ end
26
+ end
27
+
28
+ class String
29
+ class << self
30
+ def try_convert(object)
31
+ if object.is_a?(String) ||
32
+ (object.respond_to?(:to_str) && (object = object.to_str).is_a?(String))
33
+ object
34
+ else
35
+ nil
36
+ end
37
+ end unless method_defined?(:try_convert)
38
+ end
39
+ end
40
+
41
+ # In Ruby < 1.9.3 URI() does not accept a URI object.
42
+ if RUBY_VERSION < "1.9.3"
43
+ require 'uri'
44
+
45
+ begin
46
+ URI(URI(''))
47
+ rescue
48
+ def URI(url) # :nodoc:
49
+ case url
50
+ when URI
51
+ url
52
+ when String
53
+ URI.parse(url)
54
+ else
55
+ raise ArgumentError, 'bad argument (expected URI object or URI string)'
56
+ end
57
+ end
58
+ end
59
+ end
@@ -143,15 +143,23 @@ class HTTP::Cookie::Scanner < StringScanner
143
143
  year += 2000
144
144
  end
145
145
 
146
- if (time <=> [23,59,59]) > 0
146
+ hh, mm, ss = time
147
+ if hh > 23 || mm > 59 || ss > 59
147
148
  return nil
148
149
  end
149
150
 
150
151
  tuple_to_time(day_of_month, month, year, time)
151
152
  end
152
153
 
153
- def scan_cookie
154
- # cf. RFC 6265 5.2
154
+ def scan_set_cookie
155
+ unless block_given?
156
+ scan_set_cookie { |*values|
157
+ return values
158
+ }
159
+ return
160
+ end
161
+
162
+ # RFC 6265 4.1.1 & 5.2
155
163
  until eos?
156
164
  start = pos
157
165
  len = nil
@@ -159,9 +167,7 @@ class HTTP::Cookie::Scanner < StringScanner
159
167
  skip_wsp
160
168
 
161
169
  name, value = scan_name_value
162
- if name.nil?
163
- break
164
- elsif value.nil?
170
+ if value.nil?
165
171
  @logger.warn("Cookie definition lacks a name-value pair.") if @logger
166
172
  elsif name.empty?
167
173
  @logger.warn("Cookie definition has an empty name.") if @logger
@@ -171,12 +177,13 @@ class HTTP::Cookie::Scanner < StringScanner
171
177
 
172
178
  case
173
179
  when skip(/,/)
180
+ # The comma is used as separator for concatenating multiple
181
+ # values of a header.
174
182
  len = (pos - 1) - start
175
183
  break
176
184
  when skip(/;/)
177
185
  skip_wsp
178
186
  aname, avalue = scan_name_value
179
- break if aname.nil?
180
187
  next if aname.empty? || value.nil?
181
188
  aname.downcase!
182
189
  case aname
@@ -209,7 +216,29 @@ class HTTP::Cookie::Scanner < StringScanner
209
216
  next
210
217
  end
211
218
 
212
- return [name, value, attrs] if value
219
+ yield name, value, attrs if value
220
+ end
221
+ end
222
+
223
+ def scan_cookie
224
+ unless block_given?
225
+ scan_cookie { |*values|
226
+ return values
227
+ }
228
+ return
229
+ end
230
+
231
+ # RFC 6265 4.1.1 & 5.4
232
+ until eos?
233
+ skip_wsp
234
+
235
+ name, value = scan_name_value
236
+
237
+ yield name, value if value
238
+
239
+ # The comma is used as separator for concatenating multiple
240
+ # values of a header.
241
+ skip(/[;,]/)
213
242
  end
214
243
  end
215
244
  end
@@ -1,5 +1,5 @@
1
1
  module HTTP
2
2
  class Cookie
3
- VERSION = "1.0.0.pre10"
3
+ VERSION = "1.0.0.pre11"
4
4
  end
5
5
  end
@@ -49,6 +49,10 @@ class HTTP::CookieJar
49
49
  # any case. A given cookie must have domain and path attributes
50
50
  # set, or ArgumentError is raised.
51
51
  #
52
+ # Whether a cookie with the `for_domain` flag on overwrites another
53
+ # with the flag off or vice versa depends on the store used. See
54
+ # individual store classes for that matter.
55
+ #
52
56
  # ### Compatibility Note for Mechanize::Cookie users
53
57
  #
54
58
  # In HTTP::Cookie, each cookie object can store its origin URI
@@ -70,12 +74,22 @@ class HTTP::CookieJar
70
74
  end
71
75
  alias << add
72
76
 
73
- # Gets an array of cookies that should be sent for the URL/URI.
74
- def cookies(url)
75
- now = Time.now
76
- each(url).select { |cookie|
77
- !cookie.expired? && (cookie.accessed_at = now)
78
- }.sort
77
+ # Deletes a cookie that has the same name, domain and path as a
78
+ # given cookie from the jar and returns self.
79
+ #
80
+ # How the `for_domain` flag value affects the set of deleted cookies
81
+ # depends on the store used. See individual store classes for that
82
+ # matter.
83
+ def delete(cookie)
84
+ @store.delete(cookie)
85
+ self
86
+ end
87
+
88
+ # Gets an array of cookies sorted by the path and creation time. If
89
+ # `url` is given, only ones that should be sent to the URL/URI are
90
+ # selected, with the access time of each of them updated.
91
+ def cookies(url = nil)
92
+ each(url).sort
79
93
  end
80
94
 
81
95
  # Tests if the jar is empty. If `url` is given, tests if there is
@@ -89,7 +103,8 @@ class HTTP::CookieJar
89
103
  end
90
104
  end
91
105
 
92
- # Iterates over all cookies that are not expired.
106
+ # Iterates over all cookies that are not expired in no particular
107
+ # order.
93
108
  #
94
109
  # An optional argument `uri` specifies a URI/URL indicating the
95
110
  # destination of the cookies being selected. Every cookie yielded
@@ -111,10 +126,13 @@ class HTTP::CookieJar
111
126
  end
112
127
  include Enumerable
113
128
 
114
- # Parses a Set-Cookie field value `set_cookie` sent from a URI
115
- # `origin` and adds the cookies parsed as valid to the jar. Returns
116
- # an array of cookies that have been added. If a block is given, it
117
- # is called after each cookie is added.
129
+ # Parses a Set-Cookie field value `set_cookie` assuming that it is
130
+ # sent from a source URL/URI `origin`, and adds the cookies parsed
131
+ # as valid and considered acceptable to the jar. Returns an array
132
+ # of cookies that have been added.
133
+ #
134
+ # If a block is given, it is called for each cookie and the cookie
135
+ # is added only if the block returns a true value.
118
136
  #
119
137
  # `jar.parse(set_cookie, origin)` is a shorthand for this:
120
138
  #
@@ -125,16 +143,15 @@ class HTTP::CookieJar
125
143
  # See HTTP::Cookie.parse for available options.
126
144
  def parse(set_cookie, origin, options = nil) # :yield: cookie
127
145
  if block_given?
128
- HTTP::Cookie.parse(set_cookie, origin, options) { |cookie|
129
- add(cookie)
130
- yield cookie
146
+ HTTP::Cookie.parse(set_cookie, origin, options).tap { |cookies|
147
+ cookies.select! { |cookie|
148
+ yield(cookie) && add(cookie)
149
+ }
131
150
  }
132
151
  else
133
152
  HTTP::Cookie.parse(set_cookie, origin, options) { |cookie|
134
153
  add(cookie)
135
154
  }
136
- # XXX: ruby 1.8 fails to call super from a proc'ized method
137
- # HTTP::Cookie.parse(set_cookie, origin, options, &method(:add)
138
155
  end
139
156
  end
140
157
 
@@ -154,7 +171,7 @@ class HTTP::CookieJar
154
171
  # <dt>:yaml</dt>
155
172
  # <dd>YAML structure (default)</dd>
156
173
  # <dt>:cookiestxt</dt>
157
- # <dd>: Mozilla's cookies.txt format</dd>
174
+ # <dd>Mozilla's cookies.txt format</dd>
158
175
  # </dl>
159
176
  #
160
177
  # * `:session`
@@ -37,7 +37,7 @@ class HTTP::CookieJar::AbstractSaver
37
37
  # Initializes each instance variable of the same name as option
38
38
  # keyword.
39
39
  default_options.each_pair { |key, default|
40
- instance_variable_set("@#{key}", options.key?(key) ? options[key] : default)
40
+ instance_variable_set("@#{key}", options.fetch(key, default))
41
41
  }
42
42
  end
43
43
 
@@ -41,7 +41,7 @@ class HTTP::CookieJar::AbstractStore
41
41
  # Initializes each instance variable of the same name as option
42
42
  # keyword.
43
43
  default_options.each_pair { |key, default|
44
- instance_variable_set("@#{key}", options.key?(key) ? options[key] : default)
44
+ instance_variable_set("@#{key}", options.fetch(key, default))
45
45
  }
46
46
  end
47
47
 
@@ -55,14 +55,14 @@ class HTTP::CookieJar
55
55
  end
56
56
 
57
57
  def add(cookie)
58
- path_cookies = ((@jar[cookie.domain_name.hostname] ||= {})[cookie.path] ||= {})
58
+ path_cookies = ((@jar[cookie.domain] ||= {})[cookie.path] ||= {})
59
59
  path_cookies[cookie.name] = cookie
60
60
  cleanup if (@gc_index += 1) >= @gc_threshold
61
61
  self
62
62
  end
63
63
 
64
64
  def delete(cookie)
65
- path_cookies = ((@jar[cookie.domain_name.hostname] ||= {})[cookie.path] ||= {})
65
+ path_cookies = ((@jar[cookie.domain] ||= {})[cookie.path] ||= {})
66
66
  path_cookies.delete(cookie.name)
67
67
  self
68
68
  end
@@ -255,7 +255,7 @@ class HTTP::CookieJar
255
255
  db_delete(cookie)
256
256
  end
257
257
 
258
- def each(uri = nil)
258
+ def each(uri = nil, &block)
259
259
  now = Time.now
260
260
  if uri
261
261
  @st_cookies_for_domain ||=
@@ -304,7 +304,7 @@ class HTTP::CookieJar
304
304
  yield cookie
305
305
  end
306
306
  }
307
- @sjar.each(uri, &proc)
307
+ @sjar.each(uri, &block)
308
308
  else
309
309
  @st_all_cookies ||=
310
310
  @db.prepare(<<-'SQL')
@@ -333,7 +333,7 @@ class HTTP::CookieJar
333
333
 
334
334
  yield cookie
335
335
  }
336
- @sjar.each(&proc)
336
+ @sjar.each(&block)
337
337
  end
338
338
  self
339
339
  end
@@ -50,10 +50,29 @@ class TestHTTPCookie < Test::Unit::TestCase
50
50
  silently do
51
51
  assert_equal 1, HTTP::Cookie.parse(cookie, url) { |c|
52
52
  assert c.expires, "Tried parsing: #{date}"
53
- assert_equal(true, c.expires < yesterday)
53
+ assert_send [c.expires, :<, yesterday]
54
54
  }.size
55
55
  end
56
56
  end
57
+
58
+ [
59
+ ["PREF=1; expires=Wed, 01 Jan 100 12:34:56 GMT", nil],
60
+ ["PREF=1; expires=Sat, 01 Jan 1600 12:34:56 GMT", nil],
61
+ ["PREF=1; expires=Tue, 01 Jan 69 12:34:56 GMT", 2069],
62
+ ["PREF=1; expires=Thu, 01 Jan 70 12:34:56 GMT", 1970],
63
+ ["PREF=1; expires=Wed, 01 Jan 20 12:34:56 GMT", 2020],
64
+ ["PREF=1; expires=Sat, 01 Jan 2020 12:34:60 GMT", nil],
65
+ ["PREF=1; expires=Sat, 01 Jan 2020 12:60:56 GMT", nil],
66
+ ["PREF=1; expires=Sat, 01 Jan 2020 24:00:00 GMT", nil],
67
+ ["PREF=1; expires=Sat, 32 Jan 2020 12:34:56 GMT", nil],
68
+ ].each { |set_cookie, year|
69
+ cookie, = HTTP::Cookie.parse(set_cookie, url)
70
+ if year
71
+ assert_equal year, cookie.expires.year, "#{set_cookie}: expires in #{year}"
72
+ else
73
+ assert_equal nil, cookie.expires, "#{set_cookie}: invalid expiry date"
74
+ end
75
+ }
57
76
  end
58
77
 
59
78
  def test_parse_empty
@@ -105,6 +124,18 @@ class TestHTTPCookie < Test::Unit::TestCase
105
124
  }.size
106
125
  end
107
126
 
127
+ def test_parse_no_nothing
128
+ cookie = '; "", ;'
129
+ url = URI.parse('http://www.example.com/')
130
+ assert_equal 0, HTTP::Cookie.parse(cookie, url).size
131
+ end
132
+
133
+ def test_parse_no_name
134
+ cookie = '=no-name; path=/'
135
+ url = URI.parse('http://www.example.com/')
136
+ assert_equal 0, HTTP::Cookie.parse(cookie, url).size
137
+ end
138
+
108
139
  def test_parse_weird_cookie
109
140
  cookie = 'n/a, ASPSESSIONIDCSRRQDQR=FBLDGHPBNDJCPCGNCPAENELB; path=/'
110
141
  url = URI.parse('http://www.searchinnovation.com/')
@@ -400,13 +431,51 @@ class TestHTTPCookie < Test::Unit::TestCase
400
431
  cookie = HTTP::Cookie.new('foo', value)
401
432
  assert_equal(cookie_value, cookie.cookie_value)
402
433
  }
434
+
435
+ pairs = [
436
+ ['Foo', 'value1'],
437
+ ['Bar', 'value 2'],
438
+ ['Baz', 'value3'],
439
+ ['Bar', 'value"4'],
440
+ ]
441
+
442
+ cookie_value = HTTP::Cookie.cookie_value(pairs.map { |name, value|
443
+ HTTP::Cookie.new(:name => name, :value => value)
444
+ })
445
+
446
+ assert_equal 'Foo=value1; Bar="value 2"; Baz=value3; Bar="value\\"4"', cookie_value
447
+
448
+ hash = HTTP::Cookie.cookie_value_to_hash(cookie_value)
449
+
450
+ assert_equal 3, hash.size
451
+
452
+ hash.each_pair { |name, value|
453
+ _, pvalue = pairs.assoc(name)
454
+ assert_equal pvalue, value
455
+ }
403
456
  end
404
457
 
405
458
  def test_set_cookie_value
406
- url = URI.parse('http://rubyforge.org/')
459
+ url = URI.parse('http://rubyforge.org/path/')
460
+
461
+ [
462
+ HTTP::Cookie.new('a', 'b', :domain => 'rubyforge.org', :path => '/path/'),
463
+ HTTP::Cookie.new('a', 'b', :origin => url),
464
+ ].each { |cookie|
465
+ cookie.set_cookie_value
466
+ }
467
+
468
+ [
469
+ HTTP::Cookie.new('a', 'b', :domain => 'rubyforge.org'),
470
+ HTTP::Cookie.new('a', 'b', :for_domain => true, :path => '/path/'),
471
+ ].each { |cookie|
472
+ assert_raises(RuntimeError) {
473
+ cookie.set_cookie_value
474
+ }
475
+ }
407
476
 
408
477
  ['foo=bar', 'foo="bar"', 'foo="ba\"r baz"'].each { |cookie_value|
409
- cookie_params = @cookie_params.merge('secure' => 'secure', 'max-age' => 'Max-Age=1000')
478
+ cookie_params = @cookie_params.merge('path' => '/path/', 'secure' => 'secure', 'max-age' => 'Max-Age=1000')
410
479
  date = Time.at(Time.now.to_i)
411
480
  cookie_params.keys.combine.each do |keys|
412
481
  cookie_text = [cookie_value, *keys.map { |key| cookie_params[key] }].join('; ')
@@ -510,11 +579,34 @@ class TestHTTPCookie < Test::Unit::TestCase
510
579
  cookie.acceptable?
511
580
  }
512
581
 
513
- assert_raises(ArgumentError) { HTTP::Cookie.new(:name => 'name') }
582
+ assert_raises(ArgumentError) { HTTP::Cookie.new() }
514
583
  assert_raises(ArgumentError) { HTTP::Cookie.new(:value => 'value') }
515
584
  assert_raises(ArgumentError) { HTTP::Cookie.new('', 'value') }
516
585
  assert_raises(ArgumentError) { HTTP::Cookie.new('key=key', 'value') }
517
586
  assert_raises(ArgumentError) { HTTP::Cookie.new("key\tkey", 'value') }
587
+ assert_raises(ArgumentError) { HTTP::Cookie.new('key', 'value', 'something') }
588
+ assert_raises(ArgumentError) { HTTP::Cookie.new('key', 'value', {}, 'something') }
589
+
590
+ [
591
+ HTTP::Cookie.new(:name => 'name'),
592
+ HTTP::Cookie.new("key", nil, :for_domain => true),
593
+ HTTP::Cookie.new("key", nil),
594
+ HTTP::Cookie.new("key", :secure => true),
595
+ HTTP::Cookie.new("key"),
596
+ ].each { |cookie|
597
+ assert_equal '', cookie.value
598
+ assert_equal true, cookie.expired?
599
+ }
600
+
601
+ [
602
+ HTTP::Cookie.new(:name => 'name', :max_age => 3600),
603
+ HTTP::Cookie.new("key", nil, :expires => Time.now + 3600),
604
+ HTTP::Cookie.new("key", :expires => Time.now + 3600),
605
+ HTTP::Cookie.new("key", :expires => Time.now + 3600, :value => nil),
606
+ ].each { |cookie|
607
+ assert_equal '', cookie.value
608
+ assert_equal false, cookie.expired?
609
+ }
518
610
  end
519
611
 
520
612
  def cookie_values(options = {})
@@ -681,6 +773,22 @@ class TestHTTPCookie < Test::Unit::TestCase
681
773
  assert_equal false, cookie.acceptable?
682
774
  end
683
775
 
776
+ def test_value
777
+ cookie = HTTP::Cookie.new('name', 'value')
778
+ assert_equal 'value', cookie.value
779
+
780
+ cookie.value = 'new value'
781
+ assert_equal 'new value', cookie.value
782
+
783
+ assert_raises(ArgumentError) { cookie.value = "a\tb" }
784
+ assert_raises(ArgumentError) { cookie.value = "a\nb" }
785
+
786
+ assert_equal false, cookie.expired?
787
+ cookie.value = nil
788
+ assert_equal '', cookie.value
789
+ assert_equal true, cookie.expired?
790
+ end
791
+
684
792
  def test_path
685
793
  uri = URI.parse('http://example.com/foo/bar')
686
794
 
@@ -2,6 +2,24 @@ require File.expand_path('helper', File.dirname(__FILE__))
2
2
  require 'tmpdir'
3
3
 
4
4
  module TestHTTPCookieJar
5
+ class TestBasic < Test::Unit::TestCase
6
+ def test_store
7
+ jar = HTTP::CookieJar.new(:store => :hash)
8
+ assert_instance_of HTTP::CookieJar::HashStore, jar.store
9
+
10
+ assert_raises(IndexError) {
11
+ jar = HTTP::CookieJar.new(:store => :nonexistent)
12
+ }
13
+
14
+ jar = HTTP::CookieJar.new(:store => HTTP::CookieJar::HashStore.new)
15
+ assert_instance_of HTTP::CookieJar::HashStore, jar.store
16
+
17
+ assert_raises(TypeError) {
18
+ jar = HTTP::CookieJar.new(:store => HTTP::CookieJar::HashStore)
19
+ }
20
+ end
21
+ end
22
+
5
23
  module Tests
6
24
  def setup(options = nil, options2 = nil)
7
25
  default_options = {
@@ -13,7 +31,7 @@ module TestHTTPCookieJar
13
31
  @store_type = new_options[:store]
14
32
  @gc_threshold = new_options[:gc_threshold]
15
33
  @jar = HTTP::CookieJar.new(new_options)
16
- @jar2 = HTTP::CookieJar.new(new_options)
34
+ @jar2 = HTTP::CookieJar.new(new_options2)
17
35
  end
18
36
 
19
37
  def hash_store?
@@ -303,6 +321,14 @@ module TestHTTPCookieJar
303
321
  assert_equal(0, @jar.cookies(url).length)
304
322
  end
305
323
 
324
+ def test_save_nonexistent_saver
325
+ Dir.mktmpdir { |dir|
326
+ assert_raises(ArgumentError) {
327
+ @jar.save(File.join(dir, "file"), :nonexistent)
328
+ }
329
+ }
330
+ end
331
+
306
332
  def test_save_cookies_yaml
307
333
  url = URI 'http://rubyforge.org/'
308
334
 
@@ -310,7 +336,6 @@ module TestHTTPCookieJar
310
336
  cookie = HTTP::Cookie.new(cookie_values(:origin => url))
311
337
  s_cookie = HTTP::Cookie.new(cookie_values(:name => 'Bar',
312
338
  :expires => nil,
313
- :session => true,
314
339
  :origin => url))
315
340
 
316
341
  @jar.add(cookie)
@@ -335,14 +360,53 @@ module TestHTTPCookieJar
335
360
  assert_equal(3, @jar.cookies(url).length)
336
361
  end
337
362
 
363
+ def test_save_load_signature
364
+ Dir.mktmpdir { |dir|
365
+ filename = File.join(dir, "cookies.yml")
366
+
367
+ @jar.save(filename, :format => :cookiestxt, :session => true)
368
+ @jar.save(filename, :format => :cookiestxt, :session => true)
369
+ @jar.save(filename, :format => :cookiestxt)
370
+ @jar.save(filename, :cookiestxt, :session => true)
371
+ @jar.save(filename, :cookiestxt)
372
+ @jar.save(filename, :session => true)
373
+ @jar.save(filename)
374
+ assert_raises(ArgumentError) {
375
+ @jar.save()
376
+ }
377
+ assert_raises(ArgumentError) {
378
+ @jar.save(filename, { :format => :cookiestxt }, { :session => true })
379
+ }
380
+ assert_raises(ArgumentError) {
381
+ @jar.save(filename, :cookiestxt, { :session => true }, { :format => :cookiestxt })
382
+ }
383
+
384
+ @jar.load(filename, :format => :cookiestxt, :linefeed => "\n")
385
+ @jar.load(filename, :format => :cookiestxt, :linefeed => "\n")
386
+ @jar.load(filename, :format => :cookiestxt)
387
+ @jar.load(filename, :cookiestxt, :linefeed => "\n")
388
+ @jar.load(filename, :cookiestxt)
389
+ @jar.load(filename, :linefeed => "\n")
390
+ @jar.load(filename)
391
+ assert_raises(ArgumentError) {
392
+ @jar.load()
393
+ }
394
+ assert_raises(ArgumentError) {
395
+ @jar.load(filename, { :format => :cookiestxt }, { :linefeed => "\n" })
396
+ }
397
+ assert_raises(ArgumentError) {
398
+ @jar.load(filename, :cookiestxt, { :linefeed => "\n" }, { :format => :cookiestxt })
399
+ }
400
+ }
401
+ end
402
+
338
403
  def test_save_session_cookies_yaml
339
404
  url = URI 'http://rubyforge.org/'
340
405
 
341
406
  # Add one cookie with an expiration date in the future
342
407
  cookie = HTTP::Cookie.new(cookie_values)
343
408
  s_cookie = HTTP::Cookie.new(cookie_values(:name => 'Bar',
344
- :expires => nil,
345
- :session => true))
409
+ :expires => nil))
346
410
 
347
411
  @jar.add(cookie)
348
412
  @jar.add(s_cookie)
@@ -360,7 +424,6 @@ module TestHTTPCookieJar
360
424
  assert_equal(3, @jar.cookies(url).length)
361
425
  end
362
426
 
363
-
364
427
  def test_save_and_read_cookiestxt
365
428
  url = URI 'http://rubyforge.org/foo/'
366
429
 
@@ -368,8 +431,7 @@ module TestHTTPCookieJar
368
431
  cookie = HTTP::Cookie.new(cookie_values)
369
432
  expires = cookie.expires
370
433
  s_cookie = HTTP::Cookie.new(cookie_values(:name => 'Bar',
371
- :expires => nil,
372
- :session => true))
434
+ :expires => nil))
373
435
  cookie2 = HTTP::Cookie.new(cookie_values(:name => 'Baz',
374
436
  :value => 'Foo#Baz',
375
437
  :path => '/foo/',
@@ -394,6 +456,13 @@ module TestHTTPCookieJar
394
456
 
395
457
  content = File.read(filename)
396
458
 
459
+ filename2 = File.join(dir, "cookies2.txt")
460
+ open(filename2, 'w') { |w|
461
+ w.puts '# HTTP Cookie File'
462
+ @jar.save(w, :cookiestxt, :header => nil)
463
+ }
464
+ assert_equal content, File.read(filename2)
465
+
397
466
  assert_match(/^\.rubyforge\.org\t.*\tFoo\t/, content)
398
467
  assert_match(/^rubyforge\.org\t.*\tBaz\t/, content)
399
468
  assert_match(/^#HttpOnly_\.rubyforge\.org\t/, content)
@@ -554,17 +623,43 @@ module TestHTTPCookieJar
554
623
  assert_equal('Foo1 Foo2', @jar.cookies(surl).map { |c| c.name }.sort.join(' ') )
555
624
  end
556
625
 
557
- def _test_delete
558
- nurl = URI 'http://rubyforge.org/login'
559
- surl = URI 'https://rubyforge.org/login'
626
+ def test_delete
627
+ cookie1 = HTTP::Cookie.new(cookie_values)
628
+ cookie2 = HTTP::Cookie.new(:name => 'Foo', :value => '',
629
+ :domain => 'rubyforge.org',
630
+ :for_domain => false,
631
+ :path => '/')
632
+ cookie3 = HTTP::Cookie.new(:name => 'Foo', :value => '',
633
+ :domain => 'rubyforge.org',
634
+ :for_domain => true,
635
+ :path => '/')
560
636
 
561
- cookie1 = HTTP::Cookie.new(cookie_values(:name => 'Foo1', :origin => nurl))
562
- cookie2 = HTTP::Cookie.new(cookie_values(:name => 'Foo1', :origin => surl))
637
+ @jar.add(cookie1)
638
+ @jar.delete(cookie2)
563
639
 
564
- @jar.add(nncookie)
565
- @jar.add(sncookie)
566
- @jar.add(nscookie)
567
- @jar.add(sscookie)
640
+ if mozilla_store?
641
+ assert_equal(1, @jar.to_a.length)
642
+ @jar.delete(cookie3)
643
+ end
644
+
645
+ assert_equal(0, @jar.to_a.length)
646
+ end
647
+
648
+ def test_accessed_at
649
+ orig = HTTP::Cookie.new(cookie_values(:expires => nil))
650
+ @jar.add(orig)
651
+
652
+ time = orig.accessed_at
653
+
654
+ assert_in_delta 1.0, time, Time.now, "accessed_at is initialized to the current time"
655
+
656
+ cookie, = @jar.to_a
657
+
658
+ assert_equal time, cookie.accessed_at, "accessed_at is not updated by each()"
659
+
660
+ cookie, = @jar.cookies("http://rubyforge.org/")
661
+
662
+ assert_send [cookie.accessed_at, :>, time], "accessed_at is not updated by each(url)"
568
663
  end
569
664
 
570
665
  def test_max_cookies
@@ -614,14 +709,38 @@ module TestHTTPCookieJar
614
709
  }
615
710
  }
616
711
 
617
- assert_equal true, count > slimit
618
- assert_equal true, @jar.to_a.size <= slimit
712
+ assert_send [count, :>, slimit]
713
+ assert_send [@jar.to_a.size, :<=, slimit]
619
714
  @jar.cleanup
620
715
  assert_equal hlimit, @jar.to_a.size
621
716
  assert_equal false, @jar.any? { |cookie|
622
717
  cookie.domain == cookie.value
623
718
  }
624
719
  end
720
+
721
+ def test_parse
722
+ set_cookie = [
723
+ "name=Akinori; Domain=rubyforge.org; Expires=Sun, 08 Aug 2076 19:00:00 GMT; Path=/",
724
+ "country=Japan; Domain=rubyforge.org; Expires=Sun, 08 Aug 2076 19:00:00 GMT; Path=/",
725
+ "city=Tokyo; Domain=rubyforge.org; Expires=Sun, 08 Aug 2076 19:00:00 GMT; Path=/",
726
+ ].join(', ')
727
+
728
+ cookies = @jar.parse(set_cookie, 'http://rubyforge.org/')
729
+ assert_equal %w[Akinori Japan Tokyo], cookies.map { |c| c.value }
730
+ assert_equal %w[Tokyo Japan Akinori], @jar.to_a.sort_by { |c| c.name }.map { |c| c.value }
731
+ end
732
+
733
+ def test_parse_with_block
734
+ set_cookie = [
735
+ "name=Akinori; Domain=rubyforge.org; Expires=Sun, 08 Aug 2076 19:00:00 GMT; Path=/",
736
+ "country=Japan; Domain=rubyforge.org; Expires=Sun, 08 Aug 2076 19:00:00 GMT; Path=/",
737
+ "city=Tokyo; Domain=rubyforge.org; Expires=Sun, 08 Aug 2076 19:00:00 GMT; Path=/",
738
+ ].join(', ')
739
+
740
+ cookies = @jar.parse(set_cookie, 'http://rubyforge.org/') { |c| c.name != 'city' }
741
+ assert_equal %w[Akinori Japan], cookies.map { |c| c.value }
742
+ assert_equal %w[Japan Akinori], @jar.to_a.sort_by { |c| c.name }.map { |c| c.value }
743
+ end
625
744
  end
626
745
 
627
746
  class WithHashStore < Test::Unit::TestCase
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: http-cookie
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre10
4
+ version: 1.0.0.pre11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Akinori MUSHA
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2013-04-07 00:00:00.000000000 Z
14
+ date: 2013-04-15 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: domain_name
@@ -132,6 +132,7 @@ files:
132
132
  - http-cookie.gemspec
133
133
  - lib/http-cookie.rb
134
134
  - lib/http/cookie.rb
135
+ - lib/http/cookie/ruby_compat.rb
135
136
  - lib/http/cookie/scanner.rb
136
137
  - lib/http/cookie/version.rb
137
138
  - lib/http/cookie_jar.rb