compute_search_domains(request_uri)
click to toggle source
Given a URI, compute the relevant search domains for pre-existing cookies.
This includes all the valid dotted forms for a named or IP domains.
@param [String, URI] request_uri requested uri @return
[Array<String>] all cookie domain values which would match the
requested uri
def self.compute_search_domains request_uri
uri = to_uri request_uri
host = uri.host
compute_search_domains_for_host host
end
compute_search_domains_for_host(host)
click to toggle source
Given a host, compute the relevant search domains for pre-existing cookies
@param [String] host host being requested @return [Array<String>] all
cookie domain values which would match the
requested uri
def self.compute_search_domains_for_host host
host = effective_host host
result = [host]
unless host =~ IPADDR
result << ".#{host}"
base = hostname_reach host
if base
result << ".#{base}"
end
end
result
end
cookie_base_path(path)
click to toggle source
Compute the base of a path, for default cookie path assignment
@param [String, URI, Cookie] path, or object holding path @return base path
(all characters up to final '/')
def self.cookie_base_path path
BASE_PATH.match(to_path path)[1]
end
determine_cookie_domain(request_uri, cookie_domain)
click to toggle source
Processes cookie domain data using the following rules: Domains strings of
the form .foo.com match 'foo.com' and all immediate subdomains of
'foo.com'. Domain strings specified of the form 'foo.com'
are modified to '.foo.com', and as such will still apply to
subdomains.
Cookies without an explicit domain will have their domain value taken
directly from the URL, and will NOT have any leading dot applied.
For example, a request to foo.com/ will cause
an entry for 'foo.com' to be created - which applies to foo.com but
no subdomain.
Note that this will not attempt to detect a mismatch of the request uri
domain and explicitly specified cookie domain
@param [String, URI] request_uri originally requested URI @param [String]
cookie domain value @return [String] effective host
def self.determine_cookie_domain request_uri, cookie_domain
uri = to_uri request_uri
domain = to_domain cookie_domain
if domain == nil || domain.empty?
domain = effective_host uri.host
else
domain = domain.downcase
if domain =~ IPADDR || domain.start_with?('.')
domain
else
".#{domain}"
end
end
end
determine_cookie_path(request_uri, cookie_path)
click to toggle source
Processes cookie path data using the following rules: Paths are separated
by '/' characters, and accepted values are truncated to the last
'/' character. If no path is specified in the cookie, a path value
will be taken from the request URI which was used for the site.
Note that this will not attempt to detect a mismatch of the request uri
domain and explicitly specified cookie path
@param [String,URI] request URI yielding this cookie @param [String] path
on cookie
def self.determine_cookie_path request_uri, cookie_path
uri = to_uri request_uri
cookie_path = to_path cookie_path
if cookie_path == nil || cookie_path.empty?
cookie_path = cookie_base_path uri.path
end
cookie_path
end
domains_match(tested_domain, base_domain)
click to toggle source
Compare a tested domain against the base domain to see if they match, or if
the base domain is reachable.
@param [String] tested_domain domain to be tested against @param [String]
base_domain new domain being tested @return [String,nil] matching domain on
success, nil on failure
def self.domains_match tested_domain, base_domain
base = effective_host base_domain
search_domains = compute_search_domains_for_host base
result = search_domains.find do |domain|
domain == tested_domain
end
end
effective_host(host_or_uri)
click to toggle source
Compute the effective host (RFC 2965, section 1)
Has the added additional logic of searching for interior dots specifically,
and matches colons to prevent .local being suffixed on IPv6 addresses
@param [String, URI] host_or_uridomain name, or absolute URI @return
[String] effective host per RFC rules
def self.effective_host host_or_uri
hostname = to_domain host_or_uri
hostname = hostname.downcase
if /.[\.:]./.match(hostname) || hostname == '.local'
hostname
else
hostname + '.local'
end
end
hostname_reach(hostname)
click to toggle source
Compute the reach of a hostname (RFC 2965, section 1) Determines the next
highest superdomain
@param [String,URI,Cookie] hostname hostname, or object holding hostname
@return [String,nil] next highest hostname, or nil if none
def self.hostname_reach hostname
host = to_domain hostname
host = host.downcase
match = BASE_HOSTNAME.match host
if match
match[1]
end
end
parse_set_cookie(set_cookie_value)
click to toggle source
Break apart a traditional (non RFC 2965) cookie value into its core
components. This does not do any validation, or defaulting of values based
on requested URI
@param [String] set_cookie_value a Set-Cookie header formatted cookie
definition
@return [Hash] Contains the parsed values of the cookie
def self.parse_set_cookie set_cookie_value
args = { }
params=set_cookie_value.split(/;\s*/)
first=true
params.each do |param|
result = PARAM1.match param
if !result
raise InvalidCookieError.new "Invalid cookie parameter in cookie '#{set_cookie_value}'"
end
key = result[1].downcase.to_sym
keyvalue = result[2]
if first
args[:name] = result[1]
args[:value] = keyvalue
first = false
else
case key
when :expires
begin
args[:expires_at] = Time.parse keyvalue
rescue ArgumentError
raise unless $!.message == "time out of range"
args[:expires_at] = Time.at(0x7FFFFFFF)
end
when *[:domain, :path]
args[key] = keyvalue
when :secure
args[:secure] = true
when :httponly
args[:http_only] = true
else
raise InvalidCookieError.new "Unknown cookie parameter '#{key}'"
end
end
end
args[:version] = 0
args
end
parse_set_cookie2(set_cookie_value)
click to toggle source
Break apart a RFC 2965 cookie value into its core components. This does not
do any validation, or defaulting of values based on requested URI
@param [String] set_cookie_value a Set-Cookie2 header formatted cookie
definition
@return [Hash] Contains the parsed values of the cookie
def self.parse_set_cookie2 set_cookie_value
args = { }
first = true
index = 0
begin
md = PARAM2.match set_cookie_value[index..-1]
if md.nil? || md.offset(0).first != 0
raise InvalidCookieError.new "Invalid Set-Cookie2 header '#{set_cookie_value}'"
end
index+=md.offset(0)[1]
key = md[1].downcase.to_sym
keyvalue = md[2] || md[3]
if first
args[:name] = md[1]
args[:value] = keyvalue
first = false
else
keyvalue = value_to_string keyvalue
case key
when *[:comment,:commenturl,:domain,:path]
args[key] = keyvalue
when *[:discard,:secure]
args[key] = true
when :httponly
args[:http_only] = true
when :"max-age"
args[:max_age] = keyvalue.to_i
when :version
args[:version] = keyvalue.to_i
when :port
ports = keyvalue.split(/,\s*/)
args[:ports] = ports.map do |portstr| portstr.to_i end
else
raise InvalidCookieError.new "Unknown cookie parameter '#{key}'"
end
end
end until md.post_match.empty?
if args[:version] != 1
raise InvalidCookieError.new "Set-Cookie2 declares a non RFC2965 version cookie"
end
args
end
to_domain(uri_or_domain)
click to toggle source
Converts an input cookie or uri to a string representing the domain. Assume
strings are already domains. Value may not be an effective host.
@param [String, URI, Cookie] object containing the domain @return [String]
domain information.
def self.to_domain uri_or_domain
if uri_or_domain.is_a? URI
uri_or_domain.host
elsif uri_or_domain.is_a? Cookie
uri_or_domain.domain
else
uri_or_domain
end
end
to_path(uri_or_path)
click to toggle source
Converts an input cookie or uri to a string representing the path. Assume
strings are already paths
@param [String, URI, Cookie] object containing the path @return [String]
path information
def self.to_path uri_or_path
if (uri_or_path.is_a? URI) || (uri_or_path.is_a? Cookie)
uri_or_path.path
else
uri_or_path
end
end
to_uri(request_uri)
click to toggle source
Converts the input object to a URI (if not already a URI)
@param [String, URI] request_uri URI we are normalizing @param [URI] URI
representation of input string, or original URI
def self.to_uri request_uri
(request_uri.is_a? URI)? request_uri : (URI.parse request_uri)
end
validate_cookie(request_uri, cookie)
click to toggle source
Check whether a cookie meets all of the rules to be created, based on its
internal settings and the URI it came from.
@param [String,URI] request_uri originally requested URI @param [Cookie]
cookie object @param [true] will always return true on success @raise
[InvalidCookieError] on failures, containing all validation errors
def self.validate_cookie request_uri, cookie
uri = to_uri request_uri
request_host = effective_host uri.host
request_path = uri.path
request_secure = (uri.scheme == 'https')
cookie_host = cookie.domain
cookie_path = cookie.path
errors = []
unless cookie.version
errors << "Version missing"
end
unless request_path.start_with? cookie_path
errors << "Path is not a prefix of the request uri path"
end
unless cookie_host =~ IPADDR ||
cookie_host =~ /.\../ ||
cookie_host == '.local'
errors << "Domain format is illegal"
end
unless domains_match cookie_host, uri
errors << "Domain is inappropriate based on request URI hostname"
end
unless cookie.ports.nil? || cookie.ports.length != 0
unless cookie.ports.find_index uri.port
errors << "Ports list does not contain request URI port"
end
end
raise InvalidCookieError.new(errors) unless errors.empty?
true
end