# Copyright (c) 2004 Infrae. All rights reserved.
# See also LICENSE.txt

from mod_python import apache, util
import sys
import urllib

import rr_config
import rr_util

###

SELF = '/service/'

###

def _make_args ( args ):
    ret = {}
    args = urllib.unquote_plus(args)
    al = args.split('&')
    for a in al:
        try:
            n,v = a.split('=')
            ret[n] = v
        except ValueError:
            ret[n] = None
            pass
    return ret
#

def _check_auth ( req, module ):
    """Check authorization of the module via its __auth__ attribute.

    If __auth__ is callable it will be called with the request as its
    only parameter, otherwise its value will be converted to bool.
    """
    authfnc = getattr(module, '__auth__', None)
    if authfnc is None:
        # no authorization code
        # asume access granted
        return True
    if callable(authfnc):
        ret = authfnc(req)
    else:
        ret = bool(authfnc)
    return ret
#

def handler ( req ):
    """Handle a service request.

    Interprets the part after <prefix>/service/ as module path and
    function to call.
    The last element must either be a callable or '/' which is
    replaced by 'index'.

    The modules are searched only in the rr_config.extension_path
    directories.

    The callable will be called with 2 parameters:
      1. the request object
      2. the form data as FieldStorage instance
    """
    if not rr_config.initialized:
        rr_util.init_railroad(req)
    path = req.path_info
    if path.startswith(SELF):
        path = path[len(SELF):]
    else:
        # we don't feel responsible for that
        raise apache.SERVER_RETURN, apache.DECLINED #apache.HTTP_INTERNAL_SERVER_ERROR
    if not path:
        # there must be at least one module to import
        raise apache.SERVER_RETURN, apache.HTTP_INTERNAL_SERVER_ERROR
    # transform arguments into a FieldStorage instance
    args = util.FieldStorage(req, True)
    # get callable and process path for import
    pl = path.split('/')
    # last element *must* be a callable
    # or empty, in this case it is replaced with 'index'
    try:
        the_callable = pl[-1]
    except IndexError:
        # no path at all
        # this is not allowed
        raise apache.SERVER_RETURN, apache.DECLINED
    # last part not needed anymore
    pl = pl[:-1]
    if not the_callable:
        # empty callable means <something>/ was given
        # replace it with index
        the_callable = 'index'
    if not pl:
        # only module given
        # assume module/index
        pl = [ the_callable ]
        the_callable = 'index'
    # import the modules
    # apache.import_module takes care of caching and such...
    allowed = None
    ipath = rr_config.extension_path
    for i, mod in enumerate(pl):
        modpath = '.'.join(pl[:i+1])
        # check if it is allowed to access the module
        # via the web
        if allowed is not None:
            if mod not in allowed:
                raise apache.SERVER_RETURN, apache.HTTP_NOT_FOUND
        module = apache.import_module(modpath, log=True, path=ipath)
##         rr_util.ap_debug(req, 'Module found: %s' % module.__file__)
        # check if the caller is authorized
        if not _check_auth(req, module):
            raise apache.SERVER_RETURN, apache.HTTP_UNAUTHORIZED
        # the names of the allowed items from the module
        allowed = getattr(module, '__allowed__', [])
    # get the callable from the module and call it
    if the_callable not in allowed:
        raise apache.SERVER_RETURN, apache.HTTP_NOT_FOUND
    fnc = getattr(module, the_callable, None)
    if not fnc:
        # the function does not exist
        raise apache.SERVER_RETURN, apache.HTTP_NOT_FOUND
    else:
        return fnc(req, args)
    return apache.HTTP_INTERNAL_SERVER_ERROR
#
###
