# HTTP - HTTP container.
# Copyright (C) 2001 NAKAMURA, Hiroshi.
# 
# 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 PRATICULAR 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., 675 Mass
# Ave, Cambridge, MA 02139, USA.


module HTTP


class Error < StandardError; end
class BadResponseError < Error; end

# HTTP::Message -- HTTP message.
# 
# DESCRIPTION
#   A class that describes 1 HTTP request / response message.
#
class Message

  # HTTP::Message::Headers -- HTTP message header.
  # 
  # DESCRIPTION
  #   A class that describes header part of HTTP message.
  #
  class Headers
    # HTTP version string in a HTTP header.
    attr_accessor :httpVersion
    # Content-type.
    attr_accessor :bodyType
    # Charset.
    attr_accessor :bodyCharset
    # Size of body.
    attr_accessor :bodySize
    # A milestone of body.
    attr_accessor :bodyDate
    # HTTP status reason phrase.
    attr_accessor :reasonPhrase

    CRLF = "\r\n"

    StatusCodeMap = {
      200 => 'OK',
      302 => 'Object moved',
      400 => 'Bad Request',
      500 => 'Internal Server Error',
    }

    CharsetMap = {
      'NONE' => 'us-ascii',
      'EUC'  => 'euc-jp',
      'SJIS' => 'shift_jis',
      'UTF8' => 'utf-8',
    }

    # SYNOPSIS
    #    HTTP::Message.new( statusCode = nil )
    #
    # ARGS
    #   statusCode  Status code of a response to create HTTP response message.
    #
    # DESCRIPTION
    #   Create a instance of HTTP request or HTTP response.  Specify
    #   statusCode for HTTP response.
    #
    def initialize( statusCode = nil )
      @httpVersion = 'HTTP/1.1'
      @statusCode = statusCode
      @reasonPhrase = nil
      @bodyType = nil
      @bodyCharset = nil
      @bodySize = nil
      @bodyDate = nil
      @headerItem = []
    end

    def statusCode
      @statusCode
    end

    def statusCode=( statusCode )
      @statusCode = statusCode
      @reasonPhrase = StatusCodeMap[ reasonPhrase ]
    end

    def dump
      setHeader

      str = statusLine
      @headerItem.each do | key, value |
	str << dumpItem( "#{ key }: #{ value }" )
      end
      str << CRLF
      str
    end

    def set( key, value )
      @headerItem.push( [ key, value ] )
    end

    def get( key = nil )
      if !key
	@headerItem
      else
	@headerItem.find_all { | pair | pair[ 0 ].upcase == key.upcase }
      end
    end

    def []=( key, value )
      set( key, value )
    end

    def []( key )
      get( key ).collect { |item| item[ 1 ] }
    end

  private

    def statusLine
      if defined?( Apache )
	dumpItem( "#{ @httpVersion } #{ self.statusCode } #{ @reasonPhrase }" )
      else
	dumpItem( "Status: #{ self.statusCode } #{ @reasonPhrase }" )
      end
    end

    def setHeader
      if defined?( Apache )
	set( 'Date', httpDate( Time.now ))
      end

      if @bodySize
	set( 'Content-Length', @bodySize.to_s )
      else
	set( 'Connection', 'close' )
      end
      if @bodyDate
	set( 'Last-Modified', httpDate( @bodyDate ))
      end
      set( 'Content-Type',
	"#{ @bodyType || 'text/html' }; charset=#{ CharsetMap[ @bodyCharset ||
	$KCODE ] }" )
    end

    def dumpItem( str )
      str + CRLF
    end

    def httpDate( aTime )
      aTime.gmtime.strftime( "%a, %d %b %Y %H:%M:%S GMT" )
    end
  end

  class Body
    attr_accessor :type, :charset, :date

    def initialize( body = nil, date = nil, type = nil, charset = nil )
      @body = body || ''
      @type = type
      @charset = charset
      @date = date
    end

    def size
      if @body
	@body.size
      else
	nil
      end
    end

    def dump
      @body
    end

    def load( str )
      @body << str
    end

    def content
      @body
    end
  end

  def initialize
    @body = @header = nil
  end

  class << self
    alias __new new
    undef new
  end

  def self.newRequest
    m = self.__new
    m.header = Headers.new
    m.body = Body.new
    m
  end

  def self.newResponse( response = '' )
    m = self.__new
    m.header = Headers.new( 200 )
    m.body = Body.new( response )
    m
  end

  def version
    header.httpVersion
  end

  def version=( newVersion )
    header.httpVersion = newVersion
  end

  def status
    header.statusCode
  end

  def status=( newStatus )
    header.statusCode = newStatus
  end

  def reason
    header.reasonPhrase
  end

  def reason=( newReason )
    header.reasonPhrase = newReason
  end

  def dump
    syncHeader
    str = header.dump
    str << body.dump if body and body.dump
    str
  end

  def load( str )
    buf = str.dup
    unless self.header.load( buf )
      self.body.load( buf )
    end
  end

  def header
    @header
  end

  def header=( header )
    @header = header
    syncBody
  end

  def body
    @body
  end

  def body=( body )
    @body = body
    syncHeader
  end

private

  def syncHeader
    if @header and @body
      @header.bodyType = @body.type
      @header.bodyCharset = @body.charset
      @header.bodySize = @body.size
      @header.bodyDate = @body.date
    end
  end

  def syncBody
    if @header and @body
      @body.type = @header.bodyType
      @body.charset = @header.bodyCharset
      @body.size = @header.bodySize
      @body.date = @header.bodyDate
    end
  end
end


end
