class HTTP::Cookie

This class is used to represent an HTTP Cookie.

def <=> other

the same name for a URL, the value of the smallest must be used.
Compares the cookie with another. When there are many cookies with
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

def acceptable?

origin is missing, returns true.
If either domain or path is missing, raises ArgumentError. If
Tests if it is OK to accept this cookie considering its origin.
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

def acceptable_from_uri?(uri)

URI/URL, `uri`.
Tests if it is OK to accept this cookie if it is sent from a given
def acceptable_from_uri?(uri)
  uri = HTTP::Cookie::URIParser.parse(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

def cookie_value(cookies)

Cookie header, like "name1=value2; name2=value2".
Takes an array of cookies and returns a string for use in the
def cookie_value(cookies)
  cookies.join('; ')
end

def cookie_value

or `name="value"`.
Returns a string for use in the Cookie header, i.e. `name=value`
def cookie_value
  +"#{@name}=#{Scanner.quote(@value)}"
end

def cookie_value_to_hash(cookie_value)

with the same name occur.
pairs. The first appearance takes precedence if multiple pairs
Parses a Cookie header value into a hash of name-value string
def cookie_value_to_hash(cookie_value)
  {}.tap { |hash|
    Scanner.new(cookie_value).scan_cookie { |name, value|
      hash[name] ||= value
    }
  }
end

def domain= domain

See #domain.
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

def dot_domain

on.
Returns the domain, with a dot prefixed only if the domain flag is
def dot_domain
  @for_domain ? (+'.') << @domain : @domain
end

def encode_with(coder)

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

def expire!

past date.
Expires this cookie by setting the expires attribute value to a
def expire!
  self.expires = UNIX_EPOCH
  self
end

def expired?(time = Time.now)

Tests if this cookie is expired by now, or by a given time.
def expired?(time = Time.now)
  if expires = self.expires
    expires <= time
  else
    false
  end
end

def expires

def expires
  @expires or @created_at && @max_age ? @created_at + @max_age : nil
end

def expires= t

See #expires.
def expires= t
  case t
  when nil, Time
  when DateTime
    t = t.to_time
  else
    t = Time.parse(t)
  end
  @max_age = nil
  @session = t.nil?
  @expires = t
end

def init_with(coder)

YAML deserialization helper for Syck.
def init_with(coder)
  yaml_initialize(coder.tag, coder.map)
end

def initialize(*args)


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

e.g.

created unless `max_age` or `expires` (`expires_at`) is given.
If `value` is omitted or it is nil, an expiration cookie is

Ruby 2.0's keyword syntax is adopted.
Support for the latter may, however, be obsoleted in future when
either a downcased symbol or a string that may be mixed case.
TypeError) that is raised will be passed through. Each key can be
is called if defined and any error (typically ArgumentError or
Creates a cookie object. For each key of `attr_hash`, the setter

new(**attr_hash)
new(name, value = nil, **attr_hash)
new(name, value = nil)
:call-seq:
def initialize(*args)
  @name = @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

def inspect

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

def max_age= sec

See #max_age.
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

def name= name

See #name.
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

def origin= origin

See #origin.
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 = HTTP::Cookie::URIParser.parse(origin)
  if URI::HTTP === origin
    self.domain ||= origin.host
    self.path   ||= (origin + './').path
  end
  @origin = origin
end

def parse(set_cookie, origin, options = nil, &block)

expected.
cookie definitions containing double-quotes just as naturally
implementations. In particular, it is capable of parsing
not terribly breaking interoperability with broken
* HTTP::Cookie.parse is made to follow RFC 6265 to the extent

returned array. It simply ignores unparsable parts.
* HTTP::Cookie.parse does not yield nil nor include nil in an

* HTTP::Cookie.parse does not accept nil for `set_cookie`.

HTTP::Cookie.parse(set_cookie, uri[, :logger => # log])

Mechanize::Cookie.parse(uri, set_cookie[, log])

* Order of parameters changed in HTTP::Cookie.parse:

### Compatibility Note for Mechanize::Cookie users

: Logger object useful for debugging
:logger

: The creation time of the cookies parsed.
:created_at

Available option keywords are below:

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

or considered unacceptable are silently ignored.
Cookie objects. Parts (separated by commas) that are malformed
is sent from a source URI/URL `origin`, and returns an array of
Parses a Set-Cookie header value `set_cookie` assuming that it
def parse(set_cookie, origin, options = nil, &block)
  if options
    logger = options[:logger]
    created_at = options[:created_at]
  end
  origin = HTTP::Cookie::URIParser.parse(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

def path= path

See #path.
def path= path
  path = (String.try_convert(path) or
    raise TypeError, "#{path.class} is not a String")
  @path = path.start_with?('/') ? path : '/'
end

def path_match?(base_path, target_path)

path_match?('/admin', '/admin/index') == true
path_match?('/admin', '/admin/') == true
path_match?('/admin', '/admins') == false
path_match?('/admin', '/Admin') == false
path_match?('/admin', '/admin') == true

path_match?('/admin/', '/admin') == false
path_match?('/admin/', '/admin/') == true
path_match?('/admin/', '/Admin/index') == false
path_match?('/admin/', '/admin/index') == true

e.g.

root path.
+target_path+ may be empty, in which case it is treated as the
6265 5.1.4. +base_path+ must be an absolute path.
Tests if +target_path+ is under +base_path+ as described in RFC
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

def set_cookie_value

origin before calling this method.
missing, RuntimeError is raised. It is always the best to set an
information like a path or domain (when `for_domain` is set) is
Returns a string for use in the Set-Cookie header. If necessary
def set_cookie_value
  string = cookie_value
  if @for_domain
    if @domain
      string << "; Domain=#{@domain}"
    else
      raise "for_domain is specified but domain is unknown"
    end
  end
  if @path
    if !@origin || (@origin + './').path != @path
      string << "; Path=#{@path}"
    end
  else
    raise "path is unknown"
  end
  if @max_age
    string << "; Max-Age=#{@max_age}"
  elsif @expires
    string << "; Expires=#{@expires.httpdate}"
  end
  if @httponly
    string << "; HttpOnly"
  end
  if @secure
    string << "; Secure"
  end
  string
end

def to_h

Hash serialization helper for use back into other libraries (Like Selenium)
def to_h
  PERSISTENT_PROPERTIES.each_with_object({}) { |property, hash| hash[property.to_sym] = instance_variable_get("@#{property}") }
end

def to_yaml_properties

YAML serialization helper for Syck.
def to_yaml_properties
  PERSISTENT_PROPERTIES.map { |name| "@#{name}" }
end

def valid_for_uri?(uri)

RuntimeError is raised if the cookie's domain is unknown.
Tests if it is OK to send this cookie to a given `uri`. A
def valid_for_uri?(uri)
  if @domain.nil?
    raise "cannot tell if this cookie is valid because the domain is unknown"
  end
  uri = HTTP::Cookie::URIParser.parse(uri)
  # RFC 6265 5.4
  return false if secure? && !(URI::HTTPS === uri)
  acceptable_from_uri?(uri) && HTTP::Cookie.path_match?(@path, uri.path)
end

def value= value

See #value.
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

def yaml_initialize(tag, map)

YAML deserialization helper for Psych.
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