# 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'
require 'phusion_passenger/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

  # 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)
    @len = env[CONTENT_LENGTH]
    @len = @len.to_i if @len
    @socket = socket
    @tmp = @len && @len <= @@client_body_buffer_size ?
           StringIO.new("") : TmpIO.new("PassengerTeeInput")
  end

  def close
    @tmp.close
  end

  def size
    @len and return @len
    pos = @tmp.pos
    consume!
    @tmp.pos = pos
    @len = @tmp.size
  end

  def read(*args)
    if socket_drained?
      @tmp.read(*args)
    else
      tee(@socket.read(*args))
    end
  end

  def gets
    if socket_drained?
      @tmp.gets
    else
      tee(@socket.gets)
    end
  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

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)
  end

  def tee(buffer)
    if buffer && buffer.size > 0
      @tmp.write(buffer)
    end
    buffer
  end
end

end # module Utils
end # module PhusionPassenger
