1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
# frozen_string_literal: true
require "strscan"
require "time"
module HTTPX
module Plugins::Cookies
module SetCookieParser
# Whitespace.
RE_WSP = /[ \t]+/.freeze
# A pattern that matches a cookie name or attribute name which may
# be empty, capturing trailing whitespace.
RE_NAME = /(?!#{RE_WSP})[^,;\\"=]*/.freeze
RE_BAD_CHAR = /([\x00-\x20\x7F",;\\])/.freeze
# A pattern that matches the comma in a (typically date) value.
RE_COOKIE_COMMA = /,(?=#{RE_WSP}?#{RE_NAME}=)/.freeze
module_function
def scan_dquoted(scanner)
s = +""
until scanner.eos?
break if scanner.skip(/"/)
if scanner.skip(/\\/)
s << scanner.getch
elsif scanner.scan(/[^"\\]+/)
s << scanner.matched
end
end
s
end
def scan_value(scanner, comma_as_separator = false)
value = +""
until scanner.eos?
if scanner.scan(/[^,;"]+/)
value << scanner.matched
elsif scanner.skip(/"/)
# RFC 6265 2.2
# A cookie-value may be DQUOTE'd.
value << scan_dquoted(scanner)
elsif scanner.check(/;/)
break
elsif comma_as_separator && scanner.check(RE_COOKIE_COMMA)
break
else
value << scanner.getch
end
end
value.rstrip!
value
end
def scan_name_value(scanner, comma_as_separator = false)
name = scanner.scan(RE_NAME)
name.rstrip! if name
if scanner.skip(/=/)
value = scan_value(scanner, comma_as_separator)
else
scan_value(scanner, comma_as_separator)
value = nil
end
[name, value]
end
def call(set_cookie)
scanner = StringScanner.new(set_cookie)
# RFC 6265 4.1.1 & 5.2
until scanner.eos?
start = scanner.pos
len = nil
scanner.skip(RE_WSP)
name, value = scan_name_value(scanner, true)
value = nil if name && name.empty?
attrs = {}
until scanner.eos?
if scanner.skip(/,/)
# The comma is used as separator for concatenating multiple
# values of a header.
len = (scanner.pos - 1) - start
break
elsif scanner.skip(/;/)
scanner.skip(RE_WSP)
aname, avalue = scan_name_value(scanner, true)
next if (aname.nil? || aname.empty?) || value.nil?
aname.downcase!
case aname
when "expires"
next unless avalue
# RFC 6265 5.2.1
(avalue = Time.parse(avalue)) || next
when "max-age"
next unless avalue
# RFC 6265 5.2.2
next unless /\A-?\d+\z/.match?(avalue)
avalue = Integer(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 avalue && avalue.start_with?("/")
when "secure", "httponly"
# RFC 6265 5.2.5, 5.2.6
avalue = true
end
attrs[aname] = avalue
end
end
len ||= scanner.pos - start
next if len > Cookie::MAX_LENGTH
yield(name, value, attrs) if name && !name.empty? && value
end
end
end
end
end
|