@@ -222,25 +222,70 @@ def body=(value)
222222
223223 private
224224
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
234255 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
239278 end
240- @socket . read_all dest
241279 end
242280
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:
244289 len = nil
245290 total = 0
246291 while true
@@ -250,7 +295,7 @@ def read_chunked(dest)
250295 len = hexlen . hex
251296 break if len == 0
252297 begin
253- @socket . read len , dest
298+ chunk_data_io . read len , dest
254299 ensure
255300 total += len
256301 @socket . read 2 # \r\n
@@ -266,14 +311,80 @@ def stream_check
266311 end
267312
268313 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
271316 if block
272317 Net ::ReadAdapter . new ( block )
273318 else
274319 dest || ''
275320 end
276321 end
277322
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+
278389end
279390
0 commit comments