class HTTP::Cookie::Scanner

def initialize(string, logger = nil)

def initialize(string, logger = nil)
  @logger = logger
  super(string)
end

def parse_cookie_date(s)

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 quote(s)

def quote(s)
  return s unless s.match(RE_BAD_CHAR)
  (+'"') << s.gsub(/([\\"])/, "\\\\\\1") << '"'
end

def scan_cookie

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

def scan_dquoted

def scan_dquoted
  (+'').tap { |s|
    case
    when skip(/"/)
      break
    when skip(/\\/)
      s << getch
    when scan(/[^"\\]+/)
      s << matched
    end until eos?
  }
end

def scan_name

def scan_name
  scan(RE_NAME).tap { |s|
    s.rstrip! if s
  }
end

def scan_name_value(comma_as_separator = false)

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

def scan_set_cookie

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_value(comma_as_separator = false)

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 skip_wsp

def skip_wsp
  skip(RE_WSP)
end

def tuple_to_time(day_of_month, month, year, time)

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

def tuple_to_time(day_of_month, month, year, time)

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