http.rb 1.3.5 → 1.3.7
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 +25 -0
- data/lib/HTTP/REDIRECT.rb +26 -0
- data/lib/HTTP/VERSION.rb +1 -1
- data/lib/HTTP/request.rb +6 -2
- data/test/HTTP/REDIRECT_test.rb +82 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6d2e0827d0ee0a2f4215eabfd6cd2f952bacb1d12dc657ab1a488ef85a91a46f
|
|
4
|
+
data.tar.gz: 5d343996ad5cba48c51c9df5c7f0b31895f3a437d8be6cc15d5fdaff64075430
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 644e855a9c30b1ec23e0f2b3db27850ad9018574b37d1e25bcb63ce3aa11371d81a7a372c56cbd9c8f1a284ecf2cdaa0f7219b331da32c3d2fd3b6f4ea90df73
|
|
7
|
+
data.tar.gz: 751e3dfc085e8e9c1af0fc372bf948404fd6ac4839eb79ada5d38f390ea9a35ec6ce51381deda5ede866a10f6dafbe208a24ad23b4b15d143fad51a9d557c429
|
data/CHANGELOG
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 20261011
|
|
4
|
+
|
|
5
|
+
1.3.7: The options hash is no longer altered, so no_redirect, retries, and basic authentication survive a call and a redirect.
|
|
6
|
+
|
|
7
|
+
HTTP.request took no_redirect, username, password, and the retry options out of the caller's hash with delete. A hash passed twice had lost no_redirect by the second call, which then followed the redirect it was asked not to. And the redirect was made with what was left of the hash, so retries and basic authentication were lost at the first redirect, even to the same origin.
|
|
8
|
+
|
|
9
|
+
1. ~ HTTP.request: works on a copy of the options hash, leaving the caller's as it was.
|
|
10
|
+
2. ~ HTTP.request: a redirect is made with the options as given, less what goes no further than the origin.
|
|
11
|
+
3. ~ lib/HTTP/REDIRECT.rb: + HTTP.redirect_options, which drops username and password where a redirect goes to another origin, as 1.3.6 does the credential headers.
|
|
12
|
+
4. ~ test/HTTP/REDIRECT_test.rb: + the options hash left as it was, no_redirect kept for a second call, retries kept after a redirect, and basic authentication kept for the same origin and dropped for another. All but the last fail against 1.3.6, which dropped it for every origin.
|
|
13
|
+
5. ~ HTTP::VERSION: /1.3.6/1.3.7/
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## 20261011
|
|
17
|
+
|
|
18
|
+
1.3.6: Authorization and Cookie headers go no further than the origin they were given to.
|
|
19
|
+
|
|
20
|
+
A 307 or 308 was followed with every header it was given, so an Authorization or Cookie header meant for one host was sent to whichever host the Location named. A 301, 302, or 303 sent no headers on at all, so it did not leak, though only because it lost everything else too. Following curl and browsers, credentials now stay with their origin: the same scheme, host, and port.
|
|
21
|
+
|
|
22
|
+
1. + lib/HTTP/REDIRECT.rb: HTTP::REDIRECT::CREDENTIAL_HEADERS (Authorization, Cookie); HTTP.same_origin?, and HTTP.redirect_headers, which drops the credential headers, whatever their case, where a redirect goes to another origin.
|
|
23
|
+
2. ~ HTTP.request: a 307 or 308 is followed with redirect_headers rather than the headers as given.
|
|
24
|
+
3. + test/HTTP/REDIRECT_test.rb: credentials kept for the same origin, and dropped for another host by 307 and by 308, for another scheme, and for another port, with other headers carried on. All but the first fail against 1.3.5.
|
|
25
|
+
4. ~ HTTP::VERSION: /1.3.5/1.3.6/
|
|
26
|
+
|
|
27
|
+
|
|
3
28
|
## 20261011
|
|
4
29
|
|
|
5
30
|
1.3.5: Non-ASCII is URL-encoded in full, and without a warning.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# HTTP/REDIRECT.rb
|
|
2
|
+
# HTTP::REDIRECT (redirect helpers)
|
|
3
|
+
|
|
4
|
+
module HTTP
|
|
5
|
+
module REDIRECT
|
|
6
|
+
CREDENTIAL_HEADERS = %w{authorization cookie}.freeze
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def same_origin?(a, b)
|
|
10
|
+
[a.scheme, a.host, a.port] == [b.scheme, b.host, b.port]
|
|
11
|
+
end
|
|
12
|
+
module_function :same_origin?
|
|
13
|
+
|
|
14
|
+
# Credentials are for the origin they were given to, so they go no further than it.
|
|
15
|
+
def redirect_headers(headers, from_uri, to_uri)
|
|
16
|
+
return headers if same_origin?(from_uri, to_uri)
|
|
17
|
+
headers.reject{|k, v| REDIRECT::CREDENTIAL_HEADERS.include?(k.downcase)}
|
|
18
|
+
end
|
|
19
|
+
module_function :redirect_headers
|
|
20
|
+
|
|
21
|
+
def redirect_options(options, from_uri, to_uri)
|
|
22
|
+
return options if same_origin?(from_uri, to_uri)
|
|
23
|
+
options.reject{|k, v| %i{username password}.include?(k)}
|
|
24
|
+
end
|
|
25
|
+
module_function :redirect_options
|
|
26
|
+
end
|
data/lib/HTTP/VERSION.rb
CHANGED
data/lib/HTTP/request.rb
CHANGED
|
@@ -9,12 +9,15 @@ require_relative '../Net/HTTP/set_options'
|
|
|
9
9
|
require_relative '../Net/HTTPRequest/set_headers'
|
|
10
10
|
require_relative '../Net/HTTPResponse/StatusPredicates'
|
|
11
11
|
require_relative '../URI/Generic/use_sslQ'
|
|
12
|
+
require_relative './REDIRECT'
|
|
12
13
|
require_relative './RETRY'
|
|
13
14
|
|
|
14
15
|
module HTTP
|
|
15
16
|
def request(uri, request_object, headers = {}, options = {}, &block)
|
|
16
17
|
uri = uri.is_a?(URI) ? uri : URI.parse(uri)
|
|
17
18
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
19
|
+
given_options = options
|
|
20
|
+
options = options.dup
|
|
18
21
|
no_redirect = options.delete(:no_redirect)
|
|
19
22
|
username = options.delete(:username)
|
|
20
23
|
password = options.delete(:password)
|
|
@@ -44,11 +47,12 @@ module HTTP
|
|
|
44
47
|
return response
|
|
45
48
|
end
|
|
46
49
|
redirect_uri = uri.merge(response['location'])
|
|
50
|
+
onward_options = redirect_options(given_options, uri, redirect_uri)
|
|
47
51
|
if response.code =~ /^30[78]$/
|
|
48
52
|
data = METHODS::WITH_BODY.include?(request_method) ? request_object.body : {}
|
|
49
|
-
response = send(request_method, redirect_uri.to_s, data, headers,
|
|
53
|
+
response = send(request_method, redirect_uri.to_s, data, redirect_headers(headers, uri, redirect_uri), onward_options, &block)
|
|
50
54
|
else
|
|
51
|
-
response = get(redirect_uri.to_s, {}, {},
|
|
55
|
+
response = get(redirect_uri.to_s, {}, {}, onward_options, &block)
|
|
52
56
|
end
|
|
53
57
|
end
|
|
54
58
|
if block_given?
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# test/HTTP/REDIRECT_test.rb
|
|
2
|
+
|
|
3
|
+
require_relative '../helper'
|
|
4
|
+
|
|
5
|
+
describe "redirect behaviour" do
|
|
6
|
+
let(:uri){'http://example.com/path'}
|
|
7
|
+
let(:same_origin_uri){'http://example.com/elsewhere'}
|
|
8
|
+
let(:other_origin_uri){'http://redirected.com/path'}
|
|
9
|
+
|
|
10
|
+
describe "credentials" do
|
|
11
|
+
it "are kept for the same origin" do
|
|
12
|
+
stub_request(:get, uri).to_return(status: 307, headers: {'location' => same_origin_uri})
|
|
13
|
+
stub_request(:get, same_origin_uri).with(headers: {'Authorization' => 'Bearer x', 'Cookie' => 'session=1'}).to_return(status: 200)
|
|
14
|
+
_(HTTP.get(uri, {}, {'Authorization' => 'Bearer x', 'Cookie' => 'session=1'}).success?).must_equal(true)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
%w{307 308}.each do |code|
|
|
18
|
+
it "are dropped for another origin via #{code}" do
|
|
19
|
+
stub_request(:get, uri).to_return(status: code.to_i, headers: {'location' => other_origin_uri})
|
|
20
|
+
stub_request(:get, other_origin_uri).to_return(status: 200)
|
|
21
|
+
HTTP.get(uri, {}, {'Authorization' => 'Bearer x', 'cookie' => 'session=1', 'User-Agent' => 'Redback'})
|
|
22
|
+
assert_requested(:get, other_origin_uri){|request| !request.headers.key?('Authorization') && !request.headers.key?('Cookie') && request.headers['User-Agent'] == 'Redback'}
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "are dropped for another scheme" do
|
|
27
|
+
stub_request(:get, uri).to_return(status: 307, headers: {'location' => 'https://example.com/path'})
|
|
28
|
+
stub_request(:get, 'https://example.com/path').to_return(status: 200)
|
|
29
|
+
HTTP.get(uri, {}, {'Cookie' => 'session=1'})
|
|
30
|
+
assert_requested(:get, 'https://example.com/path'){|request| !request.headers.key?('Cookie')}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "are dropped for another port" do
|
|
34
|
+
stub_request(:get, uri).to_return(status: 307, headers: {'location' => 'http://example.com:8080/path'})
|
|
35
|
+
stub_request(:get, 'http://example.com:8080/path').to_return(status: 200)
|
|
36
|
+
HTTP.get(uri, {}, {'Cookie' => 'session=1'})
|
|
37
|
+
assert_requested(:get, 'http://example.com:8080/path'){|request| !request.headers.key?('Cookie')}
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe "the options hash" do
|
|
42
|
+
it "is not altered by a call" do
|
|
43
|
+
stub_request(:get, uri).to_return(status: 302, headers: {'location' => same_origin_uri})
|
|
44
|
+
stub_request(:get, same_origin_uri).to_return(status: 200)
|
|
45
|
+
options = {no_redirect: false, username: 'alice', password: 'secret', retries: 1}
|
|
46
|
+
HTTP.get(uri, {}, {}, options)
|
|
47
|
+
_(options).must_equal({no_redirect: false, username: 'alice', password: 'secret', retries: 1})
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "keeps no_redirect for a second call" do
|
|
51
|
+
stub_request(:get, uri).to_return(status: 302, headers: {'location' => other_origin_uri})
|
|
52
|
+
options = {no_redirect: true}
|
|
53
|
+
HTTP.get(uri, {}, {}, options)
|
|
54
|
+
_(HTTP.get(uri, {}, {}, options).redirection?).must_equal(true)
|
|
55
|
+
assert_not_requested(:get, other_origin_uri)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "keeps retrying after a redirect" do
|
|
59
|
+
HTTP::RETRY.stub(:sleep, nil) do
|
|
60
|
+
stub_request(:get, uri).to_return(status: 302, headers: {'location' => same_origin_uri})
|
|
61
|
+
stub_request(:get, same_origin_uri).to_return(status: 503).then.to_return(status: 200)
|
|
62
|
+
_(HTTP.get(uri, {}, {}, {retries: 1}).success?).must_equal(true)
|
|
63
|
+
assert_requested(:get, same_origin_uri, times: 2)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe "basic authentication options" do
|
|
69
|
+
it "are kept for the same origin" do
|
|
70
|
+
stub_request(:get, uri).with(basic_auth: ['alice', 'secret']).to_return(status: 302, headers: {'location' => same_origin_uri})
|
|
71
|
+
stub_request(:get, same_origin_uri).with(basic_auth: ['alice', 'secret']).to_return(status: 200)
|
|
72
|
+
_(HTTP.get(uri, {}, {}, {username: 'alice', password: 'secret'}).success?).must_equal(true)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "are dropped for another origin" do
|
|
76
|
+
stub_request(:get, uri).with(basic_auth: ['alice', 'secret']).to_return(status: 307, headers: {'location' => other_origin_uri})
|
|
77
|
+
stub_request(:get, other_origin_uri).to_return(status: 200)
|
|
78
|
+
HTTP.get(uri, {}, {}, {username: 'alice', password: 'secret'})
|
|
79
|
+
assert_requested(:get, other_origin_uri){|request| !request.headers.key?('Authorization')}
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: http.rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- thoran
|
|
@@ -95,6 +95,7 @@ files:
|
|
|
95
95
|
- http.rb.gemspec
|
|
96
96
|
- lib/HTTP.rb
|
|
97
97
|
- lib/HTTP/METHODS.rb
|
|
98
|
+
- lib/HTTP/REDIRECT.rb
|
|
98
99
|
- lib/HTTP/RETRY.rb
|
|
99
100
|
- lib/HTTP/VERSION.rb
|
|
100
101
|
- lib/HTTP/request.rb
|
|
@@ -108,6 +109,7 @@ files:
|
|
|
108
109
|
- lib/Thoran/String/Urlencode/urlencode.rb
|
|
109
110
|
- lib/URI/Generic/use_sslQ.rb
|
|
110
111
|
- test/HTTP/METHODS_test.rb
|
|
112
|
+
- test/HTTP/REDIRECT_test.rb
|
|
111
113
|
- test/HTTP/RETRY_test.rb
|
|
112
114
|
- test/HTTP/VERSION_test.rb
|
|
113
115
|
- test/HTTP/basic_auth_test.rb
|