http 5.3.1 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +241 -41
  3. data/LICENSE.txt +1 -1
  4. data/README.md +110 -13
  5. data/UPGRADING.md +491 -0
  6. data/http.gemspec +32 -29
  7. data/lib/http/base64.rb +11 -1
  8. data/lib/http/chainable/helpers.rb +62 -0
  9. data/lib/http/chainable/verbs.rb +136 -0
  10. data/lib/http/chainable.rb +232 -136
  11. data/lib/http/client.rb +158 -127
  12. data/lib/http/connection/internals.rb +141 -0
  13. data/lib/http/connection.rb +126 -97
  14. data/lib/http/content_type.rb +61 -6
  15. data/lib/http/errors.rb +25 -1
  16. data/lib/http/feature.rb +65 -5
  17. data/lib/http/features/auto_deflate.rb +124 -17
  18. data/lib/http/features/auto_inflate.rb +38 -15
  19. data/lib/http/features/caching/entry.rb +178 -0
  20. data/lib/http/features/caching/in_memory_store.rb +63 -0
  21. data/lib/http/features/caching.rb +216 -0
  22. data/lib/http/features/digest_auth.rb +234 -0
  23. data/lib/http/features/instrumentation.rb +97 -17
  24. data/lib/http/features/logging.rb +183 -5
  25. data/lib/http/features/normalize_uri.rb +17 -0
  26. data/lib/http/features/raise_error.rb +18 -3
  27. data/lib/http/form_data/composite_io.rb +106 -0
  28. data/lib/http/form_data/file.rb +95 -0
  29. data/lib/http/form_data/multipart/param.rb +62 -0
  30. data/lib/http/form_data/multipart.rb +106 -0
  31. data/lib/http/form_data/part.rb +52 -0
  32. data/lib/http/form_data/readable.rb +58 -0
  33. data/lib/http/form_data/urlencoded.rb +175 -0
  34. data/lib/http/form_data/version.rb +8 -0
  35. data/lib/http/form_data.rb +102 -0
  36. data/lib/http/headers/known.rb +3 -0
  37. data/lib/http/headers/normalizer.rb +17 -36
  38. data/lib/http/headers.rb +172 -65
  39. data/lib/http/mime_type/adapter.rb +24 -9
  40. data/lib/http/mime_type/json.rb +19 -4
  41. data/lib/http/mime_type.rb +21 -3
  42. data/lib/http/options/definitions.rb +189 -0
  43. data/lib/http/options.rb +172 -125
  44. data/lib/http/redirector.rb +80 -75
  45. data/lib/http/request/body.rb +87 -6
  46. data/lib/http/request/builder.rb +184 -0
  47. data/lib/http/request/proxy.rb +83 -0
  48. data/lib/http/request/writer.rb +76 -16
  49. data/lib/http/request.rb +214 -98
  50. data/lib/http/response/body.rb +103 -18
  51. data/lib/http/response/inflater.rb +35 -7
  52. data/lib/http/response/parser.rb +98 -4
  53. data/lib/http/response/status/reasons.rb +2 -4
  54. data/lib/http/response/status.rb +141 -31
  55. data/lib/http/response.rb +219 -61
  56. data/lib/http/retriable/delay_calculator.rb +38 -11
  57. data/lib/http/retriable/errors.rb +21 -0
  58. data/lib/http/retriable/performer.rb +82 -38
  59. data/lib/http/session.rb +280 -0
  60. data/lib/http/timeout/global.rb +147 -34
  61. data/lib/http/timeout/null.rb +155 -9
  62. data/lib/http/timeout/per_operation.rb +139 -18
  63. data/lib/http/uri/normalizer.rb +82 -0
  64. data/lib/http/uri/parsing.rb +182 -0
  65. data/lib/http/uri.rb +289 -124
  66. data/lib/http/version.rb +2 -1
  67. data/lib/http.rb +11 -2
  68. data/sig/deps.rbs +122 -0
  69. data/sig/http.rbs +1619 -0
  70. data/test/http/base64_test.rb +28 -0
  71. data/test/http/client_test.rb +739 -0
  72. data/test/http/connection_test.rb +1533 -0
  73. data/test/http/content_type_test.rb +190 -0
  74. data/test/http/errors_test.rb +28 -0
  75. data/test/http/feature_test.rb +49 -0
  76. data/test/http/features/auto_deflate_test.rb +317 -0
  77. data/test/http/features/auto_inflate_test.rb +213 -0
  78. data/test/http/features/caching_test.rb +942 -0
  79. data/test/http/features/digest_auth_test.rb +996 -0
  80. data/test/http/features/instrumentation_test.rb +246 -0
  81. data/test/http/features/logging_test.rb +654 -0
  82. data/test/http/features/normalize_uri_test.rb +41 -0
  83. data/test/http/features/raise_error_test.rb +77 -0
  84. data/test/http/form_data/composite_io_test.rb +215 -0
  85. data/test/http/form_data/file_test.rb +255 -0
  86. data/test/http/form_data/fixtures/the-http-gem.info +1 -0
  87. data/test/http/form_data/multipart_test.rb +303 -0
  88. data/test/http/form_data/part_test.rb +90 -0
  89. data/test/http/form_data/urlencoded_test.rb +164 -0
  90. data/test/http/form_data_test.rb +232 -0
  91. data/test/http/headers/normalizer_test.rb +93 -0
  92. data/test/http/headers_test.rb +888 -0
  93. data/test/http/mime_type/json_test.rb +39 -0
  94. data/test/http/mime_type_test.rb +150 -0
  95. data/test/http/options/base_uri_test.rb +148 -0
  96. data/test/http/options/body_test.rb +21 -0
  97. data/test/http/options/features_test.rb +38 -0
  98. data/test/http/options/form_test.rb +21 -0
  99. data/test/http/options/headers_test.rb +32 -0
  100. data/test/http/options/json_test.rb +21 -0
  101. data/test/http/options/merge_test.rb +78 -0
  102. data/test/http/options/new_test.rb +37 -0
  103. data/test/http/options/proxy_test.rb +32 -0
  104. data/test/http/options_test.rb +575 -0
  105. data/test/http/redirector_test.rb +639 -0
  106. data/test/http/request/body_test.rb +318 -0
  107. data/test/http/request/builder_test.rb +623 -0
  108. data/test/http/request/writer_test.rb +391 -0
  109. data/test/http/request_test.rb +1733 -0
  110. data/test/http/response/body_test.rb +292 -0
  111. data/test/http/response/parser_test.rb +105 -0
  112. data/test/http/response/status_test.rb +322 -0
  113. data/test/http/response_test.rb +502 -0
  114. data/test/http/retriable/delay_calculator_test.rb +194 -0
  115. data/test/http/retriable/errors_test.rb +71 -0
  116. data/test/http/retriable/performer_test.rb +551 -0
  117. data/test/http/session_test.rb +424 -0
  118. data/test/http/timeout/global_test.rb +239 -0
  119. data/test/http/timeout/null_test.rb +218 -0
  120. data/test/http/timeout/per_operation_test.rb +220 -0
  121. data/test/http/uri/normalizer_test.rb +89 -0
  122. data/test/http/uri_test.rb +1140 -0
  123. data/test/http/version_test.rb +15 -0
  124. data/test/http_test.rb +818 -0
  125. data/test/regression_tests.rb +27 -0
  126. data/test/support/dummy_server/encoding_routes.rb +47 -0
  127. data/test/support/dummy_server/routes.rb +201 -0
  128. data/test/support/dummy_server/servlet.rb +81 -0
  129. data/test/support/dummy_server.rb +200 -0
  130. data/{spec → test}/support/fakeio.rb +2 -2
  131. data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
  132. data/test/support/http_handling_shared/timeout_tests.rb +134 -0
  133. data/test/support/http_handling_shared.rb +11 -0
  134. data/test/support/proxy_server.rb +207 -0
  135. data/test/support/servers/runner.rb +67 -0
  136. data/{spec → test}/support/simplecov.rb +11 -2
  137. data/test/support/ssl_helper.rb +108 -0
  138. data/test/test_helper.rb +38 -0
  139. metadata +108 -168
  140. data/.github/workflows/ci.yml +0 -67
  141. data/.gitignore +0 -15
  142. data/.rspec +0 -1
  143. data/.rubocop/layout.yml +0 -8
  144. data/.rubocop/metrics.yml +0 -4
  145. data/.rubocop/rspec.yml +0 -9
  146. data/.rubocop/style.yml +0 -32
  147. data/.rubocop.yml +0 -11
  148. data/.rubocop_todo.yml +0 -219
  149. data/.yardopts +0 -2
  150. data/CHANGES_OLD.md +0 -1002
  151. data/Gemfile +0 -51
  152. data/Guardfile +0 -18
  153. data/Rakefile +0 -64
  154. data/lib/http/headers/mixin.rb +0 -34
  155. data/lib/http/retriable/client.rb +0 -37
  156. data/logo.png +0 -0
  157. data/spec/lib/http/client_spec.rb +0 -556
  158. data/spec/lib/http/connection_spec.rb +0 -88
  159. data/spec/lib/http/content_type_spec.rb +0 -47
  160. data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
  161. data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
  162. data/spec/lib/http/features/instrumentation_spec.rb +0 -81
  163. data/spec/lib/http/features/logging_spec.rb +0 -65
  164. data/spec/lib/http/features/raise_error_spec.rb +0 -62
  165. data/spec/lib/http/headers/mixin_spec.rb +0 -36
  166. data/spec/lib/http/headers/normalizer_spec.rb +0 -52
  167. data/spec/lib/http/headers_spec.rb +0 -527
  168. data/spec/lib/http/options/body_spec.rb +0 -15
  169. data/spec/lib/http/options/features_spec.rb +0 -33
  170. data/spec/lib/http/options/form_spec.rb +0 -15
  171. data/spec/lib/http/options/headers_spec.rb +0 -24
  172. data/spec/lib/http/options/json_spec.rb +0 -15
  173. data/spec/lib/http/options/merge_spec.rb +0 -68
  174. data/spec/lib/http/options/new_spec.rb +0 -30
  175. data/spec/lib/http/options/proxy_spec.rb +0 -20
  176. data/spec/lib/http/options_spec.rb +0 -13
  177. data/spec/lib/http/redirector_spec.rb +0 -530
  178. data/spec/lib/http/request/body_spec.rb +0 -211
  179. data/spec/lib/http/request/writer_spec.rb +0 -121
  180. data/spec/lib/http/request_spec.rb +0 -234
  181. data/spec/lib/http/response/body_spec.rb +0 -85
  182. data/spec/lib/http/response/parser_spec.rb +0 -74
  183. data/spec/lib/http/response/status_spec.rb +0 -253
  184. data/spec/lib/http/response_spec.rb +0 -262
  185. data/spec/lib/http/retriable/delay_calculator_spec.rb +0 -69
  186. data/spec/lib/http/retriable/performer_spec.rb +0 -302
  187. data/spec/lib/http/uri/normalizer_spec.rb +0 -95
  188. data/spec/lib/http/uri_spec.rb +0 -71
  189. data/spec/lib/http_spec.rb +0 -535
  190. data/spec/regression_specs.rb +0 -24
  191. data/spec/spec_helper.rb +0 -89
  192. data/spec/support/black_hole.rb +0 -13
  193. data/spec/support/dummy_server/servlet.rb +0 -203
  194. data/spec/support/dummy_server.rb +0 -44
  195. data/spec/support/fuubar.rb +0 -21
  196. data/spec/support/http_handling_shared.rb +0 -190
  197. data/spec/support/proxy_server.rb +0 -39
  198. data/spec/support/servers/config.rb +0 -11
  199. data/spec/support/servers/runner.rb +0 -19
  200. data/spec/support/ssl_helper.rb +0 -104
  201. /data/{spec → test}/support/capture_warning.rb +0 -0
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTP
4
+ # URI normalization and dot-segment removal
5
+ class URI
6
+ # Default URI normalizer
7
+ # @private
8
+ NORMALIZER = lambda do |uri|
9
+ uri = HTTP::URI.parse uri
10
+ scheme = uri.scheme&.downcase
11
+ host = uri.normalized_host
12
+ host = "[#{host}]" if host&.include?(":")
13
+ default_port = scheme == HTTPS_SCHEME ? 443 : 80
14
+
15
+ HTTP::URI.new(
16
+ scheme: scheme,
17
+ user: uri.user,
18
+ password: uri.password,
19
+ host: host,
20
+ port: (uri.port == default_port ? nil : uri.port),
21
+ path: uri.path.empty? ? "/" : percent_encode(remove_dot_segments(uri.path)),
22
+ query: percent_encode(uri.query),
23
+ fragment: uri.fragment
24
+ )
25
+ end
26
+
27
+ # Standalone dot segments that terminate the algorithm
28
+ # @private
29
+ DOT_SEGMENTS = %w[. ..].freeze
30
+
31
+ # Matches "/." followed by "/" or end-of-string
32
+ # @private
33
+ SINGLE_DOT_SEGMENT = %r{\A/\.(?:/|\z)}
34
+
35
+ # Matches "/.." followed by "/" or end-of-string
36
+ # @private
37
+ DOUBLE_DOT_SEGMENT = %r{\A/\.\.(?:/|\z)}
38
+
39
+ # Matches the last segment in a path (everything after the final "/")
40
+ # @private
41
+ LAST_SEGMENT = %r{/[^/]*\z}
42
+
43
+ # Matches the first path segment, with or without a leading "/"
44
+ # @private
45
+ FIRST_SEGMENT = %r{\A/?[^/]*}
46
+
47
+ # Remove dot segments from a URI path per RFC 3986 Section 5.2.4
48
+ #
49
+ # @param [String] path URI path to normalize
50
+ #
51
+ # @api private
52
+ # @return [String] path with dot segments removed
53
+ def self.remove_dot_segments(path)
54
+ input = path.dup
55
+ output = +""
56
+ until input.empty?
57
+ reduce_dot_segment(input, output) unless
58
+ input.delete_prefix!("../") || input.delete_prefix!("./") ||
59
+ input.sub!(SINGLE_DOT_SEGMENT, "/")
60
+ end
61
+ output
62
+ end
63
+ private_class_method :remove_dot_segments
64
+
65
+ # Process a single dot-segment removal step per RFC 3986 Section 5.2.4
66
+ #
67
+ # @param [String] input remaining path input (mutated)
68
+ # @param [String] output accumulated result (mutated)
69
+ #
70
+ # @api private
71
+ # @return [void]
72
+ private_class_method def self.reduce_dot_segment(input, output)
73
+ if input.sub!(DOUBLE_DOT_SEGMENT, "/")
74
+ output.sub!(LAST_SEGMENT, "")
75
+ elsif DOT_SEGMENTS.include?(input)
76
+ input.clear
77
+ else
78
+ output << input.slice!(FIRST_SEGMENT) # steep:ignore
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTP
4
+ # Class methods and private helpers for URI parsing and host processing
5
+ class URI
6
+ # Parse the given URI string, returning an HTTP::URI object
7
+ #
8
+ # @example
9
+ # HTTP::URI.parse("http://example.com/path")
10
+ #
11
+ # @param [HTTP::URI, String, #to_str] uri to parse
12
+ #
13
+ # @api public
14
+ # @return [HTTP::URI] new URI instance
15
+ def self.parse(uri)
16
+ return uri if uri.is_a?(self)
17
+ raise InvalidError, "invalid URI: nil" if uri.nil?
18
+
19
+ uri_string = begin
20
+ String(uri)
21
+ rescue TypeError, NoMethodError
22
+ raise InvalidError, "invalid URI: #{uri.inspect}"
23
+ end
24
+ new(**parse_components(uri_string))
25
+ end
26
+
27
+ # Encodes key/value pairs as application/x-www-form-urlencoded
28
+ #
29
+ # @example
30
+ # HTTP::URI.form_encode(foo: "bar")
31
+ #
32
+ # @param [#to_hash, #to_ary] form_values to encode
33
+ # @param [TrueClass, FalseClass] sort should key/value pairs be sorted first?
34
+ #
35
+ # @api public
36
+ # @return [String] encoded value
37
+ def self.form_encode(form_values, sort: false)
38
+ return ::URI.encode_www_form(form_values) unless sort
39
+
40
+ ::URI.encode_www_form(form_values.sort_by { |k, _| String(k) })
41
+ end
42
+
43
+ # Percent-encode matching characters in a string
44
+ #
45
+ # @param [String] string raw string
46
+ #
47
+ # @api private
48
+ # @return [String] encoded value
49
+ def self.percent_encode(string)
50
+ string&.gsub(PERCENT_ENCODE) do |substr|
51
+ substr.bytes.map { |c| format("%%%02X", c) }.join
52
+ end
53
+ end
54
+
55
+ # Loads the addressable gem on first use
56
+ #
57
+ # @api private
58
+ # @return [void]
59
+ # @raise [LoadError] if addressable gem is not installed
60
+ def self.require_addressable
61
+ return if defined?(@addressable_loaded)
62
+
63
+ require "addressable/uri"
64
+ @addressable_loaded = true
65
+ end
66
+
67
+ # Convert a hostname to ASCII via IDNA (requires addressable)
68
+ #
69
+ # @param [String] host hostname to encode
70
+ # @api private
71
+ # @return [String] ASCII-encoded hostname
72
+ def self.idna_to_ascii(host)
73
+ return host if host.ascii_only?
74
+
75
+ require_addressable
76
+ Addressable::IDNA.to_ascii(host) # steep:ignore
77
+ end
78
+
79
+ private
80
+
81
+ # Serialize the authority section of a URI (userinfo + host + port)
82
+ #
83
+ # @api private
84
+ # @return [String] authority component
85
+ def authority_string
86
+ str = +"//"
87
+ if (user = @user)
88
+ str << user
89
+ str << ":#{@password}" if @password
90
+ str << "@"
91
+ end
92
+ str << @raw_host # steep:ignore
93
+ str << ":#{@port}" if @port
94
+ str
95
+ end
96
+
97
+ # Adds or removes IPv6 brackets from a host
98
+ #
99
+ # @param [String] raw_host
100
+ # @param [Boolean] brackets
101
+ # @api private
102
+ # @return [String] Host with IPv6 address brackets added or removed
103
+ def process_ipv6_brackets(raw_host, brackets: false)
104
+ return unless raw_host
105
+
106
+ stripped = raw_host.delete_prefix("[").delete_suffix("]")
107
+ ip = IPAddr.new(stripped)
108
+
109
+ if ip.ipv6?
110
+ brackets ? "[#{ip}]" : ip.to_s
111
+ else
112
+ raw_host
113
+ end
114
+ rescue IPAddr::Error
115
+ raw_host
116
+ end
117
+
118
+ # Normalize a host for comparison and lookup
119
+ #
120
+ # Percent-decodes, strips trailing dot, lowercases, and IDN-encodes
121
+ # non-ASCII hostnames.
122
+ #
123
+ # @param [String, nil] host the host to normalize
124
+ # @api private
125
+ # @return [String, nil] normalized host
126
+ def normalize_host(host)
127
+ return nil unless host
128
+
129
+ h = host.gsub(/%\h{2}/) { |match| match.delete_prefix("%").to_i(16).chr }
130
+ h = h.delete_suffix(".")
131
+ h = h.downcase
132
+ self.class.idna_to_ascii(h)
133
+ end
134
+
135
+ # Parse a URI string into component parts
136
+ #
137
+ # Uses stdlib for printable-ASCII URIs (faster), falling back to
138
+ # Addressable for non-ASCII or when stdlib rejects the input.
139
+ #
140
+ # @param [String] uri_string the URI to parse
141
+ # @api private
142
+ # @return [Hash] URI components
143
+ private_class_method def self.parse_components(uri_string)
144
+ return parse_with_addressable(uri_string) if uri_string.match?(NEEDS_ADDRESSABLE)
145
+
146
+ parse_with_stdlib(uri_string) || parse_with_addressable(uri_string)
147
+ end
148
+
149
+ # Parse an ASCII URI using stdlib
150
+ #
151
+ # @param [String] uri_string the URI to parse
152
+ # @api private
153
+ # @return [Hash, nil] URI components, or nil if stdlib rejects the input
154
+ private_class_method def self.parse_with_stdlib(uri_string)
155
+ parsed = ::URI.parse(uri_string)
156
+ # stdlib always returns a port (defaulting to scheme's default);
157
+ # only store it when explicitly specified
158
+ port = parsed.port
159
+ port = nil if port.eql?(parsed.default_port)
160
+ { scheme: parsed.scheme, user: parsed.user, password: parsed.password,
161
+ host: parsed.host, port: port, path: parsed.path,
162
+ query: parsed.query, fragment: parsed.fragment }
163
+ rescue ::URI::InvalidURIError
164
+ nil
165
+ end
166
+
167
+ # Parse a non-ASCII URI using Addressable
168
+ #
169
+ # @param [String] uri_string the URI to parse
170
+ # @api private
171
+ # @return [Hash] URI components
172
+ private_class_method def self.parse_with_addressable(uri_string)
173
+ require_addressable
174
+ parsed = Addressable::URI.parse(uri_string) # steep:ignore
175
+ { scheme: parsed.scheme, user: parsed.user, password: parsed.password,
176
+ host: parsed.host, port: parsed.port, path: parsed.path,
177
+ query: parsed.query, fragment: parsed.fragment }
178
+ rescue Addressable::URI::InvalidURIError # steep:ignore
179
+ raise InvalidError, "invalid URI: #{uri_string.inspect}"
180
+ end
181
+ end
182
+ end