httpx 0.20.0 → 1.3.1
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 +0 -48
- data/README.md +54 -45
- data/doc/release_notes/0_10_0.md +2 -2
- data/doc/release_notes/0_11_0.md +3 -5
- data/doc/release_notes/0_12_0.md +5 -5
- data/doc/release_notes/0_13_0.md +5 -5
- data/doc/release_notes/0_14_0.md +2 -2
- data/doc/release_notes/0_16_0.md +3 -3
- data/doc/release_notes/0_17_0.md +1 -1
- data/doc/release_notes/0_18_0.md +4 -4
- data/doc/release_notes/0_18_2.md +1 -1
- data/doc/release_notes/0_19_0.md +1 -1
- data/doc/release_notes/0_19_8.md +1 -1
- data/doc/release_notes/0_20_0.md +2 -2
- data/doc/release_notes/0_20_1.md +5 -0
- data/doc/release_notes/0_20_2.md +7 -0
- data/doc/release_notes/0_20_3.md +6 -0
- data/doc/release_notes/0_20_4.md +17 -0
- data/doc/release_notes/0_20_5.md +3 -0
- data/doc/release_notes/0_21_0.md +96 -0
- data/doc/release_notes/0_21_1.md +12 -0
- data/doc/release_notes/0_22_0.md +13 -0
- data/doc/release_notes/0_22_1.md +11 -0
- data/doc/release_notes/0_22_2.md +5 -0
- data/doc/release_notes/0_22_3.md +55 -0
- data/doc/release_notes/0_22_4.md +6 -0
- data/doc/release_notes/0_22_5.md +6 -0
- data/doc/release_notes/0_23_0.md +42 -0
- data/doc/release_notes/0_23_1.md +5 -0
- data/doc/release_notes/0_23_2.md +5 -0
- data/doc/release_notes/0_23_3.md +6 -0
- data/doc/release_notes/0_23_4.md +5 -0
- data/doc/release_notes/0_24_0.md +48 -0
- data/doc/release_notes/0_24_1.md +12 -0
- data/doc/release_notes/0_24_2.md +12 -0
- data/doc/release_notes/0_24_3.md +12 -0
- data/doc/release_notes/0_24_4.md +18 -0
- data/doc/release_notes/0_24_5.md +6 -0
- data/doc/release_notes/0_24_6.md +5 -0
- data/doc/release_notes/0_24_7.md +10 -0
- data/doc/release_notes/1_0_0.md +60 -0
- data/doc/release_notes/1_0_1.md +5 -0
- data/doc/release_notes/1_0_2.md +7 -0
- data/doc/release_notes/1_1_0.md +32 -0
- data/doc/release_notes/1_1_1.md +17 -0
- data/doc/release_notes/1_1_2.md +12 -0
- data/doc/release_notes/1_1_3.md +18 -0
- data/doc/release_notes/1_1_4.md +6 -0
- data/doc/release_notes/1_1_5.md +12 -0
- data/doc/release_notes/1_2_0.md +49 -0
- data/doc/release_notes/1_2_1.md +6 -0
- data/doc/release_notes/1_2_2.md +10 -0
- data/doc/release_notes/1_2_3.md +16 -0
- data/doc/release_notes/1_2_4.md +8 -0
- data/doc/release_notes/1_2_5.md +7 -0
- data/doc/release_notes/1_2_6.md +13 -0
- data/doc/release_notes/1_3_0.md +18 -0
- data/doc/release_notes/1_3_1.md +17 -0
- data/lib/httpx/adapters/datadog.rb +215 -122
- data/lib/httpx/adapters/faraday.rb +145 -107
- data/lib/httpx/adapters/sentry.rb +26 -7
- data/lib/httpx/adapters/webmock.rb +34 -18
- data/lib/httpx/altsvc.rb +63 -26
- data/lib/httpx/base64.rb +27 -0
- data/lib/httpx/buffer.rb +12 -0
- data/lib/httpx/callbacks.rb +5 -3
- data/lib/httpx/chainable.rb +54 -39
- data/lib/httpx/connection/http1.rb +75 -44
- data/lib/httpx/connection/http2.rb +31 -38
- data/lib/httpx/connection.rb +287 -117
- data/lib/httpx/domain_name.rb +10 -13
- data/lib/httpx/errors.rb +52 -2
- data/lib/httpx/extensions.rb +24 -131
- data/lib/httpx/io/ssl.rb +83 -77
- data/lib/httpx/io/tcp.rb +48 -71
- data/lib/httpx/io/udp.rb +18 -52
- data/lib/httpx/io/unix.rb +10 -15
- data/lib/httpx/io.rb +3 -9
- data/lib/httpx/loggable.rb +4 -19
- data/lib/httpx/options.rb +176 -118
- data/lib/httpx/parser/http1.rb +4 -0
- data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
- data/lib/httpx/plugins/{authentication → auth}/digest.rb +14 -14
- data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
- data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
- data/lib/httpx/plugins/auth.rb +25 -0
- data/lib/httpx/plugins/aws_sdk_authentication.rb +4 -3
- data/lib/httpx/plugins/aws_sigv4.rb +12 -9
- data/lib/httpx/plugins/basic_auth.rb +29 -0
- data/lib/httpx/plugins/brotli.rb +50 -0
- data/lib/httpx/plugins/callbacks.rb +91 -0
- data/lib/httpx/plugins/circuit_breaker/circuit.rb +100 -0
- data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +53 -0
- data/lib/httpx/plugins/circuit_breaker.rb +148 -0
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
- data/lib/httpx/plugins/cookies.rb +30 -17
- data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +14 -12
- data/lib/httpx/plugins/expect.rb +21 -14
- data/lib/httpx/plugins/follow_redirects.rb +140 -41
- data/lib/httpx/plugins/grpc/call.rb +2 -3
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +88 -0
- data/lib/httpx/plugins/grpc/message.rb +7 -37
- data/lib/httpx/plugins/grpc.rb +36 -29
- data/lib/httpx/plugins/h2c.rb +26 -19
- data/lib/httpx/plugins/internal_telemetry.rb +16 -0
- data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +7 -5
- data/lib/httpx/plugins/oauth.rb +175 -0
- data/lib/httpx/plugins/persistent.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +23 -13
- data/lib/httpx/plugins/proxy/socks4.rb +9 -7
- data/lib/httpx/plugins/proxy/socks5.rb +11 -9
- data/lib/httpx/plugins/proxy.rb +80 -61
- data/lib/httpx/plugins/push_promise.rb +1 -1
- data/lib/httpx/plugins/rate_limiter.rb +5 -1
- data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
- data/lib/httpx/plugins/response_cache/store.rb +62 -25
- data/lib/httpx/plugins/response_cache.rb +105 -12
- data/lib/httpx/plugins/retries.rb +87 -17
- data/lib/httpx/plugins/ssrf_filter.rb +145 -0
- data/lib/httpx/plugins/stream.rb +27 -23
- data/lib/httpx/plugins/upgrade/h2.rb +4 -4
- data/lib/httpx/plugins/upgrade.rb +8 -10
- data/lib/httpx/plugins/webdav.rb +80 -0
- data/lib/httpx/pool/synch_pool.rb +93 -0
- data/lib/httpx/pool.rb +102 -27
- data/lib/httpx/punycode.rb +9 -291
- data/lib/httpx/request/body.rb +154 -0
- data/lib/httpx/request.rb +130 -146
- data/lib/httpx/resolver/https.rb +62 -27
- data/lib/httpx/resolver/multi.rb +9 -13
- data/lib/httpx/resolver/native.rb +192 -76
- data/lib/httpx/resolver/resolver.rb +34 -9
- data/lib/httpx/resolver/system.rb +16 -11
- data/lib/httpx/resolver.rb +38 -16
- data/lib/httpx/response/body.rb +242 -0
- data/lib/httpx/response/buffer.rb +96 -0
- data/lib/httpx/response.rb +159 -217
- data/lib/httpx/selector.rb +9 -4
- data/lib/httpx/session.rb +137 -89
- data/lib/httpx/session_extensions.rb +4 -1
- data/lib/httpx/timers.rb +34 -8
- data/lib/httpx/transcoder/body.rb +0 -2
- data/lib/httpx/transcoder/chunker.rb +0 -1
- data/lib/httpx/transcoder/deflate.rb +37 -0
- data/lib/httpx/transcoder/form.rb +52 -33
- data/lib/httpx/transcoder/gzip.rb +74 -0
- data/lib/httpx/transcoder/json.rb +21 -8
- data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
- data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +4 -4
- data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
- data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
- data/lib/httpx/transcoder/multipart.rb +17 -0
- data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
- data/lib/httpx/transcoder/utils/deflater.rb +72 -0
- data/lib/httpx/transcoder/utils/inflater.rb +19 -0
- data/lib/httpx/transcoder/xml.rb +52 -0
- data/lib/httpx/transcoder.rb +5 -6
- data/lib/httpx/utils.rb +36 -16
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +12 -14
- data/sig/altsvc.rbs +33 -0
- data/sig/buffer.rbs +2 -1
- data/sig/callbacks.rbs +3 -3
- data/sig/chainable.rbs +11 -9
- data/sig/connection/http1.rbs +8 -7
- data/sig/connection/http2.rbs +19 -19
- data/sig/connection.rbs +64 -24
- data/sig/errors.rbs +22 -3
- data/sig/httpx.rbs +5 -4
- data/sig/io/ssl.rbs +27 -0
- data/sig/io/tcp.rbs +60 -0
- data/sig/io/udp.rbs +20 -0
- data/sig/io/unix.rbs +27 -0
- data/sig/io.rbs +6 -0
- data/sig/options.rbs +32 -22
- data/sig/parser/http1.rbs +1 -1
- data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
- data/sig/plugins/{authentication → auth}/digest.rbs +2 -1
- data/sig/plugins/auth.rbs +13 -0
- data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
- data/sig/plugins/brotli.rbs +22 -0
- data/sig/plugins/callbacks.rbs +38 -0
- data/sig/plugins/circuit_breaker.rbs +71 -0
- data/sig/plugins/compression.rbs +7 -5
- data/sig/plugins/cookies/jar.rbs +2 -2
- data/sig/plugins/cookies.rbs +2 -0
- data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
- data/sig/plugins/follow_redirects.rbs +18 -4
- data/sig/plugins/grpc/call.rbs +19 -0
- data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
- data/sig/plugins/grpc/message.rbs +17 -0
- data/sig/plugins/grpc.rbs +7 -32
- data/sig/plugins/h2c.rbs +1 -1
- data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
- data/sig/plugins/oauth.rbs +54 -0
- data/sig/plugins/proxy/http.rbs +3 -0
- data/sig/plugins/proxy/socks4.rbs +9 -6
- data/sig/plugins/proxy/socks5.rbs +10 -6
- data/sig/plugins/proxy/ssh.rbs +1 -1
- data/sig/plugins/proxy.rbs +13 -5
- data/sig/plugins/push_promise.rbs +3 -3
- data/sig/plugins/rate_limiter.rbs +1 -1
- data/sig/plugins/response_cache.rbs +36 -7
- data/sig/plugins/retries.rbs +30 -8
- data/sig/plugins/stream.rbs +24 -17
- data/sig/plugins/upgrade.rbs +5 -3
- data/sig/pool.rbs +10 -7
- data/sig/request/body.rbs +38 -0
- data/sig/request.rbs +15 -24
- data/sig/resolver/https.rbs +8 -3
- data/sig/resolver/native.rbs +17 -4
- data/sig/resolver/resolver.rbs +8 -6
- data/sig/resolver/system.rbs +2 -0
- data/sig/resolver.rbs +9 -5
- data/sig/response/body.rbs +53 -0
- data/sig/response/buffer.rbs +24 -0
- data/sig/response.rbs +24 -39
- data/sig/selector.rbs +1 -1
- data/sig/session.rbs +29 -18
- data/sig/timers.rbs +18 -8
- data/sig/transcoder/body.rbs +4 -3
- data/sig/transcoder/deflate.rbs +11 -0
- data/sig/transcoder/form.rbs +5 -3
- data/sig/transcoder/gzip.rbs +24 -0
- data/sig/transcoder/json.rbs +8 -3
- data/sig/{plugins → transcoder}/multipart.rbs +15 -19
- data/sig/transcoder/utils/body_reader.rbs +15 -0
- data/sig/transcoder/utils/deflater.rbs +29 -0
- data/sig/transcoder/utils/inflater.rbs +12 -0
- data/sig/transcoder/xml.rbs +22 -0
- data/sig/transcoder.rbs +24 -9
- data/sig/utils.rbs +8 -2
- metadata +163 -41
- data/lib/httpx/plugins/authentication.rb +0 -20
- data/lib/httpx/plugins/basic_authentication.rb +0 -30
- data/lib/httpx/plugins/compression/brotli.rb +0 -54
- data/lib/httpx/plugins/compression/deflate.rb +0 -49
- data/lib/httpx/plugins/compression/gzip.rb +0 -88
- data/lib/httpx/plugins/compression.rb +0 -164
- data/lib/httpx/plugins/multipart/decoder.rb +0 -187
- data/lib/httpx/plugins/multipart.rb +0 -84
- data/lib/httpx/registry.rb +0 -85
- data/sig/plugins/authentication.rbs +0 -11
- data/sig/plugins/compression/brotli.rbs +0 -21
- data/sig/plugins/compression/deflate.rbs +0 -17
- data/sig/plugins/compression/gzip.rbs +0 -29
- data/sig/registry.rbs +0 -12
- /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
- /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
@@ -3,15 +3,20 @@
|
|
3
3
|
module HTTPX
|
4
4
|
module Plugins
|
5
5
|
#
|
6
|
-
# This plugin adds support for retrying requests when
|
6
|
+
# This plugin adds support for retrying requests when errors happen.
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# It has a default max number of retries (see *MAX_RETRIES* and the *max_retries* option),
|
9
|
+
# after which it will return the last response, error or not. It will **not** raise an exception.
|
10
|
+
#
|
11
|
+
# It does not retry which are not considered idempotent (see *retry_change_requests* to override).
|
12
|
+
#
|
13
|
+
# https://gitlab.com/os85/httpx/wikis/Retries
|
9
14
|
#
|
10
15
|
module Retries
|
11
16
|
MAX_RETRIES = 3
|
12
17
|
# TODO: pass max_retries in a configure/load block
|
13
18
|
|
14
|
-
IDEMPOTENT_METHODS = %
|
19
|
+
IDEMPOTENT_METHODS = %w[GET OPTIONS HEAD PUT DELETE].freeze
|
15
20
|
RETRYABLE_ERRORS = [
|
16
21
|
IOError,
|
17
22
|
EOFError,
|
@@ -23,9 +28,10 @@ module HTTPX
|
|
23
28
|
Parser::Error,
|
24
29
|
TLSError,
|
25
30
|
TimeoutError,
|
31
|
+
ConnectionError,
|
26
32
|
Connection::HTTP2::GoawayError,
|
27
33
|
].freeze
|
28
|
-
DEFAULT_JITTER = ->(interval) { interval * (
|
34
|
+
DEFAULT_JITTER = ->(interval) { interval * ((rand + 1) * 0.5) }
|
29
35
|
|
30
36
|
if ENV.key?("HTTPX_NO_JITTER")
|
31
37
|
def self.extra_options(options)
|
@@ -37,6 +43,14 @@ module HTTPX
|
|
37
43
|
end
|
38
44
|
end
|
39
45
|
|
46
|
+
# adds support for the following options:
|
47
|
+
#
|
48
|
+
# :max_retries :: max number of times a request will be retried (defaults to <tt>3</tt>).
|
49
|
+
# :retry_change_requests :: whether idempotent requests are retried (defaults to <tt>false</tt>).
|
50
|
+
# :retry_after:: seconds after which a request is retried; can also be a callable object (i.e. <tt>->(req, res) { ... } </tt>)
|
51
|
+
# :retry_jitter :: number of seconds applied to *:retry_after* (must be a callable, i.e. <tt>->(retry_after) { ... } </tt>).
|
52
|
+
# :retry_on :: callable which alternatively defines a different rule for when a response is to be retried
|
53
|
+
# (i.e. <tt>->(res) { ... }</tt>).
|
40
54
|
module OptionsMethods
|
41
55
|
def option_retry_after(value)
|
42
56
|
# return early if callable
|
@@ -57,7 +71,7 @@ module HTTPX
|
|
57
71
|
|
58
72
|
def option_max_retries(value)
|
59
73
|
num = Integer(value)
|
60
|
-
raise TypeError, ":max_retries must be positive" unless num
|
74
|
+
raise TypeError, ":max_retries must be positive" unless num >= 0
|
61
75
|
|
62
76
|
num
|
63
77
|
end
|
@@ -67,7 +81,7 @@ module HTTPX
|
|
67
81
|
end
|
68
82
|
|
69
83
|
def option_retry_on(value)
|
70
|
-
raise ":retry_on must be called with the response" unless value.respond_to?(:call)
|
84
|
+
raise TypeError, ":retry_on must be called with the response" unless value.respond_to?(:call)
|
71
85
|
|
72
86
|
value
|
73
87
|
end
|
@@ -75,7 +89,7 @@ module HTTPX
|
|
75
89
|
|
76
90
|
module InstanceMethods
|
77
91
|
def max_retries(n)
|
78
|
-
with(max_retries: n
|
92
|
+
with(max_retries: n)
|
79
93
|
end
|
80
94
|
|
81
95
|
private
|
@@ -87,15 +101,14 @@ module HTTPX
|
|
87
101
|
request.retries.positive? &&
|
88
102
|
__repeatable_request?(request, options) &&
|
89
103
|
(
|
90
|
-
# rubocop:disable Style/MultilineTernaryOperator
|
91
|
-
options.retry_on ?
|
92
|
-
options.retry_on.call(response) :
|
93
104
|
(
|
94
105
|
response.is_a?(ErrorResponse) && __retryable_error?(response.error)
|
106
|
+
) ||
|
107
|
+
(
|
108
|
+
options.retry_on && options.retry_on.call(response)
|
95
109
|
)
|
96
|
-
# rubocop:enable Style/MultilineTernaryOperator
|
97
110
|
)
|
98
|
-
|
111
|
+
__try_partial_retry(request, response)
|
99
112
|
log { "failed to get response, #{request.retries} tries to go..." }
|
100
113
|
request.retries -= 1
|
101
114
|
request.transition(:idle)
|
@@ -111,14 +124,20 @@ module HTTPX
|
|
111
124
|
|
112
125
|
retry_start = Utils.now
|
113
126
|
log { "retrying after #{retry_after} secs..." }
|
127
|
+
|
128
|
+
deactivate_connection(request, connections, options)
|
129
|
+
|
114
130
|
pool.after(retry_after) do
|
115
|
-
|
116
|
-
|
117
|
-
|
131
|
+
if request.response
|
132
|
+
# request has terminated abruptly meanwhile
|
133
|
+
request.emit(:response, request.response)
|
134
|
+
else
|
135
|
+
log { "retrying (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
|
136
|
+
send_request(request, connections, options)
|
137
|
+
end
|
118
138
|
end
|
119
139
|
else
|
120
|
-
|
121
|
-
connection.send(request)
|
140
|
+
send_request(request, connections, options)
|
122
141
|
end
|
123
142
|
|
124
143
|
return
|
@@ -133,15 +152,66 @@ module HTTPX
|
|
133
152
|
def __retryable_error?(ex)
|
134
153
|
RETRYABLE_ERRORS.any? { |klass| ex.is_a?(klass) }
|
135
154
|
end
|
155
|
+
|
156
|
+
def proxy_error?(request, response)
|
157
|
+
super && !request.retries.positive?
|
158
|
+
end
|
159
|
+
|
160
|
+
#
|
161
|
+
# Atttempt to set the request to perform a partial range request.
|
162
|
+
# This happens if the peer server accepts byte-range requests, and
|
163
|
+
# the last response contains some body payload.
|
164
|
+
#
|
165
|
+
def __try_partial_retry(request, response)
|
166
|
+
response = response.response if response.is_a?(ErrorResponse)
|
167
|
+
|
168
|
+
return unless response
|
169
|
+
|
170
|
+
unless response.headers.key?("accept-ranges") &&
|
171
|
+
response.headers["accept-ranges"] == "bytes" && # there's nothing else supported though...
|
172
|
+
(original_body = response.body)
|
173
|
+
response.close if response.respond_to?(:close)
|
174
|
+
return
|
175
|
+
end
|
176
|
+
|
177
|
+
request.partial_response = response
|
178
|
+
|
179
|
+
size = original_body.bytesize
|
180
|
+
|
181
|
+
request.headers["range"] = "bytes=#{size}-"
|
182
|
+
end
|
136
183
|
end
|
137
184
|
|
138
185
|
module RequestMethods
|
139
186
|
attr_accessor :retries
|
140
187
|
|
188
|
+
attr_writer :partial_response
|
189
|
+
|
141
190
|
def initialize(*args)
|
142
191
|
super
|
143
192
|
@retries = @options.max_retries
|
144
193
|
end
|
194
|
+
|
195
|
+
def response=(response)
|
196
|
+
if @partial_response
|
197
|
+
if response.is_a?(Response) && response.status == 206
|
198
|
+
response.from_partial_response(@partial_response)
|
199
|
+
else
|
200
|
+
@partial_response.close
|
201
|
+
end
|
202
|
+
@partial_response = nil
|
203
|
+
end
|
204
|
+
|
205
|
+
super
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
module ResponseMethods
|
210
|
+
def from_partial_response(response)
|
211
|
+
@status = response.status
|
212
|
+
@headers = response.headers
|
213
|
+
@body = response.body
|
214
|
+
end
|
145
215
|
end
|
146
216
|
end
|
147
217
|
register_plugin :retries, Retries
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTPX
|
4
|
+
class ServerSideRequestForgeryError < Error; end
|
5
|
+
|
6
|
+
module Plugins
|
7
|
+
#
|
8
|
+
# This plugin adds support for preventing Server-Side Request Forgery attacks.
|
9
|
+
#
|
10
|
+
# https://gitlab.com/os85/httpx/wikis/Server-Side-Request-Forgery-Filter
|
11
|
+
#
|
12
|
+
module SsrfFilter
|
13
|
+
module IPAddrExtensions
|
14
|
+
refine IPAddr do
|
15
|
+
def prefixlen
|
16
|
+
mask_addr = @mask_addr
|
17
|
+
raise "Invalid mask" if mask_addr.zero?
|
18
|
+
|
19
|
+
mask_addr >>= 1 while (mask_addr & 0x1).zero?
|
20
|
+
|
21
|
+
length = 0
|
22
|
+
while mask_addr & 0x1 == 0x1
|
23
|
+
length += 1
|
24
|
+
mask_addr >>= 1
|
25
|
+
end
|
26
|
+
|
27
|
+
length
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
using IPAddrExtensions
|
33
|
+
|
34
|
+
# https://en.wikipedia.org/wiki/Reserved_IP_addresses
|
35
|
+
IPV4_BLACKLIST = [
|
36
|
+
IPAddr.new("0.0.0.0/8"), # Current network (only valid as source address)
|
37
|
+
IPAddr.new("10.0.0.0/8"), # Private network
|
38
|
+
IPAddr.new("100.64.0.0/10"), # Shared Address Space
|
39
|
+
IPAddr.new("127.0.0.0/8"), # Loopback
|
40
|
+
IPAddr.new("169.254.0.0/16"), # Link-local
|
41
|
+
IPAddr.new("172.16.0.0/12"), # Private network
|
42
|
+
IPAddr.new("192.0.0.0/24"), # IETF Protocol Assignments
|
43
|
+
IPAddr.new("192.0.2.0/24"), # TEST-NET-1, documentation and examples
|
44
|
+
IPAddr.new("192.88.99.0/24"), # IPv6 to IPv4 relay (includes 2002::/16)
|
45
|
+
IPAddr.new("192.168.0.0/16"), # Private network
|
46
|
+
IPAddr.new("198.18.0.0/15"), # Network benchmark tests
|
47
|
+
IPAddr.new("198.51.100.0/24"), # TEST-NET-2, documentation and examples
|
48
|
+
IPAddr.new("203.0.113.0/24"), # TEST-NET-3, documentation and examples
|
49
|
+
IPAddr.new("224.0.0.0/4"), # IP multicast (former Class D network)
|
50
|
+
IPAddr.new("240.0.0.0/4"), # Reserved (former Class E network)
|
51
|
+
IPAddr.new("255.255.255.255"), # Broadcast
|
52
|
+
].freeze
|
53
|
+
|
54
|
+
IPV6_BLACKLIST = ([
|
55
|
+
IPAddr.new("::1/128"), # Loopback
|
56
|
+
IPAddr.new("64:ff9b::/96"), # IPv4/IPv6 translation (RFC 6052)
|
57
|
+
IPAddr.new("100::/64"), # Discard prefix (RFC 6666)
|
58
|
+
IPAddr.new("2001::/32"), # Teredo tunneling
|
59
|
+
IPAddr.new("2001:10::/28"), # Deprecated (previously ORCHID)
|
60
|
+
IPAddr.new("2001:20::/28"), # ORCHIDv2
|
61
|
+
IPAddr.new("2001:db8::/32"), # Addresses used in documentation and example source code
|
62
|
+
IPAddr.new("2002::/16"), # 6to4
|
63
|
+
IPAddr.new("fc00::/7"), # Unique local address
|
64
|
+
IPAddr.new("fe80::/10"), # Link-local address
|
65
|
+
IPAddr.new("ff00::/8"), # Multicast
|
66
|
+
] + IPV4_BLACKLIST.flat_map do |ipaddr|
|
67
|
+
prefixlen = ipaddr.prefixlen
|
68
|
+
|
69
|
+
ipv4_compatible = ipaddr.ipv4_compat.mask(96 + prefixlen)
|
70
|
+
ipv4_mapped = ipaddr.ipv4_mapped.mask(80 + prefixlen)
|
71
|
+
|
72
|
+
[ipv4_compatible, ipv4_mapped]
|
73
|
+
end).freeze
|
74
|
+
|
75
|
+
class << self
|
76
|
+
def extra_options(options)
|
77
|
+
options.merge(allowed_schemes: %w[https http])
|
78
|
+
end
|
79
|
+
|
80
|
+
def unsafe_ip_address?(ipaddr)
|
81
|
+
range = ipaddr.to_range
|
82
|
+
return true if range.first != range.last
|
83
|
+
|
84
|
+
return IPV6_BLACKLIST.any? { |r| r.include?(ipaddr) } if ipaddr.ipv6?
|
85
|
+
|
86
|
+
IPV4_BLACKLIST.any? { |r| r.include?(ipaddr) } # then it's IPv4
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# adds support for the following options:
|
91
|
+
#
|
92
|
+
# :allowed_schemes :: list of URI schemes allowed (defaults to <tt>["https", "http"]</tt>)
|
93
|
+
module OptionsMethods
|
94
|
+
def option_allowed_schemes(value)
|
95
|
+
Array(value)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
module InstanceMethods
|
100
|
+
def send_requests(*requests)
|
101
|
+
responses = requests.map do |request|
|
102
|
+
next if @options.allowed_schemes.include?(request.uri.scheme)
|
103
|
+
|
104
|
+
error = ServerSideRequestForgeryError.new("#{request.uri} URI scheme not allowed")
|
105
|
+
error.set_backtrace(caller)
|
106
|
+
response = ErrorResponse.new(request, error)
|
107
|
+
request.emit(:response, response)
|
108
|
+
response
|
109
|
+
end
|
110
|
+
allowed_requests = requests.select { |req| responses[requests.index(req)].nil? }
|
111
|
+
allowed_responses = super(*allowed_requests)
|
112
|
+
allowed_responses.each_with_index do |res, idx|
|
113
|
+
req = allowed_requests[idx]
|
114
|
+
responses[requests.index(req)] = res
|
115
|
+
end
|
116
|
+
|
117
|
+
responses
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
module ConnectionMethods
|
122
|
+
def initialize(*)
|
123
|
+
begin
|
124
|
+
super
|
125
|
+
rescue ServerSideRequestForgeryError => e
|
126
|
+
# may raise when IPs are passed as options via :addresses
|
127
|
+
throw(:resolve_error, e)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def addresses=(addrs)
|
132
|
+
addrs = addrs.map { |addr| addr.is_a?(IPAddr) ? addr : IPAddr.new(addr) }
|
133
|
+
|
134
|
+
addrs.reject!(&SsrfFilter.method(:unsafe_ip_address?))
|
135
|
+
|
136
|
+
raise ServerSideRequestForgeryError, "#{@origin.host} has no public IP addresses" if addrs.empty?
|
137
|
+
|
138
|
+
super
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
register_plugin :ssrf_filter, SsrfFilter
|
144
|
+
end
|
145
|
+
end
|
data/lib/httpx/plugins/stream.rb
CHANGED
@@ -2,17 +2,15 @@
|
|
2
2
|
|
3
3
|
module HTTPX
|
4
4
|
class StreamResponse
|
5
|
-
def initialize(request, session
|
5
|
+
def initialize(request, session)
|
6
6
|
@request = request
|
7
7
|
@session = session
|
8
|
-
@
|
8
|
+
@response = nil
|
9
9
|
end
|
10
10
|
|
11
11
|
def each(&block)
|
12
12
|
return enum_for(__method__) unless block
|
13
13
|
|
14
|
-
raise Error, "response already streamed" if @response
|
15
|
-
|
16
14
|
@request.stream = self
|
17
15
|
|
18
16
|
begin
|
@@ -20,7 +18,7 @@ module HTTPX
|
|
20
18
|
|
21
19
|
if @request.response
|
22
20
|
# if we've already started collecting the payload, yield it first
|
23
|
-
# before proceeding
|
21
|
+
# before proceeding.
|
24
22
|
body = @request.response.body
|
25
23
|
|
26
24
|
body.each do |chunk|
|
@@ -29,7 +27,6 @@ module HTTPX
|
|
29
27
|
end
|
30
28
|
|
31
29
|
response.raise_for_status
|
32
|
-
response.close
|
33
30
|
ensure
|
34
31
|
@on_chunk = nil
|
35
32
|
end
|
@@ -38,7 +35,7 @@ module HTTPX
|
|
38
35
|
def each_line
|
39
36
|
return enum_for(__method__) unless block_given?
|
40
37
|
|
41
|
-
line =
|
38
|
+
line = "".b
|
42
39
|
|
43
40
|
each do |chunk|
|
44
41
|
line << chunk
|
@@ -49,6 +46,8 @@ module HTTPX
|
|
49
46
|
line = line.byteslice(idx + 1..-1)
|
50
47
|
end
|
51
48
|
end
|
49
|
+
|
50
|
+
yield line unless line.empty?
|
52
51
|
end
|
53
52
|
|
54
53
|
# This is a ghost method. It's to be used ONLY internally, when processing streams
|
@@ -71,9 +70,11 @@ module HTTPX
|
|
71
70
|
private
|
72
71
|
|
73
72
|
def response
|
74
|
-
|
73
|
+
return @response if @response
|
75
74
|
|
76
|
-
@request.response
|
75
|
+
@request.response || begin
|
76
|
+
@response = @session.request(@request)
|
77
|
+
end
|
77
78
|
end
|
78
79
|
|
79
80
|
def respond_to_missing?(meth, *args)
|
@@ -91,12 +92,14 @@ module HTTPX
|
|
91
92
|
#
|
92
93
|
# This plugin adds support for stream response (text/event-stream).
|
93
94
|
#
|
94
|
-
# https://gitlab.com/
|
95
|
+
# https://gitlab.com/os85/httpx/wikis/Stream
|
95
96
|
#
|
96
97
|
module Stream
|
97
|
-
|
98
|
-
|
98
|
+
def self.extra_options(options)
|
99
|
+
options.merge(timeout: { read_timeout: Float::INFINITY, operation_timeout: 60 })
|
100
|
+
end
|
99
101
|
|
102
|
+
module InstanceMethods
|
100
103
|
def request(*args, stream: false, **options)
|
101
104
|
return super(*args, **options) unless stream
|
102
105
|
|
@@ -105,9 +108,7 @@ module HTTPX
|
|
105
108
|
|
106
109
|
request = requests.first
|
107
110
|
|
108
|
-
|
109
|
-
|
110
|
-
StreamResponse.new(request, self, connections)
|
111
|
+
StreamResponse.new(request, self)
|
111
112
|
end
|
112
113
|
end
|
113
114
|
|
@@ -117,7 +118,10 @@ module HTTPX
|
|
117
118
|
|
118
119
|
module ResponseMethods
|
119
120
|
def stream
|
120
|
-
@request.
|
121
|
+
request = @request.root_request if @request.respond_to?(:root_request)
|
122
|
+
request ||= @request
|
123
|
+
|
124
|
+
request.stream
|
121
125
|
end
|
122
126
|
end
|
123
127
|
|
@@ -130,7 +134,13 @@ module HTTPX
|
|
130
134
|
def write(chunk)
|
131
135
|
return super unless @stream
|
132
136
|
|
133
|
-
|
137
|
+
return 0 if chunk.empty?
|
138
|
+
|
139
|
+
chunk = decode_chunk(chunk)
|
140
|
+
|
141
|
+
@stream.on_chunk(chunk.dup)
|
142
|
+
|
143
|
+
chunk.size
|
134
144
|
end
|
135
145
|
|
136
146
|
private
|
@@ -141,12 +151,6 @@ module HTTPX
|
|
141
151
|
super
|
142
152
|
end
|
143
153
|
end
|
144
|
-
|
145
|
-
def self.const_missing(const_name)
|
146
|
-
super unless const_name == :StreamResponse
|
147
|
-
warn "DEPRECATION WARNING: the class #{self}::StreamResponse is deprecated. Use HTTPX::StreamResponse instead."
|
148
|
-
HTTPX::StreamResponse
|
149
|
-
end
|
150
154
|
end
|
151
155
|
register_plugin :stream, Stream
|
152
156
|
end
|
@@ -6,12 +6,12 @@ module HTTPX
|
|
6
6
|
# This plugin adds support for upgrading an HTTP/1.1 connection to HTTP/2
|
7
7
|
# via an Upgrade: h2 response declaration
|
8
8
|
#
|
9
|
-
# https://gitlab.com/
|
9
|
+
# https://gitlab.com/os85/httpx/wikis/Connection-Upgrade#h2
|
10
10
|
#
|
11
11
|
module H2
|
12
12
|
class << self
|
13
|
-
def
|
14
|
-
|
13
|
+
def extra_options(options)
|
14
|
+
options.merge(upgrade_handlers: options.upgrade_handlers.merge("h2" => self))
|
15
15
|
end
|
16
16
|
|
17
17
|
def call(connection, _request, _response)
|
@@ -32,7 +32,7 @@ module HTTPX
|
|
32
32
|
|
33
33
|
@parser = Connection::HTTP2.new(@write_buffer, @options)
|
34
34
|
set_parser_callbacks(@parser)
|
35
|
-
@upgrade_protocol =
|
35
|
+
@upgrade_protocol = "h2"
|
36
36
|
|
37
37
|
# what's happening here:
|
38
38
|
# a deviation from the state machine is done to perform the actions when a
|
@@ -6,7 +6,7 @@ module HTTPX
|
|
6
6
|
# This plugin helps negotiating a new protocol from an HTTP/1.1 connection, via the
|
7
7
|
# Upgrade header.
|
8
8
|
#
|
9
|
-
# https://gitlab.com/
|
9
|
+
# https://gitlab.com/os85/httpx/wikis/Upgrade
|
10
10
|
#
|
11
11
|
module Upgrade
|
12
12
|
class << self
|
@@ -15,16 +15,13 @@ module HTTPX
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def extra_options(options)
|
18
|
-
upgrade_handlers
|
19
|
-
extend Registry
|
20
|
-
end
|
21
|
-
options.merge(upgrade_handlers: upgrade_handlers)
|
18
|
+
options.merge(upgrade_handlers: {})
|
22
19
|
end
|
23
20
|
end
|
24
21
|
|
25
22
|
module OptionsMethods
|
26
23
|
def option_upgrade_handlers(value)
|
27
|
-
raise TypeError, ":upgrade_handlers must be a
|
24
|
+
raise TypeError, ":upgrade_handlers must be a Hash" unless value.is_a?(Hash)
|
28
25
|
|
29
26
|
value
|
30
27
|
end
|
@@ -35,19 +32,20 @@ module HTTPX
|
|
35
32
|
response = super
|
36
33
|
|
37
34
|
if response
|
38
|
-
return response unless response.
|
35
|
+
return response unless response.is_a?(Response)
|
36
|
+
|
37
|
+
return response unless response.headers.key?("upgrade")
|
39
38
|
|
40
39
|
upgrade_protocol = response.headers["upgrade"].split(/ *, */).first
|
41
40
|
|
42
|
-
return response unless upgrade_protocol && options.upgrade_handlers.
|
41
|
+
return response unless upgrade_protocol && options.upgrade_handlers.key?(upgrade_protocol)
|
43
42
|
|
44
|
-
protocol_handler = options.upgrade_handlers
|
43
|
+
protocol_handler = options.upgrade_handlers[upgrade_protocol]
|
45
44
|
|
46
45
|
return response unless protocol_handler
|
47
46
|
|
48
47
|
log { "upgrading to #{upgrade_protocol}..." }
|
49
48
|
connection = find_connection(request, connections, options)
|
50
|
-
connections << connection unless connections.include?(connection)
|
51
49
|
|
52
50
|
# do not upgrade already upgraded connections
|
53
51
|
return if connection.upgrade_protocol == upgrade_protocol
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTPX
|
4
|
+
module Plugins
|
5
|
+
#
|
6
|
+
# This plugin implements convenience methods for performing WEBDAV requests.
|
7
|
+
#
|
8
|
+
# https://gitlab.com/os85/httpx/wikis/WebDav
|
9
|
+
#
|
10
|
+
module WebDav
|
11
|
+
module InstanceMethods
|
12
|
+
def copy(src, dest)
|
13
|
+
request("COPY", src, headers: { "destination" => @options.origin.merge(dest) })
|
14
|
+
end
|
15
|
+
|
16
|
+
def move(src, dest)
|
17
|
+
request("MOVE", src, headers: { "destination" => @options.origin.merge(dest) })
|
18
|
+
end
|
19
|
+
|
20
|
+
def lock(path, timeout: nil, &blk)
|
21
|
+
headers = {}
|
22
|
+
headers["timeout"] = if timeout && timeout.positive?
|
23
|
+
"Second-#{timeout}"
|
24
|
+
else
|
25
|
+
"Infinite, Second-4100000000"
|
26
|
+
end
|
27
|
+
xml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" \
|
28
|
+
"<D:lockinfo xmlns:D=\"DAV:\">" \
|
29
|
+
"<D:lockscope><D:exclusive/></D:lockscope>" \
|
30
|
+
"<D:locktype><D:write/></D:locktype>" \
|
31
|
+
"<D:owner>null</D:owner>" \
|
32
|
+
"</D:lockinfo>"
|
33
|
+
response = request("LOCK", path, headers: headers, xml: xml)
|
34
|
+
|
35
|
+
return response unless response.is_a?(Response)
|
36
|
+
|
37
|
+
return response unless blk && response.status == 200
|
38
|
+
|
39
|
+
lock_token = response.headers["lock-token"]
|
40
|
+
|
41
|
+
begin
|
42
|
+
blk.call(response)
|
43
|
+
ensure
|
44
|
+
unlock(path, lock_token)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def unlock(path, lock_token)
|
49
|
+
request("UNLOCK", path, headers: { "lock-token" => lock_token })
|
50
|
+
end
|
51
|
+
|
52
|
+
def mkcol(dir)
|
53
|
+
request("MKCOL", dir)
|
54
|
+
end
|
55
|
+
|
56
|
+
def propfind(path, xml = nil)
|
57
|
+
body = case xml
|
58
|
+
when :acl
|
59
|
+
'<?xml version="1.0" encoding="utf-8" ?><D:propfind xmlns:D="DAV:"><D:prop><D:owner/>' \
|
60
|
+
"<D:supported-privilege-set/><D:current-user-privilege-set/><D:acl/></D:prop></D:propfind>"
|
61
|
+
when nil
|
62
|
+
'<?xml version="1.0" encoding="utf-8"?><DAV:propfind xmlns:DAV="DAV:"><DAV:allprop/></DAV:propfind>'
|
63
|
+
else
|
64
|
+
xml
|
65
|
+
end
|
66
|
+
|
67
|
+
request("PROPFIND", path, headers: { "depth" => "1" }, xml: body)
|
68
|
+
end
|
69
|
+
|
70
|
+
def proppatch(path, xml)
|
71
|
+
body = "<?xml version=\"1.0\"?>" \
|
72
|
+
"<D:propertyupdate xmlns:D=\"DAV:\" xmlns:Z=\"http://ns.example.com/standards/z39.50/\">#{xml}</D:propertyupdate>"
|
73
|
+
request("PROPPATCH", path, xml: body)
|
74
|
+
end
|
75
|
+
# %i[ orderpatch acl report search]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
register_plugin(:webdav, WebDav)
|
79
|
+
end
|
80
|
+
end
|