260 lines
6.9 KiB
Ruby
260 lines
6.9 KiB
Ruby
# encoding: binary
|
|
#
|
|
# This file is taken from Unicorn. The following license applies to this file
|
|
# (and this file only, not to the rest of Phusion Passenger):
|
|
#
|
|
# 1. You may make and give away verbatim copies of the source form of the
|
|
# software without restriction, provided that you duplicate all of the
|
|
# original copyright notices and associated disclaimers.
|
|
#
|
|
# 2. You may modify your copy of the software in any way, provided that
|
|
# you do at least ONE of the following:
|
|
#
|
|
# a) place your modifications in the Public Domain or otherwise make them
|
|
# Freely Available, such as by posting said modifications to Usenet or an
|
|
# equivalent medium, or by allowing the author to include your
|
|
# modifications in the software.
|
|
#
|
|
# b) use the modified software only within your corporation or
|
|
# organization.
|
|
#
|
|
# c) rename any non-standard executables so the names do not conflict with
|
|
# standard executables, which must also be provided.
|
|
#
|
|
# d) make other distribution arrangements with the author.
|
|
#
|
|
# 3. You may distribute the software in object code or executable
|
|
# form, provided that you do at least ONE of the following:
|
|
#
|
|
# a) distribute the executables and library files of the software,
|
|
# together with instructions (in the manual page or equivalent) on where
|
|
# to get the original distribution.
|
|
#
|
|
# b) accompany the distribution with the machine-readable source of the
|
|
# software.
|
|
#
|
|
# c) give non-standard executables non-standard names, with
|
|
# instructions on where to get the original software distribution.
|
|
#
|
|
# d) make other distribution arrangements with the author.
|
|
#
|
|
# 4. You may modify and include the part of the software into any other
|
|
# software (possibly commercial). But some files in the distribution
|
|
# are not written by the author, so that they are not under this terms.
|
|
#
|
|
# 5. The scripts and library files supplied as input to or produced as
|
|
# output from the software do not automatically fall under the
|
|
# copyright of the software, but belong to whomever generated them,
|
|
# and may be sold commercially, and may be aggregated with this
|
|
# software.
|
|
#
|
|
# 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
|
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
# PURPOSE.
|
|
|
|
require 'stringio'
|
|
PhusionPassenger.require_passenger_lib 'utils/tmpio'
|
|
|
|
module PhusionPassenger
|
|
module Utils
|
|
|
|
# acts like tee(1) on an input input to provide a input-like stream
|
|
# while providing rewindable semantics through a File/StringIO backing
|
|
# store. On the first pass, the input is only read on demand so your
|
|
# Rack application can use input notification (upload progress and
|
|
# like). This should fully conform to the Rack::Lint::InputWrapper
|
|
# specification on the public API. This class is intended to be a
|
|
# strict interpretation of Rack::Lint::InputWrapper functionality and
|
|
# will not support any deviations from it.
|
|
#
|
|
# When processing uploads, Unicorn exposes a TeeInput object under
|
|
# "rack.input" of the Rack environment.
|
|
class TeeInput
|
|
CONTENT_LENGTH = "CONTENT_LENGTH".freeze
|
|
HTTP_TRANSFER_ENCODING = "HTTP_TRANSFER_ENCODING".freeze
|
|
CHUNKED = "chunked".freeze
|
|
|
|
# The maximum size (in +bytes+) to buffer in memory before
|
|
# resorting to a temporary file. Default is 112 kilobytes.
|
|
@@client_body_buffer_size = 112 * 1024
|
|
|
|
# sets the maximum size of request bodies to buffer in memory,
|
|
# amounts larger than this are buffered to the filesystem
|
|
def self.client_body_buffer_size=(bytes)
|
|
@@client_body_buffer_size = bytes
|
|
end
|
|
|
|
# returns the maximum size of request bodies to buffer in memory,
|
|
# amounts larger than this are buffered to the filesystem
|
|
def self.client_body_buffer_size
|
|
@@client_body_buffer_size
|
|
end
|
|
|
|
# Initializes a new TeeInput object. You normally do not have to call
|
|
# this unless you are writing an HTTP server.
|
|
def initialize(socket, env)
|
|
if @len = env[CONTENT_LENGTH]
|
|
@len = @len.to_i
|
|
elsif env[HTTP_TRANSFER_ENCODING] != CHUNKED
|
|
@len = 0
|
|
end
|
|
@socket = socket
|
|
@bytes_read = 0
|
|
if @len && @len <= @@client_body_buffer_size
|
|
@tmp = StringIO.new("")
|
|
else
|
|
@tmp = TmpIO.new("PassengerTeeInput")
|
|
end
|
|
@tmp.binmode
|
|
end
|
|
|
|
def close
|
|
@tmp.close
|
|
end
|
|
|
|
def size
|
|
if @len
|
|
@len
|
|
else
|
|
pos = @tmp.pos
|
|
consume!
|
|
@tmp.pos = pos
|
|
@len = @tmp.size
|
|
end
|
|
end
|
|
|
|
def read(len = nil, buf = "")
|
|
buf ||= ""
|
|
if len
|
|
if len < 0
|
|
raise ArgumentError, "negative length #{len} given"
|
|
elsif len == 0
|
|
buf.replace('')
|
|
buf
|
|
else
|
|
if socket_drained?
|
|
@tmp.read(len, buf)
|
|
else
|
|
tee(read_exact(len, buf))
|
|
end
|
|
end
|
|
else
|
|
if socket_drained?
|
|
@tmp.read(nil, buf)
|
|
else
|
|
tee(read_all(buf))
|
|
end
|
|
end
|
|
end
|
|
|
|
def gets
|
|
if socket_drained?
|
|
@tmp.gets
|
|
else
|
|
if @bytes_read == @len
|
|
nil
|
|
elsif line = @socket.gets
|
|
if @len
|
|
max_len = @len - @bytes_read
|
|
line.slice!(max_len, line.size - max_len)
|
|
end
|
|
@bytes_read += line.size
|
|
tee(line)
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
end
|
|
|
|
def seek(*args)
|
|
if !socket_drained?
|
|
# seek may be forward, or relative to the end, so we need to consume the socket fully into tmp
|
|
pos = @tmp.pos # save/restore tmp.pos, to not break relative seeks
|
|
consume!
|
|
@tmp.pos = pos
|
|
end
|
|
@tmp.seek(*args)
|
|
end
|
|
|
|
def rewind
|
|
return 0 if 0 == @tmp.size
|
|
consume! if !socket_drained?
|
|
@tmp.rewind # Rack does not specify what the return value is here
|
|
end
|
|
|
|
def each
|
|
while line = gets
|
|
yield line
|
|
end
|
|
|
|
self # Rack does not specify what the return value is here
|
|
end
|
|
|
|
# Rack repeatedly introduces bugs that rely on this method existing
|
|
# https://github.com/rack/rack/pull/1201
|
|
def eof?
|
|
socket_drained?
|
|
end
|
|
|
|
private
|
|
|
|
def socket_drained?
|
|
if @socket
|
|
if @socket.eof?
|
|
@socket = nil
|
|
true
|
|
else
|
|
false
|
|
end
|
|
else
|
|
true
|
|
end
|
|
end
|
|
|
|
# consumes the stream of the socket
|
|
def consume!
|
|
junk = ""
|
|
nil while read(16 * 1024, junk)
|
|
@socket = nil
|
|
end
|
|
|
|
def tee(buffer)
|
|
if buffer && buffer.size > 0
|
|
@tmp.write(buffer)
|
|
end
|
|
buffer
|
|
end
|
|
|
|
def read_exact(len, buf)
|
|
if @len
|
|
max_len = @len - @bytes_read
|
|
len = max_len if len > max_len
|
|
return nil if len == 0
|
|
end
|
|
ret = @socket.read(len, buf)
|
|
@bytes_read += ret.size if ret
|
|
ret
|
|
end
|
|
|
|
def read_all(buf)
|
|
if @len
|
|
ret = @socket.read(@len - @bytes_read, buf)
|
|
if ret
|
|
@bytes_read += ret.size
|
|
ret
|
|
else
|
|
buf.replace("")
|
|
buf
|
|
end
|
|
else
|
|
ret = @socket.read(nil, buf)
|
|
@bytes_read += ret.size
|
|
ret
|
|
end
|
|
end
|
|
end
|
|
|
|
end # module Utils
|
|
end # module PhusionPassenger
|