# Copyright (C) 2003, 2004 Gregoire Lejeune <gregoire.lejeune@free.fr>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
module Asp

  require 'delegate'
  class Upload < SimpleDelegator
    attr_reader :contentType, :browserFile

    def initialize( file, content_type, original_filename )
      @contentType = content_type
      @browserFile = original_filename
      super( file )
    end

    def to_s
      @browserFile
    end

    def to_a
      out = Array::new( )

      tempfile = __getobj__
      tempfile.open
      out.push( tempfile.gets )
      tempfile.close

      out
    end
    
    def tempfile
      __getobj__.path
    end
  end

  class Params
    
    attr_reader :params

    def initialize( r )
      @oApacheRequest = r
	  @vQuery = nil
      @params = getParams;
    end
    
	def getQuery
	  @vQuery
	end

    private
    def getParams( mock=nil )
      normalize( mock || parse )
    end

    def normalize( params )
      params.each { |key, value|
        unless value.kind_of? Array
          params[key] = [value]
        end
      }
      params.default = []
      params
    end

    def parse
      if (multipart?)
        parse_multipart
      else
	    @vQuery = query_string
        parse_query_string( @vQuery )
      end
    end

    def query_string
      case ENV["REQUEST_METHOD"]
        when "GET", "HEAD"
          begin
#          if (defined? MOD_RUBY) # <= this check is untested.
                               #    search the test files for
                               #    test_parse_get_with_mod_ruby
                               #    for an explanation
            mod_ruby_query_string
          rescue
#          else
            ENV["QUERY_STRING"]
          end
        when "POST"
          $stdin.binmode
          $stdin.read(Integer(ENV['CONTENT_LENGTH']))
        end
    end

    # this method solely exists b/c of difficulties setting MOD_RUBY dynamically.
    # so this method exists so I can test the effect separately
    # from the (currently untestable) cause
    def mod_ruby_query_string
      @oApacheRequest.args
    end

    def unescape(string)
      return nil unless string
      string.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n) do
        [$1.delete('%')].pack('H*')
      end
    end

    def parse_query_string(query)
      query ||= ""
      params = Hash.new([])
        
      query.split(/[&;]/n).each do |pairs|
        key, value = pairs.split('=',2).collect{|v| unescape(v) }
        if params.has_key?(key)
          params[key].push(value)
        else
          params[key] = [value]
        end
      end
        
      params
    end

    def multipart?
      ("POST" == ENV['REQUEST_METHOD']) &&
      (/multipart\/form-data/.match(ENV['CONTENT_TYPE']))
    end

    def parse_multipart
      %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n.match(ENV['CONTENT_TYPE'])
      boundary = $1.dup

      read_multipart(boundary, Integer(ENV['CONTENT_LENGTH']))
    end

    EOL="\r\n"

    def read_multipart(boundary, content_length)
      require 'tempfile'
      params = Hash.new([])
      boundary = "--" + boundary
      buf = ""
      bufsize = 10 * 1024

      # start multipart/form-data
      $stdin.binmode
      boundary_size = boundary.size + EOL.size
      content_length -= boundary_size
      status = $stdin.read(boundary_size)
      if nil == status
        raise EOFError, "no content body"
      end

      until -1 == content_length
        head = nil
        body = Tempfile.new("Web")
        body.binmode
                
        until head and /#{boundary}(?:#{EOL}|--)/n.match(buf)
                    
          if head
            trim_size = (EOL + boundary + EOL).size
            if trim_size < buf.size
              body.print buf[0 ... (buf.size - trim_size)]
              buf[0 ... (buf.size - trim_size)] = ""
            end
          elsif /#{EOL}#{EOL}/n.match(buf)
            buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
              head = $1.dup
              ""
            end
            next
          end
                    
          c = if bufsize < content_length
            $stdin.read(bufsize) or ''
          else
            $stdin.read(content_length) or ''
          end
          buf.concat c
          content_length -= c.size
                    
        end

        /Content-Disposition:.* filename="?([^\";]*)"?/ni.match(head)
        filename = ($1 or "").dup
        if /Mac/ni.match(ENV['HTTP_USER_AGENT']) and /Mozilla/ni.match(ENV['HTTP_USER_AGENT']) and (not /MSIE/ni.match(ENV['HTTP_USER_AGENT']))
          filename = unescape(filename)
        end
        
        /Content-Type: (.*)/ni.match(head)
        content_type = ($1 or "").strip

        buf = buf.sub(/\A((?:.|\n)*?)(?:#{EOL})?#{boundary}(#{EOL}|--)/n) do
          body.print $1
          if "--" == $2
            content_length = -1
          end
          ""
        end

        body.rewind

        if (content_type.empty?)
          upload = body.read
        else
          ## body = TempFile
          upload = Asp::Upload.new( body, content_type, filename )
        end

        /Content-Disposition:.* name="?([^\";]*)"?/ni.match(head)
        name = $1.dup
        
        if params.has_key?(name)
          params[name].push(upload)
        else
          params[name] = [upload]
        end
        
      end
        
      params
    end
    
  end

end
