#
# Copyright (c) 2008 RightScale Inc
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
#

# Net::HTTP and Net::HTTPGenericRequest fixes to support 100-continue on
# POST and PUT. The request must have 'expect' field set to '100-continue'.


module Net

  class BufferedIO #:nodoc:
    # Monkey-patch Net::BufferedIO to read > 1024 bytes from the socket at a time

    # Default size (in bytes) of the max read from a socket into the user space read buffers for socket IO
    DEFAULT_SOCKET_READ_SIZE = 16*1024

    @@socket_read_size = DEFAULT_SOCKET_READ_SIZE

    def self.socket_read_size=(readsize)
      if(readsize <= 0)
        return
      end
      @@socket_read_size = readsize
    end

    def self.socket_read_size?()
      @@socket_read_size
    end

    def rbuf_fill
      Timeout.timeout(@read_timeout) {
        @rbuf << @io.sysread(@@socket_read_size)
      }
    end
  end


  #-- Net::HTTPGenericRequest --

  class HTTPGenericRequest
    # Monkey-patch Net::HTTPGenericRequest to read > 1024 bytes from the local data
    # source at a time (used in streaming PUTs)

    # Default size (in bytes) of the max read from a local source (File, String,
    # etc.) to the user space write buffers for socket IO.
    DEFAULT_LOCAL_READ_SIZE = 16*1024

    @@local_read_size = DEFAULT_LOCAL_READ_SIZE

    def self.local_read_size=(readsize)
      if(readsize <= 0)
        return
      end
      @@local_read_size = readsize
    end

    def self.local_read_size?()
      @@local_read_size
    end

    def exec(sock, ver, path, send_only=nil)   #:nodoc: internal use only
      if @body
        send_request_with_body sock, ver, path, @body, send_only
      elsif @body_stream
        send_request_with_body_stream sock, ver, path, @body_stream, send_only
      else
        write_header(sock, ver, path)
      end
    end

    private

    def send_request_with_body(sock, ver, path, body, send_only=nil)
      self.content_length = body.respond_to?(:bytesize) ? body.bytesize : body.length
      delete 'Transfer-Encoding'
      supply_default_content_type
      write_header(sock, ver, path) unless send_only == :body
      sock.write(body && body.to_s) unless send_only == :header
    end

    def send_request_with_body_stream(sock, ver, path, f, send_only=nil)
      # KD: Fix 'content-length': it must not be greater than a piece of file left to be read.
      # Otherwise the connection may behave like crazy causing 4xx or 5xx responses
      #
      # Only do this helpful thing if the stream responds to :pos (it may be something
      # that responds to :read and :size but not :pos).
      if f.respond_to?(:pos)
        file_size           = f.respond_to?(:lstat) ? f.lstat.size : f.size
        bytes_to_read       = [ file_size - f.pos, self.content_length.to_i ].sort.first
        self.content_length = bytes_to_read
      end

      unless content_length() or chunked?
        raise ArgumentError,
            "Content-Length not given and Transfer-Encoding is not `chunked'"
      end
      bytes_to_read ||= content_length()
      supply_default_content_type
      write_header(sock, ver, path) unless send_only == :body
      unless send_only == :header
        if chunked?
          while s = f.read(@@local_read_size)
            sock.write(sprintf("%x\r\n", s.length) << s << "\r\n")
          end
          sock.write "0\r\n\r\n"
        else
          # KD: When we read/write over file EOF it sometimes make the connection unstable
          read_size = [ @@local_read_size, bytes_to_read ].sort.first
          while s = f.read(read_size)
            sock.write s
            # Make sure we do not read over EOF or more than expected content-length
            bytes_to_read -= read_size
            break if bytes_to_read <= 0
            read_size = bytes_to_read if bytes_to_read < read_size
          end
        end
      end
    end
  end


  #-- Net::HTTP --

  class HTTP
    def request(req, body = nil, &block)  # :yield: +response+
      unless started?
        start {
          req['connection'] ||= 'close'
          return request(req, body, &block)
        }
      end
      if proxy_user()
        unless use_ssl?
          req.proxy_basic_auth proxy_user(), proxy_pass()
        end
      end
      # set body
      req.set_body_internal body
      begin_transport req
        # if we expect 100-continue then send a header first
        send_only = ((req.is_a?(Post)||req.is_a?(Put)) && (req['expect']=='100-continue')) ? :header : nil
        req.exec @socket, @curr_http_version, edit_path(req.path), send_only
        begin
          res = HTTPResponse.read_new(@socket)
          res.decode_content = req.decode_content if RUBY_VERSION > '2.0'
          # if we expected 100-continue then send a body
          if res.is_a?(HTTPContinue) && send_only && req['content-length'].to_i > 0
            req.exec @socket, @curr_http_version, edit_path(req.path), :body
          end
        end while res.kind_of?(HTTPContinue)
        res.reading_body(@socket, req.response_body_permitted?) {
          yield res if block_given?
        }
      end_transport req, res
      res
    end
  end

end
