File: //usr/lib/ruby/vendor_ruby/http/cookie/scanner.rb
require 'http/cookie'
require 'strscan'
require 'time'
class HTTP::Cookie::Scanner < StringScanner
# Whitespace.
RE_WSP = /[ \t]+/
# A pattern that matches a cookie name or attribute name which may
# be empty, capturing trailing whitespace.
RE_NAME = /(?!#{RE_WSP})[^,;\\"=]*/
RE_BAD_CHAR = /([\x00-\x20\x7F",;\\])/
# A pattern that matches the comma in a (typically date) value.
RE_COOKIE_COMMA = /,(?=#{RE_WSP}?#{RE_NAME}=)/
def initialize(string, logger = nil)
@logger = logger
super(string)
end
class << self
def quote(s)
return s unless s.match(RE_BAD_CHAR)
'"' << s.gsub(/([\\"])/, "\\\\\\1") << '"'
end
end
def skip_wsp
skip(RE_WSP)
end
def scan_dquoted
''.tap { |s|
case
when skip(/"/)
break
when skip(/\\/)
s << getch
when scan(/[^"\\]+/)
s << matched
end until eos?
}
end
def scan_name
scan(RE_NAME).tap { |s|
s.rstrip! if s
}
end
def scan_value(comma_as_separator = false)
''.tap { |s|
case
when scan(/[^,;"]+/)
s << matched
when skip(/"/)
# RFC 6265 2.2
# A cookie-value may be DQUOTE'd.
s << scan_dquoted
when check(/;/)
break
when comma_as_separator && check(RE_COOKIE_COMMA)
break
else
s << getch
end until eos?
s.rstrip!
}
end
def scan_name_value(comma_as_separator = false)
name = scan_name
if skip(/\=/)
value = scan_value(comma_as_separator)
else
scan_value(comma_as_separator)
value = nil
end
[name, value]
end
if Time.respond_to?(:strptime)
def tuple_to_time(day_of_month, month, year, time)
Time.strptime(
'%02d %s %04d %02d:%02d:%02d UTC' % [day_of_month, month, year, *time],
'%d %b %Y %T %Z'
).tap { |date|
date.day == day_of_month or return nil
}
end
else
def tuple_to_time(day_of_month, month, year, time)
Time.parse(
'%02d %s %04d %02d:%02d:%02d UTC' % [day_of_month, month, year, *time]
).tap { |date|
date.day == day_of_month or return nil
}
end
end
private :tuple_to_time
def parse_cookie_date(s)
# RFC 6265 5.1.1
time = day_of_month = month = year = nil
s.split(/[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]+/).each { |token|
case
when time.nil? && token.match(/\A(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?(?=\D|\z)/)
sec =
if $3
$3.to_i
else
# violation of the RFC
@logger.warn("Time lacks the second part: #{token}") if @logger
0
end
time = [$1.to_i, $2.to_i, sec]
when day_of_month.nil? && token.match(/\A(\d{1,2})(?=\D|\z)/)
day_of_month = $1.to_i
when month.nil? && token.match(/\A(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i)
month = $1.capitalize
when year.nil? && token.match(/\A(\d{2,4})(?=\D|\z)/)
year = $1.to_i
end
}
if day_of_month.nil? || month.nil? || year.nil? || time.nil?
return nil
end
case day_of_month
when 1..31
else
return nil
end
case year
when 100..1600
return nil
when 70..99
year += 1900
when 0..69
year += 2000
end
hh, mm, ss = time
if hh > 23 || mm > 59 || ss > 59
return nil
end
tuple_to_time(day_of_month, month, year, time)
end
def scan_set_cookie
# RFC 6265 4.1.1 & 5.2
until eos?
start = pos
len = nil
skip_wsp
name, value = scan_name_value(true)
if value.nil?
@logger.warn("Cookie definition lacks a name-value pair.") if @logger
elsif name.empty?
@logger.warn("Cookie definition has an empty name.") if @logger
value = nil
end
attrs = {}
case
when skip(/,/)
# The comma is used as separator for concatenating multiple
# values of a header.
len = (pos - 1) - start
break
when skip(/;/)
skip_wsp
aname, avalue = scan_name_value(true)
next if aname.empty? || value.nil?
aname.downcase!
case aname
when 'expires'
# RFC 6265 5.2.1
avalue &&= parse_cookie_date(avalue) or next
when 'max-age'
# RFC 6265 5.2.2
next unless /\A-?\d+\z/.match(avalue)
when 'domain'
# RFC 6265 5.2.3
# An empty value SHOULD be ignored.
next if avalue.nil? || avalue.empty?
when 'path'
# RFC 6265 5.2.4
# A relative path must be ignored rather than normalizing it
# to "/".
next unless /\A\//.match(avalue)
when 'secure', 'httponly'
# RFC 6265 5.2.5, 5.2.6
avalue = true
end
attrs[aname] = avalue
end until eos?
len ||= pos - start
if len > HTTP::Cookie::MAX_LENGTH
@logger.warn("Cookie definition too long: #{name}") if @logger
next
end
yield name, value, attrs if value
end
end
def scan_cookie
# RFC 6265 4.1.1 & 5.4
until eos?
skip_wsp
# Do not treat comma in a Cookie header value as separator; see CVE-2016-7401
name, value = scan_name_value(false)
yield name, value if value
skip(/;/)
end
end
end