httpx 0.7.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE.txt +48 -0
- data/README.md +9 -5
- data/doc/release_notes/0_0_1.md +7 -0
- data/doc/release_notes/0_0_2.md +9 -0
- data/doc/release_notes/0_0_3.md +9 -0
- data/doc/release_notes/0_0_4.md +7 -0
- data/doc/release_notes/0_0_5.md +5 -0
- data/doc/release_notes/0_10_0.md +66 -0
- data/doc/release_notes/0_1_0.md +9 -0
- data/doc/release_notes/0_2_0.md +5 -0
- data/doc/release_notes/0_2_1.md +16 -0
- data/doc/release_notes/0_3_0.md +12 -0
- data/doc/release_notes/0_3_1.md +6 -0
- data/doc/release_notes/0_4_0.md +51 -0
- data/doc/release_notes/0_4_1.md +3 -0
- data/doc/release_notes/0_5_0.md +15 -0
- data/doc/release_notes/0_5_1.md +14 -0
- data/doc/release_notes/0_6_0.md +5 -0
- data/doc/release_notes/0_6_1.md +6 -0
- data/doc/release_notes/0_6_2.md +6 -0
- data/doc/release_notes/0_6_3.md +13 -0
- data/doc/release_notes/0_6_4.md +21 -0
- data/doc/release_notes/0_6_5.md +22 -0
- data/doc/release_notes/0_6_6.md +19 -0
- data/doc/release_notes/0_6_7.md +5 -0
- data/doc/release_notes/0_7_0.md +46 -0
- data/doc/release_notes/0_8_0.md +27 -0
- data/doc/release_notes/0_8_1.md +8 -0
- data/doc/release_notes/0_8_2.md +7 -0
- data/doc/release_notes/0_9_0.md +38 -0
- data/lib/httpx.rb +2 -0
- data/lib/httpx/adapters/faraday.rb +1 -1
- data/lib/httpx/altsvc.rb +18 -2
- data/lib/httpx/chainable.rb +9 -8
- data/lib/httpx/connection.rb +177 -72
- data/lib/httpx/connection/http1.rb +44 -13
- data/lib/httpx/connection/http2.rb +77 -34
- data/lib/httpx/domain_name.rb +440 -0
- data/lib/httpx/errors.rb +1 -0
- data/lib/httpx/extensions.rb +23 -3
- data/lib/httpx/headers.rb +2 -2
- data/lib/httpx/io/ssl.rb +11 -4
- data/lib/httpx/io/tcp.rb +16 -5
- data/lib/httpx/io/udp.rb +4 -1
- data/lib/httpx/loggable.rb +6 -6
- data/lib/httpx/options.rb +22 -15
- data/lib/httpx/parser/http1.rb +14 -17
- data/lib/httpx/plugins/compression.rb +49 -64
- 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 +45 -17
- 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/digest_authentication.rb +2 -0
- data/lib/httpx/plugins/expect.rb +12 -1
- 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.rb +16 -12
- data/lib/httpx/plugins/proxy/http.rb +7 -2
- data/lib/httpx/plugins/proxy/socks4.rb +4 -2
- data/lib/httpx/plugins/proxy/socks5.rb +5 -1
- data/lib/httpx/plugins/push_promise.rb +2 -2
- data/lib/httpx/plugins/rate_limiter.rb +51 -0
- data/lib/httpx/plugins/retries.rb +13 -6
- data/lib/httpx/plugins/stream.rb +109 -13
- data/lib/httpx/pool.rb +13 -15
- data/lib/httpx/registry.rb +2 -1
- data/lib/httpx/request.rb +14 -19
- data/lib/httpx/resolver.rb +7 -8
- data/lib/httpx/resolver/https.rb +22 -5
- data/lib/httpx/resolver/native.rb +27 -33
- data/lib/httpx/resolver/options.rb +2 -2
- data/lib/httpx/resolver/resolver_mixin.rb +1 -1
- data/lib/httpx/response.rb +22 -17
- data/lib/httpx/selector.rb +96 -97
- data/lib/httpx/session.rb +32 -24
- data/lib/httpx/timeout.rb +7 -1
- 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 +120 -21
|
@@ -13,7 +13,7 @@ module HTTPX
|
|
|
13
13
|
Compression.register "br", self
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
module
|
|
16
|
+
module Deflater
|
|
17
17
|
module_function
|
|
18
18
|
|
|
19
19
|
def deflate(raw, buffer, chunk_size:)
|
|
@@ -25,28 +25,24 @@ module HTTPX
|
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def inflate(text)
|
|
32
|
-
::Brotli.inflate(text)
|
|
28
|
+
class Inflater
|
|
29
|
+
def initialize(bytesize)
|
|
30
|
+
@bytesize = bytesize
|
|
33
31
|
end
|
|
34
32
|
|
|
35
|
-
def
|
|
36
|
-
|
|
37
|
-
def finish
|
|
38
|
-
""
|
|
33
|
+
def inflate(chunk)
|
|
34
|
+
::Brotli.inflate(chunk)
|
|
39
35
|
end
|
|
40
36
|
end
|
|
41
37
|
|
|
42
38
|
module_function
|
|
43
39
|
|
|
44
|
-
def
|
|
45
|
-
|
|
40
|
+
def deflater
|
|
41
|
+
Deflater
|
|
46
42
|
end
|
|
47
43
|
|
|
48
|
-
def
|
|
49
|
-
|
|
44
|
+
def inflater(bytesize)
|
|
45
|
+
Inflater.new(bytesize)
|
|
50
46
|
end
|
|
51
47
|
end
|
|
52
48
|
end
|
|
@@ -4,16 +4,17 @@ module HTTPX
|
|
|
4
4
|
module Plugins
|
|
5
5
|
module Compression
|
|
6
6
|
module Deflate
|
|
7
|
-
def self.load_dependencies(
|
|
7
|
+
def self.load_dependencies(klass)
|
|
8
8
|
require "stringio"
|
|
9
9
|
require "zlib"
|
|
10
|
+
klass.plugin(:"compression/gzip")
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
def self.configure(*)
|
|
13
14
|
Compression.register "deflate", self
|
|
14
15
|
end
|
|
15
16
|
|
|
16
|
-
module
|
|
17
|
+
module Deflater
|
|
17
18
|
module_function
|
|
18
19
|
|
|
19
20
|
def deflate(raw, buffer, chunk_size:)
|
|
@@ -36,12 +37,12 @@ module HTTPX
|
|
|
36
37
|
|
|
37
38
|
module_function
|
|
38
39
|
|
|
39
|
-
def
|
|
40
|
-
|
|
40
|
+
def deflater
|
|
41
|
+
Deflater
|
|
41
42
|
end
|
|
42
43
|
|
|
43
|
-
def
|
|
44
|
-
|
|
44
|
+
def inflater(bytesize)
|
|
45
|
+
GZIP::Inflater.new(bytesize)
|
|
45
46
|
end
|
|
46
47
|
end
|
|
47
48
|
end
|
|
@@ -14,43 +14,71 @@ module HTTPX
|
|
|
14
14
|
Compression.register "gzip", self
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
class
|
|
17
|
+
class Deflater
|
|
18
|
+
def initialize
|
|
19
|
+
@compressed_chunk = "".b
|
|
20
|
+
end
|
|
21
|
+
|
|
18
22
|
def deflate(raw, buffer, chunk_size:)
|
|
19
23
|
gzip = Zlib::GzipWriter.new(self)
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
begin
|
|
26
|
+
while (chunk = raw.read(chunk_size))
|
|
27
|
+
gzip.write(chunk)
|
|
28
|
+
gzip.flush
|
|
29
|
+
compressed = compressed_chunk
|
|
30
|
+
buffer << compressed
|
|
31
|
+
yield compressed if block_given?
|
|
32
|
+
end
|
|
33
|
+
ensure
|
|
34
|
+
gzip.close
|
|
27
35
|
end
|
|
28
|
-
|
|
29
|
-
|
|
36
|
+
|
|
37
|
+
return unless (compressed = compressed_chunk)
|
|
38
|
+
|
|
39
|
+
buffer << compressed
|
|
40
|
+
yield compressed if block_given?
|
|
30
41
|
end
|
|
31
42
|
|
|
32
43
|
private
|
|
33
44
|
|
|
34
45
|
def write(chunk)
|
|
35
|
-
@compressed_chunk
|
|
46
|
+
@compressed_chunk << chunk
|
|
36
47
|
end
|
|
37
48
|
|
|
38
49
|
def compressed_chunk
|
|
39
|
-
|
|
40
|
-
compressed
|
|
50
|
+
@compressed_chunk.dup
|
|
41
51
|
ensure
|
|
42
|
-
@compressed_chunk
|
|
52
|
+
@compressed_chunk.clear
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class Inflater
|
|
57
|
+
def initialize(bytesize)
|
|
58
|
+
@inflater = Zlib::Inflate.new(32 + Zlib::MAX_WBITS)
|
|
59
|
+
@bytesize = bytesize
|
|
60
|
+
@buffer = nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def inflate(chunk)
|
|
64
|
+
buffer = @inflater.inflate(chunk)
|
|
65
|
+
@bytesize -= chunk.bytesize
|
|
66
|
+
if @bytesize <= 0
|
|
67
|
+
buffer << @inflater.finish
|
|
68
|
+
@inflater.close
|
|
69
|
+
end
|
|
70
|
+
buffer
|
|
43
71
|
end
|
|
44
72
|
end
|
|
45
73
|
|
|
46
74
|
module_function
|
|
47
75
|
|
|
48
|
-
def
|
|
49
|
-
|
|
76
|
+
def deflater
|
|
77
|
+
Deflater.new
|
|
50
78
|
end
|
|
51
79
|
|
|
52
|
-
def
|
|
53
|
-
|
|
80
|
+
def inflater(bytesize)
|
|
81
|
+
Inflater.new(bytesize)
|
|
54
82
|
end
|
|
55
83
|
end
|
|
56
84
|
end
|
|
@@ -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
|