http 5.3.1 → 6.0.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/CHANGELOG.md +241 -41
- data/LICENSE.txt +1 -1
- data/README.md +110 -13
- data/UPGRADING.md +491 -0
- data/http.gemspec +32 -29
- data/lib/http/base64.rb +11 -1
- data/lib/http/chainable/helpers.rb +62 -0
- data/lib/http/chainable/verbs.rb +136 -0
- data/lib/http/chainable.rb +232 -136
- data/lib/http/client.rb +158 -127
- data/lib/http/connection/internals.rb +141 -0
- data/lib/http/connection.rb +126 -97
- data/lib/http/content_type.rb +61 -6
- data/lib/http/errors.rb +25 -1
- data/lib/http/feature.rb +65 -5
- data/lib/http/features/auto_deflate.rb +124 -17
- data/lib/http/features/auto_inflate.rb +38 -15
- data/lib/http/features/caching/entry.rb +178 -0
- data/lib/http/features/caching/in_memory_store.rb +63 -0
- data/lib/http/features/caching.rb +216 -0
- data/lib/http/features/digest_auth.rb +234 -0
- data/lib/http/features/instrumentation.rb +97 -17
- data/lib/http/features/logging.rb +183 -5
- data/lib/http/features/normalize_uri.rb +17 -0
- data/lib/http/features/raise_error.rb +18 -3
- data/lib/http/form_data/composite_io.rb +106 -0
- data/lib/http/form_data/file.rb +95 -0
- data/lib/http/form_data/multipart/param.rb +62 -0
- data/lib/http/form_data/multipart.rb +106 -0
- data/lib/http/form_data/part.rb +52 -0
- data/lib/http/form_data/readable.rb +58 -0
- data/lib/http/form_data/urlencoded.rb +175 -0
- data/lib/http/form_data/version.rb +8 -0
- data/lib/http/form_data.rb +102 -0
- data/lib/http/headers/known.rb +3 -0
- data/lib/http/headers/normalizer.rb +17 -36
- data/lib/http/headers.rb +172 -65
- data/lib/http/mime_type/adapter.rb +24 -9
- data/lib/http/mime_type/json.rb +19 -4
- data/lib/http/mime_type.rb +21 -3
- data/lib/http/options/definitions.rb +189 -0
- data/lib/http/options.rb +172 -125
- data/lib/http/redirector.rb +80 -75
- data/lib/http/request/body.rb +87 -6
- data/lib/http/request/builder.rb +184 -0
- data/lib/http/request/proxy.rb +83 -0
- data/lib/http/request/writer.rb +76 -16
- data/lib/http/request.rb +214 -98
- data/lib/http/response/body.rb +103 -18
- data/lib/http/response/inflater.rb +35 -7
- data/lib/http/response/parser.rb +98 -4
- data/lib/http/response/status/reasons.rb +2 -4
- data/lib/http/response/status.rb +141 -31
- data/lib/http/response.rb +219 -61
- data/lib/http/retriable/delay_calculator.rb +38 -11
- data/lib/http/retriable/errors.rb +21 -0
- data/lib/http/retriable/performer.rb +82 -38
- data/lib/http/session.rb +280 -0
- data/lib/http/timeout/global.rb +147 -34
- data/lib/http/timeout/null.rb +155 -9
- data/lib/http/timeout/per_operation.rb +139 -18
- data/lib/http/uri/normalizer.rb +82 -0
- data/lib/http/uri/parsing.rb +182 -0
- data/lib/http/uri.rb +289 -124
- data/lib/http/version.rb +2 -1
- data/lib/http.rb +11 -2
- data/sig/deps.rbs +122 -0
- data/sig/http.rbs +1619 -0
- data/test/http/base64_test.rb +28 -0
- data/test/http/client_test.rb +739 -0
- data/test/http/connection_test.rb +1533 -0
- data/test/http/content_type_test.rb +190 -0
- data/test/http/errors_test.rb +28 -0
- data/test/http/feature_test.rb +49 -0
- data/test/http/features/auto_deflate_test.rb +317 -0
- data/test/http/features/auto_inflate_test.rb +213 -0
- data/test/http/features/caching_test.rb +942 -0
- data/test/http/features/digest_auth_test.rb +996 -0
- data/test/http/features/instrumentation_test.rb +246 -0
- data/test/http/features/logging_test.rb +654 -0
- data/test/http/features/normalize_uri_test.rb +41 -0
- data/test/http/features/raise_error_test.rb +77 -0
- data/test/http/form_data/composite_io_test.rb +215 -0
- data/test/http/form_data/file_test.rb +255 -0
- data/test/http/form_data/fixtures/the-http-gem.info +1 -0
- data/test/http/form_data/multipart_test.rb +303 -0
- data/test/http/form_data/part_test.rb +90 -0
- data/test/http/form_data/urlencoded_test.rb +164 -0
- data/test/http/form_data_test.rb +232 -0
- data/test/http/headers/normalizer_test.rb +93 -0
- data/test/http/headers_test.rb +888 -0
- data/test/http/mime_type/json_test.rb +39 -0
- data/test/http/mime_type_test.rb +150 -0
- data/test/http/options/base_uri_test.rb +148 -0
- data/test/http/options/body_test.rb +21 -0
- data/test/http/options/features_test.rb +38 -0
- data/test/http/options/form_test.rb +21 -0
- data/test/http/options/headers_test.rb +32 -0
- data/test/http/options/json_test.rb +21 -0
- data/test/http/options/merge_test.rb +78 -0
- data/test/http/options/new_test.rb +37 -0
- data/test/http/options/proxy_test.rb +32 -0
- data/test/http/options_test.rb +575 -0
- data/test/http/redirector_test.rb +639 -0
- data/test/http/request/body_test.rb +318 -0
- data/test/http/request/builder_test.rb +623 -0
- data/test/http/request/writer_test.rb +391 -0
- data/test/http/request_test.rb +1733 -0
- data/test/http/response/body_test.rb +292 -0
- data/test/http/response/parser_test.rb +105 -0
- data/test/http/response/status_test.rb +322 -0
- data/test/http/response_test.rb +502 -0
- data/test/http/retriable/delay_calculator_test.rb +194 -0
- data/test/http/retriable/errors_test.rb +71 -0
- data/test/http/retriable/performer_test.rb +551 -0
- data/test/http/session_test.rb +424 -0
- data/test/http/timeout/global_test.rb +239 -0
- data/test/http/timeout/null_test.rb +218 -0
- data/test/http/timeout/per_operation_test.rb +220 -0
- data/test/http/uri/normalizer_test.rb +89 -0
- data/test/http/uri_test.rb +1140 -0
- data/test/http/version_test.rb +15 -0
- data/test/http_test.rb +818 -0
- data/test/regression_tests.rb +27 -0
- data/test/support/dummy_server/encoding_routes.rb +47 -0
- data/test/support/dummy_server/routes.rb +201 -0
- data/test/support/dummy_server/servlet.rb +81 -0
- data/test/support/dummy_server.rb +200 -0
- data/{spec → test}/support/fakeio.rb +2 -2
- data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
- data/test/support/http_handling_shared/timeout_tests.rb +134 -0
- data/test/support/http_handling_shared.rb +11 -0
- data/test/support/proxy_server.rb +207 -0
- data/test/support/servers/runner.rb +67 -0
- data/{spec → test}/support/simplecov.rb +11 -2
- data/test/support/ssl_helper.rb +108 -0
- data/test/test_helper.rb +38 -0
- metadata +108 -168
- data/.github/workflows/ci.yml +0 -67
- data/.gitignore +0 -15
- data/.rspec +0 -1
- data/.rubocop/layout.yml +0 -8
- data/.rubocop/metrics.yml +0 -4
- data/.rubocop/rspec.yml +0 -9
- data/.rubocop/style.yml +0 -32
- data/.rubocop.yml +0 -11
- data/.rubocop_todo.yml +0 -219
- data/.yardopts +0 -2
- data/CHANGES_OLD.md +0 -1002
- data/Gemfile +0 -51
- data/Guardfile +0 -18
- data/Rakefile +0 -64
- data/lib/http/headers/mixin.rb +0 -34
- data/lib/http/retriable/client.rb +0 -37
- data/logo.png +0 -0
- data/spec/lib/http/client_spec.rb +0 -556
- data/spec/lib/http/connection_spec.rb +0 -88
- data/spec/lib/http/content_type_spec.rb +0 -47
- data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
- data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
- data/spec/lib/http/features/instrumentation_spec.rb +0 -81
- data/spec/lib/http/features/logging_spec.rb +0 -65
- data/spec/lib/http/features/raise_error_spec.rb +0 -62
- data/spec/lib/http/headers/mixin_spec.rb +0 -36
- data/spec/lib/http/headers/normalizer_spec.rb +0 -52
- data/spec/lib/http/headers_spec.rb +0 -527
- data/spec/lib/http/options/body_spec.rb +0 -15
- data/spec/lib/http/options/features_spec.rb +0 -33
- data/spec/lib/http/options/form_spec.rb +0 -15
- data/spec/lib/http/options/headers_spec.rb +0 -24
- data/spec/lib/http/options/json_spec.rb +0 -15
- data/spec/lib/http/options/merge_spec.rb +0 -68
- data/spec/lib/http/options/new_spec.rb +0 -30
- data/spec/lib/http/options/proxy_spec.rb +0 -20
- data/spec/lib/http/options_spec.rb +0 -13
- data/spec/lib/http/redirector_spec.rb +0 -530
- data/spec/lib/http/request/body_spec.rb +0 -211
- data/spec/lib/http/request/writer_spec.rb +0 -121
- data/spec/lib/http/request_spec.rb +0 -234
- data/spec/lib/http/response/body_spec.rb +0 -85
- data/spec/lib/http/response/parser_spec.rb +0 -74
- data/spec/lib/http/response/status_spec.rb +0 -253
- data/spec/lib/http/response_spec.rb +0 -262
- data/spec/lib/http/retriable/delay_calculator_spec.rb +0 -69
- data/spec/lib/http/retriable/performer_spec.rb +0 -302
- data/spec/lib/http/uri/normalizer_spec.rb +0 -95
- data/spec/lib/http/uri_spec.rb +0 -71
- data/spec/lib/http_spec.rb +0 -535
- data/spec/regression_specs.rb +0 -24
- data/spec/spec_helper.rb +0 -89
- data/spec/support/black_hole.rb +0 -13
- data/spec/support/dummy_server/servlet.rb +0 -203
- data/spec/support/dummy_server.rb +0 -44
- data/spec/support/fuubar.rb +0 -21
- data/spec/support/http_handling_shared.rb +0 -190
- data/spec/support/proxy_server.rb +0 -39
- data/spec/support/servers/config.rb +0 -11
- data/spec/support/servers/runner.rb +0 -19
- data/spec/support/ssl_helper.rb +0 -104
- /data/{spec → test}/support/capture_warning.rb +0 -0
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class HTTPRedirectorTest < Minitest::Test
|
|
6
|
+
cover "HTTP::Redirector*"
|
|
7
|
+
|
|
8
|
+
def simple_response(status, body = "", headers = {})
|
|
9
|
+
HTTP::Response.new(
|
|
10
|
+
status: status,
|
|
11
|
+
version: "1.1",
|
|
12
|
+
headers: headers,
|
|
13
|
+
body: body,
|
|
14
|
+
request: HTTP::Request.new(verb: :get, uri: "http://example.com")
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def redirect_response(status, location)
|
|
19
|
+
simple_response status, "", "Location" => location
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# #strict
|
|
23
|
+
|
|
24
|
+
def test_strict_returns_true_by_default
|
|
25
|
+
redirector = HTTP::Redirector.new
|
|
26
|
+
|
|
27
|
+
assert redirector.strict
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# #max_hops
|
|
31
|
+
|
|
32
|
+
def test_max_hops_returns_5_by_default
|
|
33
|
+
redirector = HTTP::Redirector.new
|
|
34
|
+
|
|
35
|
+
assert_equal 5, redirector.max_hops
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_max_hops_coerces_string_value_to_integer
|
|
39
|
+
redirector = HTTP::Redirector.new(max_hops: "3")
|
|
40
|
+
|
|
41
|
+
assert_equal 3, redirector.max_hops
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# #perform
|
|
45
|
+
|
|
46
|
+
def test_perform_fails_with_too_many_redirects_error_if_max_hops_reached
|
|
47
|
+
redirector = HTTP::Redirector.new
|
|
48
|
+
req = HTTP::Request.new verb: :head, uri: "http://example.com"
|
|
49
|
+
res = proc { |prev_req| redirect_response(301, "#{prev_req.uri}/1") }
|
|
50
|
+
|
|
51
|
+
assert_raises(HTTP::Redirector::TooManyRedirectsError) do
|
|
52
|
+
redirector.perform(req, res.call(req), &res)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_perform_fails_with_endless_redirect_error_if_endless_loop_detected
|
|
57
|
+
redirector = HTTP::Redirector.new
|
|
58
|
+
req = HTTP::Request.new verb: :head, uri: "http://example.com"
|
|
59
|
+
res = redirect_response(301, req.uri)
|
|
60
|
+
|
|
61
|
+
assert_raises(HTTP::Redirector::EndlessRedirectError) do
|
|
62
|
+
redirector.perform(req, res) { res }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_perform_fails_with_state_error_if_no_location_header
|
|
67
|
+
redirector = HTTP::Redirector.new
|
|
68
|
+
req = HTTP::Request.new verb: :head, uri: "http://example.com"
|
|
69
|
+
res = simple_response(301)
|
|
70
|
+
|
|
71
|
+
assert_raises(HTTP::StateError) do
|
|
72
|
+
redirector.perform(req, res) { |_| nil }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def test_perform_returns_first_non_redirect_response
|
|
77
|
+
redirector = HTTP::Redirector.new
|
|
78
|
+
req = HTTP::Request.new verb: :head, uri: "http://example.com"
|
|
79
|
+
hops = [
|
|
80
|
+
redirect_response(301, "http://example.com/1"),
|
|
81
|
+
redirect_response(301, "http://example.com/2"),
|
|
82
|
+
redirect_response(301, "http://example.com/3"),
|
|
83
|
+
simple_response(200, "foo"),
|
|
84
|
+
redirect_response(301, "http://example.com/4"),
|
|
85
|
+
simple_response(200, "bar")
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
res = redirector.perform(req, hops.shift) { hops.shift }
|
|
89
|
+
|
|
90
|
+
assert_equal "foo", res.to_s
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_perform_concatenates_multiple_location_headers
|
|
94
|
+
redirector = HTTP::Redirector.new
|
|
95
|
+
req = HTTP::Request.new verb: :head, uri: "http://example.com"
|
|
96
|
+
headers = HTTP::Headers.new
|
|
97
|
+
|
|
98
|
+
%w[http://example.com /123].each { |loc| headers.add("Location", loc) }
|
|
99
|
+
|
|
100
|
+
res = redirector.perform(req, simple_response(301, "", headers)) do |redirect|
|
|
101
|
+
simple_response(200, redirect.uri.to_s)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
assert_equal "http://example.com/123", res.to_s
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# on_redirect callback
|
|
108
|
+
|
|
109
|
+
def test_perform_with_on_redirect_calls_on_redirect
|
|
110
|
+
redirect_response_captured = nil
|
|
111
|
+
redirect_location_captured = nil
|
|
112
|
+
redirector = HTTP::Redirector.new(
|
|
113
|
+
on_redirect: proc do |response, location|
|
|
114
|
+
redirect_response_captured = response
|
|
115
|
+
redirect_location_captured = location
|
|
116
|
+
end
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
req = HTTP::Request.new verb: :head, uri: "http://example.com"
|
|
120
|
+
hops = [
|
|
121
|
+
redirect_response(301, "http://example.com/1"),
|
|
122
|
+
redirect_response(301, "http://example.com/2"),
|
|
123
|
+
simple_response(200, "foo")
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
redirector.perform(req, hops.shift) do |prev_req, _|
|
|
127
|
+
assert_equal prev_req.uri.to_s, redirect_location_captured.uri.to_s
|
|
128
|
+
assert_equal 301, redirect_response_captured.code
|
|
129
|
+
hops.shift
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# following 300, 301, 302 redirects (strict mode)
|
|
134
|
+
|
|
135
|
+
unsafe_verbs = %i[put post delete]
|
|
136
|
+
|
|
137
|
+
[300, 301, 302].each do |status_code|
|
|
138
|
+
define_method(:"test_following_#{status_code}_strict_follows_with_original_verb_if_safe") do
|
|
139
|
+
redirector = HTTP::Redirector.new(strict: true)
|
|
140
|
+
req = HTTP::Request.new verb: :head, uri: "http://example.com"
|
|
141
|
+
res = redirect_response status_code, "http://example.com/1"
|
|
142
|
+
|
|
143
|
+
redirector.perform(req, res) do |prev_req, _|
|
|
144
|
+
assert_equal :head, prev_req.verb
|
|
145
|
+
simple_response 200
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
unsafe_verbs.each do |verb|
|
|
150
|
+
define_method(:"test_following_#{status_code}_strict_raises_state_error_for_#{verb}") do
|
|
151
|
+
redirector = HTTP::Redirector.new(strict: true)
|
|
152
|
+
req = HTTP::Request.new verb: verb, uri: "http://example.com"
|
|
153
|
+
res = redirect_response status_code, "http://example.com/1"
|
|
154
|
+
|
|
155
|
+
assert_raises(HTTP::StateError) do
|
|
156
|
+
redirector.perform(req, res) { simple_response 200 }
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
define_method(:"test_following_#{status_code}_non_strict_follows_with_original_verb_if_safe") do
|
|
162
|
+
redirector = HTTP::Redirector.new(strict: false)
|
|
163
|
+
req = HTTP::Request.new verb: :head, uri: "http://example.com"
|
|
164
|
+
res = redirect_response status_code, "http://example.com/1"
|
|
165
|
+
|
|
166
|
+
redirector.perform(req, res) do |prev_req, _|
|
|
167
|
+
assert_equal :head, prev_req.verb
|
|
168
|
+
simple_response 200
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
unsafe_verbs.each do |verb|
|
|
173
|
+
define_method(:"test_following_#{status_code}_non_strict_follows_with_get_for_#{verb}") do
|
|
174
|
+
redirector = HTTP::Redirector.new(strict: false)
|
|
175
|
+
req = HTTP::Request.new verb: verb, uri: "http://example.com"
|
|
176
|
+
res = redirect_response status_code, "http://example.com/1"
|
|
177
|
+
|
|
178
|
+
redirector.perform(req, res) do |prev_req, _|
|
|
179
|
+
assert_equal :get, prev_req.verb
|
|
180
|
+
simple_response 200
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# following 303 redirect
|
|
187
|
+
|
|
188
|
+
def test_following_303_follows_with_head_if_original_request_was_head
|
|
189
|
+
redirector = HTTP::Redirector.new
|
|
190
|
+
req = HTTP::Request.new verb: :head, uri: "http://example.com"
|
|
191
|
+
res = redirect_response 303, "http://example.com/1"
|
|
192
|
+
|
|
193
|
+
redirector.perform(req, res) do |prev_req, _|
|
|
194
|
+
assert_equal :head, prev_req.verb
|
|
195
|
+
simple_response 200
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def test_following_303_follows_with_get_if_original_request_was_get
|
|
200
|
+
redirector = HTTP::Redirector.new
|
|
201
|
+
req = HTTP::Request.new verb: :get, uri: "http://example.com"
|
|
202
|
+
res = redirect_response 303, "http://example.com/1"
|
|
203
|
+
|
|
204
|
+
redirector.perform(req, res) do |prev_req, _|
|
|
205
|
+
assert_equal :get, prev_req.verb
|
|
206
|
+
simple_response 200
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def test_following_303_follows_with_get_if_original_request_was_neither_get_nor_head
|
|
211
|
+
redirector = HTTP::Redirector.new
|
|
212
|
+
req = HTTP::Request.new verb: :post, uri: "http://example.com"
|
|
213
|
+
res = redirect_response 303, "http://example.com/1"
|
|
214
|
+
|
|
215
|
+
redirector.perform(req, res) do |prev_req, _|
|
|
216
|
+
assert_equal :get, prev_req.verb
|
|
217
|
+
simple_response 200
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# following 307 redirect
|
|
222
|
+
|
|
223
|
+
def test_following_307_follows_with_original_requests_verb
|
|
224
|
+
redirector = HTTP::Redirector.new
|
|
225
|
+
req = HTTP::Request.new verb: :post, uri: "http://example.com"
|
|
226
|
+
res = redirect_response 307, "http://example.com/1"
|
|
227
|
+
|
|
228
|
+
redirector.perform(req, res) do |prev_req, _|
|
|
229
|
+
assert_equal :post, prev_req.verb
|
|
230
|
+
simple_response 200
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# following 308 redirect
|
|
235
|
+
|
|
236
|
+
def test_following_308_follows_with_original_requests_verb
|
|
237
|
+
redirector = HTTP::Redirector.new
|
|
238
|
+
req = HTTP::Request.new verb: :post, uri: "http://example.com"
|
|
239
|
+
res = redirect_response 308, "http://example.com/1"
|
|
240
|
+
|
|
241
|
+
redirector.perform(req, res) do |prev_req, _|
|
|
242
|
+
assert_equal :post, prev_req.verb
|
|
243
|
+
simple_response 200
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# changing verbs during redirects
|
|
248
|
+
|
|
249
|
+
def test_changing_verbs_follows_without_body_content_type_if_it_has_to_change_verb
|
|
250
|
+
redirector = HTTP::Redirector.new(strict: false)
|
|
251
|
+
post_body = HTTP::Request::Body.new("i might be way longer in real life")
|
|
252
|
+
cookie = "dont=eat my cookies"
|
|
253
|
+
|
|
254
|
+
req = HTTP::Request.new(
|
|
255
|
+
verb: :post, uri: "http://example.com",
|
|
256
|
+
body: post_body, headers: {
|
|
257
|
+
"Content-Type" => "meme",
|
|
258
|
+
"Cookie" => cookie
|
|
259
|
+
}
|
|
260
|
+
)
|
|
261
|
+
res = redirect_response 302, "http://example.com/1"
|
|
262
|
+
|
|
263
|
+
redirector.perform(req, res) do |prev_req, _|
|
|
264
|
+
assert_equal HTTP::Request::Body.new(nil), prev_req.body
|
|
265
|
+
assert_equal cookie, prev_req.headers["Cookie"]
|
|
266
|
+
assert_nil prev_req.headers["Content-Type"]
|
|
267
|
+
simple_response 200
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def test_changing_verbs_leaves_body_content_type_intact_if_it_does_not_have_to_change_verb
|
|
272
|
+
redirector = HTTP::Redirector.new(strict: false)
|
|
273
|
+
post_body = HTTP::Request::Body.new("i might be way longer in real life")
|
|
274
|
+
cookie = "dont=eat my cookies"
|
|
275
|
+
|
|
276
|
+
req = HTTP::Request.new(
|
|
277
|
+
verb: :post, uri: "http://example.com",
|
|
278
|
+
body: post_body, headers: {
|
|
279
|
+
"Content-Type" => "meme",
|
|
280
|
+
"Cookie" => cookie
|
|
281
|
+
}
|
|
282
|
+
)
|
|
283
|
+
res = redirect_response 307, "http://example.com/1"
|
|
284
|
+
|
|
285
|
+
redirector.perform(req, res) do |prev_req, _|
|
|
286
|
+
assert_equal post_body, prev_req.body
|
|
287
|
+
assert_equal cookie, prev_req.headers["Cookie"]
|
|
288
|
+
assert_equal "meme", prev_req.headers["Content-Type"]
|
|
289
|
+
simple_response 200
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# max_hops: 0
|
|
294
|
+
|
|
295
|
+
def test_with_max_hops_0_does_not_limit_redirects
|
|
296
|
+
redirector = HTTP::Redirector.new(max_hops: 0)
|
|
297
|
+
req = HTTP::Request.new verb: :head, uri: "http://example.com"
|
|
298
|
+
hops = (1..10).map { |i| redirect_response(301, "http://example.com/#{i}") }
|
|
299
|
+
hops << simple_response(200, "done")
|
|
300
|
+
|
|
301
|
+
res = redirector.perform(req, hops.shift) { hops.shift }
|
|
302
|
+
|
|
303
|
+
assert_equal "done", res.to_s
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# max_hops: 1
|
|
307
|
+
|
|
308
|
+
def test_with_max_hops_1_allows_exactly_one_redirect
|
|
309
|
+
redirector = HTTP::Redirector.new(max_hops: 1)
|
|
310
|
+
req = HTTP::Request.new verb: :head, uri: "http://example.com"
|
|
311
|
+
hops = [
|
|
312
|
+
redirect_response(301, "http://example.com/1"),
|
|
313
|
+
simple_response(200, "one hop")
|
|
314
|
+
]
|
|
315
|
+
|
|
316
|
+
res = redirector.perform(req, hops.shift) { hops.shift }
|
|
317
|
+
|
|
318
|
+
assert_equal "one hop", res.to_s
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def test_with_max_hops_1_raises_too_many_redirects_error_on_the_second_redirect
|
|
322
|
+
redirector = HTTP::Redirector.new(max_hops: 1)
|
|
323
|
+
req = HTTP::Request.new verb: :head, uri: "http://example.com"
|
|
324
|
+
hops = [
|
|
325
|
+
redirect_response(301, "http://example.com/1"),
|
|
326
|
+
redirect_response(301, "http://example.com/2"),
|
|
327
|
+
simple_response(200, "unreachable")
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
assert_raises(HTTP::Redirector::TooManyRedirectsError) do
|
|
331
|
+
redirector.perform(req, hops.shift) { hops.shift }
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# with :get verb on strict-sensitive codes
|
|
336
|
+
|
|
337
|
+
[300, 301, 302].each do |status_code|
|
|
338
|
+
define_method(:"test_strict_follows_#{status_code}_redirect_with_get_verb_without_raising") do
|
|
339
|
+
redirector = HTTP::Redirector.new(strict: true)
|
|
340
|
+
req = HTTP::Request.new verb: :get, uri: "http://example.com"
|
|
341
|
+
res = redirect_response status_code, "http://example.com/1"
|
|
342
|
+
|
|
343
|
+
result = redirector.perform(req, res) do |prev_req, _|
|
|
344
|
+
assert_equal :get, prev_req.verb
|
|
345
|
+
simple_response 200, "ok"
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
assert_equal "ok", result.to_s
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# following 303 redirect with unsafe verbs
|
|
353
|
+
|
|
354
|
+
def test_following_303_follows_with_get_if_original_request_was_put
|
|
355
|
+
redirector = HTTP::Redirector.new
|
|
356
|
+
req = HTTP::Request.new verb: :put, uri: "http://example.com"
|
|
357
|
+
res = redirect_response 303, "http://example.com/1"
|
|
358
|
+
|
|
359
|
+
redirector.perform(req, res) do |prev_req, _|
|
|
360
|
+
assert_equal :get, prev_req.verb
|
|
361
|
+
simple_response 200
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def test_following_303_follows_with_get_if_original_request_was_delete
|
|
366
|
+
redirector = HTTP::Redirector.new
|
|
367
|
+
req = HTTP::Request.new verb: :delete, uri: "http://example.com"
|
|
368
|
+
res = redirect_response 303, "http://example.com/1"
|
|
369
|
+
|
|
370
|
+
redirector.perform(req, res) do |prev_req, _|
|
|
371
|
+
assert_equal :get, prev_req.verb
|
|
372
|
+
simple_response 200
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# on_redirect callback behavior
|
|
377
|
+
|
|
378
|
+
def test_on_redirect_passes_both_response_and_request_to_on_redirect
|
|
379
|
+
captured_response = nil
|
|
380
|
+
captured_request = nil
|
|
381
|
+
redirector = HTTP::Redirector.new(
|
|
382
|
+
on_redirect: proc do |response, request|
|
|
383
|
+
captured_response = response
|
|
384
|
+
captured_request = request
|
|
385
|
+
end
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
req = HTTP::Request.new verb: :get, uri: "http://example.com"
|
|
389
|
+
hops = [
|
|
390
|
+
redirect_response(301, "http://example.com/1"),
|
|
391
|
+
simple_response(200, "done")
|
|
392
|
+
]
|
|
393
|
+
|
|
394
|
+
redirector.perform(req, hops.shift) { hops.shift }
|
|
395
|
+
|
|
396
|
+
refute_nil captured_response
|
|
397
|
+
refute_nil captured_request
|
|
398
|
+
assert_equal 301, captured_response.code
|
|
399
|
+
assert_equal "http://example.com/1", captured_request.uri.to_s
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def test_on_redirect_works_without_on_redirect_callback
|
|
403
|
+
redirector = HTTP::Redirector.new
|
|
404
|
+
|
|
405
|
+
req = HTTP::Request.new verb: :get, uri: "http://example.com"
|
|
406
|
+
hops = [
|
|
407
|
+
redirect_response(301, "http://example.com/1"),
|
|
408
|
+
simple_response(200, "done")
|
|
409
|
+
]
|
|
410
|
+
|
|
411
|
+
res = redirector.perform(req, hops.shift) { hops.shift }
|
|
412
|
+
|
|
413
|
+
assert_equal "done", res.to_s
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def test_on_redirect_works_when_on_redirect_is_explicitly_nil
|
|
417
|
+
redirector = HTTP::Redirector.new(on_redirect: nil)
|
|
418
|
+
|
|
419
|
+
req = HTTP::Request.new verb: :get, uri: "http://example.com"
|
|
420
|
+
hops = [
|
|
421
|
+
redirect_response(301, "http://example.com/1"),
|
|
422
|
+
simple_response(200, "done")
|
|
423
|
+
]
|
|
424
|
+
|
|
425
|
+
res = redirector.perform(req, hops.shift) { hops.shift }
|
|
426
|
+
|
|
427
|
+
assert_equal "done", res.to_s
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# block yielding
|
|
431
|
+
|
|
432
|
+
def test_perform_yields_the_request_to_the_block
|
|
433
|
+
redirector = HTTP::Redirector.new
|
|
434
|
+
req = HTTP::Request.new verb: :get, uri: "http://example.com"
|
|
435
|
+
hops = [
|
|
436
|
+
redirect_response(301, "http://example.com/1"),
|
|
437
|
+
simple_response(200, "done")
|
|
438
|
+
]
|
|
439
|
+
|
|
440
|
+
yielded_request = nil
|
|
441
|
+
redirector.perform(req, hops.shift) do |r|
|
|
442
|
+
yielded_request = r
|
|
443
|
+
hops.shift
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
refute_nil yielded_request
|
|
447
|
+
assert_equal "http://example.com/1", yielded_request.uri.to_s
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# flush
|
|
451
|
+
|
|
452
|
+
def test_perform_calls_flush_on_intermediate_redirect_responses
|
|
453
|
+
redirector = HTTP::Redirector.new
|
|
454
|
+
req = HTTP::Request.new verb: :get, uri: "http://example.com"
|
|
455
|
+
res = redirect_response(301, "http://example.com/1")
|
|
456
|
+
|
|
457
|
+
flushed = false
|
|
458
|
+
original_flush = res.method(:flush)
|
|
459
|
+
res.define_singleton_method(:flush) do
|
|
460
|
+
flushed = true
|
|
461
|
+
original_flush.call
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
redirector.perform(req, res) { simple_response(200, "done") }
|
|
465
|
+
|
|
466
|
+
assert flushed, "expected response.flush to be called during redirect"
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
# endless redirect detection
|
|
470
|
+
|
|
471
|
+
def test_perform_tracks_visited_urls_with_verb_uri_and_cookies
|
|
472
|
+
redirector = HTTP::Redirector.new
|
|
473
|
+
req = HTTP::Request.new verb: :head, uri: "http://example.com"
|
|
474
|
+
res = redirect_response(301, "http://example.com")
|
|
475
|
+
|
|
476
|
+
err = assert_raises(HTTP::Redirector::EndlessRedirectError) do
|
|
477
|
+
redirector.perform(req, res) { redirect_response(301, "http://example.com") }
|
|
478
|
+
end
|
|
479
|
+
assert_kind_of HTTP::Redirector::TooManyRedirectsError, err
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def test_perform_does_not_falsely_detect_endless_loop_when_cookies_change
|
|
483
|
+
redirector = HTTP::Redirector.new
|
|
484
|
+
req = HTTP::Request.new verb: :get, uri: "http://example.com"
|
|
485
|
+
res = redirect_response(302, "http://example.com")
|
|
486
|
+
|
|
487
|
+
call_count = 0
|
|
488
|
+
result = redirector.perform(req, res) do |redirect_req|
|
|
489
|
+
call_count += 1
|
|
490
|
+
redirect_req.headers.set("Cookie", "auth=ok")
|
|
491
|
+
if call_count == 1
|
|
492
|
+
redirect_response(302, "http://example.com")
|
|
493
|
+
else
|
|
494
|
+
simple_response(200, "authenticated")
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
assert_equal 2, call_count
|
|
499
|
+
assert_equal "authenticated", result.to_s
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def test_perform_raises_state_error_with_descriptive_message_when_no_location_header
|
|
503
|
+
redirector = HTTP::Redirector.new
|
|
504
|
+
req = HTTP::Request.new verb: :head, uri: "http://example.com"
|
|
505
|
+
res = simple_response(301)
|
|
506
|
+
|
|
507
|
+
err = assert_raises(HTTP::StateError) do
|
|
508
|
+
redirector.perform(req, res) { |_| nil }
|
|
509
|
+
end
|
|
510
|
+
assert_match(/no Location header/, err.message)
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
# strict mode StateError messages
|
|
514
|
+
|
|
515
|
+
def test_strict_mode_includes_status_in_the_error_message
|
|
516
|
+
redirector = HTTP::Redirector.new(strict: true)
|
|
517
|
+
req = HTTP::Request.new verb: :post, uri: "http://example.com"
|
|
518
|
+
res = redirect_response 301, "http://example.com/1"
|
|
519
|
+
|
|
520
|
+
err = assert_raises(HTTP::StateError) do
|
|
521
|
+
redirector.perform(req, res) { simple_response 200 }
|
|
522
|
+
end
|
|
523
|
+
assert_match(/301/, err.message)
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
# max_hops: 2 with endless redirect loop
|
|
527
|
+
|
|
528
|
+
def test_with_max_hops_2_detects_endless_loop_before_reaching_max_hops
|
|
529
|
+
redirector = HTTP::Redirector.new(max_hops: 2)
|
|
530
|
+
req = HTTP::Request.new verb: :head, uri: "http://example.com"
|
|
531
|
+
res = redirect_response(301, "http://example.com")
|
|
532
|
+
|
|
533
|
+
assert_raises(HTTP::Redirector::EndlessRedirectError) do
|
|
534
|
+
redirector.perform(req, res) { redirect_response(301, "http://example.com") }
|
|
535
|
+
end
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
def test_perform_detects_endless_loop_when_repeated_url_is_not_the_first_one_visited
|
|
539
|
+
redirector = HTTP::Redirector.new
|
|
540
|
+
req = HTTP::Request.new verb: :get, uri: "http://a.example.com"
|
|
541
|
+
hops = [
|
|
542
|
+
redirect_response(301, "http://b.example.com"),
|
|
543
|
+
redirect_response(301, "http://c.example.com"),
|
|
544
|
+
redirect_response(301, "http://b.example.com"),
|
|
545
|
+
redirect_response(301, "http://d.example.com"),
|
|
546
|
+
simple_response(200, "unreachable")
|
|
547
|
+
]
|
|
548
|
+
|
|
549
|
+
assert_raises(HTTP::Redirector::EndlessRedirectError) do
|
|
550
|
+
redirector.perform(req, hops.shift) { hops.shift }
|
|
551
|
+
end
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
# sensitive headers
|
|
555
|
+
|
|
556
|
+
def test_sensitive_headers_preserves_authorization_and_cookie_when_redirecting_to_same_origin
|
|
557
|
+
redirector = HTTP::Redirector.new
|
|
558
|
+
req = HTTP::Request.new verb: :get, uri: "http://example.com"
|
|
559
|
+
req.headers.set("Authorization", "Bearer secret")
|
|
560
|
+
req.headers.set("Cookie", "session=abc")
|
|
561
|
+
hops = [
|
|
562
|
+
redirect_response(301, "http://example.com/other"),
|
|
563
|
+
simple_response(200, "done")
|
|
564
|
+
]
|
|
565
|
+
|
|
566
|
+
redirector.perform(req, hops.shift) do |request|
|
|
567
|
+
assert_equal "Bearer secret", request.headers["Authorization"]
|
|
568
|
+
assert_equal "session=abc", request.headers["Cookie"]
|
|
569
|
+
hops.shift
|
|
570
|
+
end
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
def test_sensitive_headers_strips_authorization_and_cookie_when_redirecting_to_different_host
|
|
574
|
+
redirector = HTTP::Redirector.new
|
|
575
|
+
req = HTTP::Request.new verb: :get, uri: "http://example.com"
|
|
576
|
+
req.headers.set("Authorization", "Bearer secret")
|
|
577
|
+
req.headers.set("Cookie", "session=abc")
|
|
578
|
+
hops = [
|
|
579
|
+
redirect_response(301, "http://other.example.com/"),
|
|
580
|
+
simple_response(200, "done")
|
|
581
|
+
]
|
|
582
|
+
|
|
583
|
+
redirector.perform(req, hops.shift) do |request|
|
|
584
|
+
assert_nil request.headers["Authorization"]
|
|
585
|
+
assert_nil request.headers["Cookie"]
|
|
586
|
+
hops.shift
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
def test_sensitive_headers_strips_authorization_and_cookie_when_redirecting_to_different_scheme
|
|
591
|
+
redirector = HTTP::Redirector.new
|
|
592
|
+
req = HTTP::Request.new verb: :get, uri: "http://example.com"
|
|
593
|
+
req.headers.set("Authorization", "Bearer secret")
|
|
594
|
+
req.headers.set("Cookie", "session=abc")
|
|
595
|
+
hops = [
|
|
596
|
+
redirect_response(301, "https://example.com/"),
|
|
597
|
+
simple_response(200, "done")
|
|
598
|
+
]
|
|
599
|
+
|
|
600
|
+
redirector.perform(req, hops.shift) do |request|
|
|
601
|
+
assert_nil request.headers["Authorization"]
|
|
602
|
+
assert_nil request.headers["Cookie"]
|
|
603
|
+
hops.shift
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def test_sensitive_headers_strips_authorization_and_cookie_when_redirecting_to_different_port
|
|
608
|
+
redirector = HTTP::Redirector.new
|
|
609
|
+
req = HTTP::Request.new verb: :get, uri: "http://example.com"
|
|
610
|
+
req.headers.set("Authorization", "Bearer secret")
|
|
611
|
+
req.headers.set("Cookie", "session=abc")
|
|
612
|
+
hops = [
|
|
613
|
+
redirect_response(301, "http://example.com:8080/"),
|
|
614
|
+
simple_response(200, "done")
|
|
615
|
+
]
|
|
616
|
+
|
|
617
|
+
redirector.perform(req, hops.shift) do |request|
|
|
618
|
+
assert_nil request.headers["Authorization"]
|
|
619
|
+
assert_nil request.headers["Cookie"]
|
|
620
|
+
hops.shift
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
# verb change does not cause false endless loop detection
|
|
625
|
+
|
|
626
|
+
def test_perform_does_not_falsely_detect_endless_loop_when_verb_changes_for_same_url
|
|
627
|
+
req = HTTP::Request.new verb: :post, uri: "http://example.com"
|
|
628
|
+
hops = [
|
|
629
|
+
redirect_response(302, "http://example.com/done"),
|
|
630
|
+
simple_response(200, "done")
|
|
631
|
+
]
|
|
632
|
+
|
|
633
|
+
res = HTTP::Redirector.new(strict: false, max_hops: 5).perform(
|
|
634
|
+
req, redirect_response(302, "http://example.com")
|
|
635
|
+
) { hops.shift }
|
|
636
|
+
|
|
637
|
+
assert_equal "done", res.to_s
|
|
638
|
+
end
|
|
639
|
+
end
|