@@ -222,25 +222,70 @@ def body=(value)
222
222
223
223
private
224
224
225
- def read_body_0 ( dest )
226
- if chunked?
227
- read_chunked dest
228
- return
229
- end
230
- clen = content_length ( )
231
- if clen
232
- @socket . read clen , dest , true # ignore EOF
233
- return
225
+ ##
226
+ # Checks for a supported Content-Encoding header and yields an Inflate
227
+ # wrapper for this response's socket when zlib is present. If the
228
+ # Content-Encoding is unsupported or zlib is missing the plain socket is
229
+ # yielded.
230
+ #
231
+ # If a Content-Range header is present a plain socket is yielded as the
232
+ # bytes in the range may not be a complete deflate block.
233
+
234
+ def inflater # :nodoc:
235
+ return yield @socket unless Net ::HTTP ::HAVE_ZLIB
236
+ return yield @socket if self [ 'content-range' ]
237
+
238
+ case self [ 'content-encoding' ]
239
+ when 'deflate' , 'gzip' , 'x-gzip' then
240
+ self . delete 'content-encoding'
241
+
242
+ inflate_body_io = Inflater . new ( @socket )
243
+
244
+ begin
245
+ yield inflate_body_io
246
+ ensure
247
+ inflate_body_io . finish
248
+ end
249
+ when 'none' , 'identity' then
250
+ self . delete 'content-encoding'
251
+
252
+ yield @socket
253
+ else
254
+ yield @socket
234
255
end
235
- clen = range_length ( )
236
- if clen
237
- @socket . read clen , dest
238
- return
256
+ end
257
+
258
+ def read_body_0 ( dest )
259
+ inflater do |inflate_body_io |
260
+ if chunked?
261
+ read_chunked dest , inflate_body_io
262
+ return
263
+ end
264
+
265
+ @socket = inflate_body_io
266
+
267
+ clen = content_length ( )
268
+ if clen
269
+ @socket . read clen , dest , true # ignore EOF
270
+ return
271
+ end
272
+ clen = range_length ( )
273
+ if clen
274
+ @socket . read clen , dest
275
+ return
276
+ end
277
+ @socket . read_all dest
239
278
end
240
- @socket . read_all dest
241
279
end
242
280
243
- def read_chunked ( dest )
281
+ ##
282
+ # read_chunked reads from +@socket+ for chunk-size, chunk-extension, CRLF,
283
+ # etc. and +chunk_data_io+ for chunk-data which may be deflate or gzip
284
+ # encoded.
285
+ #
286
+ # See RFC 2616 section 3.6.1 for definitions
287
+
288
+ def read_chunked ( dest , chunk_data_io ) # :nodoc:
244
289
len = nil
245
290
total = 0
246
291
while true
@@ -250,7 +295,7 @@ def read_chunked(dest)
250
295
len = hexlen . hex
251
296
break if len == 0
252
297
begin
253
- @socket . read len , dest
298
+ chunk_data_io . read len , dest
254
299
ensure
255
300
total += len
256
301
@socket . read 2 # \r\n
@@ -266,14 +311,80 @@ def stream_check
266
311
end
267
312
268
313
def procdest ( dest , block )
269
- raise ArgumentError , 'both arg and block given for HTTP method' \
270
- if dest and block
314
+ raise ArgumentError , 'both arg and block given for HTTP method' if
315
+ dest and block
271
316
if block
272
317
Net ::ReadAdapter . new ( block )
273
318
else
274
319
dest || ''
275
320
end
276
321
end
277
322
323
+ ##
324
+ # Inflater is a wrapper around Net::BufferedIO that transparently inflates
325
+ # zlib and gzip streams.
326
+
327
+ class Inflater # :nodoc:
328
+
329
+ ##
330
+ # Creates a new Inflater wrapping +socket+
331
+
332
+ def initialize socket
333
+ @socket = socket
334
+ # zlib with automatic gzip detection
335
+ @inflate = Zlib ::Inflate . new ( 32 + Zlib ::MAX_WBITS )
336
+ end
337
+
338
+ ##
339
+ # Finishes the inflate stream.
340
+
341
+ def finish
342
+ @inflate . finish
343
+ end
344
+
345
+ ##
346
+ # Returns a Net::ReadAdapter that inflates each read chunk into +dest+.
347
+ #
348
+ # This allows a large response body to be inflated without storing the
349
+ # entire body in memory.
350
+
351
+ def inflate_adapter ( dest )
352
+ block = proc do |compressed_chunk |
353
+ @inflate . inflate ( compressed_chunk ) do |chunk |
354
+ dest << chunk
355
+ end
356
+ end
357
+
358
+ Net ::ReadAdapter . new ( block )
359
+ end
360
+
361
+ ##
362
+ # Reads +clen+ bytes from the socket, inflates them, then writes them to
363
+ # +dest+. +ignore_eof+ is passed down to Net::BufferedIO#read
364
+ #
365
+ # Unlike Net::BufferedIO#read, this method returns more than +clen+ bytes.
366
+ # At this time there is no way for a user of Net::HTTPResponse to read a
367
+ # specific number of bytes from the HTTP response body, so this internal
368
+ # API does not return the same number of bytes as were requested.
369
+ #
370
+ # See https://bugs.ruby-lang.org/issues/6492 for further discussion.
371
+
372
+ def read clen , dest , ignore_eof = false
373
+ temp_dest = inflate_adapter ( dest )
374
+
375
+ data = @socket . read clen , temp_dest , ignore_eof
376
+ end
377
+
378
+ ##
379
+ # Reads the rest of the socket, inflates it, then writes it to +dest+.
380
+
381
+ def read_all dest
382
+ temp_dest = inflate_adapter ( dest )
383
+
384
+ @socket . read_all temp_dest
385
+ end
386
+
387
+ end
388
+
278
389
end
279
390
0 commit comments