http 5.0.1 → 5.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: afbab6dd50416f2205ca49ae54217bff2f35e3fcf7738a8a5288644e801b4d42
4
- data.tar.gz: f4d7ee837eaeda6cd50ad6cd58dd1b7c125516a98e821d5e176a8591b2f6baff
3
+ metadata.gz: fe455ddc7caea6475216135d2748825078dfe40aa5d474e219e054a330d54967
4
+ data.tar.gz: cdc04d4ccd9e7a8d45ad5db093c63bd2172720aa0c22f6e095fe93c6892e4ea2
5
5
  SHA512:
6
- metadata.gz: 94e587b821c4839152e67d31b704cc9a20ceae5e2bb168893786aeaea32edeee80967678b348f9b2051552ebe3a6e018e1b15ca67c3231bcd51553e78486cd8f
7
- data.tar.gz: 1f0dc3544496196b8c1193509b0b3a7bf7152178488ebf071b692288caf44bc59e5edfe382f918b9f6cc4de1dfc43fdf2334ea16ce67787ef3a0ed03b1b5eb52
6
+ metadata.gz: eff3f4e56087fd798ecc435ddc662b9e3b8e248c4a39448323643238cc7f8855a98ecfbe392e26f9e77b9129f18fcab13c1a3c588c819453a2f5199a24683c28
7
+ data.tar.gz: fe8fbd07014f69631414e2644aa48198e34cac0d085a4bce8f056a5284088a2414557a5bb83456f953ce2fdaa6b69621746a31430446664f56eca2a2b454117c
@@ -2,9 +2,9 @@ name: CI
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [ master ]
5
+ branches: [ main ]
6
6
  pull_request:
7
- branches: [ master ]
7
+ branches: [ main ]
8
8
 
9
9
  env:
10
10
  BUNDLE_WITHOUT: "development"
data/CHANGES.md CHANGED
@@ -1,7 +1,37 @@
1
+ ## 5.0.2 (2021-09-10)
2
+
3
+ * [#686](https://github.com/httprb/http/pull/686)
4
+ Correctly reset the parser.
5
+ ([@bryanp])
6
+
7
+ * [#684](https://github.com/httprb/http/pull/684)
8
+ Don't set Content-Length for GET, HEAD, DELETE, or CONNECT requests without a BODY.
9
+ ([@jyn514])
10
+
11
+ * [#679](https://github.com/httprb/http/pull/679)
12
+ Use features on redirected requests.
13
+ ([@nomis])
14
+
15
+ * [#678](https://github.com/schwern)
16
+ Restore `HTTP::Response` `:uri` option for backwards compatibility.
17
+ ([@schwern])
18
+
19
+ * [#676](https://github.com/httprb/http/pull/676)
20
+ Update addressable because of CVE-2021-32740.
21
+ ([@matheussilvasantos])
22
+
23
+ * [#653](https://github.com/httprb/http/pull/653)
24
+ Avoid force encodings on frozen strings.
25
+ ([@bvicenzo])
26
+
27
+ * [#638](https://github.com/httprb/http/pull/638)
28
+ DNS failover handling.
29
+ ([@midnight-wonderer])
30
+
1
31
  ## 5.0.1 (2021-06-26)
2
32
 
3
33
  * [#670](https://github.com/httprb/http/pull/670)
4
- Revert `Response#parse` behavior introduced in #540.
34
+ Revert `Response#parse` behavior introduced in [#540].
5
35
  ([@DannyBen])
6
36
 
7
37
  * [#669](https://github.com/httprb/http/pull/669)
@@ -912,3 +942,9 @@ end
912
942
  [@meanphil]: https://github.com/meanphil
913
943
  [@odinhb]: https://github.com/odinhb
914
944
  [@DannyBen]: https://github.com/DannyBen
945
+ [@jyn514]: https://github.com/jyn514
946
+ [@bvicenzo]: https://github.com/bvicenzo
947
+ [@nomis]: https://github.com/nomis
948
+ [@midnight-wonderer]: https://github.com/midnight-wonderer
949
+ [@schwern]: https://github.com/schwern
950
+ [@matheussilvasantos]: https://github.com/matheussilvasantos
data/http.gemspec CHANGED
@@ -27,10 +27,10 @@ Gem::Specification.new do |gem|
27
27
 
28
28
  gem.required_ruby_version = ">= 2.5"
29
29
 
30
- gem.add_runtime_dependency "addressable", "~> 2.3"
30
+ gem.add_runtime_dependency "addressable", "~> 2.8"
31
31
  gem.add_runtime_dependency "http-cookie", "~> 1.0"
32
32
  gem.add_runtime_dependency "http-form_data", "~> 2.2"
33
- gem.add_runtime_dependency "llhttp-ffi", "~> 0.3.0"
33
+ gem.add_runtime_dependency "llhttp-ffi", "~> 0.4.0"
34
34
 
35
35
  gem.add_development_dependency "bundler", "~> 2.0"
36
36
 
data/lib/http/client.rb CHANGED
@@ -32,7 +32,7 @@ module HTTP
32
32
  return res unless opts.follow
33
33
 
34
34
  Redirector.new(opts.follow).perform(req, res) do |request|
35
- perform(request, opts)
35
+ perform(wrap_request(request, opts), opts)
36
36
  end
37
37
  end
38
38
 
@@ -52,9 +52,7 @@ module HTTP
52
52
  :body => body
53
53
  )
54
54
 
55
- opts.features.inject(req) do |request, (_name, feature)|
56
- feature.wrap_request(request)
57
- end
55
+ wrap_request(req, opts)
58
56
  end
59
57
 
60
58
  # @!method persistent?
@@ -104,6 +102,12 @@ module HTTP
104
102
 
105
103
  private
106
104
 
105
+ def wrap_request(req, opts)
106
+ opts.features.inject(req) do |request, (_name, feature)|
107
+ feature.wrap_request(request)
108
+ end
109
+ end
110
+
107
111
  def build_response(req, options)
108
112
  Response.new(
109
113
  :status => @connection.status_code,
@@ -21,8 +21,6 @@ module HTTP
21
21
  :request => response.request
22
22
  }
23
23
 
24
- options[:uri] = response.uri if response.uri
25
-
26
24
  Response.new(options)
27
25
  end
28
26
 
@@ -47,7 +47,11 @@ module HTTP
47
47
  # Adds the headers to the header array for the given request body we are working
48
48
  # with
49
49
  def add_body_type_headers
50
- return if @headers[Headers::CONTENT_LENGTH] || chunked?
50
+ return if @headers[Headers::CONTENT_LENGTH] || chunked? || (
51
+ @body.source.nil? && %w[GET HEAD DELETE CONNECT].any? do |method|
52
+ @request_header[0].start_with?("#{method} ")
53
+ end
54
+ )
51
55
 
52
56
  @request_header << "#{Headers::CONTENT_LENGTH}: #{@body.size}"
53
57
  end
@@ -27,7 +27,9 @@ module HTTP
27
27
  # (see HTTP::Client#readpartial)
28
28
  def readpartial(*args)
29
29
  stream!
30
- @stream.readpartial(*args)&.force_encoding(@encoding)
30
+ chunk = @stream.readpartial(*args)
31
+
32
+ String.new(chunk, :encoding => @encoding) if chunk
31
33
  end
32
34
 
33
35
  # Iterate over the body, allowing it to be enumerable
@@ -45,11 +47,11 @@ module HTTP
45
47
 
46
48
  begin
47
49
  @streaming = false
48
- @contents = String.new("").force_encoding(@encoding)
50
+ @contents = String.new("", :encoding => @encoding)
49
51
 
50
52
  while (chunk = @stream.readpartial)
51
- @contents << chunk.force_encoding(@encoding)
52
- chunk.clear # deallocate string
53
+ @contents << String.new(chunk, :encoding => @encoding)
54
+ chunk = nil # deallocate string
53
55
  end
54
56
  rescue
55
57
  @contents = nil
@@ -15,7 +15,7 @@ module HTTP
15
15
  end
16
16
 
17
17
  def reset
18
- @parser.finish
18
+ @parser.reset
19
19
  @handler.reset
20
20
  @header_finished = false
21
21
  @message_finished = false
data/lib/http/response.rb CHANGED
@@ -40,10 +40,11 @@ module HTTP
40
40
  # @option opts [HTTP::Connection] :connection
41
41
  # @option opts [String] :encoding Encoding to use when reading body
42
42
  # @option opts [String] :body
43
- # @option opts [HTTP::Request] request
43
+ # @option opts [HTTP::Request] request The request this is in response to.
44
+ # @option opts [String] :uri (DEPRECATED) used to populate a missing request
44
45
  def initialize(opts)
45
46
  @version = opts.fetch(:version)
46
- @request = opts.fetch(:request)
47
+ @request = init_request(opts)
47
48
  @status = HTTP::Response::Status.new(opts.fetch(:status))
48
49
  @headers = HTTP::Headers.coerce(opts[:headers] || {})
49
50
  @proxy_headers = HTTP::Headers.coerce(opts[:proxy_headers] || {})
@@ -164,5 +165,22 @@ module HTTP
164
165
  def inspect
165
166
  "#<#{self.class}/#{@version} #{code} #{reason} #{headers.to_h.inspect}>"
166
167
  end
168
+
169
+ private
170
+
171
+ # Initialize an HTTP::Request from options.
172
+ #
173
+ # @return [HTTP::Request]
174
+ def init_request(opts)
175
+ raise ArgumentError, ":uri is for backwards compatibilty and conflicts with :request" \
176
+ if opts[:request] && opts[:uri]
177
+
178
+ # For backwards compatibilty
179
+ if opts[:uri]
180
+ HTTP::Request.new(:uri => opts[:uri], :verb => :get)
181
+ else
182
+ opts.fetch(:request)
183
+ end
184
+ end
167
185
  end
168
186
  end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "timeout"
4
3
  require "io/wait"
4
+ require "resolv"
5
+ require "timeout"
5
6
 
6
7
  require "http/timeout/null"
7
8
 
@@ -12,6 +13,9 @@ module HTTP
12
13
  super
13
14
 
14
15
  @timeout = @time_left = options.fetch(:global_timeout)
16
+ @dns_resolver = options.fetch(:dns_resolver) do
17
+ ::Resolv.method(:getaddresses)
18
+ end
15
19
  end
16
20
 
17
21
  # To future me: Don't remove this again, past you was smarter.
@@ -19,14 +23,28 @@ module HTTP
19
23
  @time_left = @timeout
20
24
  end
21
25
 
22
- def connect(socket_class, host, port, nodelay = false)
26
+ def connect(socket_class, host_name, *args)
27
+ connect_operation = lambda do |host_address|
28
+ ::Timeout.timeout(@time_left, TimeoutError) do
29
+ super(socket_class, host_address, *args)
30
+ end
31
+ end
32
+ host_addresses = @dns_resolver.call(host_name)
33
+ # ensure something to iterates
34
+ trying_targets = host_addresses.empty? ? [host_name] : host_addresses
23
35
  reset_timer
24
- ::Timeout.timeout(@time_left, TimeoutError) do
25
- @socket = socket_class.open(host, port)
26
- @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay
36
+ trying_iterator = trying_targets.lazy
37
+ error = nil
38
+ begin
39
+ connect_operation.call(trying_iterator.next).tap do
40
+ log_time
41
+ end
42
+ rescue TimeoutError => e
43
+ error = e
44
+ retry
45
+ rescue ::StopIteration
46
+ raise error
27
47
  end
28
-
29
- log_time
30
48
  end
31
49
 
32
50
  def connect_ssl
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "resolv"
3
4
  require "timeout"
4
5
 
5
6
  require "http/timeout/null"
@@ -17,14 +18,34 @@ module HTTP
17
18
  @read_timeout = options.fetch(:read_timeout, READ_TIMEOUT)
18
19
  @write_timeout = options.fetch(:write_timeout, WRITE_TIMEOUT)
19
20
  @connect_timeout = options.fetch(:connect_timeout, CONNECT_TIMEOUT)
21
+ @dns_resolver = options.fetch(:dns_resolver) do
22
+ ::Resolv.method(:getaddresses)
23
+ end
20
24
  end
21
25
 
22
- def connect(socket_class, host, port, nodelay = false)
23
- ::Timeout.timeout(@connect_timeout, TimeoutError) do
24
- @socket = socket_class.open(host, port)
25
- @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay
26
+ # TODO: refactor
27
+ # rubocop:disable Metrics/MethodLength
28
+ def connect(socket_class, host_name, *args)
29
+ connect_operation = lambda do |host_address|
30
+ ::Timeout.timeout(@connect_timeout, TimeoutError) do
31
+ super(socket_class, host_address, *args)
32
+ end
33
+ end
34
+ host_addresses = @dns_resolver.call(host_name)
35
+ # ensure something to iterates
36
+ trying_targets = host_addresses.empty? ? [host_name] : host_addresses
37
+ trying_iterator = trying_targets.lazy
38
+ error = nil
39
+ begin
40
+ connect_operation.call(trying_iterator.next)
41
+ rescue TimeoutError => e
42
+ error = e
43
+ retry
44
+ rescue ::StopIteration
45
+ raise error
26
46
  end
27
47
  end
48
+ # rubocop:enable Metrics/MethodLength
28
49
 
29
50
  def connect_ssl
30
51
  rescue_readable(@connect_timeout) do
data/lib/http/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTP
4
- VERSION = "5.0.1"
4
+ VERSION = "5.0.2"
5
5
  end
@@ -4,6 +4,7 @@
4
4
  require "support/http_handling_shared"
5
5
  require "support/dummy_server"
6
6
  require "support/ssl_helper"
7
+ require "logger"
7
8
 
8
9
  RSpec.describe HTTP::Client do
9
10
  run_server(:dummy) { DummyServer.new }
@@ -115,6 +116,39 @@ RSpec.describe HTTP::Client do
115
116
  end
116
117
  end
117
118
 
119
+ describe "following redirects with logging" do
120
+ let(:logger) do
121
+ logger = Logger.new(logdev)
122
+ logger.formatter = ->(severity, _, _, message) { format("** %s **\n%s\n", severity, message) }
123
+ logger.level = Logger::INFO
124
+ logger
125
+ end
126
+
127
+ let(:logdev) { StringIO.new }
128
+
129
+ it "logs all requests" do
130
+ client = StubbedClient.new(:follow => true, :features => { :logging => { :logger => logger } }).stub(
131
+ "http://example.com/" => redirect_response("/1"),
132
+ "http://example.com/1" => redirect_response("/2"),
133
+ "http://example.com/2" => redirect_response("/3"),
134
+ "http://example.com/3" => simple_response("OK")
135
+ )
136
+
137
+ expect { client.get("http://example.com/") }.not_to raise_error
138
+
139
+ expect(logdev.string).to eq <<~OUTPUT
140
+ ** INFO **
141
+ > GET http://example.com/
142
+ ** INFO **
143
+ > GET http://example.com/1
144
+ ** INFO **
145
+ > GET http://example.com/2
146
+ ** INFO **
147
+ > GET http://example.com/3
148
+ OUTPUT
149
+ end
150
+ end
151
+
118
152
  describe "parsing params" do
119
153
  let(:client) { HTTP::Client.new }
120
154
  before { allow(client).to receive :perform }
@@ -74,7 +74,6 @@ RSpec.describe HTTP::Features::AutoInflate do
74
74
  :status => 200,
75
75
  :headers => {:content_encoding => "gzip"},
76
76
  :connection => connection,
77
- :uri => "https://example.com",
78
77
  :request => HTTP::Request.new(:verb => :get, :uri => "https://example.com")
79
78
  )
80
79
  end
@@ -46,7 +46,6 @@ RSpec.describe HTTP::Features::Instrumentation do
46
46
  let(:response) do
47
47
  HTTP::Response.new(
48
48
  :version => "1.1",
49
- :uri => "https://example.com",
50
49
  :status => 200,
51
50
  :headers => {:content_type => "application/json"},
52
51
  :body => '{"success": true}',
@@ -42,7 +42,6 @@ RSpec.describe HTTP::Features::Logging do
42
42
  let(:response) do
43
43
  HTTP::Response.new(
44
44
  :version => "1.1",
45
- :uri => "https://example.com",
46
45
  :status => 200,
47
46
  :headers => {:content_type => "application/json"},
48
47
  :body => '{"success": true}',
@@ -47,9 +47,20 @@ RSpec.describe HTTP::Request::Writer do
47
47
  end
48
48
  end
49
49
 
50
- context "when body is empty" do
50
+ context "when body is not set" do
51
51
  let(:body) { HTTP::Request::Body.new(nil) }
52
52
 
53
+ it "doesn't write anything to the socket and doesn't set Content-Length" do
54
+ writer.stream
55
+ expect(io.string).to eq [
56
+ "#{headerstart}\r\n\r\n"
57
+ ].join
58
+ end
59
+ end
60
+
61
+ context "when body is empty" do
62
+ let(:body) { HTTP::Request::Body.new("") }
63
+
53
64
  it "doesn't write anything to the socket and sets Content-Length" do
54
65
  writer.stream
55
66
  expect(io.string).to eq [
@@ -2,7 +2,7 @@
2
2
 
3
3
  RSpec.describe HTTP::Response::Body do
4
4
  let(:connection) { double(:sequence_id => 0) }
5
- let(:chunks) { [String.new("Hello, "), String.new("World!")] }
5
+ let(:chunks) { ["Hello, ", "World!"] }
6
6
 
7
7
  before do
8
8
  allow(connection).to receive(:readpartial) { chunks.shift }
@@ -16,7 +16,7 @@ RSpec.describe HTTP::Response::Body do
16
16
  end
17
17
 
18
18
  context "when body empty" do
19
- let(:chunks) { [String.new("")] }
19
+ let(:chunks) { [""] }
20
20
 
21
21
  it "returns responds to empty? with true" do
22
22
  expect(subject).to be_empty
@@ -45,12 +45,12 @@ RSpec.describe HTTP::Response::Body do
45
45
  it "returns content in specified encoding" do
46
46
  body = described_class.new(connection)
47
47
  expect(connection).to receive(:readpartial).
48
- and_return(String.new("content").force_encoding(Encoding::UTF_8))
48
+ and_return(String.new("content", :encoding => Encoding::UTF_8))
49
49
  expect(body.readpartial.encoding).to eq Encoding::BINARY
50
50
 
51
51
  body = described_class.new(connection, :encoding => Encoding::UTF_8)
52
52
  expect(connection).to receive(:readpartial).
53
- and_return(String.new("content").force_encoding(Encoding::BINARY))
53
+ and_return(String.new("content", :encoding => Encoding::BINARY))
54
54
  expect(body.readpartial.encoding).to eq Encoding::UTF_8
55
55
  end
56
56
  end
@@ -59,7 +59,7 @@ RSpec.describe HTTP::Response::Body do
59
59
  let(:chunks) do
60
60
  body = Zlib::Deflate.deflate("Hi, HTTP here ☺")
61
61
  len = body.length
62
- [String.new(body[0, len / 2]), String.new(body[(len / 2)..-1])]
62
+ [body[0, len / 2], body[(len / 2)..-1]]
63
63
  end
64
64
  subject(:body) do
65
65
  inflater = HTTP::Response::Inflater.new(connection)
@@ -46,9 +46,9 @@ RSpec.describe HTTP::Response::Parser do
46
46
  context "when got 100 Continue response" do
47
47
  let :raw_response do
48
48
  "HTTP/1.1 100 Continue\r\n\r\n" \
49
- "HTTP/1.1 200 OK\r\n" \
50
- "Content-Length: 12\r\n\r\n" \
51
- "Hello World!"
49
+ "HTTP/1.1 200 OK\r\n" \
50
+ "Content-Length: 12\r\n\r\n" \
51
+ "Hello World!"
52
52
  end
53
53
 
54
54
  context "when response is feeded in one part" do
@@ -4,6 +4,7 @@ RSpec.describe HTTP::Response do
4
4
  let(:body) { "Hello world!" }
5
5
  let(:uri) { "http://example.com/" }
6
6
  let(:headers) { {} }
7
+ let(:request) { HTTP::Request.new(:verb => :get, :uri => uri) }
7
8
 
8
9
  subject(:response) do
9
10
  HTTP::Response.new(
@@ -11,8 +12,7 @@ RSpec.describe HTTP::Response do
11
12
  :version => "1.1",
12
13
  :headers => headers,
13
14
  :body => body,
14
- :uri => uri,
15
- :request => HTTP::Request.new(:verb => :get, :uri => "http://example.com")
15
+ :request => request
16
16
  )
17
17
  end
18
18
 
@@ -167,7 +167,7 @@ RSpec.describe HTTP::Response do
167
167
  :version => "1.1",
168
168
  :status => 200,
169
169
  :connection => connection,
170
- :request => HTTP::Request.new(:verb => :get, :uri => "http://example.com")
170
+ :request => request
171
171
  )
172
172
  end
173
173
 
@@ -184,4 +184,43 @@ RSpec.describe HTTP::Response do
184
184
  end
185
185
  it { is_expected.not_to be_chunked }
186
186
  end
187
+
188
+ describe "backwards compatibilty with :uri" do
189
+ context "with no :verb" do
190
+ subject(:response) do
191
+ HTTP::Response.new(
192
+ :status => 200,
193
+ :version => "1.1",
194
+ :headers => headers,
195
+ :body => body,
196
+ :uri => uri
197
+ )
198
+ end
199
+
200
+ it "defaults the uri to :uri" do
201
+ expect(response.request.uri.to_s).to eq uri
202
+ end
203
+
204
+ it "defaults to the verb to :get" do
205
+ expect(response.request.verb).to eq :get
206
+ end
207
+ end
208
+
209
+ context "with both a :request and :uri" do
210
+ subject(:response) do
211
+ HTTP::Response.new(
212
+ :status => 200,
213
+ :version => "1.1",
214
+ :headers => headers,
215
+ :body => body,
216
+ :uri => uri,
217
+ :request => request
218
+ )
219
+ end
220
+
221
+ it "raises ArgumentError" do
222
+ expect { response }.to raise_error(ArgumentError)
223
+ end
224
+ end
225
+ end
187
226
  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.0.1
4
+ version: 5.0.2
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: 2021-06-26 00:00:00.000000000 Z
14
+ date: 2021-09-10 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: addressable
@@ -19,14 +19,14 @@ dependencies:
19
19
  requirements:
20
20
  - - "~>"
21
21
  - !ruby/object:Gem::Version
22
- version: '2.3'
22
+ version: '2.8'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - "~>"
28
28
  - !ruby/object:Gem::Version
29
- version: '2.3'
29
+ version: '2.8'
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: http-cookie
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -61,14 +61,14 @@ dependencies:
61
61
  requirements:
62
62
  - - "~>"
63
63
  - !ruby/object:Gem::Version
64
- version: 0.3.0
64
+ version: 0.4.0
65
65
  type: :runtime
66
66
  prerelease: false
67
67
  version_requirements: !ruby/object:Gem::Requirement
68
68
  requirements:
69
69
  - - "~>"
70
70
  - !ruby/object:Gem::Version
71
- version: 0.3.0
71
+ version: 0.4.0
72
72
  - !ruby/object:Gem::Dependency
73
73
  name: bundler
74
74
  requirement: !ruby/object:Gem::Requirement
@@ -191,7 +191,7 @@ metadata:
191
191
  source_code_uri: https://github.com/httprb/http
192
192
  wiki_uri: https://github.com/httprb/http/wiki
193
193
  bug_tracker_uri: https://github.com/httprb/http/issues
194
- changelog_uri: https://github.com/httprb/http/blob/v5.0.1/CHANGES.md
194
+ changelog_uri: https://github.com/httprb/http/blob/v5.0.2/CHANGES.md
195
195
  post_install_message:
196
196
  rdoc_options: []
197
197
  require_paths: