http 5.0.4 → 5.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92001458b2f36332dfd042ab8750595408d2be8e941ed52be9237e11c071fef7
4
- data.tar.gz: de00f9d0a8f59f3af1605f479e503269741c751b397d62d618c8866e58929140
3
+ metadata.gz: ef55bbff996952784d0917931b8eaa6f12a1adfc9cc480daf098cbc8cd8f6107
4
+ data.tar.gz: 222f8e8723969f89994fe2a0692c41786e450c200c45b84f5c8418f736254a64
5
5
  SHA512:
6
- metadata.gz: 63249cd08800a312ffba0ed4b9a508e3890b3088058956e5433c71a60957bfe9985070225855b926d78f5a64c3b587a122c52137788b54e10da65076b39f541f
7
- data.tar.gz: 89605690a8dd8ac3bf23447de8a75f90bc37a77d8ff0e85d84b0748daf2cf06b69011d8e19cc4b22067e479decdfc9d8ca46f7fc0495ec90c25a433e10c1eac8
6
+ metadata.gz: 49b0c9e508fb02fca9d9e8a26d707202c5ac67bbdf73fce15b616b321545a3a32be96eb9e23f4d389687ad1720372eaba4412e18d800c5d29fab5bb0f4bc0d93
7
+ data.tar.gz: 1b2e22b2b33abe8059e78535556a15c53959b321fb225cd84153d5a8ea608ba4d03ead9cf018720f63b1e7c27c477f8c3f1b151cae60f2d50b6811e7dd9f5bcc
@@ -16,11 +16,11 @@ jobs:
16
16
 
17
17
  strategy:
18
18
  matrix:
19
- ruby: [ ruby-2.5, ruby-2.6, ruby-2.7, ruby-3.0 ]
19
+ ruby: [ ruby-2.6, ruby-2.7, ruby-3.0, ruby-3.1 ]
20
20
  os: [ ubuntu-latest ]
21
21
 
22
22
  steps:
23
- - uses: actions/checkout@v2
23
+ - uses: actions/checkout@v3
24
24
 
25
25
  - uses: ruby/setup-ruby@v1
26
26
  with:
@@ -38,6 +38,26 @@ jobs:
38
38
  path-to-lcov: ./coverage/lcov/lcov.info
39
39
  parallel: true
40
40
 
41
+ test-flaky:
42
+ runs-on: ${{ matrix.os }}
43
+
44
+ strategy:
45
+ matrix:
46
+ ruby: [ jruby-9.3 ]
47
+ os: [ ubuntu-latest ]
48
+
49
+ steps:
50
+ - uses: actions/checkout@v3
51
+
52
+ - uses: ruby/setup-ruby@v1
53
+ with:
54
+ ruby-version: ${{ matrix.ruby }}
55
+ bundler-cache: true
56
+
57
+ - name: bundle exec rspec
58
+ continue-on-error: true
59
+ run: bundle exec rspec --format progress --force-colour
60
+
41
61
  coveralls:
42
62
  needs: test
43
63
  runs-on: ubuntu-latest
@@ -52,11 +72,11 @@ jobs:
52
72
  runs-on: ubuntu-latest
53
73
 
54
74
  steps:
55
- - uses: actions/checkout@v2
75
+ - uses: actions/checkout@v3
56
76
 
57
77
  - uses: ruby/setup-ruby@v1
58
78
  with:
59
- ruby-version: 2.5
79
+ ruby-version: 2.6
60
80
  bundler-cache: true
61
81
 
62
82
  - name: bundle exec rubocop
data/.rubocop.yml CHANGED
@@ -7,4 +7,4 @@ AllCops:
7
7
  DefaultFormatter: fuubar
8
8
  DisplayCopNames: true
9
9
  NewCops: enable
10
- TargetRubyVersion: 2.5
10
+ TargetRubyVersion: 2.6
data/.rubocop_todo.yml CHANGED
@@ -1,13 +1,21 @@
1
1
  # This configuration was generated by
2
- # `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 42`
3
- # on 2021-04-10 09:49:03 UTC using RuboCop version 1.12.1.
2
+ # `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 100`
3
+ # on 2022-06-16 14:35:44 UTC using RuboCop version 1.30.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
+ # Offense count: 1
10
+ # This cop supports safe autocorrection (--autocorrect).
11
+ # Configuration parameters: Include.
12
+ # Include: **/*.gemspec
13
+ Gemspec/DeprecatedAttributeAssignment:
14
+ Exclude:
15
+ - 'http.gemspec'
16
+
9
17
  # Offense count: 53
10
- # Cop supports --auto-correct.
18
+ # This cop supports safe autocorrection (--autocorrect).
11
19
  # Configuration parameters: EnforcedStyle.
12
20
  # SupportedStyles: leading, trailing
13
21
  Layout/DotPosition:
@@ -22,8 +30,8 @@ Layout/DotPosition:
22
30
  - 'spec/lib/http_spec.rb'
23
31
  - 'spec/support/http_handling_shared.rb'
24
32
 
25
- # Offense count: 174
26
- # Cop supports --auto-correct.
33
+ # Offense count: 176
34
+ # This cop supports safe autocorrection (--autocorrect).
27
35
  # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces.
28
36
  # SupportedStyles: space, no_space, compact
29
37
  # SupportedStylesForEmptyBraces: space, no_space
@@ -66,7 +74,7 @@ Metrics/AbcSize:
66
74
  - 'lib/http/request.rb'
67
75
  - 'lib/http/response.rb'
68
76
 
69
- # Offense count: 66
77
+ # Offense count: 70
70
78
  # Configuration parameters: CountComments, Max, CountAsOne, ExcludedMethods, IgnoredMethods.
71
79
  # IgnoredMethods: refine
72
80
  Metrics/BlockLength:
@@ -90,6 +98,7 @@ Metrics/BlockLength:
90
98
  - 'spec/lib/http/response/parser_spec.rb'
91
99
  - 'spec/lib/http/response/status_spec.rb'
92
100
  - 'spec/lib/http/response_spec.rb'
101
+ - 'spec/lib/http/uri_spec.rb'
93
102
  - 'spec/lib/http_spec.rb'
94
103
  - 'spec/support/http_handling_shared.rb'
95
104
 
@@ -109,7 +118,7 @@ Metrics/CyclomaticComplexity:
109
118
  - 'lib/http/chainable.rb'
110
119
  - 'lib/http/client.rb'
111
120
 
112
- # Offense count: 19
121
+ # Offense count: 18
113
122
  # Configuration parameters: CountComments, Max, CountAsOne, ExcludedMethods, IgnoredMethods.
114
123
  Metrics/MethodLength:
115
124
  Exclude:
@@ -132,8 +141,13 @@ Metrics/ModuleLength:
132
141
  Exclude:
133
142
  - 'lib/http/chainable.rb'
134
143
 
144
+ # Offense count: 1
145
+ Security/CompoundHash:
146
+ Exclude:
147
+ - 'lib/http/uri.rb'
148
+
135
149
  # Offense count: 2
136
- # Cop supports --auto-correct.
150
+ # This cop supports safe autocorrection (--autocorrect).
137
151
  # Configuration parameters: EnforcedStyle.
138
152
  # SupportedStyles: separated, grouped
139
153
  Style/AccessorGrouping:
@@ -141,7 +155,7 @@ Style/AccessorGrouping:
141
155
  - 'lib/http/request.rb'
142
156
 
143
157
  # Offense count: 4
144
- # Cop supports --auto-correct.
158
+ # This cop supports safe autocorrection (--autocorrect).
145
159
  Style/EmptyCaseCondition:
146
160
  Exclude:
147
161
  - 'lib/http/client.rb'
@@ -150,7 +164,7 @@ Style/EmptyCaseCondition:
150
164
  - 'lib/http/response/status.rb'
151
165
 
152
166
  # Offense count: 5
153
- # Cop supports --auto-correct.
167
+ # This cop supports safe autocorrection (--autocorrect).
154
168
  Style/Encoding:
155
169
  Exclude:
156
170
  - 'spec/lib/http/client_spec.rb'
@@ -160,7 +174,7 @@ Style/Encoding:
160
174
  - 'spec/support/dummy_server/servlet.rb'
161
175
 
162
176
  # Offense count: 17
163
- # Configuration parameters: SuspiciousParamNames.
177
+ # Configuration parameters: SuspiciousParamNames, Allowlist.
164
178
  # SuspiciousParamNames: options, opts, args, params, parameters
165
179
  Style/OptionHash:
166
180
  Exclude:
@@ -183,8 +197,8 @@ Style/OptionalBooleanParameter:
183
197
  - 'lib/http/uri.rb'
184
198
 
185
199
  # Offense count: 3
186
- # Cop supports --auto-correct.
187
- # Configuration parameters: AutoCorrect, Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
200
+ # This cop supports safe autocorrection (--autocorrect).
201
+ # Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns.
188
202
  # URISchemes: http, https
189
203
  Layout/LineLength:
190
204
  Exclude:
data/CHANGES.md CHANGED
@@ -1,3 +1,29 @@
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
+
11
+ ## 5.1.0 (2022-06-17)
12
+
13
+ * Drop ruby-2.5 support.
14
+
15
+ * [#715](https://github.com/httprb/http/pull/715)
16
+ Set default encoding to UTF-8 for `application/json`.
17
+ ([@drwl])
18
+
19
+ * [#712](https://github.com/httprb/http/pull/712)
20
+ Recognize cookies set by redirect.
21
+ ([@tkellogg])
22
+
23
+ * [#707](https://github.com/httprb/http/pull/707)
24
+ Distinguish connection timeouts.
25
+ ([@YuLeven])
26
+
1
27
  ## 5.0.4 (2021-10-07)
2
28
 
3
29
  * [#698](https://github.com/httprb/http/pull/698)
@@ -42,7 +68,7 @@
42
68
 
43
69
  * [#638](https://github.com/httprb/http/pull/638)
44
70
  DNS failover handling.
45
- ([@midnight-wonderer])
71
+ ([@midnight-wonderer])
46
72
 
47
73
 
48
74
  ## 5.0.1 (2021-06-26)
@@ -112,6 +138,7 @@
112
138
 
113
139
  * [#576](https://github.com/httprb/http/pull/576)
114
140
  [#524](https://github.com/httprb/http/issues/524)
141
+ **BREAKING CHANGE**
115
142
  Preserve header names casing.
116
143
  ([@joshuaflanagan])
117
144
 
@@ -968,3 +995,8 @@ end
968
995
  [@matheussilvasantos]: https://github.com/matheussilvasantos
969
996
  [@PhilCoggins]: https://github.com/PhilCoggins
970
997
  [@flosacca]: https://github.com/flosacca
998
+ [@YuLeven]: https://github.com/YuLeven
999
+ [@drwl]: https://github.com/drwl
1000
+ [@tkellogg]: https://github.com/tkellogg
1001
+ [@jeraki]: https://github.com/jeraki
1002
+ [@benubois]: https://github.com/benubois
data/Gemfile CHANGED
@@ -27,7 +27,7 @@ group :test do
27
27
 
28
28
  gem "backports"
29
29
 
30
- gem "rubocop", "~> 1.21"
30
+ gem "rubocop", "~> 1.30.0"
31
31
  gem "rubocop-performance"
32
32
  gem "rubocop-rake"
33
33
  gem "rubocop-rspec"
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011-2021 Tony Arcieri, Erik Michaels-Ober, Alexey V. Zapparov, Zachary Anker
1
+ Copyright (c) 2011-2022 Tony Arcieri, Erik Michaels-Ober, Alexey V. Zapparov, Zachary Anker
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -113,7 +113,8 @@ the following Ruby versions:
113
113
  - Ruby 2.6
114
114
  - Ruby 2.7
115
115
  - Ruby 3.0
116
- - JRuby 9.2
116
+ - Ruby 3.1
117
+ - JRuby 9.3
117
118
 
118
119
  If something doesn't work on one of these versions, it's a bug.
119
120
 
@@ -141,7 +142,7 @@ dropped.
141
142
 
142
143
  ## Copyright
143
144
 
144
- Copyright © 2011-2021 Tony Arcieri, Alexey V. Zapparov, Erik Michaels-Ober, Zachary Anker.
145
+ Copyright © 2011-2022 Tony Arcieri, Alexey V. Zapparov, Erik Michaels-Ober, Zachary Anker.
145
146
  See LICENSE.txt for further details.
146
147
 
147
148
 
data/SECURITY.md ADDED
@@ -0,0 +1,5 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ Please report security issues to `bascule@gmail.com`
data/http.gemspec CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |gem|
25
25
  gem.require_paths = ["lib"]
26
26
  gem.version = HTTP::VERSION
27
27
 
28
- gem.required_ruby_version = ">= 2.5"
28
+ gem.required_ruby_version = ">= 2.6"
29
29
 
30
30
  gem.add_runtime_dependency "addressable", "~> 2.8"
31
31
  gem.add_runtime_dependency "http-cookie", "~> 1.0"
@@ -35,9 +35,10 @@ Gem::Specification.new do |gem|
35
35
  gem.add_development_dependency "bundler", "~> 2.0"
36
36
 
37
37
  gem.metadata = {
38
- "source_code_uri" => "https://github.com/httprb/http",
39
- "wiki_uri" => "https://github.com/httprb/http/wiki",
40
- "bug_tracker_uri" => "https://github.com/httprb/http/issues",
41
- "changelog_uri" => "https://github.com/httprb/http/blob/v#{HTTP::VERSION}/CHANGES.md"
38
+ "source_code_uri" => "https://github.com/httprb/http",
39
+ "wiki_uri" => "https://github.com/httprb/http/wiki",
40
+ "bug_tracker_uri" => "https://github.com/httprb/http/issues",
41
+ "changelog_uri" => "https://github.com/httprb/http/blob/v#{HTTP::VERSION}/CHANGES.md",
42
+ "rubygems_mfa_required" => "true"
42
43
  }
43
44
  end
data/lib/http/errors.rb CHANGED
@@ -19,6 +19,9 @@ module HTTP
19
19
  # Generic Timeout error
20
20
  class TimeoutError < Error; end
21
21
 
22
+ # Timeout when first establishing the conncetion
23
+ class ConnectTimeoutError < TimeoutError; end
24
+
22
25
  # Header value is of unexpected format (similar to Net::HTTPHeaderSyntaxError)
23
26
  class HeaderError < Error; end
24
27
  end
data/lib/http/headers.rb CHANGED
@@ -111,7 +111,7 @@ module HTTP
111
111
  #
112
112
  # @return [Hash]
113
113
  def to_h
114
- keys.map { |k| [k, self[k]] }.to_h
114
+ keys.to_h { |k| [k, self[k]] }
115
115
  end
116
116
  alias to_hash to_h
117
117
 
@@ -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 = opts.fetch(:strict, true)
44
- @max_hops = opts.fetch(:max_hops, 5).to_i
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
@@ -49,6 +50,8 @@ module HTTP
49
50
  @request = request
50
51
  @response = response
51
52
  @visited = []
53
+ collect_cookies_from_request
54
+ collect_cookies_from_response
52
55
 
53
56
  while REDIRECT_CODES.include? @response.status.code
54
57
  @visited << "#{@request.verb} #{@request.uri}"
@@ -59,8 +62,13 @@ module HTTP
59
62
  @response.flush
60
63
 
61
64
  # XXX(ixti): using `Array#inject` to return `nil` if no Location header.
62
- @request = redirect_to(@response.headers.get(Headers::LOCATION).inject(:+))
65
+ @request = redirect_to(@response.headers.get(Headers::LOCATION).inject(:+))
66
+ unless cookie_jar.empty?
67
+ @request.headers.set(Headers::COOKIE, cookie_jar.cookies.map { |c| "#{c.name}=#{c.value}" }.join("; "))
68
+ end
69
+ @on_redirect.call @response, @request if @on_redirect.respond_to?(:call)
63
70
  @response = yield @request
71
+ collect_cookies_from_response
64
72
  end
65
73
 
66
74
  @response
@@ -68,6 +76,48 @@ module HTTP
68
76
 
69
77
  private
70
78
 
79
+ # All known cookies. On the original request, this is only the original cookies, but after that,
80
+ # Set-Cookie headers can add, set or delete cookies.
81
+ def cookie_jar
82
+ # it seems that @response.cookies instance is reused between responses, so we have to "clone"
83
+ @cookie_jar ||= HTTP::CookieJar.new
84
+ end
85
+
86
+ def collect_cookies_from_request
87
+ request_cookie_header = @request.headers["Cookie"]
88
+ cookies =
89
+ if request_cookie_header
90
+ HTTP::Cookie.cookie_value_to_hash(request_cookie_header)
91
+ else
92
+ {}
93
+ end
94
+
95
+ cookies.each do |key, value|
96
+ cookie_jar.add(HTTP::Cookie.new(key, value, :path => @request.uri.path, :domain => @request.host))
97
+ end
98
+ end
99
+
100
+ # Carry cookies from one response to the next. Carrying cookies to the next response ends up
101
+ # carrying them to the next request as well.
102
+ #
103
+ # Note that this isn't part of the IETF standard, but all major browsers support setting cookies
104
+ # on redirect: https://blog.dubbelboer.com/2012/11/25/302-cookie.html
105
+ def collect_cookies_from_response
106
+ # Overwrite previous cookies
107
+ @response.cookies.each do |cookie|
108
+ if cookie.value == ""
109
+ cookie_jar.delete(cookie)
110
+ else
111
+ cookie_jar.add(cookie)
112
+ end
113
+ end
114
+
115
+ # I wish we could just do @response.cookes = cookie_jar
116
+ cookie_jar.each do |cookie|
117
+ @response.cookies.add(cookie)
118
+ end
119
+ end
120
+
71
121
  # Check if we reached max amount of redirect hops
72
122
  # @return [Boolean]
73
123
  def too_many_hops?
@@ -69,7 +69,7 @@ module HTTP
69
69
  # SYMBOL_CODES[:im_a_teapot] # => 418
70
70
  #
71
71
  # @return [Hash<Symbol => Fixnum>]
72
- SYMBOL_CODES = SYMBOLS.map { |k, v| [v, k] }.to_h.freeze
72
+ SYMBOL_CODES = SYMBOLS.to_h { |k, v| [v, k] }.freeze
73
73
 
74
74
  # @return [Fixnum] status code
75
75
  attr_reader :code
data/lib/http/response.rb CHANGED
@@ -53,7 +53,7 @@ module HTTP
53
53
  @body = opts.fetch(:body)
54
54
  else
55
55
  connection = opts.fetch(:connection)
56
- encoding = opts[:encoding] || charset || Encoding::BINARY
56
+ encoding = opts[:encoding] || charset || default_encoding
57
57
 
58
58
  @body = Response::Body.new(connection, :encoding => encoding)
59
59
  end
@@ -168,6 +168,12 @@ module HTTP
168
168
 
169
169
  private
170
170
 
171
+ def default_encoding
172
+ return Encoding::UTF_8 if mime_type == "application/json"
173
+
174
+ Encoding::BINARY
175
+ end
176
+
171
177
  # Initialize an HTTP::Request from options.
172
178
  #
173
179
  # @return [HTTP::Request]
@@ -21,7 +21,7 @@ module HTTP
21
21
 
22
22
  def connect(socket_class, host, port, nodelay = false)
23
23
  reset_timer
24
- ::Timeout.timeout(@time_left, TimeoutError) do
24
+ ::Timeout.timeout(@time_left, ConnectTimeoutError) do
25
25
  @socket = socket_class.open(host, port)
26
26
  @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay
27
27
  end
@@ -20,7 +20,7 @@ module HTTP
20
20
  end
21
21
 
22
22
  def connect(socket_class, host, port, nodelay = false)
23
- ::Timeout.timeout(@connect_timeout, TimeoutError) do
23
+ ::Timeout.timeout(@connect_timeout, ConnectTimeoutError) do
24
24
  @socket = socket_class.open(host, port)
25
25
  @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay
26
26
  end
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,6 +19,18 @@ 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
 
@@ -83,6 +94,9 @@ module HTTP
83
94
  else
84
95
  raise TypeError, "expected Hash for options, got #{options_or_uri.class}"
85
96
  end
97
+
98
+ @host = process_ipv6_brackets(@uri.host)
99
+ @normalized_host = process_ipv6_brackets(@uri.normalized_host)
86
100
  end
87
101
 
88
102
  # Are these URI objects equal? Normalizes both URIs prior to comparison
@@ -110,6 +124,17 @@ module HTTP
110
124
  @hash ||= to_s.hash * -1
111
125
  end
112
126
 
127
+ # Sets the host component for the URI.
128
+ #
129
+ # @param [String, #to_str] new_host The new host component.
130
+ # @return [void]
131
+ def host=(new_host)
132
+ @uri.host = process_ipv6_brackets(new_host, :brackets => true)
133
+
134
+ @host = process_ipv6_brackets(@uri.host)
135
+ @normalized_host = process_ipv6_brackets(@uri.normalized_host)
136
+ end
137
+
113
138
  # Port number, either as specified or the default if unspecified
114
139
  #
115
140
  # @return [Integer] port number
@@ -146,5 +171,25 @@ module HTTP
146
171
  def inspect
147
172
  format("#<%s:0x%014x URI:%s>", self.class.name, object_id << 1, to_s)
148
173
  end
174
+
175
+ private
176
+
177
+ # Process a URI host, adding or removing surrounding brackets if the host is an IPv6 address.
178
+ #
179
+ # @param [Boolean] brackets When true, brackets will be added to IPv6 addresses if missing. When
180
+ # false, they will be removed if present.
181
+ #
182
+ # @return [String] Host with IPv6 address brackets added or removed
183
+ def process_ipv6_brackets(raw_host, brackets: false)
184
+ ip = IPAddr.new(raw_host)
185
+
186
+ if ip.ipv6?
187
+ brackets ? "[#{ip}]" : ip.to_s
188
+ else
189
+ raw_host
190
+ end
191
+ rescue IPAddr::Error
192
+ raw_host
193
+ end
149
194
  end
150
195
  end
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.4"
4
+ VERSION = "5.1.1"
5
5
  end
@@ -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 }
@@ -389,8 +391,8 @@ RSpec.describe HTTP::Client do
389
391
  client.use(:test_feature => feature_instance).
390
392
  timeout(0.001).
391
393
  request(:post, sleep_url)
392
- end.to raise_error(HTTP::TimeoutError)
393
- expect(feature_instance.captured_error).to be_a(HTTP::TimeoutError)
394
+ end.to raise_error(HTTP::ConnectTimeoutError)
395
+ expect(feature_instance.captured_error).to be_a(HTTP::ConnectTimeoutError)
394
396
  end
395
397
  end
396
398
  end
@@ -11,8 +11,12 @@ RSpec.describe HTTP::Redirector do
11
11
  )
12
12
  end
13
13
 
14
- def redirect_response(status, location)
15
- simple_response status, "", "Location" => location
14
+ def redirect_response(status, location, set_cookie = {})
15
+ res = simple_response status, "", "Location" => location
16
+ set_cookie.each do |name, value|
17
+ res.headers.add("Set-Cookie", "#{name}=#{value}; path=/; httponly; secure; SameSite=none; Secure")
18
+ end
19
+ res
16
20
  end
17
21
 
18
22
  describe "#strict" do
@@ -89,6 +93,87 @@ RSpec.describe HTTP::Redirector do
89
93
  expect(res.to_s).to eq "http://example.com/123"
90
94
  end
91
95
 
96
+ it "returns cookies in response" do
97
+ req = HTTP::Request.new :verb => :head, :uri => "http://example.com"
98
+ hops = [
99
+ redirect_response(301, "http://example.com/1", {"foo" => "42"}),
100
+ redirect_response(301, "http://example.com/2", {"bar" => "53", "deleted" => "foo"}),
101
+ redirect_response(301, "http://example.com/3", {"baz" => "64", "deleted" => ""}),
102
+ redirect_response(301, "http://example.com/4", {"baz" => "65"}),
103
+ simple_response(200, "bar")
104
+ ]
105
+
106
+ request_cookies = [
107
+ {"foo" => "42"},
108
+ {"foo" => "42", "bar" => "53", "deleted" => "foo"},
109
+ {"foo" => "42", "bar" => "53", "baz" => "64"},
110
+ {"foo" => "42", "bar" => "53", "baz" => "65"}
111
+ ]
112
+
113
+ res = redirector.perform(req, hops.shift) do |request|
114
+ req_cookie = HTTP::Cookie.cookie_value_to_hash(request.headers["Cookie"] || "")
115
+ expect(req_cookie).to eq request_cookies.shift
116
+ hops.shift
117
+ end
118
+ expect(res.to_s).to eq "bar"
119
+ cookies = res.cookies.cookies.to_h { |c| [c.name, c.value] }
120
+ expect(cookies["foo"]).to eq "42"
121
+ expect(cookies["bar"]).to eq "53"
122
+ expect(cookies["baz"]).to eq "65"
123
+ expect(cookies["deleted"]).to eq nil
124
+ end
125
+
126
+ it "returns original cookies in response" do
127
+ req = HTTP::Request.new :verb => :head, :uri => "http://example.com"
128
+ req.headers.set("Cookie", "foo=42; deleted=baz")
129
+ hops = [
130
+ redirect_response(301, "http://example.com/1", {"bar" => "64", "deleted" => ""}),
131
+ simple_response(200, "bar")
132
+ ]
133
+
134
+ request_cookies = [
135
+ {"foo" => "42", "bar" => "64"},
136
+ {"foo" => "42", "bar" => "64"}
137
+ ]
138
+
139
+ res = redirector.perform(req, hops.shift) do |request|
140
+ req_cookie = HTTP::Cookie.cookie_value_to_hash(request.headers["Cookie"] || "")
141
+ expect(req_cookie).to eq request_cookies.shift
142
+ hops.shift
143
+ end
144
+ expect(res.to_s).to eq "bar"
145
+ cookies = res.cookies.cookies.to_h { |c| [c.name, c.value] }
146
+ expect(cookies["foo"]).to eq "42"
147
+ expect(cookies["bar"]).to eq "64"
148
+ expect(cookies["deleted"]).to eq nil
149
+ end
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
+
92
177
  context "following 300 redirect" do
93
178
  context "with strict mode" do
94
179
  let(:options) { {:strict => true} }
@@ -400,7 +485,7 @@ RSpec.describe HTTP::Redirector do
400
485
  describe "changing verbs during redirects" do
401
486
  let(:options) { {:strict => false} }
402
487
  let(:post_body) { HTTP::Request::Body.new("i might be way longer in real life") }
403
- let(:cookie) { "dont eat my cookies" }
488
+ let(:cookie) { "dont=eat my cookies" }
404
489
 
405
490
  def a_dangerous_request(verb)
406
491
  HTTP::Request.new(
@@ -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
- [body[0, len / 2], body[(len / 2)..-1]]
62
+ [body[0, len / 2], body[(len / 2)..]]
63
63
  end
64
64
  subject(:body) do
65
65
  inflater = HTTP::Response::Inflater.new(connection)
@@ -223,4 +223,40 @@ RSpec.describe HTTP::Response do
223
223
  end
224
224
  end
225
225
  end
226
+
227
+ describe "#body" do
228
+ let(:connection) { double(:sequence_id => 0) }
229
+ let(:chunks) { ["Hello, ", "World!"] }
230
+
231
+ subject(:response) do
232
+ HTTP::Response.new(
233
+ :status => 200,
234
+ :version => "1.1",
235
+ :headers => headers,
236
+ :request => request,
237
+ :connection => connection
238
+ )
239
+ end
240
+
241
+ before do
242
+ allow(connection).to receive(:readpartial) { chunks.shift }
243
+ allow(connection).to receive(:body_completed?) { chunks.empty? }
244
+ end
245
+
246
+ context "with no Content-Type" do
247
+ let(:headers) { {} }
248
+
249
+ it "returns a body with default binary encoding" do
250
+ expect(response.body.to_s.encoding).to eq Encoding::BINARY
251
+ end
252
+ end
253
+
254
+ context "with Content-Type: application/json" do
255
+ let(:headers) { {"Content-Type" => "application/json"} }
256
+
257
+ it "returns a body with a default UTF_8 encoding" do
258
+ expect(response.body.to_s.encoding).to eq Encoding::UTF_8
259
+ end
260
+ end
261
+ end
226
262
  end
@@ -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
@@ -1,6 +1,8 @@
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
@@ -77,7 +77,7 @@ RSpec.shared_context "HTTP handling" do
77
77
  sleep 1.25
78
78
  end
79
79
 
80
- expect { response }.to raise_error(HTTP::TimeoutError, /execution/)
80
+ expect { response }.to raise_error(HTTP::ConnectTimeoutError, /execution/)
81
81
  end
82
82
 
83
83
  it "errors if reading takes too long" do
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.4
4
+ version: 5.1.1
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-10-07 00:00:00.000000000 Z
14
+ date: 2022-12-17 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: addressable
@@ -106,6 +106,7 @@ files:
106
106
  - LICENSE.txt
107
107
  - README.md
108
108
  - Rakefile
109
+ - SECURITY.md
109
110
  - http.gemspec
110
111
  - lib/http.rb
111
112
  - lib/http/chainable.rb
@@ -191,7 +192,8 @@ metadata:
191
192
  source_code_uri: https://github.com/httprb/http
192
193
  wiki_uri: https://github.com/httprb/http/wiki
193
194
  bug_tracker_uri: https://github.com/httprb/http/issues
194
- changelog_uri: https://github.com/httprb/http/blob/v5.0.4/CHANGES.md
195
+ changelog_uri: https://github.com/httprb/http/blob/v5.1.1/CHANGES.md
196
+ rubygems_mfa_required: 'true'
195
197
  post_install_message:
196
198
  rdoc_options: []
197
199
  require_paths:
@@ -200,7 +202,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
200
202
  requirements:
201
203
  - - ">="
202
204
  - !ruby/object:Gem::Version
203
- version: '2.5'
205
+ version: '2.6'
204
206
  required_rubygems_version: !ruby/object:Gem::Requirement
205
207
  requirements:
206
208
  - - ">="