http 5.1.0 → 5.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +21 -19
- data/.rubocop/metrics.yml +4 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +2 -1
- data/CHANGELOG.md +41 -0
- data/{CHANGES.md → CHANGES_OLD.md} +13 -1
- data/README.md +5 -2
- data/SECURITY.md +13 -1
- data/http.gemspec +3 -2
- data/lib/http/client.rb +1 -1
- data/lib/http/connection.rb +8 -1
- data/lib/http/features/instrumentation.rb +6 -1
- data/lib/http/redirector.rb +4 -2
- data/lib/http/request.rb +12 -3
- data/lib/http/timeout/null.rb +8 -5
- data/lib/http/uri.rb +64 -3
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/client_spec.rb +4 -1
- data/spec/lib/http/connection_spec.rb +23 -1
- data/spec/lib/http/features/instrumentation_spec.rb +19 -0
- data/spec/lib/http/options/headers_spec.rb +5 -1
- data/spec/lib/http/redirector_spec.rb +26 -0
- data/spec/lib/http/uri/normalizer_spec.rb +95 -0
- data/spec/lib/http/uri_spec.rb +39 -0
- data/spec/lib/http_spec.rb +20 -3
- data/spec/support/dummy_server/servlet.rb +8 -6
- metadata +25 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8593353cd2283b2ac49c557ff71d5f1a668066dbac59b3d2a6f59429b9b2f5e8
|
4
|
+
data.tar.gz: e9316b3c4dd519825eeaa255a4f86932ce4a9e7f374f9be8273ca4051f5b9c3d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ae34b411a233ca8601aebe2295f2a1d8e0bec7e742a95fdd6e075b4f7dd3c68d42448cfbac1146b645ee59424e91af8eb3878c43e7a7fb17c3569681cf4ccf3
|
7
|
+
data.tar.gz: a76bc0a41338e67677370014d803ea79e3a34078172cc81491f31b085613394c7290b2402e199a2e1de77c7fecf7e05fde8fc2d9924016105ce429751f202e85
|
data/.github/workflows/ci.yml
CHANGED
@@ -2,9 +2,9 @@ name: CI
|
|
2
2
|
|
3
3
|
on:
|
4
4
|
push:
|
5
|
-
branches: [ main ]
|
5
|
+
branches: [ main, 5-x-stable ]
|
6
6
|
pull_request:
|
7
|
-
branches: [ main ]
|
7
|
+
branches: [ main, 5-x-stable ]
|
8
8
|
|
9
9
|
env:
|
10
10
|
BUNDLE_WITHOUT: "development"
|
@@ -16,11 +16,11 @@ jobs:
|
|
16
16
|
|
17
17
|
strategy:
|
18
18
|
matrix:
|
19
|
-
ruby: [ ruby-2.6, ruby-2.7, ruby-3.0, ruby-3.1 ]
|
19
|
+
ruby: [ ruby-2.6, ruby-2.7, ruby-3.0, ruby-3.1, ruby-3.2, ruby-3.3 ]
|
20
20
|
os: [ ubuntu-latest ]
|
21
21
|
|
22
22
|
steps:
|
23
|
-
- uses: actions/checkout@
|
23
|
+
- uses: actions/checkout@v4
|
24
24
|
|
25
25
|
- uses: ruby/setup-ruby@v1
|
26
26
|
with:
|
@@ -30,29 +30,31 @@ jobs:
|
|
30
30
|
- name: bundle exec rspec
|
31
31
|
run: bundle exec rspec --format progress --force-colour
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
33
|
+
test-flaky:
|
34
|
+
runs-on: ${{ matrix.os }}
|
35
|
+
|
36
|
+
strategy:
|
37
|
+
matrix:
|
38
|
+
ruby: [ jruby-9.3 ]
|
39
|
+
os: [ ubuntu-latest ]
|
40
40
|
|
41
|
-
coveralls:
|
42
|
-
needs: test
|
43
|
-
runs-on: ubuntu-latest
|
44
41
|
steps:
|
45
|
-
-
|
46
|
-
|
42
|
+
- uses: actions/checkout@v4
|
43
|
+
|
44
|
+
- uses: ruby/setup-ruby@v1
|
47
45
|
with:
|
48
|
-
|
49
|
-
|
46
|
+
ruby-version: ${{ matrix.ruby }}
|
47
|
+
bundler-cache: true
|
48
|
+
|
49
|
+
- name: bundle exec rspec
|
50
|
+
continue-on-error: true
|
51
|
+
run: bundle exec rspec --format progress --force-colour
|
50
52
|
|
51
53
|
lint:
|
52
54
|
runs-on: ubuntu-latest
|
53
55
|
|
54
56
|
steps:
|
55
|
-
- uses: actions/checkout@
|
57
|
+
- uses: actions/checkout@v4
|
56
58
|
|
57
59
|
- uses: ruby/setup-ruby@v1
|
58
60
|
with:
|
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
CHANGED
@@ -74,7 +74,7 @@ Metrics/AbcSize:
|
|
74
74
|
- 'lib/http/request.rb'
|
75
75
|
- 'lib/http/response.rb'
|
76
76
|
|
77
|
-
# Offense count:
|
77
|
+
# Offense count: 70
|
78
78
|
# Configuration parameters: CountComments, Max, CountAsOne, ExcludedMethods, IgnoredMethods.
|
79
79
|
# IgnoredMethods: refine
|
80
80
|
Metrics/BlockLength:
|
@@ -98,6 +98,7 @@ Metrics/BlockLength:
|
|
98
98
|
- 'spec/lib/http/response/parser_spec.rb'
|
99
99
|
- 'spec/lib/http/response/status_spec.rb'
|
100
100
|
- 'spec/lib/http/response_spec.rb'
|
101
|
+
- 'spec/lib/http/uri_spec.rb'
|
101
102
|
- 'spec/lib/http_spec.rb'
|
102
103
|
- 'spec/support/http_handling_shared.rb'
|
103
104
|
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [Unreleased]
|
9
|
+
|
10
|
+
## [5.2.0] - 2024-02-05
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- Add `Connection#finished_request?`
|
15
|
+
([#743](https://github.com/httprb/http/pull/743))
|
16
|
+
- Add `Instrumentation#on_error`
|
17
|
+
([#746](https://github.com/httprb/http/pull/746))
|
18
|
+
- Add `base64` dependency (suppresses warnings on Ruby 3.0)
|
19
|
+
([#759](https://github.com/httprb/http/pull/759))
|
20
|
+
- Add `PURGE` HTTP verb
|
21
|
+
([#757](https://github.com/httprb/http/pull/757))
|
22
|
+
- Add Ruby-3.3 support
|
23
|
+
|
24
|
+
### Changed
|
25
|
+
|
26
|
+
- **BREAKING** Process features in reverse order
|
27
|
+
([#766](https://github.com/httprb/http/pull/766))
|
28
|
+
- **BREAKING** Downcase Content-Type charset name
|
29
|
+
([#753](https://github.com/httprb/http/pull/753))
|
30
|
+
- **BREAKING** Make URI normalization more conservative
|
31
|
+
([#758](https://github.com/httprb/http/pull/758))
|
32
|
+
|
33
|
+
### Fixed
|
34
|
+
|
35
|
+
- Close sockets on initialize failure
|
36
|
+
([#762](https://github.com/httprb/http/pull/762))
|
37
|
+
- Prevent CRLF injection due to broken URL normalizer
|
38
|
+
([#765](https://github.com/httprb/http/pull/765))
|
39
|
+
|
40
|
+
[unreleased]: https://github.com/httprb/http/compare/v5.2.0...5-x-stable
|
41
|
+
[5.2.0]: https://github.com/httprb/http/compare/v5.1.1...v5.2.0
|
@@ -1,3 +1,13 @@
|
|
1
|
+
## 5.1.1 (2022-12-17)
|
2
|
+
|
3
|
+
* [#731](https://github.com/httprb/http/pull/731)
|
4
|
+
Strip brackets from IPv6 addresses in `HTTP::URI`.
|
5
|
+
([@jeraki])
|
6
|
+
|
7
|
+
* [#722](https://github.com/httprb/http/pull/722)
|
8
|
+
Add `on_redirect` callback.
|
9
|
+
([@benubois])
|
10
|
+
|
1
11
|
## 5.1.0 (2022-06-17)
|
2
12
|
|
3
13
|
* Drop ruby-2.5 support.
|
@@ -44,7 +54,7 @@
|
|
44
54
|
Use features on redirected requests.
|
45
55
|
([@nomis])
|
46
56
|
|
47
|
-
* [#678](https://github.com/
|
57
|
+
* [#678](https://github.com/httprb/http/pull/678)
|
48
58
|
Restore `HTTP::Response` `:uri` option for backwards compatibility.
|
49
59
|
([@schwern])
|
50
60
|
|
@@ -988,3 +998,5 @@ end
|
|
988
998
|
[@YuLeven]: https://github.com/YuLeven
|
989
999
|
[@drwl]: https://github.com/drwl
|
990
1000
|
[@tkellogg]: https://github.com/tkellogg
|
1001
|
+
[@jeraki]: https://github.com/jeraki
|
1002
|
+
[@benubois]: https://github.com/benubois
|
data/README.md
CHANGED
@@ -110,10 +110,13 @@ and call `#readpartial` on it repeatedly until it returns `nil`:
|
|
110
110
|
This library aims to support and is [tested against][build-link]
|
111
111
|
the following Ruby versions:
|
112
112
|
|
113
|
+
- JRuby 9.3
|
113
114
|
- Ruby 2.6
|
114
115
|
- Ruby 2.7
|
115
116
|
- Ruby 3.0
|
116
|
-
-
|
117
|
+
- Ruby 3.1
|
118
|
+
- Ruby 3.2
|
119
|
+
- Ruby 3.3
|
117
120
|
|
118
121
|
If something doesn't work on one of these versions, it's a bug.
|
119
122
|
|
@@ -159,5 +162,5 @@ See LICENSE.txt for further details.
|
|
159
162
|
[//]: # (links)
|
160
163
|
|
161
164
|
[documentation]: https://github.com/httprb/http/wiki
|
162
|
-
[requests]:
|
165
|
+
[requests]: https://docs.python-requests.org/en/latest/
|
163
166
|
[llhttp]: https://llhttp.org/
|
data/SECURITY.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# Security Policy
|
2
2
|
|
3
|
+
## Supported Versions
|
4
|
+
|
5
|
+
Security updates are applied only to the most recent release.
|
6
|
+
|
3
7
|
## Reporting a Vulnerability
|
4
8
|
|
5
|
-
|
9
|
+
If you have discovered a security vulnerability in this project, please report
|
10
|
+
it privately. **Do not disclose it as a public issue.** This gives us time to
|
11
|
+
work with you to fix the issue before public exposure, reducing the chance that
|
12
|
+
the exploit will be used before a patch is released.
|
13
|
+
|
14
|
+
Please disclose it at [security advisory](https://github.com/httprb/http/security/advisories/new).
|
15
|
+
|
16
|
+
This project is maintained by a team of volunteers on a reasonable-effort basis.
|
17
|
+
As such, please give us at least 90 days to work on a fix before public exposure.
|
data/http.gemspec
CHANGED
@@ -28,9 +28,10 @@ Gem::Specification.new do |gem|
|
|
28
28
|
gem.required_ruby_version = ">= 2.6"
|
29
29
|
|
30
30
|
gem.add_runtime_dependency "addressable", "~> 2.8"
|
31
|
+
gem.add_runtime_dependency "base64", "~> 0.1"
|
31
32
|
gem.add_runtime_dependency "http-cookie", "~> 1.0"
|
32
33
|
gem.add_runtime_dependency "http-form_data", "~> 2.2"
|
33
|
-
gem.add_runtime_dependency "llhttp-ffi", "~> 0.
|
34
|
+
gem.add_runtime_dependency "llhttp-ffi", "~> 0.5.0"
|
34
35
|
|
35
36
|
gem.add_development_dependency "bundler", "~> 2.0"
|
36
37
|
|
@@ -38,7 +39,7 @@ Gem::Specification.new do |gem|
|
|
38
39
|
"source_code_uri" => "https://github.com/httprb/http",
|
39
40
|
"wiki_uri" => "https://github.com/httprb/http/wiki",
|
40
41
|
"bug_tracker_uri" => "https://github.com/httprb/http/issues",
|
41
|
-
"changelog_uri" => "https://github.com/httprb/http/blob/v#{HTTP::VERSION}/
|
42
|
+
"changelog_uri" => "https://github.com/httprb/http/blob/v#{HTTP::VERSION}/CHANGELOG.md",
|
42
43
|
"rubygems_mfa_required" => "true"
|
43
44
|
}
|
44
45
|
end
|
data/lib/http/client.rb
CHANGED
@@ -184,7 +184,7 @@ module HTTP
|
|
184
184
|
form
|
185
185
|
when opts.json
|
186
186
|
body = MimeType[:json].encode opts.json
|
187
|
-
headers[Headers::CONTENT_TYPE] ||= "application/json; charset=#{body.encoding.name}"
|
187
|
+
headers[Headers::CONTENT_TYPE] ||= "application/json; charset=#{body.encoding.name.downcase}"
|
188
188
|
body
|
189
189
|
end
|
190
190
|
end
|
data/lib/http/connection.rb
CHANGED
@@ -46,6 +46,9 @@ module HTTP
|
|
46
46
|
reset_timer
|
47
47
|
rescue IOError, SocketError, SystemCallError => e
|
48
48
|
raise ConnectionError, "failed to connect: #{e}", e.backtrace
|
49
|
+
rescue TimeoutError
|
50
|
+
close
|
51
|
+
raise
|
49
52
|
end
|
50
53
|
|
51
54
|
# @see (HTTP::Response::Parser#status_code)
|
@@ -126,12 +129,16 @@ module HTTP
|
|
126
129
|
# Close the connection
|
127
130
|
# @return [void]
|
128
131
|
def close
|
129
|
-
@socket.close unless @socket
|
132
|
+
@socket.close unless @socket&.closed?
|
130
133
|
|
131
134
|
@pending_response = false
|
132
135
|
@pending_request = false
|
133
136
|
end
|
134
137
|
|
138
|
+
def finished_request?
|
139
|
+
!@pending_request && !@pending_response
|
140
|
+
end
|
141
|
+
|
135
142
|
# Whether we're keeping the conn alive
|
136
143
|
# @return [Boolean]
|
137
144
|
def keep_alive?
|
@@ -19,11 +19,12 @@ module HTTP
|
|
19
19
|
# and `finish` so the duration of the request can be calculated.
|
20
20
|
#
|
21
21
|
class Instrumentation < Feature
|
22
|
-
attr_reader :instrumenter, :name
|
22
|
+
attr_reader :instrumenter, :name, :error_name
|
23
23
|
|
24
24
|
def initialize(instrumenter: NullInstrumenter.new, namespace: "http")
|
25
25
|
@instrumenter = instrumenter
|
26
26
|
@name = "request.#{namespace}"
|
27
|
+
@error_name = "error.#{namespace}"
|
27
28
|
end
|
28
29
|
|
29
30
|
def wrap_request(request)
|
@@ -39,6 +40,10 @@ module HTTP
|
|
39
40
|
response
|
40
41
|
end
|
41
42
|
|
43
|
+
def on_error(request, error)
|
44
|
+
instrumenter.instrument(error_name, :request => request, :error => error)
|
45
|
+
end
|
46
|
+
|
42
47
|
HTTP::Options.register_feature(:instrumentation, self)
|
43
48
|
|
44
49
|
class NullInstrumenter
|
data/lib/http/redirector.rb
CHANGED
@@ -40,8 +40,9 @@ module HTTP
|
|
40
40
|
# @option opts [Boolean] :strict (true) redirector hops policy
|
41
41
|
# @option opts [#to_i] :max_hops (5) maximum allowed amount of hops
|
42
42
|
def initialize(opts = {})
|
43
|
-
@strict
|
44
|
-
@max_hops
|
43
|
+
@strict = opts.fetch(:strict, true)
|
44
|
+
@max_hops = opts.fetch(:max_hops, 5).to_i
|
45
|
+
@on_redirect = opts.fetch(:on_redirect, nil)
|
45
46
|
end
|
46
47
|
|
47
48
|
# Follows redirects until non-redirect response found
|
@@ -65,6 +66,7 @@ module HTTP
|
|
65
66
|
unless cookie_jar.empty?
|
66
67
|
@request.headers.set(Headers::COOKIE, cookie_jar.cookies.map { |c| "#{c.name}=#{c.value}" }.join("; "))
|
67
68
|
end
|
69
|
+
@on_redirect.call @response, @request if @on_redirect.respond_to?(:call)
|
68
70
|
@response = yield @request
|
69
71
|
collect_cookies_from_response
|
70
72
|
end
|
data/lib/http/request.rb
CHANGED
@@ -49,7 +49,10 @@ module HTTP
|
|
49
49
|
:search,
|
50
50
|
|
51
51
|
# RFC 4791: Calendaring Extensions to WebDAV -- CalDAV
|
52
|
-
:mkcalendar
|
52
|
+
:mkcalendar,
|
53
|
+
|
54
|
+
# Implemented by several caching servers, like Squid, Varnish or Fastly
|
55
|
+
:purge
|
53
56
|
].freeze
|
54
57
|
|
55
58
|
# Allowed schemes
|
@@ -172,7 +175,9 @@ module HTTP
|
|
172
175
|
uri.omit(:fragment)
|
173
176
|
else
|
174
177
|
uri.request_uri
|
175
|
-
end
|
178
|
+
end.to_s
|
179
|
+
|
180
|
+
raise RequestError, "Invalid request URI: #{request_uri.inspect}" if request_uri.match?(/\s/)
|
176
181
|
|
177
182
|
"#{verb.to_s.upcase} #{request_uri} HTTP/#{version}"
|
178
183
|
end
|
@@ -230,7 +235,11 @@ module HTTP
|
|
230
235
|
|
231
236
|
# @return [String] Default host (with port if needed) header value.
|
232
237
|
def default_host_header_value
|
233
|
-
PORTS[@scheme] == port ? host : "#{host}:#{port}"
|
238
|
+
value = PORTS[@scheme] == port ? host : "#{host}:#{port}"
|
239
|
+
|
240
|
+
raise RequestError, "Invalid host: #{value.inspect}" if value.match?(/\s/)
|
241
|
+
|
242
|
+
value
|
234
243
|
end
|
235
244
|
|
236
245
|
def prepare_body(body)
|
data/lib/http/timeout/null.rb
CHANGED
@@ -1,15 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "forwardable"
|
4
3
|
require "io/wait"
|
5
4
|
|
6
5
|
module HTTP
|
7
6
|
module Timeout
|
8
7
|
class Null
|
9
|
-
extend Forwardable
|
10
|
-
|
11
|
-
def_delegators :@socket, :close, :closed?
|
12
|
-
|
13
8
|
attr_reader :options, :socket
|
14
9
|
|
15
10
|
def initialize(options = {})
|
@@ -27,6 +22,14 @@ module HTTP
|
|
27
22
|
@socket.connect
|
28
23
|
end
|
29
24
|
|
25
|
+
def close
|
26
|
+
@socket&.close
|
27
|
+
end
|
28
|
+
|
29
|
+
def closed?
|
30
|
+
@socket&.closed?
|
31
|
+
end
|
32
|
+
|
30
33
|
# Configures the SSL connection and starts the connection
|
31
34
|
def start_tls(host, ssl_socket_class, ssl_context)
|
32
35
|
@socket = ssl_socket_class.new(socket, ssl_context)
|
data/lib/http/uri.rb
CHANGED
@@ -9,7 +9,6 @@ module HTTP
|
|
9
9
|
def_delegators :@uri, :scheme, :normalized_scheme, :scheme=
|
10
10
|
def_delegators :@uri, :user, :normalized_user, :user=
|
11
11
|
def_delegators :@uri, :password, :normalized_password, :password=
|
12
|
-
def_delegators :@uri, :host, :normalized_host, :host=
|
13
12
|
def_delegators :@uri, :authority, :normalized_authority, :authority=
|
14
13
|
def_delegators :@uri, :origin, :origin=
|
15
14
|
def_delegators :@uri, :normalized_port, :port=
|
@@ -20,12 +19,27 @@ module HTTP
|
|
20
19
|
def_delegators :@uri, :fragment, :normalized_fragment, :fragment=
|
21
20
|
def_delegators :@uri, :omit, :join, :normalize
|
22
21
|
|
22
|
+
# Host, either a domain name or IP address. If the host is an IPv6 address, it will be returned
|
23
|
+
# without brackets surrounding it.
|
24
|
+
#
|
25
|
+
# @return [String] The host of the URI
|
26
|
+
attr_reader :host
|
27
|
+
|
28
|
+
# Normalized host, either a domain name or IP address. If the host is an IPv6 address, it will
|
29
|
+
# be returned without brackets surrounding it.
|
30
|
+
#
|
31
|
+
# @return [String] The normalized host of the URI
|
32
|
+
attr_reader :normalized_host
|
33
|
+
|
23
34
|
# @private
|
24
35
|
HTTP_SCHEME = "http"
|
25
36
|
|
26
37
|
# @private
|
27
38
|
HTTPS_SCHEME = "https"
|
28
39
|
|
40
|
+
# @private
|
41
|
+
PERCENT_ENCODE = /[^\x21-\x7E]+/.freeze
|
42
|
+
|
29
43
|
# @private
|
30
44
|
NORMALIZER = lambda do |uri|
|
31
45
|
uri = HTTP::URI.parse uri
|
@@ -33,8 +47,8 @@ module HTTP
|
|
33
47
|
HTTP::URI.new(
|
34
48
|
:scheme => uri.normalized_scheme,
|
35
49
|
:authority => uri.normalized_authority,
|
36
|
-
:path => uri.
|
37
|
-
:query => uri.query,
|
50
|
+
:path => uri.path.empty? ? "/" : percent_encode(Addressable::URI.normalize_path(uri.path)),
|
51
|
+
:query => percent_encode(uri.query),
|
38
52
|
:fragment => uri.normalized_fragment
|
39
53
|
)
|
40
54
|
end
|
@@ -60,6 +74,19 @@ module HTTP
|
|
60
74
|
Addressable::URI.form_encode(form_values, sort)
|
61
75
|
end
|
62
76
|
|
77
|
+
# Percent-encode all characters matching a regular expression.
|
78
|
+
#
|
79
|
+
# @param [String] string raw string
|
80
|
+
#
|
81
|
+
# @return [String] encoded value
|
82
|
+
#
|
83
|
+
# @private
|
84
|
+
def self.percent_encode(string)
|
85
|
+
string&.gsub(PERCENT_ENCODE) do |substr|
|
86
|
+
substr.encode(Encoding::UTF_8).bytes.map { |c| format("%%%02X", c) }.join
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
63
90
|
# Creates an HTTP::URI instance from the given options
|
64
91
|
#
|
65
92
|
# @param [Hash, Addressable::URI] options_or_uri
|
@@ -83,6 +110,9 @@ module HTTP
|
|
83
110
|
else
|
84
111
|
raise TypeError, "expected Hash for options, got #{options_or_uri.class}"
|
85
112
|
end
|
113
|
+
|
114
|
+
@host = process_ipv6_brackets(@uri.host)
|
115
|
+
@normalized_host = process_ipv6_brackets(@uri.normalized_host)
|
86
116
|
end
|
87
117
|
|
88
118
|
# Are these URI objects equal? Normalizes both URIs prior to comparison
|
@@ -110,6 +140,17 @@ module HTTP
|
|
110
140
|
@hash ||= to_s.hash * -1
|
111
141
|
end
|
112
142
|
|
143
|
+
# Sets the host component for the URI.
|
144
|
+
#
|
145
|
+
# @param [String, #to_str] new_host The new host component.
|
146
|
+
# @return [void]
|
147
|
+
def host=(new_host)
|
148
|
+
@uri.host = process_ipv6_brackets(new_host, :brackets => true)
|
149
|
+
|
150
|
+
@host = process_ipv6_brackets(@uri.host)
|
151
|
+
@normalized_host = process_ipv6_brackets(@uri.normalized_host)
|
152
|
+
end
|
153
|
+
|
113
154
|
# Port number, either as specified or the default if unspecified
|
114
155
|
#
|
115
156
|
# @return [Integer] port number
|
@@ -146,5 +187,25 @@ module HTTP
|
|
146
187
|
def inspect
|
147
188
|
format("#<%s:0x%014x URI:%s>", self.class.name, object_id << 1, to_s)
|
148
189
|
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
# Process a URI host, adding or removing surrounding brackets if the host is an IPv6 address.
|
194
|
+
#
|
195
|
+
# @param [Boolean] brackets When true, brackets will be added to IPv6 addresses if missing. When
|
196
|
+
# false, they will be removed if present.
|
197
|
+
#
|
198
|
+
# @return [String] Host with IPv6 address brackets added or removed
|
199
|
+
def process_ipv6_brackets(raw_host, brackets: false)
|
200
|
+
ip = IPAddr.new(raw_host)
|
201
|
+
|
202
|
+
if ip.ipv6?
|
203
|
+
brackets ? "[#{ip}]" : ip.to_s
|
204
|
+
else
|
205
|
+
raw_host
|
206
|
+
end
|
207
|
+
rescue IPAddr::Error
|
208
|
+
raw_host
|
209
|
+
end
|
149
210
|
end
|
150
211
|
end
|
data/lib/http/version.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require "cgi"
|
5
|
+
require "logger"
|
6
|
+
|
4
7
|
require "support/http_handling_shared"
|
5
8
|
require "support/dummy_server"
|
6
9
|
require "support/ssl_helper"
|
7
|
-
require "logger"
|
8
10
|
|
9
11
|
RSpec.describe HTTP::Client do
|
10
12
|
run_server(:dummy) { DummyServer.new }
|
@@ -259,6 +261,7 @@ RSpec.describe HTTP::Client do
|
|
259
261
|
|
260
262
|
expect(HTTP::Request).to receive(:new) do |opts|
|
261
263
|
expect(opts[:body]).to eq '{"foo":"bar"}'
|
264
|
+
expect(opts[:headers]["Content-Type"]).to eq "application/json; charset=utf-8"
|
262
265
|
end
|
263
266
|
|
264
267
|
client.get("http://example.com/", :json => {:foo => :bar})
|
@@ -8,11 +8,31 @@ RSpec.describe HTTP::Connection do
|
|
8
8
|
:headers => {}
|
9
9
|
)
|
10
10
|
end
|
11
|
-
let(:socket) { double(:connect => nil) }
|
11
|
+
let(:socket) { double(:connect => nil, :close => nil) }
|
12
12
|
let(:timeout_class) { double(:new => socket) }
|
13
13
|
let(:opts) { HTTP::Options.new(:timeout_class => timeout_class) }
|
14
14
|
let(:connection) { HTTP::Connection.new(req, opts) }
|
15
15
|
|
16
|
+
describe "#initialize times out" do
|
17
|
+
let(:req) do
|
18
|
+
HTTP::Request.new(
|
19
|
+
:verb => :get,
|
20
|
+
:uri => "https://example.com/",
|
21
|
+
:headers => {}
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
before do
|
26
|
+
expect(socket).to receive(:start_tls).and_raise(HTTP::TimeoutError)
|
27
|
+
expect(socket).to receive(:closed?) { false }
|
28
|
+
expect(socket).to receive(:close)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "closes the connection" do
|
32
|
+
expect { connection }.to raise_error(HTTP::TimeoutError)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
16
36
|
describe "#read_headers!" do
|
17
37
|
before do
|
18
38
|
connection.instance_variable_set(:@pending_response, true)
|
@@ -58,9 +78,11 @@ RSpec.describe HTTP::Connection do
|
|
58
78
|
connection.read_headers!
|
59
79
|
buffer = String.new
|
60
80
|
while (s = connection.readpartial(3))
|
81
|
+
expect(connection.finished_request?).to be false if s != ""
|
61
82
|
buffer << s
|
62
83
|
end
|
63
84
|
expect(buffer).to eq "1234567890"
|
85
|
+
expect(connection.finished_request?).to be true
|
64
86
|
end
|
65
87
|
end
|
66
88
|
end
|
@@ -59,4 +59,23 @@ RSpec.describe HTTP::Features::Instrumentation do
|
|
59
59
|
expect(instrumenter.output[:finish]).to eq(:response => response)
|
60
60
|
end
|
61
61
|
end
|
62
|
+
|
63
|
+
describe "logging errors" do
|
64
|
+
let(:request) do
|
65
|
+
HTTP::Request.new(
|
66
|
+
:verb => :post,
|
67
|
+
:uri => "https://example.com/",
|
68
|
+
:headers => {:accept => "application/json"},
|
69
|
+
:body => '{"hello": "world!"}'
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
let(:error) { HTTP::TimeoutError.new }
|
74
|
+
|
75
|
+
it "should log the error" do
|
76
|
+
feature.on_error(request, error)
|
77
|
+
|
78
|
+
expect(instrumenter.output[:finish]).to eq(:request => request, :error => error)
|
79
|
+
end
|
80
|
+
end
|
62
81
|
end
|
@@ -14,7 +14,11 @@ RSpec.describe HTTP::Options, "headers" do
|
|
14
14
|
end
|
15
15
|
|
16
16
|
it "accepts any object that respond to :to_hash" do
|
17
|
-
x =
|
17
|
+
x = if RUBY_VERSION >= "3.2.0"
|
18
|
+
Data.define(:to_hash).new(:to_hash => { "accept" => "json" })
|
19
|
+
else
|
20
|
+
Struct.new(:to_hash).new({ "accept" => "json" })
|
21
|
+
end
|
18
22
|
expect(opts.with_headers(x).headers["accept"]).to eq("json")
|
19
23
|
end
|
20
24
|
end
|
@@ -148,6 +148,32 @@ RSpec.describe HTTP::Redirector do
|
|
148
148
|
expect(cookies["deleted"]).to eq nil
|
149
149
|
end
|
150
150
|
|
151
|
+
context "with on_redirect callback" do
|
152
|
+
let(:options) do
|
153
|
+
{
|
154
|
+
:on_redirect => proc do |response, location|
|
155
|
+
@redirect_response = response
|
156
|
+
@redirect_location = location
|
157
|
+
end
|
158
|
+
}
|
159
|
+
end
|
160
|
+
|
161
|
+
it "calls on_redirect" do
|
162
|
+
req = HTTP::Request.new :verb => :head, :uri => "http://example.com"
|
163
|
+
hops = [
|
164
|
+
redirect_response(301, "http://example.com/1"),
|
165
|
+
redirect_response(301, "http://example.com/2"),
|
166
|
+
simple_response(200, "foo")
|
167
|
+
]
|
168
|
+
|
169
|
+
redirector.perform(req, hops.shift) do |prev_req, _|
|
170
|
+
expect(@redirect_location.uri.to_s).to eq prev_req.uri.to_s
|
171
|
+
expect(@redirect_response.code).to eq 301
|
172
|
+
hops.shift
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
151
177
|
context "following 300 redirect" do
|
152
178
|
context "with strict mode" do
|
153
179
|
let(:options) { {:strict => true} }
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe HTTP::URI::NORMALIZER do
|
4
|
+
describe "scheme" do
|
5
|
+
it "lower-cases scheme" do
|
6
|
+
expect(HTTP::URI::NORMALIZER.call("HttP://example.com").scheme).to eq "http"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "hostname" do
|
11
|
+
it "lower-cases hostname" do
|
12
|
+
expect(HTTP::URI::NORMALIZER.call("http://EXAMPLE.com").host).to eq "example.com"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "decodes percent-encoded hostname" do
|
16
|
+
expect(HTTP::URI::NORMALIZER.call("http://ex%61mple.com").host).to eq "example.com"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "removes trailing period in hostname" do
|
20
|
+
expect(HTTP::URI::NORMALIZER.call("http://example.com.").host).to eq "example.com"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "IDN-encodes non-ASCII hostname" do
|
24
|
+
expect(HTTP::URI::NORMALIZER.call("http://exämple.com").host).to eq "xn--exmple-cua.com"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "path" do
|
29
|
+
it "ensures path is not empty" do
|
30
|
+
expect(HTTP::URI::NORMALIZER.call("http://example.com").path).to eq "/"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "preserves double slashes in path" do
|
34
|
+
expect(HTTP::URI::NORMALIZER.call("http://example.com//a///b").path).to eq "//a///b"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "resolves single-dot segments in path" do
|
38
|
+
expect(HTTP::URI::NORMALIZER.call("http://example.com/a/./b").path).to eq "/a/b"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "resolves double-dot segments in path" do
|
42
|
+
expect(HTTP::URI::NORMALIZER.call("http://example.com/a/b/../c").path).to eq "/a/c"
|
43
|
+
end
|
44
|
+
|
45
|
+
it "resolves leading double-dot segments in path" do
|
46
|
+
expect(HTTP::URI::NORMALIZER.call("http://example.com/../a/b").path).to eq "/a/b"
|
47
|
+
end
|
48
|
+
|
49
|
+
it "percent-encodes control characters in path" do
|
50
|
+
expect(HTTP::URI::NORMALIZER.call("http://example.com/\x00\x7F\n").path).to eq "/%00%7F%0A"
|
51
|
+
end
|
52
|
+
|
53
|
+
it "percent-encodes space in path" do
|
54
|
+
expect(HTTP::URI::NORMALIZER.call("http://example.com/a b").path).to eq "/a%20b"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "percent-encodes non-ASCII characters in path" do
|
58
|
+
expect(HTTP::URI::NORMALIZER.call("http://example.com/キョ").path).to eq "/%E3%82%AD%E3%83%A7"
|
59
|
+
end
|
60
|
+
|
61
|
+
it "does not percent-encode non-special characters in path" do
|
62
|
+
expect(HTTP::URI::NORMALIZER.call("http://example.com/~.-_!$&()*,;=:@{}").path).to eq "/~.-_!$&()*,;=:@{}"
|
63
|
+
end
|
64
|
+
|
65
|
+
it "preserves escape sequences in path" do
|
66
|
+
expect(HTTP::URI::NORMALIZER.call("http://example.com/%41").path).to eq "/%41"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "query" do
|
71
|
+
it "allows no query" do
|
72
|
+
expect(HTTP::URI::NORMALIZER.call("http://example.com").query).to be_nil
|
73
|
+
end
|
74
|
+
|
75
|
+
it "percent-encodes control characters in query" do
|
76
|
+
expect(HTTP::URI::NORMALIZER.call("http://example.com/?\x00\x7F\n").query).to eq "%00%7F%0A"
|
77
|
+
end
|
78
|
+
|
79
|
+
it "percent-encodes space in query" do
|
80
|
+
expect(HTTP::URI::NORMALIZER.call("http://example.com/?a b").query).to eq "a%20b"
|
81
|
+
end
|
82
|
+
|
83
|
+
it "percent-encodes non-ASCII characters in query" do
|
84
|
+
expect(HTTP::URI::NORMALIZER.call("http://example.com?キョ").query).to eq "%E3%82%AD%E3%83%A7"
|
85
|
+
end
|
86
|
+
|
87
|
+
it "does not percent-encode non-special characters in query" do
|
88
|
+
expect(HTTP::URI::NORMALIZER.call("http://example.com/?~.-_!$&()*,;=:@{}?").query).to eq "~.-_!$&()*,;=:@{}?"
|
89
|
+
end
|
90
|
+
|
91
|
+
it "preserves escape sequences in query" do
|
92
|
+
expect(HTTP::URI::NORMALIZER.call("http://example.com/?%41").query).to eq "%41"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/spec/lib/http/uri_spec.rb
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
RSpec.describe HTTP::URI do
|
4
|
+
let(:example_ipv6_address) { "2606:2800:220:1:248:1893:25c8:1946" }
|
5
|
+
|
4
6
|
let(:example_http_uri_string) { "http://example.com" }
|
5
7
|
let(:example_https_uri_string) { "https://example.com" }
|
8
|
+
let(:example_ipv6_uri_string) { "https://[#{example_ipv6_address}]" }
|
6
9
|
|
7
10
|
subject(:http_uri) { described_class.parse(example_http_uri_string) }
|
8
11
|
subject(:https_uri) { described_class.parse(example_https_uri_string) }
|
12
|
+
subject(:ipv6_uri) { described_class.parse(example_ipv6_uri_string) }
|
9
13
|
|
10
14
|
it "knows URI schemes" do
|
11
15
|
expect(http_uri.scheme).to eq "http"
|
@@ -20,6 +24,41 @@ RSpec.describe HTTP::URI do
|
|
20
24
|
expect(https_uri.port).to eq 443
|
21
25
|
end
|
22
26
|
|
27
|
+
describe "#host" do
|
28
|
+
it "strips brackets from IPv6 addresses" do
|
29
|
+
expect(ipv6_uri.host).to eq("2606:2800:220:1:248:1893:25c8:1946")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#normalized_host" do
|
34
|
+
it "strips brackets from IPv6 addresses" do
|
35
|
+
expect(ipv6_uri.normalized_host).to eq("2606:2800:220:1:248:1893:25c8:1946")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#host=" do
|
40
|
+
it "updates cached values for #host and #normalized_host" do
|
41
|
+
expect(http_uri.host).to eq("example.com")
|
42
|
+
expect(http_uri.normalized_host).to eq("example.com")
|
43
|
+
|
44
|
+
http_uri.host = "[#{example_ipv6_address}]"
|
45
|
+
|
46
|
+
expect(http_uri.host).to eq(example_ipv6_address)
|
47
|
+
expect(http_uri.normalized_host).to eq(example_ipv6_address)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "ensures IPv6 addresses are bracketed in the inner Addressable::URI" do
|
51
|
+
expect(http_uri.host).to eq("example.com")
|
52
|
+
expect(http_uri.normalized_host).to eq("example.com")
|
53
|
+
|
54
|
+
http_uri.host = example_ipv6_address
|
55
|
+
|
56
|
+
expect(http_uri.host).to eq(example_ipv6_address)
|
57
|
+
expect(http_uri.normalized_host).to eq(example_ipv6_address)
|
58
|
+
expect(http_uri.instance_variable_get(:@uri).host).to eq("[#{example_ipv6_address}]")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
23
62
|
describe "#dup" do
|
24
63
|
it "doesn't share internal value between duplicates" do
|
25
64
|
duplicated_uri = http_uri.dup
|
data/spec/lib/http_spec.rb
CHANGED
@@ -460,20 +460,37 @@ RSpec.describe HTTP do
|
|
460
460
|
|
461
461
|
context "with :normalize_uri" do
|
462
462
|
it "normalizes URI" do
|
463
|
-
response = HTTP.get "#{dummy.endpoint}/
|
463
|
+
response = HTTP.get "#{dummy.endpoint}/héllö-wörld"
|
464
464
|
expect(response.to_s).to eq("hello world")
|
465
465
|
end
|
466
466
|
|
467
467
|
it "uses the custom URI Normalizer method" do
|
468
468
|
client = HTTP.use(:normalize_uri => {:normalizer => :itself.to_proc})
|
469
|
-
response = client.get("#{dummy.endpoint}/
|
469
|
+
response = client.get("#{dummy.endpoint}/héllö-wörld")
|
470
470
|
expect(response.status).to eq(400)
|
471
471
|
end
|
472
472
|
|
473
|
+
it "raises if custom URI Normalizer returns invalid path" do
|
474
|
+
client = HTTP.use(:normalize_uri => {:normalizer => :itself.to_proc})
|
475
|
+
expect { client.get("#{dummy.endpoint}/hello\nworld") }.
|
476
|
+
to raise_error HTTP::RequestError, 'Invalid request URI: "/hello\nworld"'
|
477
|
+
end
|
478
|
+
|
479
|
+
it "raises if custom URI Normalizer returns invalid host" do
|
480
|
+
normalizer = lambda do |uri|
|
481
|
+
uri.port = nil
|
482
|
+
uri.instance_variable_set(:@host, "example\ncom")
|
483
|
+
uri
|
484
|
+
end
|
485
|
+
client = HTTP.use(:normalize_uri => {:normalizer => normalizer})
|
486
|
+
expect { client.get(dummy.endpoint) }.
|
487
|
+
to raise_error HTTP::RequestError, 'Invalid host: "example\ncom"'
|
488
|
+
end
|
489
|
+
|
473
490
|
it "uses the default URI normalizer" do
|
474
491
|
client = HTTP.use :normalize_uri
|
475
492
|
expect(HTTP::URI::NORMALIZER).to receive(:call).and_call_original
|
476
|
-
response = client.get("#{dummy.endpoint}/
|
493
|
+
response = client.get("#{dummy.endpoint}/héllö-wörld")
|
477
494
|
expect(response.to_s).to eq("hello world")
|
478
495
|
end
|
479
496
|
end
|
@@ -1,15 +1,17 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require "cgi"
|
5
|
+
|
4
6
|
class DummyServer < WEBrick::HTTPServer
|
5
7
|
class Servlet < WEBrick::HTTPServlet::AbstractServlet # rubocop:disable Metrics/ClassLength
|
6
8
|
def self.sockets
|
7
9
|
@sockets ||= []
|
8
10
|
end
|
9
11
|
|
10
|
-
def not_found(
|
12
|
+
def not_found(req, res)
|
11
13
|
res.status = 404
|
12
|
-
res.body = "
|
14
|
+
res.body = "#{req.unparsed_uri} not found"
|
13
15
|
end
|
14
16
|
|
15
17
|
def self.handlers
|
@@ -25,7 +27,7 @@ class DummyServer < WEBrick::HTTPServer
|
|
25
27
|
def do_#{method.upcase}(req, res)
|
26
28
|
handler = self.class.handlers["#{method}:\#{req.path}"]
|
27
29
|
return instance_exec(req, res, &handler) if handler
|
28
|
-
not_found
|
30
|
+
not_found(req, res)
|
29
31
|
end
|
30
32
|
RUBY
|
31
33
|
end
|
@@ -66,7 +68,7 @@ class DummyServer < WEBrick::HTTPServer
|
|
66
68
|
end
|
67
69
|
|
68
70
|
get "/params" do |req, res|
|
69
|
-
next not_found unless "foo=bar" == req.query_string
|
71
|
+
next not_found(req, res) unless "foo=bar" == req.query_string
|
70
72
|
|
71
73
|
res.status = 200
|
72
74
|
res.body = "Params!"
|
@@ -75,7 +77,7 @@ class DummyServer < WEBrick::HTTPServer
|
|
75
77
|
get "/multiple-params" do |req, res|
|
76
78
|
params = CGI.parse req.query_string
|
77
79
|
|
78
|
-
next not_found unless {"foo" => ["bar"], "baz" => ["quux"]} == params
|
80
|
+
next not_found(req, res) unless {"foo" => ["bar"], "baz" => ["quux"]} == params
|
79
81
|
|
80
82
|
res.status = 200
|
81
83
|
res.body = "More Params!"
|
@@ -147,7 +149,7 @@ class DummyServer < WEBrick::HTTPServer
|
|
147
149
|
res.body = req.body
|
148
150
|
end
|
149
151
|
|
150
|
-
get "/
|
152
|
+
get "/héllö-wörld".b do |_req, res|
|
151
153
|
res.status = 200
|
152
154
|
res.body = "hello world"
|
153
155
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: http
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tony Arcieri
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date:
|
14
|
+
date: 2024-02-05 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: addressable
|
@@ -27,6 +27,20 @@ dependencies:
|
|
27
27
|
- - "~>"
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: '2.8'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: base64
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - "~>"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0.1'
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0.1'
|
30
44
|
- !ruby/object:Gem::Dependency
|
31
45
|
name: http-cookie
|
32
46
|
requirement: !ruby/object:Gem::Requirement
|
@@ -61,14 +75,14 @@ dependencies:
|
|
61
75
|
requirements:
|
62
76
|
- - "~>"
|
63
77
|
- !ruby/object:Gem::Version
|
64
|
-
version: 0.
|
78
|
+
version: 0.5.0
|
65
79
|
type: :runtime
|
66
80
|
prerelease: false
|
67
81
|
version_requirements: !ruby/object:Gem::Requirement
|
68
82
|
requirements:
|
69
83
|
- - "~>"
|
70
84
|
- !ruby/object:Gem::Version
|
71
|
-
version: 0.
|
85
|
+
version: 0.5.0
|
72
86
|
- !ruby/object:Gem::Dependency
|
73
87
|
name: bundler
|
74
88
|
requirement: !ruby/object:Gem::Requirement
|
@@ -96,10 +110,12 @@ files:
|
|
96
110
|
- ".rspec"
|
97
111
|
- ".rubocop.yml"
|
98
112
|
- ".rubocop/layout.yml"
|
113
|
+
- ".rubocop/metrics.yml"
|
99
114
|
- ".rubocop/style.yml"
|
100
115
|
- ".rubocop_todo.yml"
|
101
116
|
- ".yardopts"
|
102
|
-
-
|
117
|
+
- CHANGELOG.md
|
118
|
+
- CHANGES_OLD.md
|
103
119
|
- CONTRIBUTING.md
|
104
120
|
- Gemfile
|
105
121
|
- Guardfile
|
@@ -169,6 +185,7 @@ files:
|
|
169
185
|
- spec/lib/http/response/parser_spec.rb
|
170
186
|
- spec/lib/http/response/status_spec.rb
|
171
187
|
- spec/lib/http/response_spec.rb
|
188
|
+
- spec/lib/http/uri/normalizer_spec.rb
|
172
189
|
- spec/lib/http/uri_spec.rb
|
173
190
|
- spec/lib/http_spec.rb
|
174
191
|
- spec/regression_specs.rb
|
@@ -192,7 +209,7 @@ metadata:
|
|
192
209
|
source_code_uri: https://github.com/httprb/http
|
193
210
|
wiki_uri: https://github.com/httprb/http/wiki
|
194
211
|
bug_tracker_uri: https://github.com/httprb/http/issues
|
195
|
-
changelog_uri: https://github.com/httprb/http/blob/v5.
|
212
|
+
changelog_uri: https://github.com/httprb/http/blob/v5.2.0/CHANGELOG.md
|
196
213
|
rubygems_mfa_required: 'true'
|
197
214
|
post_install_message:
|
198
215
|
rdoc_options: []
|
@@ -209,7 +226,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
209
226
|
- !ruby/object:Gem::Version
|
210
227
|
version: '0'
|
211
228
|
requirements: []
|
212
|
-
rubygems_version: 3.
|
229
|
+
rubygems_version: 3.5.4
|
213
230
|
signing_key:
|
214
231
|
specification_version: 4
|
215
232
|
summary: HTTP should be easy
|
@@ -240,6 +257,7 @@ test_files:
|
|
240
257
|
- spec/lib/http/response/parser_spec.rb
|
241
258
|
- spec/lib/http/response/status_spec.rb
|
242
259
|
- spec/lib/http/response_spec.rb
|
260
|
+
- spec/lib/http/uri/normalizer_spec.rb
|
243
261
|
- spec/lib/http/uri_spec.rb
|
244
262
|
- spec/lib/http_spec.rb
|
245
263
|
- spec/regression_specs.rb
|