class HTTP::Cookie
Constants
- MAX_COOKIES_PER_DOMAIN
Maximum number of cookies per domain (RFC 6265 6.1 requires 50 at least)
- MAX_COOKIES_TOTAL
Maximum number of cookies total (RFC 6265 6.1 requires 3000 at least)
- MAX_LENGTH
Maximum number of bytes per cookie (RFC 6265 6.1 requires 4096 at least)
- VERSION
Attributes
The time this cookie was last accessed at.
The time this cookie was created at. This value is used as a base date for interpreting the Max-Age attribute value. See expires
.
The cookie domain.
Setting a domain with a leading dot implies that the for_domain
flag should be turned on. The setter accepts a DomainName object as well as a string-like.
Returns the domain attribute value as a DomainName object.
The HttpOnly flag. (http-only-flag)
A cookie with this flag on should be hidden from a client script.
The HttpOnly flag. (http-only-flag)
A cookie with this flag on should be hidden from a client script.
The Max-Age attribute value as an integer, the number of seconds before expiration.
The setter method accepts an integer, or a string-like that represents an integer which will be stringified and then integerized using to_i.
This value is reset to nil when expires=
is called.
The cookie name. It may not be nil or empty.
Assign a string containing any of the following characters will raise ArgumentError: control characters (\x00-\x1F
and \x7F
), space and separators ,;\"=
.
Note that RFC 6265 4.1.1 lists more characters disallowed for use in a cookie name, which are these: <>@:/[]?{}
. Using these characters will reduce interoperability.
The origin of the cookie.
Setting this will initialize the domain
and path
attribute values if unknown yet. If the cookie already has a domain value set, it is checked against the origin URL to see if the origin is allowed to issue a cookie of the domain, and ArgumentError is raised if the check fails.
The path attribute value.
The setter treats an empty path (“”) as the root path (“/”).
The secure flag. (secure-only-flag)
A cookie with this flag on should only be sent via a secure protocol like HTTPS.
The secure flag. (secure-only-flag)
A cookie with this flag on should only be sent via a secure protocol like HTTPS.
The session flag. (the opposite of persistent-flag)
A cookie with this flag on should be hidden from a client script.
The session flag. (the opposite of persistent-flag)
A cookie with this flag on should be hidden from a client script.
The cookie value.
Assign a string containing a control character (\x00-\x1F
and \x7F
) will raise ArgumentError.
Assigning nil sets the value to an empty string and the expiration date to the Unix epoch. This is a handy way to make a cookie for expiration.
Note that RFC 6265 4.1.1 lists more characters disallowed for use in a cookie value, which are these: <code>“,;</code>. Using these characters will reduce interoperability.
Public Class Methods
Creates a cookie object. For each key of attr_hash
, the setter is called if defined and any error (typically ArgumentError or TypeError) that is raised will be passed through. Each key can be either a downcased symbol or a string that may be mixed case. Support for the latter may, however, be obsoleted in future when Ruby 2.0's keyword syntax is adopted.
If value
is omitted or it is nil, an expiration cookie is created unless max_age
or expires
(expires_at
) is given.
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')
# File lib/http/cookie.rb, line 130 def initialize(*args) @origin = @domain = @path = @expires = @max_age = nil @for_domain = @secure = @httponly = false @session = true @created_at = @accessed_at = Time.now case argc = args.size when 1 if attr_hash = Hash.try_convert(args.last) args.pop else self.name, self.value = args # value is set to nil return end when 2..3 if attr_hash = Hash.try_convert(args.last) args.pop self.name, value = args else argc == 2 or raise ArgumentError, "wrong number of arguments (#{argc} for 1-3)" self.name, self.value = args return end else raise ArgumentError, "wrong number of arguments (#{argc} for 1-3)" end for_domain = false domain = max_age = origin = nil attr_hash.each_pair { |okey, val| case key ||= okey when :name self.name = val when :value value = val when :domain domain = val when :path self.path = val when :origin origin = val when :for_domain, :for_domain? for_domain = val when :max_age # Let max_age take precedence over expires max_age = val when :expires, :expires_at self.expires = val unless max_age when :httponly, :httponly? @httponly = val when :secure, :secure? @secure = val when Symbol setter = :"#{key}=" if respond_to?(setter) __send__(setter, val) else warn "unknown attribute name: #{okey.inspect}" if $VERBOSE next end when String warn "use downcased symbol for keyword: #{okey.inspect}" if $VERBOSE key = key.downcase.to_sym redo else warn "invalid keyword ignored: #{okey.inspect}" if $VERBOSE next end } if @name.nil? raise ArgumentError, "name must be specified" end @for_domain = for_domain self.domain = domain if domain self.origin = origin if origin self.max_age = max_age if max_age self.value = value.nil? && (@expires || @max_age) ? '' : value end
Parses a Set-Cookie header value set_cookie
assuming that it is sent from a source URI/URL origin
, and returns an array of Cookie
objects. Parts (separated by commas) that are malformed or considered unacceptable are silently ignored.
If a block is given, each cookie object is passed to the block.
Available option keywords are below:
- :created_at
-
The creation time of the cookies parsed.
- :logger
-
Logger object useful for debugging
Compatibility Note for Mechanize::Cookie users¶ ↑
-
Order of parameters changed in
HTTP::Cookie.parse
:Mechanize::Cookie.parse(uri, set_cookie[, log]) HTTP::Cookie.parse(set_cookie, uri[, :logger => # log])
-
HTTP::Cookie.parse
does not accept nil forset_cookie
. -
HTTP::Cookie.parse
does not yield nil nor include nil in an returned array. It simply ignores unparsable parts. -
HTTP::Cookie.parse
is made to follow RFC 6265 to the extent not terribly breaking interoperability with broken implementations. In particular, it is capable of parsing cookie definitions containing double-quotes just as naturally expected.
# File lib/http/cookie.rb, line 273 def parse(set_cookie, origin, options = nil, &block) if options logger = options[:logger] created_at = options[:created_at] end origin = URI(origin) [].tap { |cookies| Scanner.new(set_cookie, logger).scan_set_cookie { |name, value, attrs| break if name.nil? || name.empty? begin cookie = new(name, value) rescue => e logger.warn("Invalid name or value: #{e}") if logger next end cookie.created_at = created_at if created_at attrs.each { |aname, avalue| begin case aname when 'domain' cookie.for_domain = true # The following may negate @for_domain if the value is # an eTLD or IP address, hence this order. cookie.domain = avalue when 'path' cookie.path = avalue when 'expires' # RFC 6265 4.1.2.2 # The Max-Age attribute has precedence over the Expires # attribute. cookie.expires = avalue unless cookie.max_age when 'max-age' cookie.max_age = avalue when 'secure' cookie.secure = avalue when 'httponly' cookie.httponly = avalue end rescue => e logger.warn("Couldn't parse #{aname} '#{avalue}': #{e}") if logger end } cookie.origin = origin cookie.acceptable? or next yield cookie if block_given? cookies << cookie } } end
Tests if target_path
is under base_path
as described in RFC 6265 5.1.4. base_path
must be an absolute path. target_path
may be empty, in which case it is treated as the root path.
e.g.
path_match?('/admin/', '/admin/index') == true path_match?('/admin/', '/Admin/index') == false path_match?('/admin/', '/admin/') == true path_match?('/admin/', '/admin') == false path_match?('/admin', '/admin') == true path_match?('/admin', '/Admin') == false path_match?('/admin', '/admins') == false path_match?('/admin', '/admin/') == true path_match?('/admin', '/admin/index') == true
# File lib/http/cookie.rb, line 229 def path_match?(base_path, target_path) base_path.start_with?('/') or return false # RFC 6265 5.1.4 bsize = base_path.size tsize = target_path.size return bsize == 1 if tsize == 0 # treat empty target_path as "/" return false unless target_path.start_with?(base_path) return true if bsize == tsize || base_path.end_with?('/') target_path[bsize] == ?/ end
Public Instance Methods
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.
# File lib/http/cookie.rb, line 643 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
Tests if it is OK to accept this cookie considering its origin. If either domain or path is missing, raises ArgumentError. If origin is missing, returns true.
# File lib/http/cookie.rb, line 569 def acceptable? case when @domain.nil? raise "domain is missing" when @path.nil? raise "path is missing" when @origin.nil? true else acceptable_from_uri?(@origin) end end
Tests if it is OK to accept this cookie if it is sent from a given URI/URL, uri
.
# File lib/http/cookie.rb, line 550 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 case when host.hostname == @domain true when @for_domain # !host-only-flag host.cookie_domain?(@domain_name) else @domain.nil? end end
See domain
.
# File lib/http/cookie.rb, line 386 def domain= domain case domain when nil @for_domain = false if @origin @domain_name = DomainName.new(@origin.host) @domain = @domain_name.hostname else @domain_name = @domain = nil end return nil when DomainName @domain_name = domain else domain = (String.try_convert(domain) or raise TypeError, "#{domain.class} is not a String") if domain.start_with?('.') for_domain = true domain = domain[1..-1] end if domain.empty? return self.domain = nil end # Do we really need to support this? if domain.match(/\A([^:]+):[0-9]+\z/) domain = $1 end @domain_name = DomainName.new(domain) end # RFC 6265 5.3 5. if domain_name.domain.nil? # a public suffix or IP address @for_domain = false else @for_domain = for_domain unless for_domain.nil? end @domain = @domain_name.hostname end
Returns the domain, with a dot prefixed only if the domain flag is on.
# File lib/http/cookie.rb, line 426 def dot_domain @for_domain ? '.' << @domain : @domain end
YAML serialization helper for Psych.
# File lib/http/cookie.rb, line 659 def encode_with(coder) PERSISTENT_PROPERTIES.each { |key| coder[key.to_s] = instance_variable_get(:"@#{key}") } end
Expires this cookie by setting the expires attribute value to a past date.
# File lib/http/cookie.rb, line 536 def expire! self.expires = UNIX_EPOCH self end
Tests if this cookie is expired by now, or by a given time.
# File lib/http/cookie.rb, line 526 def expired?(time = Time.now) if expires = self.expires expires <= time else false end end
See expires
.
# File lib/http/cookie.rb, line 490 def expires= t case t when nil, Time else t = Time.parse(t) end @max_age = nil @session = t.nil? @expires = t end
YAML deserialization helper for Syck.
# File lib/http/cookie.rb, line 666 def init_with(coder) yaml_initialize(coder.tag, coder.map) end
# File lib/http/cookie.rb, line 635 def inspect '#<%s:' % self.class << PERSISTENT_PROPERTIES.map { |key| '%s=%s' % [key, instance_variable_get(:"@#{key}").inspect] }.join(', ') << ' origin=%s>' % [@origin ? @origin.to_s : 'nil'] end
See max_age
.
# File lib/http/cookie.rb, line 507 def max_age= sec case sec when Integer, nil else str = String.try_convert(sec) or raise TypeError, "#{sec.class} is not an Integer or String" /\A-?\d+\z/.match(str) or raise ArgumentError, "invalid Max-Age: #{sec.inspect}" sec = str.to_i end @expires = nil if @session = sec.nil? @max_age = nil else @max_age = sec end end
See name
.
# File lib/http/cookie.rb, line 350 def name= name name = (String.try_convert(name) or raise TypeError, "#{name.class} is not a String") if name.empty? raise ArgumentError, "cookie name cannot be empty" elsif name.match(/[\x00-\x20\x7F,;\\"=]/) raise ArgumentError, "invalid cookie name" end # RFC 6265 4.1.1 # cookie-name may not match: # /[\x00-\x20\x7F()<>@,;:\\"\/\[\]?={}]/ @name = name end
See origin
.
# File lib/http/cookie.rb, line 453 def origin= origin return origin if origin == @origin @origin.nil? or raise ArgumentError, "origin cannot be changed once it is set" # Delay setting @origin because #domain= or #path= may fail origin = URI(origin) if URI::HTTP === origin self.domain ||= origin.host self.path ||= (origin + './').path end @origin = origin end
See path
.
# File lib/http/cookie.rb, line 444 def path= path path = (String.try_convert(path) or raise TypeError, "#{path.class} is not a String") @path = path.start_with?('/') ? path : '/' end
YAML serialization helper for Syck.
# File lib/http/cookie.rb, line 654 def to_yaml_properties PERSISTENT_PROPERTIES.map { |name| "@#{name}" } end
Tests if it is OK to send this cookie to a given uri
. A RuntimeError is raised if the cookie's domain is unknown.
# File lib/http/cookie.rb, line 584 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) # RFC 6265 5.4 return false if secure? && !(URI::HTTPS === uri) acceptable_from_uri?(uri) && HTTP::Cookie.path_match?(@path, uri.path) end
See value
.
# File lib/http/cookie.rb, line 367 def value= value if value.nil? self.expires = UNIX_EPOCH return @value = '' end value = (String.try_convert(value) or raise TypeError, "#{value.class} is not a String") if value.match(/[\x00-\x1F\x7F]/) raise ArgumentError, "invalid cookie value" end # RFC 6265 4.1.1 # cookie-name may not match: # /[^\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/ @value = value end
YAML deserialization helper for Psych.
# File lib/http/cookie.rb, line 671 def yaml_initialize(tag, map) expires = nil @origin = nil map.each { |key, value| case key when 'expires' # avoid clobbering max_age expires = value when *PERSISTENT_PROPERTIES __send__(:"#{key}=", value) end } self.expires = expires if self.max_age.nil? end