http 6.0.0-java

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 (142) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +267 -0
  3. data/CONTRIBUTING.md +26 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.md +263 -0
  6. data/SECURITY.md +17 -0
  7. data/UPGRADING.md +491 -0
  8. data/http.gemspec +48 -0
  9. data/lib/http/base64.rb +22 -0
  10. data/lib/http/chainable/helpers.rb +62 -0
  11. data/lib/http/chainable/verbs.rb +136 -0
  12. data/lib/http/chainable.rb +377 -0
  13. data/lib/http/client.rb +230 -0
  14. data/lib/http/connection/internals.rb +141 -0
  15. data/lib/http/connection.rb +265 -0
  16. data/lib/http/content_type.rb +89 -0
  17. data/lib/http/errors.rb +67 -0
  18. data/lib/http/feature.rb +86 -0
  19. data/lib/http/features/auto_deflate.rb +230 -0
  20. data/lib/http/features/auto_inflate.rb +64 -0
  21. data/lib/http/features/caching/entry.rb +178 -0
  22. data/lib/http/features/caching/in_memory_store.rb +63 -0
  23. data/lib/http/features/caching.rb +216 -0
  24. data/lib/http/features/digest_auth.rb +234 -0
  25. data/lib/http/features/instrumentation.rb +149 -0
  26. data/lib/http/features/logging.rb +231 -0
  27. data/lib/http/features/normalize_uri.rb +34 -0
  28. data/lib/http/features/raise_error.rb +37 -0
  29. data/lib/http/form_data/composite_io.rb +106 -0
  30. data/lib/http/form_data/file.rb +95 -0
  31. data/lib/http/form_data/multipart/param.rb +62 -0
  32. data/lib/http/form_data/multipart.rb +106 -0
  33. data/lib/http/form_data/part.rb +52 -0
  34. data/lib/http/form_data/readable.rb +58 -0
  35. data/lib/http/form_data/urlencoded.rb +175 -0
  36. data/lib/http/form_data/version.rb +8 -0
  37. data/lib/http/form_data.rb +102 -0
  38. data/lib/http/headers/known.rb +90 -0
  39. data/lib/http/headers/normalizer.rb +50 -0
  40. data/lib/http/headers.rb +343 -0
  41. data/lib/http/mime_type/adapter.rb +43 -0
  42. data/lib/http/mime_type/json.rb +41 -0
  43. data/lib/http/mime_type.rb +96 -0
  44. data/lib/http/options/definitions.rb +189 -0
  45. data/lib/http/options.rb +241 -0
  46. data/lib/http/redirector.rb +157 -0
  47. data/lib/http/request/body.rb +181 -0
  48. data/lib/http/request/builder.rb +184 -0
  49. data/lib/http/request/proxy.rb +83 -0
  50. data/lib/http/request/writer.rb +186 -0
  51. data/lib/http/request.rb +375 -0
  52. data/lib/http/response/body.rb +172 -0
  53. data/lib/http/response/inflater.rb +60 -0
  54. data/lib/http/response/parser.rb +223 -0
  55. data/lib/http/response/status/reasons.rb +79 -0
  56. data/lib/http/response/status.rb +263 -0
  57. data/lib/http/response.rb +350 -0
  58. data/lib/http/retriable/delay_calculator.rb +91 -0
  59. data/lib/http/retriable/errors.rb +35 -0
  60. data/lib/http/retriable/performer.rb +197 -0
  61. data/lib/http/session.rb +280 -0
  62. data/lib/http/timeout/global.rb +229 -0
  63. data/lib/http/timeout/null.rb +225 -0
  64. data/lib/http/timeout/per_operation.rb +197 -0
  65. data/lib/http/uri/normalizer.rb +82 -0
  66. data/lib/http/uri/parsing.rb +182 -0
  67. data/lib/http/uri.rb +376 -0
  68. data/lib/http/version.rb +6 -0
  69. data/lib/http.rb +36 -0
  70. data/sig/deps.rbs +122 -0
  71. data/sig/http.rbs +1619 -0
  72. data/test/http/base64_test.rb +28 -0
  73. data/test/http/client_test.rb +739 -0
  74. data/test/http/connection_test.rb +1533 -0
  75. data/test/http/content_type_test.rb +190 -0
  76. data/test/http/errors_test.rb +28 -0
  77. data/test/http/feature_test.rb +49 -0
  78. data/test/http/features/auto_deflate_test.rb +317 -0
  79. data/test/http/features/auto_inflate_test.rb +213 -0
  80. data/test/http/features/caching_test.rb +942 -0
  81. data/test/http/features/digest_auth_test.rb +996 -0
  82. data/test/http/features/instrumentation_test.rb +246 -0
  83. data/test/http/features/logging_test.rb +654 -0
  84. data/test/http/features/normalize_uri_test.rb +41 -0
  85. data/test/http/features/raise_error_test.rb +77 -0
  86. data/test/http/form_data/composite_io_test.rb +215 -0
  87. data/test/http/form_data/file_test.rb +255 -0
  88. data/test/http/form_data/fixtures/the-http-gem.info +1 -0
  89. data/test/http/form_data/multipart_test.rb +303 -0
  90. data/test/http/form_data/part_test.rb +90 -0
  91. data/test/http/form_data/urlencoded_test.rb +164 -0
  92. data/test/http/form_data_test.rb +232 -0
  93. data/test/http/headers/normalizer_test.rb +93 -0
  94. data/test/http/headers_test.rb +888 -0
  95. data/test/http/mime_type/json_test.rb +39 -0
  96. data/test/http/mime_type_test.rb +150 -0
  97. data/test/http/options/base_uri_test.rb +148 -0
  98. data/test/http/options/body_test.rb +21 -0
  99. data/test/http/options/features_test.rb +38 -0
  100. data/test/http/options/form_test.rb +21 -0
  101. data/test/http/options/headers_test.rb +32 -0
  102. data/test/http/options/json_test.rb +21 -0
  103. data/test/http/options/merge_test.rb +78 -0
  104. data/test/http/options/new_test.rb +37 -0
  105. data/test/http/options/proxy_test.rb +32 -0
  106. data/test/http/options_test.rb +575 -0
  107. data/test/http/redirector_test.rb +639 -0
  108. data/test/http/request/body_test.rb +318 -0
  109. data/test/http/request/builder_test.rb +623 -0
  110. data/test/http/request/writer_test.rb +391 -0
  111. data/test/http/request_test.rb +1733 -0
  112. data/test/http/response/body_test.rb +292 -0
  113. data/test/http/response/parser_test.rb +105 -0
  114. data/test/http/response/status_test.rb +322 -0
  115. data/test/http/response_test.rb +502 -0
  116. data/test/http/retriable/delay_calculator_test.rb +194 -0
  117. data/test/http/retriable/errors_test.rb +71 -0
  118. data/test/http/retriable/performer_test.rb +551 -0
  119. data/test/http/session_test.rb +424 -0
  120. data/test/http/timeout/global_test.rb +239 -0
  121. data/test/http/timeout/null_test.rb +218 -0
  122. data/test/http/timeout/per_operation_test.rb +220 -0
  123. data/test/http/uri/normalizer_test.rb +89 -0
  124. data/test/http/uri_test.rb +1140 -0
  125. data/test/http/version_test.rb +15 -0
  126. data/test/http_test.rb +818 -0
  127. data/test/regression_tests.rb +27 -0
  128. data/test/support/capture_warning.rb +10 -0
  129. data/test/support/dummy_server/encoding_routes.rb +47 -0
  130. data/test/support/dummy_server/routes.rb +201 -0
  131. data/test/support/dummy_server/servlet.rb +81 -0
  132. data/test/support/dummy_server.rb +200 -0
  133. data/test/support/fakeio.rb +21 -0
  134. data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
  135. data/test/support/http_handling_shared/timeout_tests.rb +134 -0
  136. data/test/support/http_handling_shared.rb +11 -0
  137. data/test/support/proxy_server.rb +207 -0
  138. data/test/support/servers/runner.rb +67 -0
  139. data/test/support/simplecov.rb +28 -0
  140. data/test/support/ssl_helper.rb +108 -0
  141. data/test/test_helper.rb +38 -0
  142. metadata +218 -0
@@ -0,0 +1,375 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+ require "time"
5
+
6
+ require "http/base64"
7
+ require "http/errors"
8
+ require "http/headers"
9
+ require "http/request/body"
10
+ require "http/request/proxy"
11
+ require "http/request/writer"
12
+ require "http/version"
13
+ require "http/uri"
14
+
15
+ module HTTP
16
+ # Represents an HTTP request with verb, URI, headers, and body
17
+ class Request
18
+ extend Forwardable
19
+
20
+ include HTTP::Base64
21
+ include Proxy
22
+
23
+ # The method given was not understood
24
+ class UnsupportedMethodError < RequestError; end
25
+
26
+ # The scheme of given URI was not understood
27
+ class UnsupportedSchemeError < RequestError; end
28
+
29
+ # Default User-Agent header value
30
+ USER_AGENT = "http.rb/#{HTTP::VERSION}".freeze
31
+
32
+ # Supported HTTP methods
33
+ METHODS = [
34
+ # RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1
35
+ :options, :get, :head, :post, :put, :delete, :trace, :connect,
36
+
37
+ # RFC 2518: HTTP Extensions for Distributed Authoring -- WEBDAV
38
+ :propfind, :proppatch, :mkcol, :copy, :move, :lock, :unlock,
39
+
40
+ # RFC 3648: WebDAV Ordered Collections Protocol
41
+ :orderpatch,
42
+
43
+ # RFC 3744: WebDAV Access Control Protocol
44
+ :acl,
45
+
46
+ # RFC 6352: vCard Extensions to WebDAV -- CardDAV
47
+ :report,
48
+
49
+ # RFC 5789: PATCH Method for HTTP
50
+ :patch,
51
+
52
+ # draft-reschke-webdav-search: WebDAV Search
53
+ :search,
54
+
55
+ # RFC 4791: Calendaring Extensions to WebDAV -- CalDAV
56
+ :mkcalendar,
57
+
58
+ # Implemented by several caching servers, like Squid, Varnish or Fastly
59
+ :purge
60
+ ].freeze
61
+
62
+ # Allowed schemes
63
+ SCHEMES = %i[http https ws wss].freeze
64
+
65
+ # Default ports of supported schemes
66
+ PORTS = {
67
+ http: 80,
68
+ https: 443,
69
+ ws: 80,
70
+ wss: 443
71
+ }.freeze
72
+
73
+ # HTTP method as a lowercase symbol
74
+ #
75
+ # @example
76
+ # request.verb # => :get
77
+ #
78
+ # @return [Symbol]
79
+ # @api public
80
+ attr_reader :verb
81
+
82
+ # URI scheme as a lowercase symbol
83
+ #
84
+ # @example
85
+ # request.scheme # => :https
86
+ #
87
+ # @return [Symbol]
88
+ # @api public
89
+ attr_reader :scheme
90
+
91
+ # URI normalizer callable
92
+ #
93
+ # @example
94
+ # request.uri_normalizer
95
+ #
96
+ # @return [#call]
97
+ # @api public
98
+ attr_reader :uri_normalizer
99
+
100
+ # Request URI
101
+ #
102
+ # @example
103
+ # request.uri # => #<HTTP::URI ...>
104
+ #
105
+ # @return [HTTP::URI]
106
+ # @api public
107
+ attr_reader :uri
108
+
109
+ # Proxy configuration hash
110
+ #
111
+ # @example
112
+ # request.proxy
113
+ #
114
+ # @return [Hash]
115
+ # @api public
116
+ attr_reader :proxy
117
+
118
+ # Request body object
119
+ #
120
+ # @example
121
+ # request.body
122
+ #
123
+ # @return [HTTP::Request::Body]
124
+ # @api public
125
+ attr_reader :body
126
+
127
+ # The HTTP headers collection
128
+ #
129
+ # @example
130
+ # request.headers
131
+ #
132
+ # @return [HTTP::Headers]
133
+ # @api public
134
+ attr_reader :headers
135
+
136
+ # HTTP version string
137
+ #
138
+ # @example
139
+ # request.version # => "1.1"
140
+ #
141
+ # @return [String]
142
+ # @api public
143
+ attr_reader :version
144
+
145
+ # Create a new HTTP request
146
+ #
147
+ # @param [#to_s] verb HTTP request method
148
+ # @param [HTTP::URI, #to_s] uri
149
+ # @param [Hash] headers
150
+ # @param [Hash] proxy
151
+ # @param [String, Enumerable, IO, nil] body
152
+ # @param [String] version
153
+ # @param [#call] uri_normalizer
154
+ #
155
+ # @example
156
+ # Request.new(verb: :get, uri: "https://example.com")
157
+ #
158
+ # @return [HTTP::Request]
159
+ # @api public
160
+ def initialize(verb:, uri:, headers: nil, proxy: {}, body: nil, version: "1.1",
161
+ uri_normalizer: nil)
162
+ @uri_normalizer = uri_normalizer || HTTP::URI::NORMALIZER
163
+ @verb = verb.to_s.downcase.to_sym
164
+ parse_uri!(uri)
165
+ validate_method_and_scheme!
166
+
167
+ @proxy = proxy
168
+ @version = version
169
+ @headers = prepare_headers(headers)
170
+ @body = prepare_body(body)
171
+ end
172
+
173
+ # Returns new Request with updated uri
174
+ #
175
+ # @example
176
+ # request.redirect("https://example.com/new")
177
+ #
178
+ # @return [HTTP::Request]
179
+ # @api public
180
+ def redirect(uri, verb = @verb)
181
+ redirect_uri = @uri.join(uri)
182
+ headers = redirect_headers(redirect_uri, verb)
183
+ new_body = verb == :get ? nil : body.source
184
+
185
+ self.class.new(
186
+ verb: verb,
187
+ uri: redirect_uri,
188
+ headers: headers,
189
+ proxy: proxy,
190
+ body: new_body,
191
+ version: version,
192
+ uri_normalizer: uri_normalizer
193
+ )
194
+ end
195
+
196
+ # Stream the request to a socket
197
+ #
198
+ # @example
199
+ # request.stream(socket)
200
+ #
201
+ # @return [void]
202
+ # @api public
203
+ def stream(socket)
204
+ include_proxy_headers if using_proxy? && !@uri.https?
205
+ Request::Writer.new(socket, body, headers, headline).stream
206
+ end
207
+
208
+ # Is this request using a proxy?
209
+ #
210
+ # @example
211
+ # request.using_proxy?
212
+ #
213
+ # @return [Boolean]
214
+ # @api public
215
+ def using_proxy?
216
+ proxy && proxy.keys.size >= 2
217
+ end
218
+
219
+ # Is this request using an authenticated proxy?
220
+ #
221
+ # @example
222
+ # request.using_authenticated_proxy?
223
+ #
224
+ # @return [Boolean]
225
+ # @api public
226
+ def using_authenticated_proxy?
227
+ proxy && proxy.keys.size >= 4
228
+ end
229
+
230
+ # Compute HTTP request header for direct or proxy request
231
+ #
232
+ # @example
233
+ # request.headline
234
+ #
235
+ # @return [String]
236
+ # @api public
237
+ def headline
238
+ request_uri =
239
+ if using_proxy? && !uri.https?
240
+ uri.omit(:fragment)
241
+ else
242
+ uri.request_uri
243
+ end.to_s
244
+
245
+ raise RequestError, "Invalid request URI: #{request_uri.inspect}" if request_uri.match?(/\s/)
246
+
247
+ "#{verb.to_s.upcase} #{request_uri} HTTP/#{version}"
248
+ end
249
+
250
+ # Host for tcp socket
251
+ #
252
+ # @example
253
+ # request.socket_host
254
+ #
255
+ # @return [String]
256
+ # @api public
257
+ def socket_host
258
+ using_proxy? ? proxy[:proxy_address] : host
259
+ end
260
+
261
+ # Port for tcp socket
262
+ #
263
+ # @example
264
+ # request.socket_port
265
+ #
266
+ # @return [Integer]
267
+ # @api public
268
+ def socket_port
269
+ using_proxy? ? proxy[:proxy_port] : port
270
+ end
271
+
272
+ # Human-readable representation of base request info
273
+ #
274
+ # @example
275
+ #
276
+ # req.inspect
277
+ # # => #<HTTP::Request/1.1 GET https://example.com>
278
+ #
279
+ # @return [String]
280
+ # @api public
281
+ def inspect
282
+ format("#<%s/%s %s %s>", self.class, @version, String(verb).upcase, uri)
283
+ end
284
+
285
+ private
286
+
287
+ # Build headers for a redirect request
288
+ #
289
+ # Strips the Host header (it will be regenerated), sensitive credentials
290
+ # on cross-origin redirects, and Content-Type on GET verb changes.
291
+ #
292
+ # @param [HTTP::URI] redirect_uri the target URI
293
+ # @param [Symbol] verb the HTTP verb for the redirected request
294
+ # @return [HTTP::Headers] headers for the redirect
295
+ # @api private
296
+ def redirect_headers(redirect_uri, verb)
297
+ headers = self.headers.dup
298
+ headers.delete(Headers::HOST)
299
+
300
+ # Strip sensitive headers when redirecting to a different origin
301
+ # (scheme + host + port) to prevent credential leakage.
302
+ unless @uri.origin == redirect_uri.origin
303
+ headers.delete(Headers::AUTHORIZATION)
304
+ headers.delete(Headers::COOKIE)
305
+ end
306
+
307
+ headers.delete(Headers::CONTENT_TYPE) if verb == :get
308
+
309
+ headers
310
+ end
311
+
312
+ # @!attribute [r] host
313
+ # Host from the URI
314
+ # @return [String]
315
+ # @api private
316
+ def_delegator :@uri, :host
317
+
318
+ # Return the port for the request URI
319
+ # @return [Fixnum]
320
+ # @api private
321
+ def port
322
+ @uri.port || @uri.default_port
323
+ end
324
+
325
+ # Build default Host header value
326
+ # @return [String]
327
+ # @api private
328
+ def default_host_header_value
329
+ value = PORTS[@scheme] == port ? host : "#{host}:#{port}"
330
+
331
+ raise RequestError, "Invalid host: #{value.inspect}" if value.match?(/\s/)
332
+
333
+ value
334
+ end
335
+
336
+ # Parse and normalize the URI, setting scheme
337
+ # @return [void]
338
+ # @api private
339
+ def parse_uri!(uri)
340
+ raise ArgumentError, "uri is nil" if uri.nil?
341
+ raise ArgumentError, "uri is empty" if uri.is_a?(String) && uri.empty?
342
+
343
+ @uri = @uri_normalizer.call(uri)
344
+ @scheme = String(@uri.scheme).downcase.to_sym if @uri.scheme
345
+ end
346
+
347
+ # Validate HTTP method and URI scheme
348
+ # @return [void]
349
+ # @api private
350
+ def validate_method_and_scheme!
351
+ raise(UnsupportedMethodError, "unknown method: #{verb}") unless METHODS.include?(@verb)
352
+ raise(HTTP::URI::InvalidError, "invalid URI: #{@uri}") unless @scheme
353
+ raise(UnsupportedSchemeError, "unknown scheme: #{scheme}") unless SCHEMES.include?(@scheme)
354
+ end
355
+
356
+ # Coerce input into a Body object
357
+ # @return [HTTP::Request::Body]
358
+ # @api private
359
+ def prepare_body(body)
360
+ body.is_a?(Body) ? body : Body.new(body)
361
+ end
362
+
363
+ # Build headers with default values
364
+ # @return [HTTP::Headers]
365
+ # @api private
366
+ def prepare_headers(headers)
367
+ headers = Headers.coerce(headers || {})
368
+
369
+ headers[Headers::HOST] ||= default_host_header_value
370
+ headers[Headers::USER_AGENT] ||= USER_AGENT
371
+
372
+ headers
373
+ end
374
+ end
375
+ end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+ require "http/client"
5
+
6
+ module HTTP
7
+ class Response
8
+ # A streamable response body, also easily converted into a string
9
+ class Body
10
+ extend Forwardable
11
+ include Enumerable
12
+
13
+ def_delegator :to_s, :empty?
14
+
15
+ # The connection object for the request
16
+ #
17
+ # @example
18
+ # body.connection
19
+ #
20
+ # @return [HTTP::Connection]
21
+ # @api public
22
+ attr_reader :connection
23
+
24
+ # The encoding used for the body content
25
+ #
26
+ # @example
27
+ # body.encoding # => Encoding::UTF_8
28
+ #
29
+ # @return [Encoding]
30
+ # @api public
31
+ attr_reader :encoding
32
+
33
+ # Create a new Body instance
34
+ #
35
+ # @example
36
+ # Body.new(stream, encoding: Encoding::UTF_8)
37
+ #
38
+ # @param stream [#readpartial] the response stream
39
+ # @param encoding [Encoding] the encoding to use
40
+ # @return [Body]
41
+ # @api public
42
+ def initialize(stream, encoding: Encoding::BINARY)
43
+ @stream = stream
44
+ @connection = stream.respond_to?(:connection) ? stream.connection : stream
45
+ @streaming = nil
46
+ @contents = nil
47
+ @encoding = find_encoding(encoding)
48
+ end
49
+
50
+ # Read a chunk of the body
51
+ #
52
+ # @example
53
+ # body.readpartial # => "chunk of data"
54
+ #
55
+ # (see HTTP::Client#readpartial)
56
+ # @return [String]
57
+ # @raise [EOFError] when no more data left
58
+ # @api public
59
+ def readpartial(*)
60
+ stream!
61
+ chunk = @stream.readpartial(*)
62
+ String.new(chunk, encoding: @encoding)
63
+ end
64
+
65
+ # Iterate over the body, allowing it to be enumerable
66
+ #
67
+ # @example
68
+ # body.each { |chunk| puts chunk }
69
+ #
70
+ # @yield [chunk] Passes each chunk to the block
71
+ # @yieldparam chunk [String]
72
+ # @return [void]
73
+ # @api public
74
+ def each
75
+ loop do
76
+ yield readpartial
77
+ end
78
+ rescue EOFError # rubocop:disable Lint/SuppressedException
79
+ end
80
+
81
+ # Eagerly consume the entire body as a string
82
+ #
83
+ # @example
84
+ # body.to_s # => "full response body"
85
+ #
86
+ # @return [String]
87
+ # @api public
88
+ def to_s
89
+ return @contents if @contents
90
+
91
+ raise StateError, "body is being streamed" unless @streaming.nil?
92
+
93
+ begin
94
+ @streaming = false
95
+ @contents = read_contents
96
+ rescue
97
+ @contents = nil
98
+ raise
99
+ end
100
+
101
+ @contents
102
+ end
103
+ alias to_str to_s
104
+
105
+ # Assert that the body is actively being streamed
106
+ #
107
+ # @example
108
+ # body.stream!
109
+ #
110
+ # @return [true]
111
+ # @api public
112
+ def stream!
113
+ raise StateError, "body has already been consumed" if @streaming.eql?(false)
114
+
115
+ @streaming = true
116
+ end
117
+
118
+ # Whether the body content is suitable for logging
119
+ #
120
+ # Returns true when the body encoding is not binary. Binary responses
121
+ # (images, audio, compressed data) produce unreadable log output.
122
+ #
123
+ # @example
124
+ # body.loggable? # => true
125
+ #
126
+ # @return [Boolean]
127
+ # @api public
128
+ def loggable?
129
+ @encoding != Encoding::BINARY
130
+ end
131
+
132
+ # Easier to interpret string inspect
133
+ #
134
+ # @example
135
+ # body.inspect # => "#<HTTP::Response::Body:3ff2 @streaming=false>"
136
+ #
137
+ # @return [String]
138
+ # @api public
139
+ def inspect
140
+ "#<#{self.class}:#{object_id.to_s(16)} @streaming=#{!!@streaming}>"
141
+ end
142
+
143
+ private
144
+
145
+ # Read all chunks into a single string
146
+ #
147
+ # @return [String]
148
+ # @api private
149
+ def read_contents
150
+ contents = String.new("", encoding: @encoding)
151
+
152
+ loop do
153
+ contents << String.new(@stream.readpartial, encoding: @encoding)
154
+ rescue EOFError
155
+ break
156
+ end
157
+
158
+ contents
159
+ end
160
+
161
+ # Retrieve encoding by name
162
+ #
163
+ # @return [Encoding]
164
+ # @api private
165
+ def find_encoding(encoding)
166
+ Encoding.find encoding
167
+ rescue ArgumentError
168
+ Encoding::BINARY
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+
5
+ module HTTP
6
+ class Response
7
+ # Decompresses gzip/deflate response body streams
8
+ class Inflater
9
+ # The underlying connection
10
+ #
11
+ # @example
12
+ # inflater.connection
13
+ #
14
+ # @return [HTTP::Connection] the underlying connection
15
+ # @api public
16
+ attr_reader :connection
17
+
18
+ # Create a new Inflater wrapping a connection
19
+ #
20
+ # @example
21
+ # Inflater.new(connection)
22
+ #
23
+ # @param connection [HTTP::Connection] the connection to inflate
24
+ # @return [Inflater]
25
+ # @api public
26
+ def initialize(connection)
27
+ @connection = connection
28
+ end
29
+
30
+ # Read and inflate a chunk of the response body
31
+ #
32
+ # @example
33
+ # inflater.readpartial # => "decompressed data"
34
+ #
35
+ # @return [String]
36
+ # @raise [EOFError] when no more data left
37
+ # @api public
38
+ def readpartial(*)
39
+ chunk = @connection.readpartial(*)
40
+ zstream.inflate(chunk)
41
+ rescue EOFError
42
+ unless zstream.closed?
43
+ zstream.finished? ? zstream.finish : zstream.reset
44
+ zstream.close
45
+ end
46
+
47
+ raise
48
+ end
49
+
50
+ private
51
+
52
+ # Return the zlib inflate stream
53
+ # @return [Zlib::Inflate]
54
+ # @api private
55
+ def zstream
56
+ @zstream ||= Zlib::Inflate.new(32 + Zlib::MAX_WBITS)
57
+ end
58
+ end
59
+ end
60
+ end