"""
msHTTPHandler.py - class for handling HTTP requests
(c) by Michael Stroeder <michael@stroeder.com>

This module is distributed under the terms of the
GPL (GNU GENERAL PUBLIC LICENSE) Version 2
(see http://www.gnu.org/copyleft/gpl.html)

$Id: msHTTPHandler.py,v 1.16 2001/11/23 12:41:50 michael Exp $

The HTTPHandlerClass is derived from
class SimpleHTTPServer.SimpleHTTPRequestHandler.
"""

__version__ = '0.1.1'

import sys,os,string,time,SimpleHTTPServer,urllib,msbase,ipadr

def getExtensionMap(mime_types_pathname):
  """
  Return dictionary with MIME types either read from file
  mime_types_pathname or a minimal default dictionay
  """
  # MIME-mapping
  if mime_types_pathname and os.path.isfile(mime_types_pathname):
    # Read mapping from file
    print 'Read MIME-type mapping from file %s.' % (mime_types_pathname)
    import mimetypes
    extensions_map = mimetypes.read_mime_types(mime_types_pathname)
    extensions_map[''] = 'text/plain' # Default, *must* be present
  else:
    # Define very simple default mapping suitable for our needs
    extensions_map = {
      '': 'text/plain',   # Default, *must* be present
      '.html': 'text/html',
      '.htm': 'text/html',
      '.gif': 'image/gif',
      '.jpg': 'image/jpeg',
      '.jpeg': 'image/jpeg',
      '.css': 'text/css',
    }
  return extensions_map

class HTTPHandlerClass(SimpleHTTPServer.SimpleHTTPRequestHandler):
  """
  Sub-class for serving HTTP requests
  """
  script_name = None # must be overloaded
  # server_env should be overloaded
  server_env = {
    'SERVER_SOFTWARE':'msHTTPHandler %s' % __version__,
    'DOCUMENT_ROOT':os.sep, 
  }
  dir_listing_allowed = 0
  access_allowed   = map(ipadr.AddrMask_Str2Tuple,['127.0.0.1/255.0.0.0'])
  # Log file objects should be overloaded if running detached
  access_log = sys.stdout
  error_log = sys.stderr
  debug_log = sys.stdout
  extensions_map = getExtensionMap('')

  # Make sure the connection is closed
  def finish(self):
    SimpleHTTPServer.SimpleHTTPRequestHandler.finish(self)
    self.connection.close()

  def log_error(self, format, *args):
    """Log error messages"""
    self.error_log.write("%s - - [%s] %s\n" %
                     (self.address_string(),
                      self.log_date_time_string(),
                      format%args))
    self.error_log.flush()

  def log_message(self, format, *args):
    """Log all access messages"""
    self.access_log.write("%s - - [%s] %s\n" %
                     (self.address_string(),
                      self.log_date_time_string(),
                      format%args))
    self.access_log.flush()

  def log_request(self, code='-', size='-'):
      """Log an accepted request."""
      if code!=400:
        referer = self.headers.getheader('Referer','-')
        userAgent = self.headers.getheader('User-Agent','-')
      else:
        referer = '-';userAgent = '-'
      self.log_message('"%s" %s %s "%s" "%s"',
        self.requestline,str(code),str(size),referer,userAgent
      )

  # Return usual CGI-BIN environment of current request as dictionary
  def get_http_env(self):
    dir, rest = '', self.path[1:]
    i = string.find(rest, '?')
    if i >= 0:
        rest, query = rest[:i], rest[i+1:]
    else:
        query = ''
    i = string.find(rest, '/')
    if i >= 0:
        script, rest = rest[:i], rest[i:]
    else:
        script, rest = rest, ''

    # env is the connection-dependent environment
    env = {}
    env.update(self.server_env)
    env['SERVER_NAME'] = self.server.server_name
    env['SERVER_PORT'] = str(self.server.server_port)
    if self.headers.typeheader is None:
      env['CONTENT_TYPE'] = self.headers.type
    else:
      env['CONTENT_TYPE'] = self.headers.typeheader
    env['REQUEST_METHOD'] = self.command
    env['SCRIPT_NAME'] = self.script_name
    env['PATH_INFO'] = urllib.unquote(rest)
    env['QUERY_STRING'] = query
    env['REMOTE_ADDR'] = self.client_address[0]
    env['REMOTE_PORT'] = self.client_address[1]
    env['SCRIPT_FILENAME'] = sys.argv[0]
    env['REQUEST_URI'] = self.path[1:]
    for envitem in [
      ('Content-length','CONTENT_LENGTH'),
      ('User-Agent','HTTP_USER_AGENT'),
      ('Accept','HTTP_ACCEPT'),
      ('Accept-Charset','HTTP_ACCEPT_CHARSET'),
      ('Accept-Encoding','HTTP_ACCEPT_ENCODING'),
      ('Accept-Language','HTTP_ACCEPT_LANGUAGE'),
      ('Referer','HTTP_REFERER'),
      ('Connection','HTTP_CONNECTION'),
    ]:
      http_header_value = self.headers.getheader(envitem[0])
      if http_header_value:
        env[envitem[1]] = http_header_value

    # SERVER_SIGNATURE is built with string template and all connection data
    disp_env = msbase.DefaultDict('')
    disp_env.update(env)
    env['SERVER_SIGNATURE'] = self.server_env['SERVER_SIGNATURE'] % disp_env

    return env

  # Checks if remote IP address is allowed to access
  def check_IPAdress(self):
    return ipadr.MatchIPAdrList(self.client_address[0],self.access_allowed)

  # Send HTTP response 403 - access denied
  def Send403Error(self,http_env):
    disp_env = msbase.DefaultDict('')
    disp_env.update(http_env)
    self.send_response(403, "access denied")
    self.wfile.write("""Content-type: text/html

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
  <head>
    <title>403 Access denied</title>
  </head>
  <body>
    <h1>Access denied</h1>
    Your are not allowed to access URL
    <blockquote>
      <strong>%(REQUEST_URI)s</strong>
    </blockquote>
    from IP address
    <blockquote>
      <strong>%(REMOTE_ADDR)s</strong>.
    </blockquote>
    %(SERVER_SIGNATURE)s
  </body>
</html>
""" % disp_env)

  def is_webapp(self):
    """Determine if the web application is accessed."""
    if len(self.path)<len(self.script_name):
      return 0
    elif self.path==self.script_name:
      return 1
    elif self.path.startswith(self.script_name) and \
         self.path[len(self.script_name)] in ['?','/']:
      return 1
    else:
      return 0

  def list_directory(self,path):
    """List directory if allowed."""
    if self.dir_listing_allowed:
      return SimpleHTTPServer.SimpleHTTPRequestHandler.list_directory(self,path)
    else:
      self.send_error(403, "No permission to list directory");
      return None

  def do_HEAD(self):
    """Serve a HEAD request."""
    http_env = self.get_http_env()
    if not self.check_IPAdress():
      self.Send403Error(http_env);return
    if self.is_webapp():
      self.send_error(405,"HEAD not allowed for web application.")
    else:
      SimpleHTTPServer.SimpleHTTPRequestHandler.do_HEAD(self)

  def do_POST(self):
    """Serve a POST request."""
    http_env = self.get_http_env()
    if not self.check_IPAdress():
      self.Send403Error(http_env);return
    if self.is_webapp():
      self.run_app(http_env)
    else:
      self.send_error(405,"POST only allowed for web application.")

  def send_head(self):
    """Serve a HEAD request."""
    http_env = self.get_http_env()
    if not self.check_IPAdress():
      self.Send403Error(http_env);return
    if self.is_webapp():
      # run the web application
      self.run_app(http_env)
    else:
      # Deliver static content from file system
      return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)

  def run_app(self,http_env):
    """Start web application itself"""
    # Send start of HTTP response header
    self.send_response(200, "%s output follows" % (
        self.server_env['SERVER_SOFTWARE']
      )
    )
    return

