#
# This file is part of Zaplet
# Copyright 1999 - 2001 Adam Feuer <adamf@pobox.com>
#
# Zaplet is free software; you can redistribute it and/or modify
# it under the terms of the Python License as published by the
# Python Software Foundation, or GNU General Public License as published
# by the Free Software Foundation (either version 2 of the License, or
# (at your option) any later version).
#
# Zaplet 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 Zaplet; see the file COPYING-Zaplet. If not, write to the
# Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
#
# You should have received a copy of the Python License along
# with Zaplet; see the file COPYING.
#
#
# ui_handler.py
# adzapper User Interface handler
#
# for use with adzapper filtering proxy

# standard modules
import re
import string
import StringIO
import sys
from urlparse import urlparse
import urllib

# medusa modules
from medusa import counter, default_handler, producers

# adzapper modules
from debug import debug
from ui import actions,dtml

actions.dir = dir(actions)

unquote    = urllib.unquote

ERROR_METHOD_NOT_FOUND = "Error: Method not found.<p>\n"

ERROR_ACCESSCONTROL = "You are not authorized to access this server.<p>\n"


class ui_handler:
    """handles requests to the web-based User Interface.
    when a request to http://adzapper/* is made, it dispatches the request
    to the appropriate UI module.
    """

    uimodule_re_str = 'http://adzapper/(.*)'
    
    uimodule_re = re.compile (uimodule_re_str,re.I)

    def __init__ (self,combined_engine,direct=0):
        # if direct != None, every URL matches and is handled by the ui_handler
        self.direct = direct 

	self.hits = counter.counter()
	self.exceptions = counter.counter()


        # we need the combined engine to do accesscontrol
        self.ce = combined_engine

        # make the User Interface actions object
        # this needs access to the core engines: adzapper and zaplet
        self.ui_actions = actions.uiactions(combined_engine)


    def match (self, request):
        if self.direct != 0:
            debug.debug(1,"ui_handler.match- direct != 0, web_ui is on.")
            return 1
	uri = request.uri
	debug.debug(1,"ui_handler.match- uri: %s" % uri)
        self.matchobj = self.uimodule_re.match(uri)
	if self.matchobj != None:
            debug.debug(1,"ui_handler.match- web_ui is on.")
            return 1
	else:
	    debug.debug(1,"ui_handler.match- no match.")
	    return 0



    def handle_request (self, request):
        """start handling the http request- check access control and for PUT or POST data"""

        # check access control- is this IP allowed access to adzapper?
	if self.ce == None:
	    debug.debug(2,"UI handler: combined_engine == None, no access control")
	else:
            if self.ce.adzapper == None:
                debug.debug(2,"UI handler: combined_engine.adzapper == None, no access control")
            else:
                # access control - check requester's IP against our list of valid IPs
                debug.debug(2,"UI handler: combined_engine.adzapper ok, checking access control...")
                (requesterIP,requesterPort) = request.channel.socket.getpeername()
                if self.ce.adzapper.accesscontroller(requesterIP):
                    s = ERROR_ACCESSCONTROL
                    self.finish_request(request,s)
                    return

            if self.ce.adzapper.web_ui == 1:
                debug.debug(1,"ui_handler.match- web_ui is on.")
            else:
                debug.debug(1,"ui_handler.match- web_ui is off.")
                s = ERROR_ACCESSCONTROL
                self.finish_request(request,s)
                return


        if request.command in ('put', 'post'):
            debug.debug(4,"UI handler- put or post command found.")
            debug.debug(4,"UI handler- headers: %s" % request.header)
            # look for a Content-Length header.
            cl = request.get_header ('content-length')
            if cl == None:
                request.error (411)
            else:
                length = int(cl)
                debug.debug(4,"UI handler- cl: %s" % cl)
                if length > 0:
                    collector (self, length, request)
                else:
                    self.continue_request (request,StringIO.StringIO())

        else:
            self.continue_request (request,StringIO.StringIO())




    def continue_request(self,request,data):
        """continue processing the request- after PUT or POST data has been collected"""

	[scheme, netloc, path, params, query, fragment] = urlparse(request.uri)

	debug.debug(1,"UI handler- path: %s" % path)

	while path and path[0] == '/':
	    path = path[1:]

	if '%' in path:
	    path = unquote(path)

	debug.debug(1,"UI handler- unquoted path: %s" % path)

        pathlist = string.split(path,'/')

        # is it an action or a dtml page?
        action = 0
        uimethodname = pathlist[0]
        restofpath = pathlist[1:]
        if uimethodname == 'actions':
            action = 1
            if len(restofpath) > 0:
                uimethodname = restofpath[0]
            else:
                uimethodname = ""
            restofpath = restofpath[1:]

        restofpath = string.join(pathlist[1:],'/')

	debug.debug(1,"UI handler- uimethodname: %s" % uimethodname)
	debug.debug(1,"UI handler- restofpath: %s" % restofpath)

        # process the query arguments into a dictionary
	debug.debug(1,"UI handler- query: %s" % query)

        querylist = string.split(query,'&')
	debug.debug(1,"UI handler- query: %s" % querylist)

        args = {}
        args['title']=uimethodname

        for tuple in querylist:
            if tuple != '' and string.find(tuple,'='):
                [name,value]=string.split(tuple,'=')
                args[name]=value

        s = ""
        if action == 1:
            if hasattr(self.ui_actions,uimethodname):
                uimethod=eval("self.ui_actions.%s" % uimethodname)
                s=uimethod(args,data,restofpath,request)
            else:
                s = ERROR_METHOD_NOT_FOUND
        else:
            # HTML or DTML file?
            if uimethodname == "":
                uimethodname = "default.dtml"

            if len(uimethodname) > 5:
                if uimethodname[-5:] == '.dtml':
                    s=dtml.process_dtml(uimethodname,args,restofpath)
                    request['Content-Type']='text/html'
                else:
                    [content_type,s]=dtml.process_file(uimethodname)
                    request['Content-Type']=content_type
            else:
                [content_type,s]=dtml.process_file(uimethodname)
                request['Content-Type']=content_type


        self.finish_request(request,s)
        return


    def finish_request(self,request,bytes):
        """send the file"""
	request['Content-Length'] = "%d" % len(bytes)
	request.push (bytes)
	request.done()


class collector:

    def __init__ (self, handler, length, request):
	self.handler = handler
	self.request = request
	self.request.collector = self
	self.request.channel.set_terminator (length)
	self.buffer = StringIO.StringIO()

    def collect_incoming_data (self, data):
	self.buffer.write (data)

    def found_terminator (self):
	self.buffer.seek(0)
	self.request.collector = None
	self.request.channel.set_terminator ('\r\n\r\n')

        debug.debug(4,"UI handler- post data:")
        debug.debug(4,self.buffer.getvalue())
        
	self.handler.continue_request (self.request,self.buffer)
