http-cookie 0.1.5 → 1.0.0.pre1

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: c45ee093b1364bc1ce8c5c4120a4f4fae298a5e6
4
- data.tar.gz: d007f3979523ad48a557281b99292d660a5d781b
3
+ metadata.gz: 9f5ccaec04a2b731f1120d21079f05fda32dc05f
4
+ data.tar.gz: aea993fb843b5218522f7a12adf189112dd8a477
5
5
  SHA512:
6
- metadata.gz: 62257900f2b0e4ffad506bff326f11b724ebcb0f3495cdc156338188efc8db7c4bfb49d155ae43ea50889f5e0acb4059586f016a0c5fd84f659e58605b512d49
7
- data.tar.gz: c3f9f8a5deafe3335063e8cf3b09d054da55d01729b47e7b55171e06585b8e270da021bcea1804132b0fabe855b90d3d21c2cc333913b7283c318167f799496b
6
+ metadata.gz: f02c9afbb827b9a35e2417e4104488d3cfc237ac9ec8007c6b77b96b61c2b5c1f3a74f10660211ff1e4e2ee5000f28ddb1052148ce3cba5ccc1d2d3d49e135a8
7
+ data.tar.gz: 8316457b65faeb9d19449bf49e11b920d53d9c5c8e37edf8d99d02b32fcf30350dff5c270cad654a8e0d4dcc45950894d59496450a7fde2f14e339a1b5b0d077
@@ -1,12 +1,14 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 1.8.7
4
+ - ree
4
5
  - 1.9.2
5
6
  - 1.9.3
6
7
  - 2.0.0
7
- - ree
8
+ - ruby-head
8
9
  - jruby-18mode
9
10
  - jruby-19mode
11
+ - jruby-head
10
12
  - rbx-18mode
11
13
  - rbx-19mode
12
14
  matrix:
@@ -1,3 +1,4 @@
1
+ # :markup: markdown
1
2
  require 'http/cookie/version'
2
3
  require 'time'
3
4
  require 'uri'
@@ -7,11 +8,12 @@ module HTTP
7
8
  autoload :CookieJar, 'http/cookie_jar'
8
9
  end
9
10
 
10
- # In Ruby < 1.9.3 URI() does not accept an URI object.
11
+ # In Ruby < 1.9.3 URI() does not accept a URI object.
11
12
  if RUBY_VERSION < "1.9.3"
12
13
  begin
13
14
  URI(URI(''))
14
15
  rescue
16
+ # :nodoc:
15
17
  def URI(url)
16
18
  url.is_a?(URI) ? url : URI.parse(url)
17
19
  end
@@ -20,13 +22,17 @@ end
20
22
 
21
23
  # This class is used to represent an HTTP Cookie.
22
24
  class HTTP::Cookie
23
- # Maximum number of bytes per cookie (RFC 6265 6.1 requires 4096 at least)
25
+ # Maximum number of bytes per cookie (RFC 6265 6.1 requires 4096 at
26
+ # least)
24
27
  MAX_LENGTH = 4096
25
- # Maximum number of cookies per domain (RFC 6265 6.1 requires 50 at least)
28
+ # Maximum number of cookies per domain (RFC 6265 6.1 requires 50 at
29
+ # least)
26
30
  MAX_COOKIES_PER_DOMAIN = 50
27
- # Maximum number of cookies total (RFC 6265 6.1 requires 3000 at least)
31
+ # Maximum number of cookies total (RFC 6265 6.1 requires 3000 at
32
+ # least)
28
33
  MAX_COOKIES_TOTAL = 3000
29
34
 
35
+ # :stopdoc:
30
36
  UNIX_EPOCH = Time.at(0)
31
37
 
32
38
  PERSISTENT_PROPERTIES = %w[
@@ -52,31 +58,91 @@ class HTTP::Cookie
52
58
  end
53
59
  private :check_string_type
54
60
  end
61
+ # :startdoc:
55
62
 
56
- attr_reader :name, :domain, :path, :origin
57
- attr_accessor :secure, :httponly, :value, :version
58
- attr_reader :domain_name, :expires
59
- attr_accessor :comment, :max_age
63
+ # The cookie name. It may not be nil or empty.
64
+ #
65
+ # Trying to set a value with the normal setter method will raise
66
+ # ArgumentError only when it contains any of these characters:
67
+ # control characters (\x00-\x1F and \x7F), space and separators
68
+ # `,;\"=`.
69
+ #
70
+ # Note that RFC 6265 4.1.1 lists more characters disallowed for use
71
+ # in a cookie name, which are these: `<>@:/[]?{}`. Using these
72
+ # characters will reduce interoperability.
73
+ #
74
+ # :attr_accessor: name
75
+
76
+ # The cookie value.
77
+ #
78
+ # Trying to set a value with the normal setter method will raise an
79
+ # ArgumentError only when it contains any of these characters:
80
+ # control characters (\x00-\x1F and \x7F).
81
+ #
82
+ # Note that RFC 6265 4.1.1 lists more characters disallowed for use
83
+ # in a cookie value, which are these: ` ",;\`. Using these
84
+ # characters will reduce interoperability.
85
+ #
86
+ # :attr_accessor: value
60
87
 
61
- attr_accessor :session
88
+ # The cookie domain.
89
+ #
90
+ # Setting a domain with a leading dot implies that the #for_domain
91
+ # flag should be turned on. The setter accepts a `DomainName`
92
+ # object as well as a string-like.
93
+ #
94
+ # :attr_accessor: domain
62
95
 
63
- attr_accessor :created_at
64
- attr_accessor :accessed_at
96
+ # The path attribute value.
97
+ #
98
+ # The setter treats an empty path ("") as the root path ("/").
99
+ #
100
+ # :attr_accessor: path
101
+
102
+ # The origin of the cookie.
103
+ #
104
+ # Setting this will initialize the #domain and #path attribute
105
+ # values if unknown yet.
106
+ #
107
+ # :attr_accessor: origin
108
+
109
+ # The Expires attribute value as a Time object.
110
+ #
111
+ # The setter method accepts a Time object, a string representation
112
+ # of date/time, or `nil`.
113
+ #
114
+ # Note that #max_age and #expires are mutually exclusive. Setting
115
+ # \#max_age resets #expires to nil, and vice versa.
116
+ #
117
+ # :attr_accessor: expires
118
+
119
+ # The Max-Age attribute value as an integer, the number of seconds
120
+ # before expiration.
121
+ #
122
+ # The setter method accepts an integer, or a string-like that
123
+ # represents an integer which will be stringified and then
124
+ # integerized using #to_i.
125
+ #
126
+ # Note that #max_age and #expires are mutually exclusive. Setting
127
+ # \#max_age resets #expires to nil, and vice versa.
128
+ #
129
+ # :attr_accessor: max_age
65
130
 
66
131
  # :call-seq:
67
132
  # new(name, value)
68
133
  # new(name, value, attr_hash)
69
134
  # new(attr_hash)
70
135
  #
71
- # Creates a cookie object. For each key of +attr_hash+, the setter
136
+ # Creates a cookie object. For each key of `attr_hash`, the setter
72
137
  # is called if defined. Each key can be either a symbol or a
73
138
  # string, downcased or not.
74
139
  #
75
140
  # This methods accepts any attribute name for which a setter method
76
- # is defined. Beware, however, any error (typically ArgumentError)
77
- # a setter method raises will be passed through.
141
+ # is defined. Beware, however, any error (typically
142
+ # `ArgumentError`) a setter method raises will be passed through.
78
143
  #
79
144
  # e.g.
145
+ #
80
146
  # new("uid", "a12345")
81
147
  # new("uid", "a12345", :domain => 'example.org',
82
148
  # :for_domain => true, :expired => Time.now + 7*86400)
@@ -86,11 +152,12 @@ class HTTP::Cookie
86
152
  @version = 0 # Netscape Cookie
87
153
 
88
154
  @origin = @domain = @path =
89
- @secure = @httponly =
90
155
  @expires = @max_age =
91
156
  @comment = nil
92
-
157
+ @secure = @httponly = false
158
+ @session = true
93
159
  @created_at = @accessed_at = Time.now
160
+
94
161
  case args.size
95
162
  when 2
96
163
  self.name, self.value = *args
@@ -129,45 +196,43 @@ class HTTP::Cookie
129
196
  end
130
197
  end
131
198
 
132
- # If this flag is true, this cookie will be sent to any host in the
133
- # +domain+. If it is false, this cookie will be sent only to the
134
- # host indicated by the +domain+.
135
- attr_accessor :for_domain
136
- alias for_domain? for_domain
199
+ autoload :Scanner, 'http/cookie/scanner'
137
200
 
138
201
  class << self
139
- include URIFix if defined?(URIFix)
140
-
141
- # Normalizes a given path. If it is empty, the root path '/' is
142
- # returned. If a URI object is given, returns a new URI object
143
- # with the path part normalized.
144
- def normalize_path(uri)
145
- # Currently does not replace // to /
146
- case uri
147
- when URI
148
- uri.path.empty? ? uri + '/' : uri
149
- else
150
- uri.empty? ? '/' : uri
151
- end
202
+ # Normalizes a given path. If it is empty or it is a relative
203
+ # path, the root path '/' is returned.
204
+ #
205
+ # If a URI object is given, returns a new URI object with the path
206
+ # part normalized.
207
+ def normalize_path(path)
208
+ return path + normalize_path(path.path) if URI === path
209
+ # RFC 6265 5.1.4
210
+ path.start_with?('/') ? path : '/'
152
211
  end
153
212
 
154
- # Parses a Set-Cookie header value +set_cookie+ into an array of
213
+ # Parses a Set-Cookie header value `set_cookie` into an array of
155
214
  # Cookie objects. Parts (separated by commas) that are malformed
156
215
  # or invalid are silently ignored. For example, a cookie that a
157
216
  # given origin is not allowed to issue is not included in the
158
217
  # resulted array.
159
218
  #
219
+ # Any Max-Age attribute value found is converted to an expires
220
+ # value computing from the current time so that expiration check
221
+ # (#expired?) can be performed.
222
+ #
160
223
  # If a block is given, each cookie object is passed to the block.
161
224
  #
162
225
  # Available option keywords are below:
163
226
  #
164
- # * +origin+
165
- # The cookie's origin URI/URL
166
- # * +date+
167
- # The base date used for interpreting Max-Age attribute values
227
+ # `origin`
228
+ # : The cookie's origin URI/URL
229
+ #
230
+ # `date`
231
+ # : The base date used for interpreting Max-Age attribute values
168
232
  # instead of the current time
169
- # * +logger+
170
- # Logger object useful for debugging
233
+ #
234
+ # `logger`
235
+ # : Logger object useful for debugging
171
236
  def parse(set_cookie, options = nil, *_, &block)
172
237
  _.empty? && !options.is_a?(String) or
173
238
  raise ArgumentError, 'HTTP::Cookie equivalent for Mechanize::Cookie.parse(uri, set_cookie[, log]) is HTTP::Cookie.parse(set_cookie, :origin => uri[, :logger => log]).'
@@ -180,83 +245,44 @@ class HTTP::Cookie
180
245
  date ||= Time.now
181
246
 
182
247
  [].tap { |cookies|
183
- # The expires attribute may include a comma in the value.
184
- set_cookie.split(/,(?=[^;,]*=|\s*\z)/).each { |c|
185
- if c.bytesize > MAX_LENGTH
186
- logger.warn("Cookie definition too long: #{c}") if logger
187
- next
188
- end
189
-
190
- first_elem, *cookie_elem = c.split(/;+/)
191
- first_elem.strip!
192
- key, value = first_elem.split(/\=/, 2)
248
+ s = Scanner.new(set_cookie, logger)
249
+ until s.eos?
250
+ name, value, attrs = s.scan_cookie
251
+ break if name.nil? || name.empty?
193
252
 
194
- begin
195
- cookie = new(key, value.dup)
196
- rescue
197
- logger.warn("Couldn't parse key/value: #{first_elem}") if logger
198
- next
199
- end
200
-
201
- cookie_elem.each do |pair|
202
- pair.strip!
203
- key, value = pair.split(/=/, 2) #/)
204
- next unless key
205
- case value # may be nil
206
- when /\A"(.*)"\z/
207
- value = $1.gsub(/\\(.)/, "\\1")
208
- end
209
-
210
- case key.downcase
211
- when 'domain'
212
- next unless value && !value.empty?
213
- begin
214
- cookie.domain = value
253
+ cookie = new(name, value)
254
+ attrs.each { |aname, avalue|
255
+ begin
256
+ case aname
257
+ when 'domain'
258
+ cookie.domain = avalue
215
259
  cookie.for_domain = true
216
- rescue
217
- logger.warn("Couldn't parse domain: #{value}") if logger
218
- end
219
- when 'path'
220
- next unless value && !value.empty?
221
- cookie.path = value
222
- when 'expires'
223
- next unless value && !value.empty?
224
- begin
225
- cookie.expires = Time.parse(value)
226
- rescue
227
- logger.warn("Couldn't parse expires: #{value}") if logger
228
- end
229
- when 'max-age'
230
- next unless value && !value.empty?
231
- begin
232
- cookie.max_age = Integer(value)
233
- rescue
234
- logger.warn("Couldn't parse max age '#{value}'") if logger
235
- end
236
- when 'comment'
237
- next unless value
238
- cookie.comment = value
239
- when 'version'
240
- next unless value
241
- begin
242
- cookie.version = Integer(value)
243
- rescue
244
- logger.warn("Couldn't parse version '#{value}'") if logger
245
- cookie.version = nil
260
+ when 'path'
261
+ cookie.path = avalue
262
+ when 'expires'
263
+ # RFC 6265 4.1.2.2
264
+ # The Max-Age attribute has precedence over the Expires
265
+ # attribute.
266
+ cookie.expires = avalue unless cookie.max_age
267
+ when 'max-age'
268
+ cookie.max_age = avalue
269
+ when 'comment'
270
+ cookie.comment = avalue
271
+ when 'version'
272
+ cookie.version = avalue
273
+ when 'secure'
274
+ cookie.secure = avalue
275
+ when 'httponly'
276
+ cookie.httponly = avalue
246
277
  end
247
- when 'secure'
248
- cookie.secure = true
249
- when 'httponly'
250
- cookie.httponly = true
278
+ rescue => e
279
+ logger.warn("Couldn't parse #{aname} '#{avalue}': #{e}") if logger
251
280
  end
252
- end
253
-
254
- cookie.secure ||= false
255
- cookie.httponly ||= false
281
+ }
256
282
 
257
- # RFC 6265 4.1.2.2
258
- cookie.expires = date + cookie.max_age if cookie.max_age
259
- cookie.session = !cookie.expires
283
+ # Have `expires` set instead of `max_age`, so that
284
+ # expiration check (`expired?`) can be performed.
285
+ cookie.expires = date + cookie.max_age if cookie.max_age
260
286
 
261
287
  if origin
262
288
  begin
@@ -270,22 +296,46 @@ class HTTP::Cookie
270
296
  yield cookie if block_given?
271
297
 
272
298
  cookies << cookie
273
- }
299
+ end
274
300
  }
275
301
  end
276
302
  end
277
303
 
304
+ attr_reader :name
305
+
306
+ # See #name.
278
307
  def name=(name)
279
- if name.nil? || name.empty?
308
+ name = check_string_type(name) or
309
+ raise TypeError, "#{name.class} is not a String"
310
+ if name.empty?
280
311
  raise ArgumentError, "cookie name cannot be empty"
281
- elsif name.match(/[\x00-\x1F=\x7F]/)
282
- raise ArgumentError, "cookie name cannot contain a control character or an equal sign"
312
+ elsif name.match(/[\x00-\x20\x7F,;\\"=]/)
313
+ raise ArgumentError, "invalid cookie name"
283
314
  end
315
+ # RFC 6265 4.1.1
316
+ # cookie-name may not match:
317
+ # /[\x00-\x20\x7F()<>@,;:\\"\/\[\]?={}]/
284
318
  @name = name
285
319
  end
286
320
 
287
- # Sets the domain attribute. A leading dot in +domain+ implies
288
- # turning the +for_domain?+ flag on.
321
+ attr_reader :value
322
+
323
+ # See #value.
324
+ def value=(value)
325
+ value = check_string_type(value) or
326
+ raise TypeError, "#{value.class} is not a String"
327
+ if value.match(/[\x00-\x1F\x7F]/)
328
+ raise ArgumentError, "invalid cookie value"
329
+ end
330
+ # RFC 6265 4.1.1
331
+ # cookie-name may not match:
332
+ # /[^\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/
333
+ @value = value
334
+ end
335
+
336
+ attr_reader :domain
337
+
338
+ # See #domain.
289
339
  def domain=(domain)
290
340
  if DomainName === domain
291
341
  @domain_name = domain
@@ -310,13 +360,29 @@ class HTTP::Cookie
310
360
  raise NoMethodError, 'HTTP::Cookie equivalent for Mechanize::CookieJar#set_domain() is #domain=().'
311
361
  end
312
362
 
313
- # Sets the path attribute.
363
+ # Returns the domain attribute value as a DomainName object.
364
+ attr_reader :domain_name
365
+
366
+ # The domain flag.
367
+ #
368
+ # If this flag is true, this cookie will be sent to any host in the
369
+ # \#domain, including the host domain itself. If it is false, this
370
+ # cookie will be sent only to the host indicated by the #domain.
371
+ attr_accessor :for_domain
372
+ alias for_domain? for_domain
373
+
374
+ attr_reader :path
375
+
376
+ # See #path.
314
377
  def path=(path)
378
+ path = check_string_type(path) or
379
+ raise TypeError, "#{path.class} is not a String"
315
380
  @path = HTTP::Cookie.normalize_path(path)
316
381
  end
317
382
 
318
- # Sets the origin of the cookie. This initializes the `domain` and
319
- # `path` attribute values if unknown yet.
383
+ attr_reader :origin
384
+
385
+ # See #origin.
320
386
  def origin=(origin)
321
387
  @origin.nil? or
322
388
  raise ArgumentError, "origin cannot be changed once it is set"
@@ -328,14 +394,58 @@ class HTTP::Cookie
328
394
  @origin = origin
329
395
  end
330
396
 
331
- # Sets the expires attribute. A `Time` object, a string
332
- # representation of date/time, and `nil` are good values to set.
397
+ # The secure flag.
398
+ #
399
+ # A cookie with this flag on should only be sent via a secure
400
+ # protocol like HTTPS.
401
+ attr_accessor :secure
402
+ alias secure? secure
403
+
404
+ # The HttpOnly flag.
405
+ #
406
+ # A cookie with this flag on should be hidden from a client script.
407
+ attr_accessor :httponly
408
+ alias httponly? httponly
409
+
410
+ # The session flag.
411
+ #
412
+ # A cookie with this flag on should be hidden from a client script.
413
+ attr_reader :session
414
+ alias session? session
415
+
416
+ attr_reader :expires
417
+
418
+ # See #expires.
333
419
  def expires=(t)
334
420
  case t
335
421
  when nil, Time
336
- @expires = t
337
422
  else
338
- @expires = Time.parse(t)
423
+ t = Time.parse(t)
424
+ end
425
+ @max_age = nil
426
+ @session = t.nil?
427
+ @expires = t
428
+ end
429
+
430
+ alias expires_at expires
431
+ alias expires_at= expires=
432
+
433
+ attr_reader :max_age
434
+
435
+ # See #max_age.
436
+ def max_age=(sec)
437
+ @expires = nil
438
+ case sec
439
+ when Integer, nil
440
+ else
441
+ str = check_string_type(sec) or
442
+ raise TypeError, "#{sec.class} is not an Integer or String"
443
+ sec = str.to_i
444
+ end
445
+ if @session = sec.nil?
446
+ @max_age = nil
447
+ else
448
+ @max_age = sec
339
449
  end
340
450
  end
341
451
 
@@ -347,17 +457,26 @@ class HTTP::Cookie
347
457
 
348
458
  # Expires this cookie by setting the expires attribute value to a
349
459
  # past date.
350
- def expire
351
- @expires = UNIX_EPOCH
460
+ def expire!
461
+ self.expires = UNIX_EPOCH
352
462
  self
353
463
  end
354
464
 
355
- alias secure? secure
356
- alias httponly? httponly
357
- alias session? session
465
+ # The version attribute. The only known version of the cookie
466
+ # format is 0.
467
+ attr_accessor :version
468
+
469
+ # The comment attribute.
470
+ attr_accessor :comment
471
+
472
+ # The time this cookie was created at.
473
+ attr_accessor :created_at
474
+
475
+ # The time this cookie was last accessed at.
476
+ attr_accessor :accessed_at
358
477
 
359
478
  # Tests if it is OK to accept this cookie if it is sent from a given
360
- # +uri.
479
+ # `uri`.
361
480
  def acceptable_from_uri?(uri)
362
481
  uri = URI(uri)
363
482
  return false unless URI::HTTP === uri && uri.host
@@ -377,7 +496,7 @@ class HTTP::Cookie
377
496
  end
378
497
  end
379
498
 
380
- # Tests if it is OK to send this cookie to a given +uri+, A runtime
499
+ # Tests if it is OK to send this cookie to a given `uri`. A runtime
381
500
  # error is raised if the cookie's domain is unknown.
382
501
  def valid_for_uri?(uri)
383
502
  if @domain.nil?
@@ -405,28 +524,37 @@ class HTTP::Cookie
405
524
  origin = origin ? URI(origin) : @origin or
406
525
  raise "origin must be specified to produce a value for Set-Cookie"
407
526
 
408
- string = cookie_value
527
+ string = "#{@name}=#{Scanner.quote(@value)}"
409
528
  if @for_domain || @domain != DomainName.new(origin.host).hostname
410
- string << "; domain=#{@domain}"
529
+ string << "; Domain=#{@domain}"
411
530
  end
412
531
  if (HTTP::Cookie.normalize_path(origin) + './').path != @path
413
- string << "; path=#{@path}"
532
+ string << "; Path=#{@path}"
414
533
  end
415
- if @expires
416
- string << "; expires=#{@expires.httpdate}"
534
+ if @max_age
535
+ string << "; Max-Age=#{@max_age}"
536
+ elsif @expires
537
+ string << "; Expires=#{@expires.httpdate}"
417
538
  end
418
539
  if @comment
419
- string << "; comment=#{@comment}"
540
+ string << "; Comment=#{Scanner.quote(@comment)}"
420
541
  end
421
542
  if @httponly
422
543
  string << "; HttpOnly"
423
544
  end
424
545
  if @secure
425
- string << "; secure"
546
+ string << "; Secure"
426
547
  end
427
548
  string
428
549
  end
429
550
 
551
+ def inspect
552
+ '#<%s:' % self.class << PERSISTENT_PROPERTIES.map { |key|
553
+ '%s=%s' % [key, instance_variable_get(:"@#{key}").inspect]
554
+ }.join(', ') << ' origin=%s>' % [@origin ? @origin.to_s : 'nil']
555
+
556
+ end
557
+
430
558
  # Compares the cookie with another. When there are many cookies with
431
559
  # the same name for a URL, the value of the smallest must be used.
432
560
  def <=>(other)
@@ -461,7 +589,7 @@ class HTTP::Cookie
461
589
  map.each { |key, value|
462
590
  case key
463
591
  when *PERSISTENT_PROPERTIES
464
- send(:"#{key}=", value)
592
+ __send__(:"#{key}=", value)
465
593
  end
466
594
  }
467
595
  end
@@ -0,0 +1,215 @@
1
+ require 'http/cookie'
2
+ require 'strscan'
3
+ require 'time'
4
+
5
+ class HTTP::Cookie::Scanner < StringScanner
6
+ # Whitespace.
7
+ RE_WSP = /[ \t]+/
8
+
9
+ # A pattern that matches a cookie name or attribute name which may
10
+ # be empty, capturing trailing whitespace.
11
+ RE_NAME = /(?!#{RE_WSP})[^,;\\"=]*/
12
+
13
+ RE_BAD_CHAR = /([\x00-\x20\x7F",;\\])/
14
+
15
+ # A pattern that matches the comma in a (typically date) value.
16
+ RE_COOKIE_COMMA = /,(?=#{RE_WSP}?#{RE_NAME}=)/
17
+
18
+ def initialize(string, logger = nil)
19
+ @logger = logger
20
+ super(string)
21
+ end
22
+
23
+ class << self
24
+ def quote(s)
25
+ return s unless s.match(RE_BAD_CHAR)
26
+ '"' << s.gsub(RE_BAD_CHAR, "\\\\\\1") << '"'
27
+ end
28
+ end
29
+
30
+ def skip_wsp
31
+ skip(RE_WSP)
32
+ end
33
+
34
+ def scan_dquoted
35
+ ''.tap { |s|
36
+ case
37
+ when skip(/"/)
38
+ break
39
+ when skip(/\\/)
40
+ s << getch
41
+ when scan(/[^"\\]+/)
42
+ s << matched
43
+ end until eos?
44
+ }
45
+ end
46
+
47
+ def scan_name
48
+ scan(RE_NAME).tap { |s|
49
+ s.rstrip! if s
50
+ }
51
+ end
52
+
53
+ def scan_value
54
+ ''.tap { |s|
55
+ case
56
+ when scan(/[^,;"]+/)
57
+ s << matched
58
+ when skip(/"/)
59
+ # RFC 6265 2.2
60
+ # A cookie-value may be DQUOTE'd.
61
+ s << scan_dquoted
62
+ when check(/;|#{RE_COOKIE_COMMA}/o)
63
+ break
64
+ else
65
+ s << getch
66
+ end until eos?
67
+ s.rstrip!
68
+ }
69
+ end
70
+
71
+ def scan_name_value
72
+ name = scan_name
73
+ if skip(/\=/)
74
+ value = scan_value
75
+ else
76
+ scan_value
77
+ value = nil
78
+ end
79
+ [name, value]
80
+ end
81
+
82
+ if Time.respond_to?(:strptime)
83
+ def tuple_to_time(day_of_month, month, year, time)
84
+ Time.strptime(
85
+ '%02d %s %04d %02d:%02d:%02d UTC' % [day_of_month, month, year, *time],
86
+ '%d %b %Y %T %Z'
87
+ ).tap { |date|
88
+ date.day == day_of_month or return nil
89
+ }
90
+ end
91
+ else
92
+ def tuple_to_time(day_of_month, month, year, time)
93
+ Time.parse(
94
+ '%02d %s %04d %02d:%02d:%02d UTC' % [day_of_month, month, year, *time]
95
+ ).tap { |date|
96
+ date.day == day_of_month or return nil
97
+ }
98
+ end
99
+ end
100
+ private :tuple_to_time
101
+
102
+ def parse_cookie_date(s)
103
+ # RFC 6265 5.1.1
104
+ time = day_of_month = month = year = nil
105
+
106
+ s.split(/[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]+/).each { |token|
107
+ case
108
+ when time.nil? && token.match(/\A(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?(?=\D|\z)/)
109
+ sec =
110
+ if $3
111
+ $3.to_i
112
+ else
113
+ # violation of the RFC
114
+ @logger.warn("Time lacks the second part: #{token}") if @logger
115
+ 0
116
+ end
117
+ time = [$1.to_i, $2.to_i, sec]
118
+ when day_of_month.nil? && token.match(/\A(\d{1,2})(?=\D|\z)/)
119
+ day_of_month = $1.to_i
120
+ when month.nil? && token.match(/\A(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i)
121
+ month = $1.capitalize
122
+ when year.nil? && token.match(/\A(\d{2,4})(?=\D|\z)/)
123
+ year = $1.to_i
124
+ end
125
+ }
126
+
127
+ if day_of_month.nil? || month.nil? || year.nil? || time.nil?
128
+ return nil
129
+ end
130
+
131
+ case day_of_month
132
+ when 1..31
133
+ else
134
+ return nil
135
+ end
136
+
137
+ case year
138
+ when 100..1600
139
+ return nil
140
+ when 70..99
141
+ year += 1900
142
+ when 0..69
143
+ year += 2000
144
+ end
145
+
146
+ if (time <=> [23,59,59]) > 0
147
+ return nil
148
+ end
149
+
150
+ tuple_to_time(day_of_month, month, year, time)
151
+ end
152
+
153
+ def scan_cookie
154
+ # cf. RFC 6265 5.2
155
+ until eos?
156
+ start = pos
157
+ len = nil
158
+
159
+ skip_wsp
160
+
161
+ name, value = scan_name_value
162
+ if name.nil?
163
+ break
164
+ elsif value.nil?
165
+ @logger.warn("Cookie definition lacks a name-value pair.") if @logger
166
+ elsif name.empty?
167
+ @logger.warn("Cookie definition has an empty name.") if @logger
168
+ value = nil
169
+ end
170
+ attrs = {}
171
+
172
+ case
173
+ when skip(/,/)
174
+ len = (pos - 1) - start
175
+ break
176
+ when skip(/;/)
177
+ skip_wsp
178
+ aname, avalue = scan_name_value
179
+ break if aname.nil?
180
+ next if aname.empty? || value.nil?
181
+ aname.downcase!
182
+ case aname
183
+ when 'expires'
184
+ # RFC 6265 5.2.1
185
+ avalue &&= parse_cookie_date(avalue) or next
186
+ when 'max-age'
187
+ # RFC 6265 5.2.2
188
+ next unless /\A-?\d+\z/.match(avalue)
189
+ when 'domain'
190
+ # RFC 6265 5.2.3
191
+ # An empty value SHOULD be ignored.
192
+ next if avalue.nil? || avalue.empty?
193
+ when 'path'
194
+ # RFC 6265 5.2.4
195
+ # A relative path must be ignored rather than normalizing it
196
+ # to "/".
197
+ next unless /\A\//.match(avalue)
198
+ when 'secure', 'httponly'
199
+ # RFC 6265 5.2.5, 5.2.6
200
+ avalue = true
201
+ end
202
+ attrs[aname] = avalue
203
+ end until eos?
204
+
205
+ len ||= pos - start
206
+
207
+ if len > HTTP::Cookie::MAX_LENGTH
208
+ @logger.warn("Cookie definition too long: #{name}") if @logger
209
+ next
210
+ end
211
+
212
+ return [name, value, attrs] if value
213
+ end
214
+ end
215
+ end
@@ -1,5 +1,5 @@
1
1
  module HTTP
2
2
  class Cookie
3
- VERSION = "0.1.5"
3
+ VERSION = "1.0.0.pre1"
4
4
  end
5
5
  end
@@ -224,7 +224,7 @@ class HTTP::CookieJar
224
224
  end
225
225
 
226
226
  # Used to exist in Mechanize::CookieJar. Use #clear().
227
- def clear!(*args)
227
+ def clear!
228
228
  raise NoMethodError, 'HTTP::Cookie equivalent for Mechanize::CookieJar#clear!() is #clear().'
229
229
  end
230
230
 
@@ -114,7 +114,7 @@ class HTTP::CookieJar
114
114
  if (debt = domain_cookies.size - HTTP::Cookie::MAX_COOKIES_PER_DOMAIN) > 0
115
115
  domain_cookies.sort_by!(&:created_at)
116
116
  domain_cookies.slice!(0, debt).each { |cookie|
117
- add(cookie.expire)
117
+ add(cookie.expire!)
118
118
  }
119
119
  end
120
120
 
@@ -124,7 +124,7 @@ class HTTP::CookieJar
124
124
  if (debt = all_cookies.size - HTTP::Cookie::MAX_COOKIES_TOTAL) > 0
125
125
  all_cookies.sort_by!(&:created_at)
126
126
  all_cookies.slice!(0, debt).each { |cookie|
127
- add(cookie.expire)
127
+ add(cookie.expire!)
128
128
  }
129
129
  end
130
130
 
@@ -33,7 +33,7 @@ class TestHTTPCookie < Test::Unit::TestCase
33
33
  "Fri, 17 Mar 89 4:01:33",
34
34
  "Fri, 17 Mar 89 4:01 GMT",
35
35
  "Mon Jan 16 16:12 PDT 1989",
36
- "Mon Jan 16 16:12 +0130 1989",
36
+ #"Mon Jan 16 16:12 +0130 1989",
37
37
  "6 May 1992 16:41-JST (Wednesday)",
38
38
  #"22-AUG-1993 10:59:12.82",
39
39
  "22-AUG-1993 10:59pm",
@@ -42,7 +42,7 @@ class TestHTTPCookie < Test::Unit::TestCase
42
42
  #"Friday, August 04, 1995 3:54 PM",
43
43
  #"06/21/95 04:24:34 PM",
44
44
  #"20/06/95 21:07",
45
- "95-06-08 19:32:48 EDT",
45
+ #"95-06-08 19:32:48 EDT",
46
46
  ]
47
47
 
48
48
  dates.each do |date|
@@ -83,7 +83,7 @@ class TestHTTPCookie < Test::Unit::TestCase
83
83
  def test_parse_too_long_cookie
84
84
  uri = URI.parse 'http://example'
85
85
 
86
- cookie_str = "foo=#{'クッキー' * 340}; path=/ab/"
86
+ cookie_str = "foo=#{'Cookie' * 680}; path=/ab/"
87
87
  assert_equal(HTTP::Cookie::MAX_LENGTH - 1, cookie_str.bytesize)
88
88
 
89
89
  assert_equal 1, HTTP::Cookie.parse(cookie_str, :origin => uri).size
@@ -101,7 +101,7 @@ class TestHTTPCookie < Test::Unit::TestCase
101
101
 
102
102
  assert_equal 1, HTTP::Cookie.parse(cookie_str, :origin => uri) { |cookie|
103
103
  assert_equal 'quoted', cookie.name
104
- assert_equal '"value"', cookie.value
104
+ assert_equal 'value', cookie.value
105
105
  assert_equal 'comment is "comment"', cookie.comment
106
106
  }.size
107
107
  end
@@ -248,12 +248,14 @@ class TestHTTPCookie < Test::Unit::TestCase
248
248
  "no_path1=no_path; Expires=Sun, 06 Nov 2011 00:29:52 GMT, no_expires=nope; Path=/, " \
249
249
  "no_path2=no_path; Expires=Sun, 06 Nov 2011 00:29:52 GMT; no_expires=nope; Path, " \
250
250
  "no_path3=no_path; Expires=Sun, 06 Nov 2011 00:29:52 GMT; no_expires=nope; Path=, " \
251
+ "rel_path1=rel_path; Expires=Sun, 06 Nov 2011 00:29:52 GMT; no_expires=nope; Path=foo/bar, " \
252
+ "rel_path1=rel_path; Expires=Sun, 06 Nov 2011 00:29:52 GMT; no_expires=nope; Path=foo, " \
251
253
  "no_domain1=no_domain; Expires=Sun, 06 Nov 2011 00:29:53 GMT; no_expires=nope, " \
252
254
  "no_domain2=no_domain; Expires=Sun, 06 Nov 2011 00:29:53 GMT; no_expires=nope; Domain, " \
253
255
  "no_domain3=no_domain; Expires=Sun, 06 Nov 2011 00:29:53 GMT; no_expires=nope; Domain="
254
256
 
255
257
  cookies = HTTP::Cookie.parse cookie_str, :origin => url
256
- assert_equal 13, cookies.length
258
+ assert_equal 15, cookies.length
257
259
 
258
260
  name = cookies.find { |c| c.name == 'name' }
259
261
  assert_equal "Aaron", name.value
@@ -277,6 +279,13 @@ class TestHTTPCookie < Test::Unit::TestCase
277
279
  assert_equal Time.at(1320539392), c.expires, c.name
278
280
  }
279
281
 
282
+ rel_path_cookies = cookies.select { |c| c.value == 'rel_path' }
283
+ assert_equal 2, rel_path_cookies.size
284
+ rel_path_cookies.each { |c|
285
+ assert_equal "/", c.path, c.name
286
+ assert_equal Time.at(1320539392), c.expires, c.name
287
+ }
288
+
280
289
  no_domain_cookies = cookies.select { |c| c.value == 'no_domain' }
281
290
  assert_equal 3, no_domain_cookies.size
282
291
  no_domain_cookies.each { |c|
@@ -364,23 +373,32 @@ class TestHTTPCookie < Test::Unit::TestCase
364
373
 
365
374
  def test_set_cookie_value
366
375
  url = URI.parse('http://rubyforge.org/')
367
- cookie_params = @cookie_params.merge('secure' => 'secure')
368
- cookie_value = 'foo=bar'
369
376
 
370
- cookie_params.keys.combine.each do |keys|
371
- cookie_text = [cookie_value, *keys.map { |key| cookie_params[key] }].join('; ')
372
- cookie, = HTTP::Cookie.parse(cookie_text, :origin => url)
373
- cookie2, = HTTP::Cookie.parse(cookie.set_cookie_value, :origin => url)
374
-
375
- assert_equal(cookie.name, cookie2.name)
376
- assert_equal(cookie.value, cookie2.value)
377
- assert_equal(cookie.domain, cookie2.domain)
378
- assert_equal(cookie.for_domain?, cookie2.for_domain?)
379
- assert_equal(cookie.path, cookie2.path)
380
- assert_equal(cookie.expires, cookie2.expires)
381
- assert_equal(cookie.secure?, cookie2.secure?)
382
- assert_equal(cookie.httponly?, cookie2.httponly?)
383
- end
377
+ ['foo=bar', 'foo="bar"', 'foo="ba\"r baz"'].each { |cookie_value|
378
+ cookie_params = @cookie_params.merge('secure' => 'secure', 'max-age' => 'Max-Age=1000')
379
+ date = Time.at(Time.now.to_i)
380
+ cookie_params.keys.combine.each do |keys|
381
+ cookie_text = [cookie_value, *keys.map { |key| cookie_params[key] }].join('; ')
382
+ cookie, = HTTP::Cookie.parse(cookie_text, :origin => url, :date => date)
383
+ cookie2, = HTTP::Cookie.parse(cookie.set_cookie_value, :origin => url, :date => date)
384
+
385
+ assert_equal(cookie.name, cookie2.name)
386
+ assert_equal(cookie.value, cookie2.value)
387
+ assert_equal(cookie.domain, cookie2.domain)
388
+ assert_equal(cookie.for_domain?, cookie2.for_domain?)
389
+ assert_equal(cookie.path, cookie2.path)
390
+ assert_equal(cookie.expires, cookie2.expires)
391
+ if keys.include?('max-age')
392
+ assert_equal(date + 1000, cookie2.expires)
393
+ elsif keys.include?('expires')
394
+ assert_equal(@expires, cookie2.expires)
395
+ else
396
+ assert_equal(nil, cookie2.expires)
397
+ end
398
+ assert_equal(cookie.secure?, cookie2.secure?)
399
+ assert_equal(cookie.httponly?, cookie2.httponly?)
400
+ end
401
+ }
384
402
  end
385
403
 
386
404
  def test_parse_cookie_no_spaces
@@ -445,6 +463,37 @@ class TestHTTPCookie < Test::Unit::TestCase
445
463
  }.merge(options)
446
464
  end
447
465
 
466
+ def test_bad_name
467
+ [
468
+ "a\tb", "a\vb", "a\rb", "a\nb", 'a b',
469
+ "a\\b", 'a"b', # 'a:b', 'a/b', 'a[b]',
470
+ 'a=b', 'a,b', 'a;b',
471
+ ].each { |name|
472
+ assert_raises(ArgumentError) {
473
+ HTTP::Cookie.new(cookie_values(:name => name))
474
+ }
475
+ cookie = HTTP::Cookie.new(cookie_values)
476
+ assert_raises(ArgumentError) {
477
+ cookie.name = name
478
+ }
479
+ }
480
+ end
481
+
482
+ def test_bad_value
483
+ [
484
+ "a\tb", "a\vb", "a\rb", "a\nb",
485
+ "a\\b", 'a"b', # 'a:b', 'a/b', 'a[b]',
486
+ ].each { |name|
487
+ assert_raises(ArgumentError) {
488
+ HTTP::Cookie.new(cookie_values(:name => name))
489
+ }
490
+ cookie = HTTP::Cookie.new(cookie_values)
491
+ assert_raises(ArgumentError) {
492
+ cookie.name = name
493
+ }
494
+ }
495
+ end
496
+
448
497
  def test_compare
449
498
  time = Time.now
450
499
  cookies = [
@@ -466,10 +515,33 @@ class TestHTTPCookie < Test::Unit::TestCase
466
515
  assert_equal false, cookie.expired?
467
516
  assert_equal true, cookie.expired?(cookie.expires + 1)
468
517
  assert_equal false, cookie.expired?(cookie.expires - 1)
469
- cookie.expire
518
+ cookie.expire!
470
519
  assert_equal true, cookie.expired?
471
520
  end
472
521
 
522
+ def test_session
523
+ cookie = HTTP::Cookie.new(cookie_values)
524
+
525
+ assert_equal false, cookie.session?
526
+ assert_equal nil, cookie.max_age
527
+
528
+ cookie.expires = nil
529
+ assert_equal true, cookie.session?
530
+ assert_equal nil, cookie.max_age
531
+
532
+ cookie.expires = Time.now + 3600
533
+ assert_equal false, cookie.session?
534
+ assert_equal nil, cookie.max_age
535
+
536
+ cookie.max_age = 3600
537
+ assert_equal false, cookie.session?
538
+ assert_equal nil, cookie.expires
539
+
540
+ cookie.max_age = nil
541
+ assert_equal true, cookie.session?
542
+ assert_equal nil, cookie.expires
543
+ end
544
+
473
545
  def test_equal
474
546
  assert_not_equal(HTTP::Cookie.new(cookie_values),
475
547
  HTTP::Cookie.new(cookie_values(:value => 'bar')))
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: 0.1.5
4
+ version: 1.0.0.pre1
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-03-17 00:00:00.000000000 Z
14
+ date: 2013-03-21 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: domain_name
@@ -116,6 +116,7 @@ files:
116
116
  - http-cookie.gemspec
117
117
  - lib/http-cookie.rb
118
118
  - lib/http/cookie.rb
119
+ - lib/http/cookie/scanner.rb
119
120
  - lib/http/cookie/version.rb
120
121
  - lib/http/cookie_jar.rb
121
122
  - lib/http/cookie_jar/abstract_saver.rb
@@ -141,9 +142,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
141
142
  version: '0'
142
143
  required_rubygems_version: !ruby/object:Gem::Requirement
143
144
  requirements:
144
- - - '>='
145
+ - - '>'
145
146
  - !ruby/object:Gem::Version
146
- version: '0'
147
+ version: 1.3.1
147
148
  requirements: []
148
149
  rubyforge_project:
149
150
  rubygems_version: 2.0.3