httpx 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +48 -0
- data/README.md +2 -0
- data/doc/release_notes/0_10_0.md +66 -0
- data/lib/httpx.rb +2 -0
- data/lib/httpx/adapters/faraday.rb +1 -1
- data/lib/httpx/chainable.rb +2 -2
- data/lib/httpx/connection.rb +3 -9
- data/lib/httpx/connection/http1.rb +1 -1
- data/lib/httpx/domain_name.rb +440 -0
- data/lib/httpx/errors.rb +1 -0
- data/lib/httpx/extensions.rb +21 -1
- data/lib/httpx/io/ssl.rb +0 -1
- data/lib/httpx/io/tcp.rb +6 -5
- data/lib/httpx/io/udp.rb +4 -1
- data/lib/httpx/options.rb +2 -0
- data/lib/httpx/parser/http1.rb +14 -17
- data/lib/httpx/plugins/compression.rb +28 -63
- data/lib/httpx/plugins/compression/brotli.rb +10 -14
- data/lib/httpx/plugins/compression/deflate.rb +7 -6
- data/lib/httpx/plugins/compression/gzip.rb +23 -5
- data/lib/httpx/plugins/cookies.rb +21 -60
- data/lib/httpx/plugins/cookies/cookie.rb +173 -0
- data/lib/httpx/plugins/cookies/jar.rb +74 -0
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +142 -0
- data/lib/httpx/plugins/expect.rb +3 -5
- data/lib/httpx/plugins/follow_redirects.rb +20 -2
- data/lib/httpx/plugins/h2c.rb +1 -1
- data/lib/httpx/plugins/multipart.rb +0 -8
- data/lib/httpx/plugins/persistent.rb +6 -1
- data/lib/httpx/plugins/proxy/socks4.rb +3 -1
- data/lib/httpx/plugins/rate_limiter.rb +51 -0
- data/lib/httpx/plugins/retries.rb +3 -2
- data/lib/httpx/plugins/stream.rb +109 -13
- data/lib/httpx/pool.rb +6 -6
- data/lib/httpx/request.rb +7 -19
- data/lib/httpx/resolver/https.rb +7 -2
- data/lib/httpx/resolver/native.rb +7 -3
- data/lib/httpx/response.rb +16 -23
- data/lib/httpx/selector.rb +2 -4
- data/lib/httpx/session.rb +17 -11
- data/lib/httpx/transcoder/chunker.rb +0 -2
- data/lib/httpx/transcoder/form.rb +0 -6
- data/lib/httpx/transcoder/json.rb +0 -4
- data/lib/httpx/utils.rb +45 -0
- data/lib/httpx/version.rb +1 -1
- data/sig/buffer.rbs +24 -0
- data/sig/callbacks.rbs +14 -0
- data/sig/chainable.rbs +37 -0
- data/sig/connection.rbs +2 -0
- data/sig/connection/http2.rbs +4 -0
- data/sig/domain_name.rbs +17 -0
- data/sig/errors.rbs +3 -0
- data/sig/headers.rbs +42 -0
- data/sig/httpx.rbs +14 -0
- data/sig/loggable.rbs +11 -0
- data/sig/missing.rbs +12 -0
- data/sig/options.rbs +118 -0
- data/sig/parser/http1.rbs +50 -0
- data/sig/plugins/authentication.rbs +11 -0
- data/sig/plugins/basic_authentication.rbs +13 -0
- data/sig/plugins/compression.rbs +55 -0
- data/sig/plugins/compression/brotli.rbs +21 -0
- data/sig/plugins/compression/deflate.rbs +17 -0
- data/sig/plugins/compression/gzip.rbs +29 -0
- data/sig/plugins/cookies.rbs +26 -0
- data/sig/plugins/cookies/cookie.rbs +50 -0
- data/sig/plugins/cookies/jar.rbs +27 -0
- data/sig/plugins/digest_authentication.rbs +33 -0
- data/sig/plugins/expect.rbs +19 -0
- data/sig/plugins/follow_redirects.rbs +37 -0
- data/sig/plugins/h2c.rbs +26 -0
- data/sig/plugins/multipart.rbs +19 -0
- data/sig/plugins/persistent.rbs +17 -0
- data/sig/plugins/proxy.rbs +47 -0
- data/sig/plugins/proxy/http.rbs +14 -0
- data/sig/plugins/proxy/socks4.rbs +33 -0
- data/sig/plugins/proxy/socks5.rbs +36 -0
- data/sig/plugins/proxy/ssh.rbs +18 -0
- data/sig/plugins/push_promise.rbs +22 -0
- data/sig/plugins/rate_limiter.rbs +11 -0
- data/sig/plugins/retries.rbs +48 -0
- data/sig/plugins/stream.rbs +39 -0
- data/sig/pool.rbs +2 -0
- data/sig/registry.rbs +9 -0
- data/sig/request.rbs +61 -0
- data/sig/response.rbs +87 -0
- data/sig/session.rbs +49 -0
- data/sig/test.rbs +9 -0
- data/sig/timeout.rbs +29 -0
- data/sig/transcoder.rbs +16 -0
- data/sig/transcoder/body.rbs +18 -0
- data/sig/transcoder/chunker.rbs +32 -0
- data/sig/transcoder/form.rbs +16 -0
- data/sig/transcoder/json.rbs +14 -0
- metadata +60 -17
@@ -12,93 +12,55 @@ module HTTPX
|
|
12
12
|
# https://gitlab.com/honeyryderchuck/httpx/wikis/Cookies
|
13
13
|
#
|
14
14
|
module Cookies
|
15
|
-
|
15
|
+
def self.load_dependencies(*)
|
16
|
+
require "httpx/plugins/cookies/jar"
|
17
|
+
require "httpx/plugins/cookies/cookie"
|
18
|
+
require "httpx/plugins/cookies/set_cookie_parser"
|
19
|
+
end
|
16
20
|
|
17
21
|
def self.extra_options(options)
|
18
22
|
Class.new(options.class) do
|
19
23
|
def_option(:cookies) do |cookies|
|
20
|
-
if cookies.is_a?(
|
24
|
+
if cookies.is_a?(Jar)
|
21
25
|
cookies
|
22
26
|
else
|
23
|
-
|
27
|
+
Jar.new(cookies)
|
24
28
|
end
|
25
29
|
end
|
26
30
|
end.new(options)
|
27
31
|
end
|
28
32
|
|
29
|
-
class Store
|
30
|
-
def self.new(cookies = nil)
|
31
|
-
return cookies if cookies.is_a?(self)
|
32
|
-
|
33
|
-
super
|
34
|
-
end
|
35
|
-
|
36
|
-
def initialize(cookies = nil)
|
37
|
-
@store = Hash.new { |hash, origin| hash[origin] = HTTP::CookieJar.new }
|
38
|
-
return unless cookies
|
39
|
-
|
40
|
-
cookies = cookies.split(/ *; */) if cookies.is_a?(String)
|
41
|
-
@default_cookies = cookies.map do |cookie, v|
|
42
|
-
if cookie.is_a?(HTTP::Cookie)
|
43
|
-
cookie
|
44
|
-
else
|
45
|
-
HTTP::Cookie.new(cookie.to_s, v.to_s)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def set(origin, cookies)
|
51
|
-
return unless cookies
|
52
|
-
|
53
|
-
@store[origin].parse(cookies, origin)
|
54
|
-
end
|
55
|
-
|
56
|
-
def [](uri)
|
57
|
-
store = @store[uri.origin]
|
58
|
-
@default_cookies.each do |cookie|
|
59
|
-
c = cookie.dup
|
60
|
-
c.domain ||= uri.authority
|
61
|
-
c.path ||= uri.path
|
62
|
-
store.add(c)
|
63
|
-
end if @default_cookies
|
64
|
-
store
|
65
|
-
end
|
66
|
-
|
67
|
-
def ==(other)
|
68
|
-
@store == other.instance_variable_get(:@store)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def self.load_dependencies(*)
|
73
|
-
require "http/cookie"
|
74
|
-
end
|
75
|
-
|
76
33
|
module InstanceMethods
|
77
34
|
extend Forwardable
|
78
35
|
|
79
36
|
def_delegator :@options, :cookies
|
80
37
|
|
81
38
|
def initialize(options = {}, &blk)
|
82
|
-
super({ cookies:
|
39
|
+
super({ cookies: Jar.new }.merge(options), &blk)
|
83
40
|
end
|
84
41
|
|
85
42
|
def wrap
|
86
43
|
return super unless block_given?
|
87
44
|
|
88
45
|
super do |session|
|
89
|
-
|
46
|
+
old_cookies_jar = @options.cookies.dup
|
90
47
|
begin
|
91
48
|
yield session
|
92
49
|
ensure
|
93
|
-
@options = @options.
|
50
|
+
@options = @options.merge(cookies: old_cookies_jar)
|
94
51
|
end
|
95
52
|
end
|
96
53
|
end
|
97
54
|
|
98
55
|
private
|
99
56
|
|
100
|
-
def on_response(
|
101
|
-
|
57
|
+
def on_response(reuest, response)
|
58
|
+
if response && response.respond_to?(:headers) && (set_cookie = response.headers["set-cookie"])
|
59
|
+
|
60
|
+
log { "cookies: set-cookie is over #{Cookie::MAX_LENGTH}" } if set_cookie.bytesize > Cookie::MAX_LENGTH
|
61
|
+
|
62
|
+
@options.cookies.parse(set_cookie)
|
63
|
+
end
|
102
64
|
|
103
65
|
super
|
104
66
|
end
|
@@ -111,13 +73,12 @@ module HTTPX
|
|
111
73
|
end
|
112
74
|
|
113
75
|
module HeadersMethods
|
114
|
-
def set_cookie(
|
115
|
-
return
|
76
|
+
def set_cookie(cookies)
|
77
|
+
return if cookies.empty?
|
116
78
|
|
117
|
-
|
118
|
-
return if cookie_value.empty?
|
79
|
+
header_value = cookies.sort.join("; ")
|
119
80
|
|
120
|
-
add("cookie",
|
81
|
+
add("cookie", header_value)
|
121
82
|
end
|
122
83
|
end
|
123
84
|
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTPX
|
4
|
+
module Plugins::Cookies
|
5
|
+
# The HTTP Cookie.
|
6
|
+
#
|
7
|
+
# Contains the single cookie info: name, value and attributes.
|
8
|
+
class Cookie
|
9
|
+
include Comparable
|
10
|
+
# Maximum number of bytes per cookie (RFC 6265 6.1 requires 4096 at
|
11
|
+
# least)
|
12
|
+
MAX_LENGTH = 4096
|
13
|
+
|
14
|
+
attr_reader :domain, :path, :name, :value, :created_at
|
15
|
+
|
16
|
+
def path=(path)
|
17
|
+
path = String(path)
|
18
|
+
@path = path.start_with?("/") ? path : "/"
|
19
|
+
end
|
20
|
+
|
21
|
+
# See #domain.
|
22
|
+
def domain=(domain)
|
23
|
+
domain = String(domain)
|
24
|
+
|
25
|
+
if domain.start_with?(".")
|
26
|
+
@for_domain = true
|
27
|
+
domain = domain[1..-1]
|
28
|
+
end
|
29
|
+
|
30
|
+
return if domain.empty?
|
31
|
+
|
32
|
+
@domain_name = DomainName.new(domain)
|
33
|
+
# RFC 6265 5.3 5.
|
34
|
+
@for_domain = false if @domain_name.domain.nil? # a public suffix or IP address
|
35
|
+
|
36
|
+
@domain = @domain_name.hostname
|
37
|
+
end
|
38
|
+
|
39
|
+
# Compares the cookie with another. When there are many cookies with
|
40
|
+
# the same name for a URL, the value of the smallest must be used.
|
41
|
+
def <=>(other)
|
42
|
+
# RFC 6265 5.4
|
43
|
+
# Precedence: 1. longer path 2. older creation
|
44
|
+
(@name <=> other.name).nonzero? ||
|
45
|
+
(other.path.length <=> @path.length).nonzero? ||
|
46
|
+
(@created_at <=> other.created_at).nonzero? ||
|
47
|
+
@value <=> other.value
|
48
|
+
end
|
49
|
+
|
50
|
+
class << self
|
51
|
+
def new(cookie, *args)
|
52
|
+
return cookie if cookie.is_a?(self)
|
53
|
+
|
54
|
+
super
|
55
|
+
end
|
56
|
+
|
57
|
+
# Tests if +target_path+ is under +base_path+ as described in RFC
|
58
|
+
# 6265 5.1.4. +base_path+ must be an absolute path.
|
59
|
+
# +target_path+ may be empty, in which case it is treated as the
|
60
|
+
# root path.
|
61
|
+
#
|
62
|
+
# e.g.
|
63
|
+
#
|
64
|
+
# path_match?('/admin/', '/admin/index') == true
|
65
|
+
# path_match?('/admin/', '/Admin/index') == false
|
66
|
+
# path_match?('/admin/', '/admin/') == true
|
67
|
+
# path_match?('/admin/', '/admin') == false
|
68
|
+
#
|
69
|
+
# path_match?('/admin', '/admin') == true
|
70
|
+
# path_match?('/admin', '/Admin') == false
|
71
|
+
# path_match?('/admin', '/admins') == false
|
72
|
+
# path_match?('/admin', '/admin/') == true
|
73
|
+
# path_match?('/admin', '/admin/index') == true
|
74
|
+
def path_match?(base_path, target_path)
|
75
|
+
base_path.start_with?("/") || (return false)
|
76
|
+
# RFC 6265 5.1.4
|
77
|
+
bsize = base_path.size
|
78
|
+
tsize = target_path.size
|
79
|
+
return bsize == 1 if tsize.zero? # treat empty target_path as "/"
|
80
|
+
return false unless target_path.start_with?(base_path)
|
81
|
+
return true if bsize == tsize || base_path.end_with?("/")
|
82
|
+
|
83
|
+
target_path[bsize] == "/"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def initialize(arg, *attrs)
|
88
|
+
@created_at = Time.now
|
89
|
+
|
90
|
+
if attrs.empty?
|
91
|
+
attr_hash = Hash.try_convert(arg)
|
92
|
+
else
|
93
|
+
@name = arg
|
94
|
+
@value, attr_hash = attrs
|
95
|
+
attr_hash = Hash.try_convert(attr_hash)
|
96
|
+
end
|
97
|
+
|
98
|
+
attr_hash.each do |key, val|
|
99
|
+
key = key.downcase.tr("-", "_").to_sym unless key.is_a?(Symbol)
|
100
|
+
|
101
|
+
case key
|
102
|
+
when :domain, :path
|
103
|
+
__send__(:"#{key}=", val)
|
104
|
+
else
|
105
|
+
instance_variable_set(:"@#{key}", val)
|
106
|
+
end
|
107
|
+
end if attr_hash
|
108
|
+
|
109
|
+
@path ||= "/"
|
110
|
+
raise ArgumentError, "name must be specified" if @name.nil?
|
111
|
+
end
|
112
|
+
|
113
|
+
def expires
|
114
|
+
@expires || (@created_at && @max_age ? @created_at + @max_age : nil)
|
115
|
+
end
|
116
|
+
|
117
|
+
def expired?(time = Time.now)
|
118
|
+
return false unless expires
|
119
|
+
|
120
|
+
expires <= time
|
121
|
+
end
|
122
|
+
|
123
|
+
# Returns a string for use in the Cookie header, i.e. `name=value`
|
124
|
+
# or `name="value"`.
|
125
|
+
def cookie_value
|
126
|
+
"#{@name}=#{Scanner.quote(@value)}"
|
127
|
+
end
|
128
|
+
alias_method :to_s, :cookie_value
|
129
|
+
|
130
|
+
# Tests if it is OK to send this cookie to a given `uri`. A
|
131
|
+
# RuntimeError is raised if the cookie's domain is unknown.
|
132
|
+
def valid_for_uri?(uri)
|
133
|
+
uri = URI(uri)
|
134
|
+
# RFC 6265 5.4
|
135
|
+
|
136
|
+
return false if @secure && uri.scheme != "https"
|
137
|
+
|
138
|
+
acceptable_from_uri?(uri) && Cookie.path_match?(@path, uri.path)
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
# Tests if it is OK to accept this cookie if it is sent from a given
|
144
|
+
# URI/URL, `uri`.
|
145
|
+
def acceptable_from_uri?(uri)
|
146
|
+
uri = URI(uri)
|
147
|
+
|
148
|
+
host = DomainName.new(uri.host)
|
149
|
+
|
150
|
+
# RFC 6265 5.3
|
151
|
+
if host.hostname == @domain
|
152
|
+
true
|
153
|
+
elsif @for_domain # !host-only-flag
|
154
|
+
host.cookie_domain?(@domain_name)
|
155
|
+
else
|
156
|
+
@domain.nil?
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
module Scanner
|
161
|
+
RE_BAD_CHAR = /([\x00-\x20\x7F",;\\])/.freeze
|
162
|
+
|
163
|
+
module_function
|
164
|
+
|
165
|
+
def quote(s)
|
166
|
+
return s unless s.match(RE_BAD_CHAR)
|
167
|
+
|
168
|
+
"\"#{s.gsub(/([\\"])/, "\\\\\\1")}\""
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTPX
|
4
|
+
module Plugins::Cookies
|
5
|
+
# The Cookie Jar
|
6
|
+
#
|
7
|
+
# It holds a bunch of cookies.
|
8
|
+
class Jar
|
9
|
+
using URIExtensions
|
10
|
+
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
def initialize_dup(orig)
|
14
|
+
super
|
15
|
+
@cookies = orig.instance_variable_get(:@cookies).dup
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(cookies = nil)
|
19
|
+
@cookies = []
|
20
|
+
|
21
|
+
cookies.each do |elem|
|
22
|
+
cookie = case elem
|
23
|
+
when Cookie
|
24
|
+
elem
|
25
|
+
when Array
|
26
|
+
Cookie.new(*elem)
|
27
|
+
else
|
28
|
+
Cookie.new(elem)
|
29
|
+
end
|
30
|
+
|
31
|
+
@cookies << cookie
|
32
|
+
end if cookies
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse(set_cookie)
|
36
|
+
SetCookieParser.call(set_cookie) do |name, value, attrs|
|
37
|
+
add(Cookie.new(name, value, attrs))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def add(cookie, path = nil)
|
42
|
+
c = cookie.dup
|
43
|
+
|
44
|
+
c.path = path if path && c.path == "/"
|
45
|
+
|
46
|
+
@cookies << c
|
47
|
+
end
|
48
|
+
|
49
|
+
def [](uri)
|
50
|
+
each(uri).sort
|
51
|
+
end
|
52
|
+
|
53
|
+
def each(uri = nil, &blk)
|
54
|
+
return enum_for(__method__, uri) unless block_given?
|
55
|
+
|
56
|
+
return @store.each(&blk) unless uri
|
57
|
+
|
58
|
+
uri = URI(uri)
|
59
|
+
|
60
|
+
now = Time.now
|
61
|
+
tpath = uri.path
|
62
|
+
|
63
|
+
@cookies.delete_if do |cookie|
|
64
|
+
if cookie.expired?(now)
|
65
|
+
true
|
66
|
+
else
|
67
|
+
yield cookie if cookie.valid_for_uri?(uri) && Cookie.path_match?(cookie.path, tpath)
|
68
|
+
false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "strscan"
|
4
|
+
require "time"
|
5
|
+
|
6
|
+
module HTTPX
|
7
|
+
module Plugins::Cookies
|
8
|
+
module SetCookieParser
|
9
|
+
using(RegexpExtensions) unless Regexp.method_defined?(:match?)
|
10
|
+
|
11
|
+
# Whitespace.
|
12
|
+
RE_WSP = /[ \t]+/.freeze
|
13
|
+
|
14
|
+
# A pattern that matches a cookie name or attribute name which may
|
15
|
+
# be empty, capturing trailing whitespace.
|
16
|
+
RE_NAME = /(?!#{RE_WSP})[^,;\\"=]*/.freeze
|
17
|
+
|
18
|
+
RE_BAD_CHAR = /([\x00-\x20\x7F",;\\])/.freeze
|
19
|
+
|
20
|
+
# A pattern that matches the comma in a (typically date) value.
|
21
|
+
RE_COOKIE_COMMA = /,(?=#{RE_WSP}?#{RE_NAME}=)/.freeze
|
22
|
+
|
23
|
+
module_function
|
24
|
+
|
25
|
+
def scan_dquoted(scanner)
|
26
|
+
s = +""
|
27
|
+
|
28
|
+
until scanner.eos?
|
29
|
+
break if scanner.skip(/"/)
|
30
|
+
|
31
|
+
if scanner.skip(/\\/)
|
32
|
+
s << scanner.getch
|
33
|
+
elsif scanner.scan(/[^"\\]+/)
|
34
|
+
s << scanner.matched
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
s
|
39
|
+
end
|
40
|
+
|
41
|
+
def scan_value(scanner, comma_as_separator = false)
|
42
|
+
value = +""
|
43
|
+
|
44
|
+
until scanner.eos?
|
45
|
+
if scanner.scan(/[^,;"]+/)
|
46
|
+
value << scanner.matched
|
47
|
+
elsif scanner.skip(/"/)
|
48
|
+
# RFC 6265 2.2
|
49
|
+
# A cookie-value may be DQUOTE'd.
|
50
|
+
value << scan_dquoted(scanner)
|
51
|
+
elsif scanner.check(/;/)
|
52
|
+
break
|
53
|
+
elsif comma_as_separator && scanner.check(RE_COOKIE_COMMA)
|
54
|
+
break
|
55
|
+
else
|
56
|
+
value << scanner.getch
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
value.rstrip!
|
61
|
+
value
|
62
|
+
end
|
63
|
+
|
64
|
+
def scan_name_value(scanner, comma_as_separator = false)
|
65
|
+
name = scanner.scan(RE_NAME)
|
66
|
+
name.rstrip! if name
|
67
|
+
|
68
|
+
if scanner.skip(/=/)
|
69
|
+
value = scan_value(scanner, comma_as_separator)
|
70
|
+
else
|
71
|
+
scan_value(scanner, comma_as_separator)
|
72
|
+
value = nil
|
73
|
+
end
|
74
|
+
[name, value]
|
75
|
+
end
|
76
|
+
|
77
|
+
def call(set_cookie)
|
78
|
+
scanner = StringScanner.new(set_cookie)
|
79
|
+
|
80
|
+
# RFC 6265 4.1.1 & 5.2
|
81
|
+
until scanner.eos?
|
82
|
+
start = scanner.pos
|
83
|
+
len = nil
|
84
|
+
|
85
|
+
scanner.skip(RE_WSP)
|
86
|
+
|
87
|
+
name, value = scan_name_value(scanner, true)
|
88
|
+
value = nil if name.empty?
|
89
|
+
|
90
|
+
attrs = {}
|
91
|
+
|
92
|
+
until scanner.eos?
|
93
|
+
if scanner.skip(/,/)
|
94
|
+
# The comma is used as separator for concatenating multiple
|
95
|
+
# values of a header.
|
96
|
+
len = (scanner.pos - 1) - start
|
97
|
+
break
|
98
|
+
elsif scanner.skip(/;/)
|
99
|
+
scanner.skip(RE_WSP)
|
100
|
+
|
101
|
+
aname, avalue = scan_name_value(scanner, true)
|
102
|
+
|
103
|
+
next if aname.empty? || value.nil?
|
104
|
+
|
105
|
+
aname.downcase!
|
106
|
+
|
107
|
+
case aname
|
108
|
+
when "expires"
|
109
|
+
# RFC 6265 5.2.1
|
110
|
+
(avalue &&= Time.httpdate(avalue)) || next
|
111
|
+
when "max-age"
|
112
|
+
# RFC 6265 5.2.2
|
113
|
+
next unless /\A-?\d+\z/.match?(avalue)
|
114
|
+
|
115
|
+
avalue = Integer(avalue)
|
116
|
+
when "domain"
|
117
|
+
# RFC 6265 5.2.3
|
118
|
+
# An empty value SHOULD be ignored.
|
119
|
+
next if avalue.nil? || avalue.empty?
|
120
|
+
when "path"
|
121
|
+
# RFC 6265 5.2.4
|
122
|
+
# A relative path must be ignored rather than normalizing it
|
123
|
+
# to "/".
|
124
|
+
next unless avalue.start_with?("/")
|
125
|
+
when "secure", "httponly"
|
126
|
+
# RFC 6265 5.2.5, 5.2.6
|
127
|
+
avalue = true
|
128
|
+
end
|
129
|
+
attrs[aname] = avalue
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
len ||= scanner.pos - start
|
134
|
+
|
135
|
+
next if len > Cookie::MAX_LENGTH
|
136
|
+
|
137
|
+
yield(name, value, attrs) if name && !name.empty? && value
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|