Class: HTTP::Cookie

Inherits:
Object
  • Object
show all
Includes:
Comparable, URIFix
Defined in:
lib/http/cookie.rb,
lib/http/cookie/version.rb

Overview

This class is used to represent an HTTP Cookie.

Constant Summary collapse

MAX_LENGTH =

Maximum number of bytes per cookie (RFC 6265 6.1 requires 4096 at least)

4096
MAX_COOKIES_PER_DOMAIN =

Maximum number of cookies per domain (RFC 6265 6.1 requires 50 at least)

50
MAX_COOKIES_TOTAL =

Maximum number of cookies total (RFC 6265 6.1 requires 3000 at least)

3000
UNIX_EPOCH =
Time.at(0)
PERSISTENT_PROPERTIES =
%w[
  name        value
  domain      for_domain  path
  secure      httponly
  expires     created_at  accessed_at
]
VERSION =
"0.1.4"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ Cookie

:call-seq:

new(name, value)
new(name, value, attr_hash)
new(attr_hash)

Creates a cookie object. For each key of attr_hash, the setter is called if defined. Each key can be either a symbol or a string, downcased or not.

This methods accepts any attribute name for which a setter method is defined. Beware, however, any error (typically ArgumentError) a setter method raises will be passed through.

e.g.

new("uid", "a12345")
new("uid", "a12345", :domain => 'example.org',
                     :for_domain => true, :expired => Time.now + 7*86400)
new("name" => "uid", "value" => "a12345", "Domain" => 'www.example.org')


85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/http/cookie.rb', line 85

def initialize(*args)
  @version = 0     # Netscape Cookie

  @origin = @domain = @path =
    @secure = @httponly =
    @expires = @max_age =
    @comment = nil

  @created_at = @accessed_at = Time.now
  case args.size
  when 2
    self.name, self.value = *args
    @for_domain = false
    return
  when 3
    self.name, self.value, attr_hash = *args
  when 1
    attr_hash = args.first
  else
    raise ArgumentError, "wrong number of arguments (#{args.size} for 1-3)"
  end
  for_domain = false
  origin = nil
  attr_hash.each_pair { |key, val|
    skey = key.to_s.downcase
    if skey.sub!(/\?\z/, '')
      val = val ? true : false
    end
    case skey
    when 'for_domain'
      for_domain = !!val
    when 'origin'
      origin = val
    else
      setter = :"#{skey}="
      send(setter, val) if respond_to?(setter)
    end
  }
  if @name.nil? || @value.nil?
    raise ArgumentError, "at least name and value must be specified"
  end
  @for_domain = for_domain
  if origin
    self.origin = origin
  end
end

Instance Attribute Details

#accessed_atObject

Returns the value of attribute accessed_at.



64
65
66
# File 'lib/http/cookie.rb', line 64

def accessed_at
  @accessed_at
end

#commentObject

Returns the value of attribute comment.



59
60
61
# File 'lib/http/cookie.rb', line 59

def comment
  @comment
end

#created_atObject

Returns the value of attribute created_at.



63
64
65
# File 'lib/http/cookie.rb', line 63

def created_at
  @created_at
end

#domainObject

Returns the value of attribute domain.



56
57
58
# File 'lib/http/cookie.rb', line 56

def domain
  @domain
end

#domain_nameObject (readonly)

Returns the value of attribute domain_name.



58
59
60
# File 'lib/http/cookie.rb', line 58

def domain_name
  @domain_name
end

#expiresObject

Returns the value of attribute expires.



58
59
60
# File 'lib/http/cookie.rb', line 58

def expires
  @expires
end

#for_domainObject Also known as: for_domain?

If this flag is true, this cookie will be sent to any host in the domain. If it is false, this cookie will be sent only to the host indicated by the domain.



135
136
137
# File 'lib/http/cookie.rb', line 135

def for_domain
  @for_domain
end

#httponlyObject Also known as: httponly?

Returns the value of attribute httponly.



57
58
59
# File 'lib/http/cookie.rb', line 57

def httponly
  @httponly
end

#max_ageObject

Returns the value of attribute max_age.



59
60
61
# File 'lib/http/cookie.rb', line 59

def max_age
  @max_age
end

#nameObject

Returns the value of attribute name.



56
57
58
# File 'lib/http/cookie.rb', line 56

def name
  @name
end

#originObject

Returns the value of attribute origin.



56
57
58
# File 'lib/http/cookie.rb', line 56

def origin
  @origin
end

#pathObject

Returns the value of attribute path.



56
57
58
# File 'lib/http/cookie.rb', line 56

def path
  @path
end

#secureObject Also known as: secure?

Returns the value of attribute secure.



57
58
59
# File 'lib/http/cookie.rb', line 57

def secure
  @secure
end

#sessionObject Also known as: session?

Returns the value of attribute session.



61
62
63
# File 'lib/http/cookie.rb', line 61

def session
  @session
end

#valueObject

Returns the value of attribute value.



57
58
59
# File 'lib/http/cookie.rb', line 57

def value
  @value
end

#versionObject

Returns the value of attribute version.



57
58
59
# File 'lib/http/cookie.rb', line 57

def version
  @version
end

Class Method Details

.normalize_path(uri) ⇒ Object

Normalizes a given path. If it is empty, the root path ‘/’ is returned. If a URI object is given, returns a new URI object with the path part normalized.



144
145
146
147
148
149
150
151
152
# File 'lib/http/cookie.rb', line 144

def normalize_path(uri)
  # Currently does not replace // to /
  case uri
  when URI
    uri.path.empty? ? uri + '/' : uri
  else
    uri.empty? ? '/' : uri
  end
end

.parse(set_cookie, options = nil, *_, &block) ⇒ Object

Parses a Set-Cookie header value set_cookie into an array of Cookie objects. Parts (separated by commas) that are malformed or invalid are silently ignored. For example, a cookie that a given origin is not allowed to issue is not included in the resulted array.

If a block is given, each cookie object is passed to the block.

Available option keywords are below:

  • origin The cookie’s origin URI/URL

  • date The base date used for interpreting Max-Age attribute values instead of the current time

  • logger Logger object useful for debugging



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/http/cookie.rb', line 171

def parse(set_cookie, options = nil, *_, &block)
  _.empty? && !options.is_a?(String) or
    raise ArgumentError, 'HTTP::Cookie equivalent for Mechanize::Cookie.parse(uri, set_cookie[, log]) is HTTP::Cookie.parse(set_cookie, :origin => uri[, :logger => log]).'

  if options
    logger = options[:logger]
    origin = options[:origin] and origin = URI(origin)
    date = options[:date]
  end
  date ||= Time.now

  [].tap { |cookies|
    set_cookie.split(/,(?=[^;,]*=)|,$/).each { |c|
      if c.bytesize > MAX_LENGTH
        logger.warn("Cookie definition too long: #{c}") if logger
        next
      end

      cookie_elem = c.split(/;+/)
      first_elem = cookie_elem.shift
      first_elem.strip!
      key, value = first_elem.split(/\=/, 2)

      begin
        cookie = new(key, value.dup)
      rescue
        logger.warn("Couldn't parse key/value: #{first_elem}") if logger
        next
      end

      cookie_elem.each do |pair|
        pair.strip!
        key, value = pair.split(/=/, 2) #/)
        next unless key
        value = WEBrick::HTTPUtils.dequote(value.strip) if value

        case key.downcase
        when 'domain'
          next unless value && !value.empty?
          begin
            cookie.domain = value
            cookie.for_domain = true
          rescue
            logger.warn("Couldn't parse domain: #{value}") if logger
          end
        when 'path'
          next unless value && !value.empty?
          cookie.path = value
        when 'expires'
          next unless value && !value.empty?
          begin
            cookie.expires = Time.parse(value)
          rescue
            logger.warn("Couldn't parse expires: #{value}") if logger
          end
        when 'max-age'
          next unless value && !value.empty?
          begin
            cookie.max_age = Integer(value)
          rescue
            logger.warn("Couldn't parse max age '#{value}'") if logger
          end
        when 'comment'
          next unless value
          cookie.comment = value
        when 'version'
          next unless value
          begin
            cookie.version = Integer(value)
          rescue
            logger.warn("Couldn't parse version '#{value}'") if logger
            cookie.version = nil
          end
        when 'secure'
          cookie.secure = true
        when 'httponly'
          cookie.httponly = true
        end
      end

      cookie.secure   ||= false
      cookie.httponly ||= false

      # RFC 6265 4.1.2.2
      cookie.expires    = date + cookie.max_age if cookie.max_age
      cookie.session    = !cookie.expires

      if origin
        begin
          cookie.origin = origin
        rescue => e
          logger.warn("Invalid cookie for the origin: #{origin} (#{e})") if logger
          next
        end
      end

      yield cookie if block_given?

      cookies << cookie
    }
  }
end

Instance Method Details

#<=>(other) ⇒ Object

Compares the cookie with another. When there are many cookies with the same name for a URL, the value of the smallest must be used.



429
430
431
432
433
434
435
436
# File 'lib/http/cookie.rb', line 429

def <=>(other)
  # RFC 6265 5.4
  # Precedence: 1. longer path  2. older creation
  (@name <=> other.name).nonzero? ||
    (other.path.length <=> @path.length).nonzero? ||
    (@created_at <=> other.created_at).nonzero? ||
    @value <=> other.value
end

#acceptable_from_uri?(uri) ⇒ Boolean

Tests if it is OK to accept this cookie if it is sent from a given +uri.

Returns:

  • (Boolean)


358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# File 'lib/http/cookie.rb', line 358

def acceptable_from_uri?(uri)
  uri = URI(uri)
  return false unless URI::HTTP === uri && uri.host
  host = DomainName.new(uri.host)

  # RFC 6265 5.3
  # When the user agent "receives a cookie":
  return @domain.nil? || host.hostname == @domain unless @for_domain

  if host.cookie_domain?(@domain_name)
    true
  elsif host.hostname == @domain
    @for_domain = false
    true
  else
    false
  end
end

Returns a string for use in a Cookie header value, i.e. “name=value”.



390
391
392
# File 'lib/http/cookie.rb', line 390

def cookie_value
  "#{@name}=#{@value}"
end

#encode_with(coder) ⇒ Object

YAML serialization helper for Psych.



445
446
447
448
449
# File 'lib/http/cookie.rb', line 445

def encode_with(coder)
  PERSISTENT_PROPERTIES.each { |key|
    coder[key.to_s] = instance_variable_get(:"@#{key}")
  }
end

#expireObject

Expires this cookie by setting the expires attribute value to a past date.



347
348
349
350
# File 'lib/http/cookie.rb', line 347

def expire
  @expires = UNIX_EPOCH
  self
end

#expired?(time = Time.now) ⇒ Boolean

Tests if this cookie is expired by now, or by a given time.

Returns:

  • (Boolean)


340
341
342
343
# File 'lib/http/cookie.rb', line 340

def expired?(time = Time.now)
  return false unless @expires
  time > @expires
end

#init_with(coder) ⇒ Object

YAML deserialization helper for Syck.



452
453
454
# File 'lib/http/cookie.rb', line 452

def init_with(coder)
  yaml_initialize(coder.tag, coder.map)
end

Returns a string for use in a Set-Cookie header value. If the cookie does not have an origin set, one must be given from the argument.

This method does not check if this cookie will be accepted from the origin.



401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/http/cookie.rb', line 401

def set_cookie_value(origin = nil)
  origin = origin ? URI(origin) : @origin or
    raise "origin must be specified to produce a value for Set-Cookie"

  string = cookie_value
  if @for_domain || @domain != DomainName.new(origin.host).hostname
    string << "; domain=#{@domain}"
  end
  if (HTTP::Cookie.normalize_path(origin) + './').path != @path
    string << "; path=#{@path}"
  end
  if @expires
    string << "; expires=#{@expires.httpdate}"
  end
  if @comment
    string << "; comment=#{@comment}"
  end
  if @httponly
    string << "; HttpOnly"
  end
  if @secure
    string << "; secure"
  end
  string
end

#set_domain(domain) ⇒ Object

Used to exist in Mechanize::CookieJar. Use #domain=().

Raises:

  • (NoMethodError)


306
307
308
# File 'lib/http/cookie.rb', line 306

def set_domain(domain)
  raise NoMethodError, 'HTTP::Cookie equivalent for Mechanize::CookieJar#set_domain() is #domain=().'
end

#to_yaml_propertiesObject

YAML serialization helper for Syck.



440
441
442
# File 'lib/http/cookie.rb', line 440

def to_yaml_properties
  PERSISTENT_PROPERTIES.map { |name| "@#{name}" }
end

#valid_for_uri?(uri) ⇒ Boolean

Tests if it is OK to send this cookie to a given uri, A runtime error is raised if the cookie’s domain is unknown.

Returns:

  • (Boolean)


379
380
381
382
383
384
385
386
# File 'lib/http/cookie.rb', line 379

def valid_for_uri?(uri)
  if @domain.nil?
    raise "cannot tell if this cookie is valid because the domain is unknown"
  end
  uri = URI(uri)
  return false if secure? && !(URI::HTTPS === uri)
  acceptable_from_uri?(uri) && HTTP::Cookie.normalize_path(uri.path).start_with?(@path)
end

#yaml_initialize(tag, map) ⇒ Object

YAML deserialization helper for Psych.



457
458
459
460
461
462
463
464
# File 'lib/http/cookie.rb', line 457

def yaml_initialize(tag, map)
  map.each { |key, value|
    case key
    when *PERSISTENT_PROPERTIES
      send(:"#{key}=", value)
    end
  }
end