# -*- coding: iso-8859-1 -*-
"""
    MoinMoin - Data associated with a single Request

    @copyright: 2001-2003 by Jürgen Hermann <jh@web.de>
    @copyright: 2003-2004 by Thomas Waldmann
    @license: GNU GPL, see COPYING for details.
"""

import os, time, sys, cgi
from MoinMoin import config, wikiutil, user, error
from MoinMoin.util import MoinMoinNoFooter, IsWin9x, FixScriptName


# Timing ---------------------------------------------------------------

class Clock:
    """ Helper class for code profiling
        we do not use time.clock() as this does not work across threads
    """

    def __init__(self):
        self.timings = {'total': time.time()}

    def start(self, timer):
        self.timings[timer] = time.time() - self.timings.get(timer, 0)

    def stop(self, timer):
        self.timings[timer] = time.time() - self.timings[timer]

    def value(self, timer):
        return "%.3f" % (self.timings[timer],)

    def dump(self):
        outlist = []
        for timing in self.timings.items():
            outlist.append("%s = %.3fs" % timing)
        outlist.sort()
        return outlist



# Request Base ----------------------------------------------------------

class RequestBase(object):
    """ A collection for all data associated with ONE request. """

    # Header set to force misbehaved proxies and browsers to keep their
    # hands off a page
    # Details: http://support.microsoft.com/support/kb/articles/Q234/0/67.ASP
    nocache = [
        "Pragma: no-cache",
        "Cache-Control: no-cache",
        "Expires: -1",
    ]

    def __init__(self, properties={}):
        self.failed = 0
        self._available_actions = None
        self._known_actions = None

        # Pages meta data that we collect in one request
        self.pages = {}
              
        self.sent_headers = 0
        self.user_headers = []
        self.page = None
        self._dicts = None
        
        # Fix dircaching problems on Windows 9x
        if IsWin9x():
            import dircache
            dircache.reset()

        # Check for dumb proxy requests
        # TODO relying on request_uri will not work on all servers, especially
        # not on external non-Apache servers
        self.forbidden = False
        if self.request_uri.startswith('http://'):
            self.makeForbidden()

        # Init
        else:
            self.writestack = []
            self.clock = Clock()
            # order is important here!
            self._load_multi_cfg()
            
            # Set decode charsets.  Input from the user is always in
            # config.charset, which is the page charsets. Except
            # path_info, which may use utf-8, and handled by decodePagename.
            self.decode_charsets = [config.charset]
            
            # hierarchical wiki - set rootpage
            from MoinMoin.Page import Page
            #path = self.getPathinfo()
            #if path.startswith('/'):
            #    pages = path[1:].split('/')
            #    if 0: # len(path) > 1:
            #        ## breaks MainPage/SubPage on flat storage
            #        rootname = u'/'.join(pages[:-1])
            #    else:
            #        # this is the usual case, as it ever was...
            #        rootname = u""
            #else:
            #    # no extra path after script name
            #    rootname = u""

            rootname = u''
            self.rootpage = Page(self, rootname, is_rootpage=1)

            self.user = user.User(self)

            from MoinMoin import i18n

            # Set theme - forced theme, user theme or wiki default
            if self.cfg.theme_force:
                theme_name = self.cfg.theme_default
            else:
                theme_name = self.user.theme_name
            self.loadTheme(theme_name)
            
            self.args = {}
            self.form = {}
            self.logger = None
            self.pragma = {}
            self.mode_getpagelinks = 0
            self.no_closing_html_code = 0

            self.__dict__.update(properties)

            self.i18n = i18n
            self.lang = i18n.requestLanguage(self) 
            # Language for content. Page content should use the wiki
            # default lang, but generated content like search results
            # should use the user language.
            self.content_lang = self.cfg.default_lang
            self.getText = lambda text, i18n=self.i18n, request=self, lang=self.lang, **kv: i18n.getText(text, request, lang, kv.get('formatted', True))

            self.opened_logs = 0
            self.reset()
        
    def getDicts(self):
        """ Lazy initialize the dicts on the first access """
        if self._dicts is None:
            from MoinMoin import wikidicts
            dicts = wikidicts.GroupDict(self)
            dicts.scandicts()
            self._dicts = dicts
        return self._dicts
        
    def delDicts(self):
        """ Delete the dicts, used by some tests """
        del self._dicts
        self._dicts = None

    dicts = property(getDicts, None, delDicts)

    def _load_multi_cfg(self):
        # protect against calling multiple times
        if not hasattr(self, 'cfg'):
            from MoinMoin import multiconfig
            self.cfg = multiconfig.getConfig(self.url)
            
    def parse_accept_charset(self, accept_charset):
        """ Parse http accept charset header

        Set self.accepted_charsets to an ordered list based on
        http_accept_charset. 
        
        Reference: http://www.w3.org/Protocols/rfc2616/rfc2616.txt

        @param accept_charset: HTTP_ACCEPT_CHARSET string
        @rtype: list of strings
        @return: sorted list of accepted charsets
        """        
        charsets = []
        if accept_charset:
            accept_charset = accept_charset.lower()
            # Add iso-8859-1 if needed
            if (not '*' in accept_charset and
                accept_charset.find('iso-8859-1') < 0):
                accept_charset += ',iso-8859-1'

            # Make a list, sorted by quality value, using Schwartzian Transform
            # Create list of tuples (value, name) , sort, extract names  
            for item in accept_charset.split(','):
                if ';' in item:
                    name, qval = item.split(';')
                    qval = 1.0 - float(qval.split('=')[1])
                else:
                    name, qval = item, 0
                charsets.append((qval, name))                 
            charsets.sort()
            # Remove *, its not clear what we should do with it later
            charsets = [name for qval, name in charsets if name != '*']

        return charsets
        
    def _setup_vars_from_std_env(self, env):
        """ Sets the common Request members by parsing a standard
            HTTPD environment (as created as environment by most common
            webservers. To be used by derived classes.

            @param env: the environment to use
        """
        self.http_accept_language = env.get('HTTP_ACCEPT_LANGUAGE', 'en')
        self.server_name = env.get('SERVER_NAME', 'localhost')
        self.server_port = env.get('SERVER_PORT', '80')
        self.http_host = env.get('HTTP_HOST','localhost')
        # Make sure http referer use only ascii (IE again)
        self.http_referer = unicode(env.get('HTTP_REFERER', ''), 'ascii',
                                    'replace').encode('ascii', 'replace')
        self.saved_cookie = env.get('HTTP_COOKIE', '')
        self.script_name = env.get('SCRIPT_NAME', '')

        self.request_uri = env.get('REQUEST_URI', '')
        path_info = env.get('PATH_INFO', '')

        query_string = env.get('QUERY_STRING', '')
        self.query_string = self.decodePagename(query_string)
        server_software = env.get('SERVER_SOFTWARE', '')

        # Handle the strange charset semantics on *Windows*
        # path_info is transformed into the system code page by the webserver
        # Additionally, paths containing dots let most webservers choke.

        # Fig. I - Broken environment variables in different environments:
        #         path_info script_name
        # Apache1     X          X      PI does not contain dots
        # Apache2     X          X      PI is not encoded correctly
        # IIS         X          X (is fixed somewhere else)
        # Other       ?          -      ? := Possible and even RFC-compatible.
        #                               - := Hopefully not.
        
        # Fix the script_name if run on Windows/Apache
        if os.name == 'nt' and server_software.find('Apache/') != -1:
            self.script_name = FixScriptName(self.script_name)

        # Check if we can use Apache's request_uri variable in order to
        # gather the correct user input
        if (os.name != 'posix' and self.request_uri != ''):
            import urllib
            path_info = urllib.unquote(self.request_uri.replace(
                self.script_name, '', 1).replace('?' + query_string, '', 1))
        
        # Decode according to filesystem semantics if we cannot gather the
        # request_uri
        elif os.name == 'nt':
            path_info = wikiutil.decodeWindowsPath(path_info).encode("utf-8")

        self.path_info = self.decodePagename(path_info)
        self.request_method = env.get('REQUEST_METHOD', None)

        self.remote_addr = env.get('REMOTE_ADDR', '')
        self.http_user_agent = env.get('HTTP_USER_AGENT', '')
        self.is_ssl = env.get('SSL_PROTOCOL', '') != '' \
            or env.get('SSL_PROTOCOL_VERSION', '') != '' \
            or env.get('HTTPS', 'off') == 'on'

        # We cannot rely on request_uri being set. In fact,
        # it is just an addition of Apache to the CGI specs.
        if self.request_uri == '':
            import urllib
            if self.server_port.strip() and self.server_port != '80':
                port = ':' + str(self.server_port)
            else:
                port = ''
            self.url = (self.server_name + port + self.script_name +
                        urllib.quote(self.path_info.replace(
                            self.script_name, '', 1).encode("utf-8"))
                        + '?' + self.query_string)
        else:
            self.url = self.server_name + self.request_uri
        
        ac = env.get('HTTP_ACCEPT_CHARSET', '')
        self.accepted_charsets = self.parse_accept_charset(ac)
        
        self.auth_username = None

        # need config here, so check:
        self._load_multi_cfg()

        if self.cfg.auth_http_enabled:
            auth_type = env.get('AUTH_TYPE','')
            if auth_type in ['Basic', 'Digest', 'NTLM', ]:
                username = env.get('REMOTE_USER','')
                if auth_type == 'NTLM':
                    # converting to standard case so that the user can even enter wrong case
                    # (added since windows does not distinguish between e.g. "Mike" and "mike")
                    username = username.split('\\')[-1] # split off domain e.g. from DOMAIN\user
                    # this "normalizes" the login name from {meier, Meier, MEIER} to Meier
                    # put a comment sign in front of next line if you don't want that:
                    username = username.title()
                self.auth_username = username
                                    
##        f=open('/tmp/env.log','a')
##        f.write('---ENV\n')
##        f.write('script_name = %s\n'%(self.script_name))
##        f.write('path_info   = %s\n'%(self.path_info))
##        f.write('server_name = %s\n'%(self.server_name))
##        f.write('server_port = %s\n'%(self.server_port))
##        f.write('http_host   = %s\n'%(self.http_host))
##        f.write('------\n')
##        f.write('%s\n'%(repr(env)))
##        f.write('------\n')
##        f.close()
  
    def reset(self):
        """ Reset request state.

        Called after saving a page, before serving the updated
        page. Solves some practical problems with request state
        modified during saving.

        """
        # This is the content language and has nothing to do with
        # The user interface language. The content language can change
        # during the rendering of a page by lang macros
        self.current_lang = self.cfg.default_lang

        self._footer_fragments = {}
        self._all_pages = None
        # caches unique ids
        self._page_ids = {}
        # keeps track of pagename/heading combinations
        # parsers should use this dict and not a local one, so that
        # macros like TableOfContents in combination with Include
        # can work
        self._page_headings = {}

        if hasattr(self, "_fmt_hd_counters"):
            del self._fmt_hd_counters

    def loadTheme(self, theme_name):
        """ Load the Theme to use for this request.

        @param theme_name: the name of the theme
        @type theme_name: str
        @returns: 0 on success, 1 if user theme could not be loaded,
                  2 if a hard fallback to modern theme was required.
        @rtype: int
        @return: success code
        """
        fallback = 0
        Theme = wikiutil.importPlugin(self.cfg, 'theme', theme_name, 'Theme')
        if Theme is None:
            fallback = 1
            Theme = wikiutil.importPlugin(self.cfg, 'theme',
                                          self.cfg.theme_default, 'Theme')
            if Theme is None:
                fallback = 2
                from MoinMoin.theme.modern import Theme
        self.theme = Theme(self)

        return fallback

    def setContentLanguage(self, lang):
        """ Set the content language, used for the content div

        Actions that generate content in the user language, like search,
        should set the content direction to the user language before they
        call send_title!
        """
        self.content_lang = lang
        self.current_lang = lang

    def add2footer(self, key, htmlcode):
        """ Add a named HTML fragment to the footer, after the default links
        """
        self._footer_fragments[key] = htmlcode

    def getPragma(self, key, defval=None):
        """ Query a pragma value (#pragma processing instruction)

            Keys are not case-sensitive.
        """
        return self.pragma.get(key.lower(), defval)

    def setPragma(self, key, value):
        """ Set a pragma value (#pragma processing instruction)

            Keys are not case-sensitive.
        """
        self.pragma[key.lower()] = value

    def getPathinfo(self):
        """ Return the remaining part of the URL. """
        return self.path_info

    def getScriptname(self):
        """ Return the scriptname part of the URL ('/path/to/my.cgi'). """
        if self.script_name == '/':
            return ''
        return self.script_name

    def getKnownActions(self):
        """ Create a dict of avaiable actions

        Return cached version if avaiable.
       
        @rtype: dict
        @return: dict of all known actions
        """
        try:
            self.cfg._known_actions # check
        except AttributeError:
            from MoinMoin import wikiaction
            # Add built in  actions from wikiaction
            actions = [name[3:] for name in wikiaction.__dict__
                       if name.startswith('do_')]

            # Add plugins           
            dummy, plugins = wikiaction.getPlugins(self)
            actions.extend(plugins)

            # Add extensions
            from MoinMoin.action import extension_actions
            actions.extend(extension_actions)           
           
            # TODO: Use set when we require Python 2.3
            actions = dict(zip(actions, [''] * len(actions)))            
            self.cfg._known_actions = actions

        # Return a copy, so clients will not change the dict.
        return self.cfg._known_actions.copy()        

    def getAvailableActions(self, page):
        """ Get list of avaiable actions for this request

        The dict does not contain actions that starts with lower
        case. Themes use this dict to display the actions to the user.

        @param page: current page, Page object
        @rtype: dict
        @return: dict of avaiable actions
        """
        if self._available_actions is None:
            # Add actions for existing pages only, including deleted pages.
            # Fix *OnNonExistingPage bugs.
            if not (page.exists(includeDeleted=1) and
                    self.user.may.read(page.page_name)):
                return []

            # Filter non ui actions (starts with lower case letter)
            actions = self.getKnownActions()
            for key in actions.keys():
                if key[0].islower():
                    del actions[key]

            # Filter wiki excluded actions
            for key in self.cfg.excluded_actions:
                if key in actions:
                    del actions[key]                

            # Filter actions by page type, acl and user state
            excluded = []
            if ((page.isUnderlayPage() and not page.isStandardPage()) or
                not self.user.may.write(page.page_name)):
                # Prevent modification of underlay only pages, or pages
                # the user can't write to
                excluded = [u'RenamePage', u'DeletePage',] # AttachFile must NOT be here!
            elif not self.user.valid:
                # Prevent rename and delete for non registered users
                excluded = [u'RenamePage', u'DeletePage']
            for key in excluded:
                if key in actions:
                    del actions[key]                

            self._available_actions = actions

        # Return a copy, so clients will not change the dict.
        return self._available_actions.copy()
    
    def redirect(self, file=None):
        if file: # redirect output to "file"
            self.writestack.append(self.write)
            self.write = file.write
        else: # restore saved output file
            self.write = self.writestack.pop()

    def reset_output(self):
        """ restore default output method
            destroy output stack
            (useful for error messages)
        """
        if self.writestack:
            self.write = self.writestack[0]
            self.writestack = []

    def log(self, msg):
        """ Log to stderr, which may be error.log """
        msg = msg.strip()
        # Encode unicode msg
        if isinstance(msg, unicode):
            msg = msg.encode(config.charset)
        # Add time stamp
        msg = '[%s] %s\n' % (time.asctime(), msg)
        sys.stderr.write(msg)
    
    def write(self, *data):
        """ Write to output stream.
        """
        raise NotImplementedError

    def encode(self, data):
        """ encode data (can be both unicode strings and strings),
            preparing for a single write()
        """
        wd = []
        for d in data:
            try:
                if isinstance(d, unicode):
                    # if we are REALLY sure, we can use "strict"
                    d = d.encode(config.charset, 'replace') 
                wd.append(d)
            except UnicodeError:
                print >>sys.stderr, "Unicode error on: %s" % repr(d)
        return ''.join(wd)
    
    def decodePagename(self, name):
        """ Decode path, possibly using non ascii characters

        Does not change the name, only decode to Unicode.

        First split the path to pages, then decode each one. This enables
        us to decode one page using config.charset and another using
        utf-8. This situation happens when you try to add to a name of
        an existing page.

        See http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.2.1
        
        @param name: page name, string
        @rtype: unicode
        @return decoded page name
        """
        # Split to pages and decode each one
        pages = name.split('/')
        decoded = []
        for page in pages:
            # Recode from utf-8 into config charset. If the path
            # contains user typed parts, they are encoded using 'utf-8'.
            if config.charset != 'utf-8':
                try:
                    page = unicode(page, 'utf-8', 'strict')
                    # Fit data into config.charset, replacing what won't
                    # fit. Better have few "?" in the name than crash.
                    page = page.encode(config.charset, 'replace')
                except UnicodeError:
                    pass
                
            # Decode from config.charset, replacing what can't be decoded.
            page = unicode(page, config.charset, 'replace')
            decoded.append(page)

        # Assemble decoded parts
        name = u'/'.join(decoded)
        return name

    def normalizePagename(self, name):
        """ Normalize page name 

        Convert '_' to spaces - allows using nice URLs with spaces, with no
        need to quote.

        Prevent creating page names with invisible characters or funny
        whitespace that might confuse the users or abuse the wiki, or
        just does not make sense.

        Restrict even more group pages, so they can be used inside acl
        lines.
        
        @param name: page name, unicode
        @rtype: unicode
        @return: decoded and sanitized page name
        """
        # Replace underscores with spaces
        name = name.replace(u'_', u' ')

        # Strip invalid characters
        name = config.page_invalid_chars_regex.sub(u'', name)

        # Split to pages and normalize each one
        pages = name.split(u'/')
        normalized = []
        for page in pages:            
            # Ignore empty or whitespace only pages
            if not page or page.isspace():
                continue

            # Cleanup group pages.
            # Strip non alpha numeric characters, keep white space
            if wikiutil.isGroupPage(self, page):
                page = u''.join([c for c in page
                                 if c.isalnum() or c.isspace()])

            # Normalize white space. Each name can contain multiple 
            # words separated with only one space. Split handle all
            # 30 unicode spaces (isspace() == True)
            page = u' '.join(page.split())
            
            normalized.append(page)            
        
        # Assemble components into full pagename
        name = u'/'.join(normalized)
        return name
        
    def read(self, n):
        """ Read n bytes from input stream.
        """
        raise NotImplementedError

    def flush(self):
        """ Flush output stream.
        """
        raise NotImplementedError
        
    def isForbidden(self):
        """ check for web spiders and refuse anything except viewing """
        forbidden = 0
        # we do not have a parsed query string here
        # so we can just do simple matching
        if ((self.query_string != '' or self.request_method != 'GET') and
            self.query_string != 'action=rss_rc' and not
            # allow spiders to get attachments and do 'show'
            (self.query_string.find('action=AttachFile') >= 0 and self.query_string.find('do=get') >= 0) and not
            (self.query_string.find('action=show') >= 0)
            ):
            from MoinMoin.util import web
            forbidden = web.isSpiderAgent(self)

        if not forbidden and self.cfg.hosts_deny:
            ip = self.remote_addr
            for host in self.cfg.hosts_deny:
                if ip == host or host[-1] == '.' and ip.startswith(host):
                    forbidden = 1
                    break
        return forbidden


    def setup_args(self, form=None):
        return {}

    def _setup_args_from_cgi_form(self, form=None):
        """ Setup args from a FieldStorage form
        
        Create the args from a standard cgi.FieldStorage to be used by
        derived classes, or from given form.

        All values are decoded using config.charset.

        @keyword form: a cgi.FieldStorage
        @rtype: dict
        @return dict with form keys, each contains a list of values
        """
        decode = wikiutil.decodeUserInput

        # Use cgi.FieldStorage by default
        if form is None:
            form = cgi.FieldStorage()

        args = {}
        # Convert form keys to dict keys, each key contains a list of
        # values.
        for key in form.keys():
            values = form[key]
            if not isinstance(values, list):
                values = [values]
            fixedResult = []
            for i in values:
                if isinstance(i, cgi.MiniFieldStorage):
                    data = decode(i.value, self.decode_charsets)
                elif isinstance(i, cgi.FieldStorage):
                    data = i.value
                    if i.filename:
                        # multiple uploads to same form field are stupid!
                        args[key+'__filename__'] = decode(i.filename, self.decode_charsets)
                    else:
                        data = decode(data, self.decode_charsets)
                # Append decoded value
                fixedResult.append(data)
            
            args[key] = fixedResult
            
        return args

    def getBaseURL(self):
        """ Return a fully qualified URL to this script. """
        return self.getQualifiedURL(self.getScriptname())

    def getQualifiedURL(self, uri=None):
        """ Return a full URL starting with schema, server name and port.

            *uri* -- append this server-rooted uri (must start with a slash)
        """
        if uri and uri[:4] == "http":
            return uri

        schema, stdport = (('http', '80'), ('https', '443'))[self.is_ssl]
        host = self.http_host
        if not host:
            host = self.server_name
            port = self.server_port
            if port != stdport:
                host = "%s:%s" % (host, port)

        result = "%s://%s" % (schema, host)
        if uri:
            result = result + uri

        # return mapped result
        return wikiutil.mapURL(self, result)

    def getUserAgent(self):
        """ Get the user agent. """
        return self.http_user_agent

    def makeForbidden(self):
        self.forbidden = True
        self.http_headers([
            'Status: 403 FORBIDDEN',
            'Content-Type: text/plain'
        ])
        self.write('You are not allowed to access this!\r\n')
        self.setResponseCode(403)
        
    def run(self):
        # __init__ may have failed
        if self.failed or self.forbidden:
            return self.finish()
        
        if self.isForbidden():
            self.makeForbidden()
            if self.forbidden:
                return self.finish()

        self.open_logs()
        _ = self.getText
        self.clock.start('run')

        # Imports
        from MoinMoin.Page import Page

        if self.query_string == 'action=xmlrpc':
            from MoinMoin.wikirpc import xmlrpc
            xmlrpc(self)
            return self.finish()
        
        if self.query_string == 'action=xmlrpc2':
            from MoinMoin.wikirpc import xmlrpc2
            xmlrpc2(self)
            return self.finish()

        # parse request data
        try:
            self.args = self.setup_args()
            self.form = self.args    
            action = self.form.get('action',[None])[0]

            # Get pagename
            # The last component in path_info is the page name, if any
            path = self.getPathinfo()
            if path.startswith('/'):
                pagename = self.normalizePagename(path)
            else:
                pagename = None
        except: # catch and print any exception
            self.reset_output()
            self.http_headers()
            self.print_exception()
            return self.finish()
        
        try:
            # Handle request. We have these options:
            
            # 1. If user has a bad user name, delete its bad cookie and
            # send him to UserPreferences to make a new account.
            if not user.isValidName(self, self.user.name):
                msg = _("""Invalid user name {{{'%s'}}}.
Name may contain any Unicode alpha numeric character, with optional one
space between words. Group page name is not allowed.""") % self.user.name
                self.deleteCookie()
                page = wikiutil.getSysPage(self, 'UserPreferences')
                page.send_page(self, msg=msg)

            # 2. Or jump to page where user left off
            elif not pagename and not action and self.user.remember_last_visit:
                pagetrail = self.user.getTrail()
                if pagetrail:
                    # Redirect to last page visited
                    url = Page(self, pagetrail[-1]).url(self)
                else:
                    # Or to localized FrontPage
                    url = wikiutil.getFrontPage(self).url(self)
                self.http_redirect(url)
                return self.finish()
            
            # 3. Or save drawing
            elif (self.form.has_key('filepath') and
                self.form.has_key('noredirect')):
                # looks like user wants to save a drawing
                from MoinMoin.action.AttachFile import execute
                # TODO: what if pagename is None?
                execute(pagename, self)
                raise MoinMoinNoFooter           

            # 4. Or handle action
            elif action:
                # Use localized FrontPage if pagename is empty
                if not pagename:
                    self.page = wikiutil.getFrontPage(self)
                else:
                    self.page = Page(self, pagename)

                # Complain about unknown actions
                if not action in self.getKnownActions():
                    self.http_headers()
                    self.write(u'<html><body><h1>Unknown action %s</h1></body>' % wikiutil.escape(action))

                # Disallow non available actions
                elif (action[0].isupper() and
                      not action in self.getAvailableActions(self.page)):
                    # Send page with error
                    msg = _("You are not allowed to do %s on this page.") % wikiutil.escape(action)
                    if not self.user.valid:
                        # Suggest non valid user to login
                        login = wikiutil.getSysPage(self, 'UserPreferences')
                        login = login.link_to(self, _('Login'))
                        msg += _(" %s and try again.", formatted=0) % login
                    self.page.send_page(self, msg=msg)

                # Try action
                else:
                    from MoinMoin.wikiaction import getHandler
                    handler = getHandler(self, action)
                    handler(self.page.page_name, self)

            # 5. Or redirect to another page
            elif self.form.has_key('goto'):
                self.http_redirect(Page(self, self.form['goto'][0]).url(self))
                return self.finish()

            # 6. Or (at last) visit pagename
            else:
                if not pagename:
                    # Get pagename from the query string
                    pagename = self.normalizePagename(self.query_string)
                    
                # pagename could be empty after normalization e.g. '///' -> ''
                if not pagename:
                    pagename = wikiutil.getFrontPage(self).page_name

                # Visit pagename
                if self.cfg.allow_extended_names:
                    self.page = Page(self, pagename)
                    self.page.send_page(self, count_hit=1)
                else:
                    # TODO: kill this. Why disable allow extended names?
                    from MoinMoin.parser.wiki import Parser
                    import re
                    word_match = re.match(Parser.word_rule, pagename)
                    if word_match:
                        word = word_match.group(0)
                        self.page = Page(self, word)
                        self.page.send_page(self, count_hit=1)
                    else:
                        self.http_headers()
                        err = u'<p>%s "<pre>%s</pre>"' % (
                            _("Can't work out query"), pagename)
                        self.write(err)

            # generate page footer (actions that do not want this footer
            # use raise util.MoinMoinNoFooter to break out of the
            # default execution path, see the "except MoinMoinNoFooter"
            # below)

            self.clock.stop('run')
            self.clock.stop('total')

            # Close html code
            if not self.no_closing_html_code:
                if (self.cfg.show_timings and
                    self.form.get('action', [None])[0] != 'print'):
                    self.write('<ul id="timings">\n')
                    for t in self.clock.dump():
                        self.write('<li>%s</li>\n' % t)
                    self.write('</ul>\n')

                self.write('</body>\n</html>\n\n')
            
        except MoinMoinNoFooter:
            pass

        except error.FatalError, err:
            self.fail(err)
            return self.finish()
            
        except: 
            # Catch and print any exception
            saved_exc = sys.exc_info()
            self.reset_output()
            
            # Send 500 error code
            self.http_headers(['Status: 500 MoinMoin Internal Error'])
            self.setResponseCode(500)
            self.http_headers()
            
            self.write(u"\n<!-- ERROR REPORT FOLLOWS -->\n")
            try:
                from MoinMoin.support import cgitb
            except:
                # no cgitb, for whatever reason
                self.print_exception(*saved_exc)
            else:
                try:
                    cgitb.Hook(file=self).handle(saved_exc)
                    # was: cgitb.handler()
                except:
                    self.print_exception(*saved_exc)
                    self.write("\n\n<hr>\n")
                    self.write("<p><strong>Additionally, cgitb raised this exception:</strong></p>\n")
                    self.print_exception()
            del saved_exc

        return self.finish()

    def http_redirect(self, url):
        """ Redirect to a fully qualified, or server-rooted URL """
        if url.find("://") == -1:
            url = self.getQualifiedURL(url)

        self.http_headers(["Status: 302", "Location: %s" % url])

    def setResponseCode(self, code, message=None):
        pass

    def fail(self, err):
        """ Fail with nice error message when we can't continue

        Log the error, then try to print nice error message. Send 500
        status code with the error name. Reference: 
        http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1

        @param err: MoinMoin.error.FatalError instance or subclass.
        """
        self.failed = 1 # save state for self.run()
        self.log(err.asLog())
        self.http_headers(['Status: 500 %(name)s' % err])
        self.setResponseCode(500)
        self.write(err.asHTML())    
            
    def print_exception(self, type=None, value=None, tb=None, limit=None):
        if type is None:
            type, value, tb = sys.exc_info()
        import traceback
        self.write("<h2>request.print_exception handler</h2>\n")
        self.write("<h3>Traceback (most recent call last):</h3>\n")
        list = traceback.format_tb(tb, limit) + \
               traceback.format_exception_only(type, value)
        self.write("<pre>%s<strong>%s</strong></pre>\n" % (
            wikiutil.escape("".join(list[:-1])),
            wikiutil.escape(list[-1]),))
        del tb

    def open_logs(self):
        pass

    def makeUniqueID(self, base):
        """
        Generates a unique ID using a given base name. Appends a
        running count to the base.

        @param base: the base of the id
        @type base: unicode

        @returns: an unique id
        @rtype: unicode
        """
        if not isinstance(base, unicode):
            base=unicode(str(base),'ascii','ignore')
        count = self._page_ids.get(base,-1) + 1
        self._page_ids[base] = count
        if count==0:
            return base
        return u'%s_%04d' % (base, count)

    def httpDate(self, when=None, rfc='1123'):
        """ Returns http date string, according to rfc2068

        See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-3.3

        A http 1.1 server should use only rfc1123 date, but cookie's
        "expires" field should use the older obsolete rfc850 date.

        Note: we can not use strftime() because that honors the locale
        and rfc2822 requires english day and month names.

        We can not use email.Utils.formatdate because it formats the
        zone as '-0000' instead of 'GMT', and creates only rfc1123
        dates. This is a modified version of email.Utils.formatdate
        from Python 2.4.

        @param when: seconds from epoch, as returned by time.time()
        @param rfc: conform to rfc ('1123' or '850')
        @rtype: string
        @return: http date conforming to rfc1123 or rfc850
        """
        if when is None:
            when = time.time()
        now = time.gmtime(when)
        month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
                 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][now.tm_mon - 1]
        if rfc == '1123':
            day = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][now.tm_wday]
            date = '%02d %s %04d' % (now.tm_mday, month, now.tm_year)
        elif rfc == '850':
            day = ["Monday", "Tuesday", "Wednesday", "Thursday",
                    "Friday", "Saturday", "Sunday"][now.tm_wday]
            date = '%02d-%s-%s' % (now.tm_mday, month, str(now.tm_year)[-2:])
        else:
            raise ValueError("Invalid rfc value: %s" % rfc)
        
        return '%s, %s %02d:%02d:%02d GMT' % (day, date, now.tm_hour,
                                              now.tm_min, now.tm_sec)
    
    def disableHttpCaching(self):
        """ Prevent caching of pages that should not be cached

        This is important to prevent caches break acl by providing one
        user pages meant to be seen only by another user, when both users
        share the same caching proxy.
        """
        # Run only once
        if hasattr(self, 'http_caching_disabled'):
            return
        self.http_caching_disabled = 1

        # Set Cache control header for http 1.1 caches
        # See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2109.html#sec-4.2.3
        # and http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-14.9
        self.setHttpHeader('Cache-Control: no-cache="set-cookie"')
        self.setHttpHeader('Cache-Control: private')
        self.setHttpHeader('Cache-Control: max-age=0')       

        # Set Expires for http 1.0 caches (does not support Cache-Control)
        yearago = time.time() - (3600 * 24 * 365)
        self.setHttpHeader('Expires: %s' % self.httpDate(when=yearago))

        # Set Pragma for http 1.0 caches
        # See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-14.32
        self.setHttpHeader('Pragma: no-cache')
       
    def setCookie(self):
        """ Set cookie for the current user
        
        cfg.cookie_lifetime and the user 'remember_me' setting set the
        lifetime of the cookie. lifetime in int hours, see table:
        
        value   cookie lifetime
        ----------------------------------------------------------------
         = 0    forever, ignoring user 'remember_me' setting
         > 0    n hours, or forever if user checked 'remember_me'
         < 0    -n hours, ignoring user 'remember_me' setting

        TODO: do we really need this cookie_lifetime setting?
        """
        # Calculate cookie maxage and expires
        lifetime = int(self.cfg.cookie_lifetime) * 3600 
        forever = 10*365*24*3600 # 10 years
        now = time.time()
        if not lifetime:
            maxage = forever
        elif lifetime > 0:
            if self.user.remember_me:
                maxage = forever
            else:
                maxage = lifetime
        elif lifetime < 0:
            maxage = (-lifetime)
        expires = now + maxage
        
        # Set the cookie
        from Cookie import SimpleCookie
        c = SimpleCookie()
        c['MOIN_ID'] = self.user.id
        c['MOIN_ID']['max-age'] = maxage
        c['MOIN_ID']['path'] = self.getScriptname()
        # Set expires for older clients
        c['MOIN_ID']['expires'] = self.httpDate(when=expires, rfc='850')        
        self.setHttpHeader(c.output())

        # Update the saved cookie, so other code works with new setup
        self.saved_cookie = c.output()

        # IMPORTANT: Prevent caching of current page and cookie
        self.disableHttpCaching()

    def deleteCookie(self):
        """ Delete the user cookie by sending expired cookie with null value

        According to http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2109.html#sec-4.2.2
        Deleted cookie should have Max-Age=0. We also have expires
        attribute, which is probably needed for older browsers.

        Finally, delete the saved cookie and create a new user based on
        the new settings.
        """
        # Set cookie
        from Cookie import SimpleCookie
        c = SimpleCookie()
        c['MOIN_ID'] = ''
        c['MOIN_ID']['path'] = self.getScriptname()
        c['MOIN_ID']['max-age'] = 0
        # Set expires to one year ago for older clients
        yearago = time.time() - (3600 * 24 * 365)
        c['MOIN_ID']['expires'] = self.httpDate(when=yearago, rfc='850')
        self.setHttpHeader(c.output())

        # Update saved cookie and set new unregistered user
        self.saved_cookie = ''
        self.auth_username = ''
        self.user = user.User(self)

        # IMPORTANT: Prevent caching of current page and cookie        
        self.disableHttpCaching()

    def finish(self):
        """ General cleanup on end of request
        
        Delete circular references - all object that we create using
        self.name = class(self)
        This helps Python to collect these objects and keep our
        memory footprint lower
        """
        try:
            del self.user
            del self.theme
            del self.dicts
        except:
            pass

# CGI ---------------------------------------------------------------

class RequestCGI(RequestBase):
    """ specialized on CGI requests """

    def __init__(self, properties={}):
        try:
            self._setup_vars_from_std_env(os.environ)
            #sys.stderr.write("----\n")
            #for key in os.environ.keys():    
            #    sys.stderr.write("    %s = '%s'\n" % (key, os.environ[key]))
            RequestBase.__init__(self, properties)

            # force input/output to binary
            if sys.platform == "win32":
                import msvcrt
                msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
                msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)

        except error.FatalError, err:
            self.fail(err)
            
    def open_logs(self):
        # create log file for catching stderr output
        if not self.opened_logs:
            sys.stderr = open(os.path.join(self.cfg.data_dir, 'error.log'), 'at')
            self.opened_logs = 1

    def setup_args(self, form=None):
        return self._setup_args_from_cgi_form(form)
        
    def read(self, n=None):
        """ Read from input stream.
        """
        if n is None:
            return sys.stdin.read()
        else:
            return sys.stdin.read(n)

    def write(self, *data):
        """ Write to output stream.
        """
        sys.stdout.write(self.encode(data))

    def flush(self):
        sys.stdout.flush()
        
    def finish(self):
        RequestBase.finish(self)
        # flush the output, ignore errors caused by the user closing the socket
        try:
            sys.stdout.flush()
        except IOError, ex:
            import errno
            if ex.errno != errno.EPIPE: raise

    # Accessors --------------------------------------------------------
    
    def getPathinfo(self):
        """ Return the remaining part of the URL. """
        pathinfo = self.path_info

        # Fix for bug in IIS/4.0
        if os.name == 'nt':
            scriptname = self.getScriptname()
            if pathinfo.startswith(scriptname):
                pathinfo = pathinfo[len(scriptname):]

        return pathinfo

    # Headers ----------------------------------------------------------
    
    def setHttpHeader(self, header):
        self.user_headers.append(header)

    def http_headers(self, more_headers=[]):
        # Send only once
        if getattr(self, 'sent_headers', None):
            return
        
        self.sent_headers = 1
        have_ct = 0

        # send http headers
        for header in more_headers + getattr(self, 'user_headers', []):
            if header.lower().startswith("content-type:"):
                # don't send content-type multiple times!
                if have_ct: continue
                have_ct = 1
            if type(header) is unicode:
                header = header.encode('ascii')
            self.write("%s\r\n" % header)

        if not have_ct:
            self.write("Content-type: text/html;charset=%s\r\n" % config.charset)

        self.write('\r\n')

        #from pprint import pformat
        #sys.stderr.write(pformat(more_headers))
        #sys.stderr.write(pformat(self.user_headers))


# Twisted -----------------------------------------------------------

class RequestTwisted(RequestBase):
    """ specialized on Twisted requests """

    def __init__(self, twistedRequest, pagename, reactor, properties={}):
        try:
            self.twistd = twistedRequest
            self.http_accept_language = self.twistd.getHeader('Accept-Language')
            self.reactor = reactor
            self.saved_cookie = self.twistd.getHeader('Cookie')
            self.server_protocol = self.twistd.clientproto
            self.server_name = self.twistd.getRequestHostname().split(':')[0]
            self.server_port = str(self.twistd.getHost()[2])
            self.is_ssl = self.twistd.isSecure()
            if self.server_port != ('80', '443')[self.is_ssl]:
                self.http_host = self.server_name + ':' + self.server_port
            else:
                self.http_host = self.server_name
            self.script_name = "/" + '/'.join(self.twistd.prepath[:-1])
            path_info = '/' + '/'.join([pagename] + self.twistd.postpath)
            self.path_info = self.decodePagename(path_info)
            self.request_method = self.twistd.method
            self.remote_host = self.twistd.getClient()
            self.remote_addr = self.twistd.getClientIP()
            self.http_user_agent = self.twistd.getHeader('User-Agent')
            self.request_uri = self.twistd.uri
            self.url = self.http_host + self.request_uri # was: self.server_name + self.request_uri

            ac = self.twistd.getHeader('Accept-Charset') or ''
            self.accepted_charsets = self.parse_accept_charset(ac)

            qindex = self.request_uri.find('?')
            if qindex != -1:
                query_string = self.request_uri[qindex+1:]
                self.query_string = self.decodePagename(query_string)
            else:
                self.query_string = ''
            self.outputlist = []
            self.auth_username = None

            # need config here, so check:
            self._load_multi_cfg()

            if self.cfg.auth_http_enabled and self.cfg.auth_http_insecure:
                self.auth_username = self.twistd.getUser()
            # TODO password check, twisted does NOT do that for us
            # this maybe requires bigger or critical changes, so we delay that
            # to 1.4's new auth stuff

            RequestBase.__init__(self, properties)
            #print "request.RequestTwisted.__init__: received_headers=\n" + str(self.twistd.received_headers)

        except error.FatalError, err:
            self.delayedError = err

    def run(self):
        """ Handle delayed errors then invoke base class run """
        if hasattr(self, 'delayedError'):
            self.fail(self.delayedError)
            return self.finish()
        RequestBase.run(self)
            
    def setup_args(self, form=None):
        args = {}
        for key,values in self.twistd.args.items():
            if key[-12:] == "__filename__":
                args[key] = wikiutil.decodeUserInput(values, self.decode_charsets)
                continue
            if not isinstance(values, list):
                values = [values]
            fixedResult = []
            for v in values:
                if not self.twistd.args.has_key(key+"__filename__"):
                    v = wikiutil.decodeUserInput(v, self.decode_charsets)
                fixedResult.append(v)
            args[key] = fixedResult
        return args
        
    def read(self, n=None):
        """ Read from input stream.
        """
        # XXX why is that wrong?:
        #rd = self.reactor.callFromThread(self.twistd.read)
        
        # XXX do we need self.reactor.callFromThread with that?
        # XXX if yes, why doesn't it work?
        self.twistd.content.seek(0, 0)
        if n is None:
            rd = self.twistd.content.read()
        else:
            rd = self.twistd.content.read(n)
        #print "request.RequestTwisted.read: data=\n" + str(rd)
        return rd
    
    def write(self, *data):
        """ Write to output stream.
        """
        #print "request.RequestTwisted.write: data=\n" + wd
        self.reactor.callFromThread(self.twistd.write, self.encode(data))

    def flush(self):
        pass # XXX is there a flush in twisted?

    def finish(self):
        RequestBase.finish(self)
        self.reactor.callFromThread(self.twistd.finish)

    def open_logs(self):
        return
        # create log file for catching stderr output
        if not self.opened_logs:
            sys.stderr = open(os.path.join(self.cfg.data_dir, 'error.log'), 'at')
            self.opened_logs = 1

    # Headers ----------------------------------------------------------

    def setHttpHeader(self, header):
        self.user_headers.append(header)

    def __setHttpHeader(self, header):
        if type(header) is unicode:
            header = header.encode('ascii')
        key, value = header.split(':',1)
        value = value.lstrip()
        if key.lower()=='set-cookie':
            key, value = value.split('=',1)
            self.twistd.addCookie(key, value)
        else:
            self.twistd.setHeader(key, value)
        #print "request.RequestTwisted.setHttpHeader: %s" % header

    def http_headers(self, more_headers=[]):
        if getattr(self, 'sent_headers', None):
            return
        self.sent_headers = 1
        have_ct = 0

        # set http headers
        for header in more_headers + getattr(self, 'user_headers', []):
            if header.lower().startswith("content-type:"):
                # don't send content-type multiple times!
                if have_ct: continue
                have_ct = 1
            self.__setHttpHeader(header)

        if not have_ct:
            self.__setHttpHeader("Content-type: text/html;charset=%s" % config.charset)

    def http_redirect(self, url):
        """ Redirect to a fully qualified, or server-rooted URL """
        if url.count("://") == 0:
            # no https method??
            url = "http://%s:%s%s" % (self.server_name, self.server_port, url)

        if isinstance(url, unicode):
            url = url.encode('ascii')
        self.twistd.redirect(url)
        # calling finish here will send the rest of the data to the next
        # request. leave the finish call to run()
        #self.twistd.finish()
        raise MoinMoinNoFooter

    def setResponseCode(self, code, message=None):
        self.twistd.setResponseCode(code, message)
        
# CLI ------------------------------------------------------------------

class RequestCLI(RequestBase):
    """ specialized on command line interface and script requests """

    def __init__(self, url='CLI', pagename='', properties={}):
        self.http_accept_language = ''
        self.saved_cookie = ''
        self.path_info = self.decodePagename('/' + pagename)
        self.query_string = ''
        self.remote_addr = '127.0.0.1'
        self.is_ssl = 0
        self.auth_username = None
        self.http_user_agent = 'CLI/Script'
        self.outputlist = []
        self.url = url
        self.accepted_charsets = ['utf-8']
        self.decode_charsets = self.accepted_charsets
        self.request_method = 'GET'
        self.request_uri = '/' + pagename # TODO check
        self.server_name = 'localhost'
        self.server_port = '80'
        self.http_host = 'localhost'
        self.http_referer = ''
        self.script_name = ''
        RequestBase.__init__(self, properties)
        self.cfg.caching_formats = [] # don't spoil the cache

    def read(self, n=None):
        """ Read from input stream.
        """
        if n is None:
            return sys.stdin.read()
        else:
            return sys.stdin.read(n)

    def write(self, *data):
        """ Write to output stream.
        """
        sys.stdout.write(self.encode(data))

    def flush(self):
        sys.stdout.flush()
        
    def finish(self):
        RequestBase.finish(self)
        # flush the output, ignore errors caused by the user closing the socket
        try:
            sys.stdout.flush()
        except IOError, ex:
            import errno
            if ex.errno != errno.EPIPE: raise

    def isForbidden(self):
        """ check for web spiders and refuse anything except viewing """
        return 0

    # Accessors --------------------------------------------------------

    def getScriptname(self):
        """ Return the scriptname part of the URL ("/path/to/my.cgi"). """
        return '.'

    def getQualifiedURL(self, uri = None):
        """ Return a full URL starting with schema, server name and port.

            *uri* -- append this server-rooted uri (must start with a slash)
        """
        return uri

    def getBaseURL(self):
        """ Return a fully qualified URL to this script. """
        return self.getQualifiedURL(self.getScriptname())

    # Headers ----------------------------------------------------------

    def setHttpHeader(self, header):
        pass

    def http_headers(self, more_headers=[]):
        pass

    def http_redirect(self, url):
        """ Redirect to a fully qualified, or server-rooted URL """
        raise Exception("Redirect not supported for command line tools!")


# StandAlone Server ----------------------------------------------------

class RequestStandAlone(RequestBase):
    """
    specialized on StandAlone Server (MoinMoin.server.standalone) requests
    """

    def __init__(self, sa, properties={}):
        """
        @param sa: stand alone server object
        @param properties: ...
        """
        try:
            self.sareq = sa
            self.wfile = sa.wfile
            self.rfile = sa.rfile
            self.headers = sa.headers
            self.is_ssl = 0

            # Split path and query string and unquote path
            # query is unquoted by setup_args
            import urllib
            if '?' in sa.path:
                path, query = sa.path.split('?', 1)
            else:
                path, query = sa.path, ''
            path = urllib.unquote(path)

            #HTTP headers
            self.env = {} 
            for hline in sa.headers.headers:
                key = sa.headers.isheader(hline)
                if key:
                    hdr = sa.headers.getheader(key)
                    self.env[key] = hdr

            #accept = []
            #for line in sa.headers.getallmatchingheaders('accept'):
            #    if line[:1] in string.whitespace:
            #        accept.append(line.strip())
            #    else:
            #        accept = accept + line[7:].split(',')
            #
            #env['HTTP_ACCEPT'] = ','.join(accept)

            co = filter(None, sa.headers.getheaders('cookie'))

            self.http_accept_language = sa.headers.getheader('Accept-Language')
            self.server_name = sa.server.server_name
            self.server_port = str(sa.server.server_port)
            self.http_host = sa.headers.getheader('host')
            # Make sure http referer use only ascii
            referer = sa.headers.getheader('referer') or ''
            self.http_referer = unicode(referer, 'ascii',
                                        'replace').encode('ascii', 'replace')
            self.saved_cookie = ', '.join(co) or ''
            self.script_name = ''
            self.request_method = sa.command
            self.request_uri = sa.path
            self.remote_addr = sa.client_address[0]
            self.http_user_agent = sa.headers.getheader('user-agent') or ''
            # next must be http_host as server_name == reverse_lookup(IP)
            if self.http_host:
                self.url = self.http_host + self.request_uri
            else:
                self.url = "localhost" + self.request_uri

            ac = sa.headers.getheader('Accept-Charset') or ''
            self.accepted_charsets = self.parse_accept_charset(ac)

            # Decode path_info and query string, they may contain non-ascii
            self.path_info = self.decodePagename(path) 
            self.query_string = self.decodePagename(query)        

            # from standalone script:
            # XXX AUTH_TYPE
            # XXX REMOTE_USER
            # XXX REMOTE_IDENT
            self.auth_username = None
            #env['PATH_TRANSLATED'] = uqrest #self.translate_path(uqrest)
            #host = self.address_string()
            #if host != self.client_address[0]:
            #    env['REMOTE_HOST'] = host
            # env['SERVER_PROTOCOL'] = self.protocol_version
            RequestBase.__init__(self, properties)

        except error.FatalError, err:
            self.fail(err)

    def setup_args(self, form=None):
        self.env['REQUEST_METHOD'] = self.request_method

        # This is a ugly hack to get the query into the environment, due
        # to the wired way standalone form is created.
        self.env['QUERY_STRING'] = self.query_string.encode(config.charset)

        ct = self.headers.getheader('content-type')
        if ct:
            self.env['CONTENT_TYPE'] = ct
        cl = self.headers.getheader('content-length')
        if cl:
            self.env['CONTENT_LENGTH'] = cl
        
        #print "env = ", self.env
        #form = cgi.FieldStorage(self, headers=self.env, environ=self.env)
        if form is None:
            form = cgi.FieldStorage(self.rfile, environ=self.env)
        return self._setup_args_from_cgi_form(form)
        
    def read(self, n=None):
        """ Read from input stream.
        """
        if n is None:
            return self.rfile.read()
        else:
            return self.rfile.read(n)

    def write(self, *data):
        """ Write to output stream.
        """
        self.wfile.write(self.encode(data))

    def flush(self):
        self.wfile.flush()
        
    def finish(self):
        RequestBase.finish(self)
        self.wfile.flush()

    # Headers ----------------------------------------------------------

    def setHttpHeader(self, header):
        self.user_headers.append(header)

    def http_headers(self, more_headers=[]):
        if getattr(self, 'sent_headers', None):
            return
        
        self.sent_headers = 1
        user_headers = getattr(self, 'user_headers', [])
        
        # check for status header and send it
        our_status = 200
        for header in more_headers + user_headers:
            if header.lower().startswith("status:"):
                try:
                    our_status = int(header.split(':',1)[1].strip().split(" ", 1)[0]) 
                except:
                    pass
                # there should be only one!
                break
        # send response
        self.sareq.send_response(our_status)

        # send http headers
        have_ct = 0
        for header in more_headers + user_headers:
            if type(header) is unicode:
                header = header.encode('ascii')
            if header.lower().startswith("content-type:"):
                # don't send content-type multiple times!
                if have_ct: continue
                have_ct = 1

            self.write("%s\r\n" % header)

        if not have_ct:
            self.write("Content-type: text/html;charset=%s\r\n" % config.charset)

        self.write('\r\n')

        #from pprint import pformat
        #sys.stderr.write(pformat(more_headers))
        #sys.stderr.write(pformat(self.user_headers))


# mod_python/Apache ----------------------------------------------------

class RequestModPy(RequestBase):
    """ specialized on mod_python requests """

    def __init__(self, req):
        """ Saves mod_pythons request and sets basic variables using
            the req.subprocess_env, cause this provides a standard
            way to access the values we need here.

            @param req: the mod_python request instance
        """
        try:
            # flags if headers sent out contained content-type or status
            self._have_ct = 0
            self._have_status = 0

            req.add_common_vars()
            self.mpyreq = req
            # some mod_python 2.7.X has no get method for table objects,
            # so we make a real dict out of it first.
            if not hasattr(req.subprocess_env,'get'):
                env=dict(req.subprocess_env)
            else:
                env=req.subprocess_env
            # Must be called BEFORE _setup_vars_from_std_env
            self.fixScriptNameAndPathInfo(env)
            self._setup_vars_from_std_env(env)
            RequestBase.__init__(self)

        except error.FatalError, err:
            self.fail(err)
            
    def fixScriptNameAndPathInfo(self, env):
        """ Set script_name and path_info using httpd config
        
        When configuring using Apache <location> directive and path
        like /a/b/c, the only way to set correct script_name and
        path_info is to pass the required script_name using
        PythonOption. See example configuration:
        
        <Location /url/to/mywiki>
            PythonOption Location /url/to/mywiki      
        </location>
        
        Must be called before self._setup_vars_from_std_env
        """
        location = self.mpyreq.get_options().get('Location')
        if location is None:
            return
        restoredURI = env['SCRIPT_NAME'] + env['PATH_INFO']
        if not restoredURI.startswith(location):
            message = '''Wrong "PythonOption Location %s".

PythonOption Location value should match Apache Location directive. Fix
and restart Apache.              
''' % location
            raise error.ConfigurationError(message)
        env['SCRIPT_NAME'] = location
        env['PATH_INFO'] = restoredURI[len(location):]

    def setup_args(self, form=None):
        """ Sets up args by using mod_python.util.FieldStorage, which
            is different to cgi.FieldStorage. So we need a separate
            method for this.
        """
        from mod_python import util
        if form is None:
            form = util.FieldStorage(self.mpyreq)

        args = {}
        for key in form.keys():
            values = form[key]
            if not isinstance(values, list):
                values = [values]
            fixedResult = []
            for i in values:
                ## if object has a filename attribute, remember it
                ## with a name hack
                if hasattr(i,'filename') and i.filename:
                    args[key+'__filename__']=i.filename
                    has_fn=1
                else:
                    has_fn=0
                ## mod_python 2.7 might return strings instead
                ## of Field objects
                if hasattr(i,'value'):
                    i = i.value
                ## decode if it is not a file
                if not has_fn:
                    i = wikiutil.decodeUserInput(i, self.decode_charsets)
                fixedResult.append(i)
            args[key] = fixedResult
        return args

    def run(self, req):
        """ mod_python calls this with its request object. We don't
            need it cause its already passed to __init__. So ignore
            it and just return RequestBase.run.

            @param req: the mod_python request instance
        """
        return RequestBase.run(self)

    def read(self, n=None):
        """ Read from input stream.
        """
        if n is None:
            return self.mpyreq.read()
        else:
            return self.mpyreq.read(n)

    def write(self, *data):
        """ Write to output stream.
        """
        self.mpyreq.write(self.encode(data))

    def flush(self):
        """ We can't flush it, so do nothing.
        """
        pass
        
    def finish(self):
        """ Just return apache.OK. Status is set in req.status.
        """
        RequestBase.finish(self)
        # is it possible that we need to return something else here?
        from mod_python import apache
        return apache.OK

    # Headers ----------------------------------------------------------

    def setHttpHeader(self, header):
        """ Filters out content-type and status to set them directly
            in the mod_python request. Rest is put into the headers_out
            member of the mod_python request.

            @param header: string, containing valid HTTP header.
        """
        if type(header) is unicode:
            header = header.encode('ascii')
        key, value = header.split(':',1)
        value = value.lstrip()
        if key.lower() == 'content-type':
            # save content-type for http_headers
            if not self._have_ct:
                # we only use the first content-type!
                self.mpyreq.content_type = value
                self._have_ct = 1
        elif key.lower() == 'status':
            # save status for finish
            try:
                self.mpyreq.status = int(value.split(' ',1)[0])
            except:
                pass
            else:
                self._have_status = 1
        else:
            # this is a header we sent out
            self.mpyreq.headers_out[key]=value

    def http_headers(self, more_headers=[]):
        """ Sends out headers and possibly sets default content-type
            and status.

            @keyword more_headers: list of strings, defaults to []
        """
        for header in more_headers + getattr(self, 'user_headers', []):
            self.setHttpHeader(header)
        # if we don't had an content-type header, set text/html
        if self._have_ct == 0:
            self.mpyreq.content_type = "text/html;charset=%s" % config.charset
        # if we don't had a status header, set 200
        if self._have_status == 0:
            self.mpyreq.status = 200
        # this is for mod_python 2.7.X, for 3.X it's a NOP
        self.mpyreq.send_http_header()

# FastCGI -----------------------------------------------------------

class RequestFastCGI(RequestBase):
    """ specialized on FastCGI requests """

    def __init__(self, fcgRequest, env, form, properties={}):
        """ Initializes variables from FastCGI environment and saves
            FastCGI request and form for further use.

            @param fcgRequest: the FastCGI request instance.
            @param env: environment passed by FastCGI.
            @param form: FieldStorage passed by FastCGI.
        """
        try:
            self.fcgreq = fcgRequest
            self.fcgenv = env
            self.fcgform = form
            self._setup_vars_from_std_env(env)
            RequestBase.__init__(self, properties)

        except error.FatalError, err:
            self.fail(err)

    def setup_args(self, form=None):
        """ Use the FastCGI form to setup arguments. """
        if form is None:
            form = self.fcgform
        return self._setup_args_from_cgi_form(form)

    def read(self, n=None):
        """ Read from input stream.
        """
        if n is None:
            return self.fcgreq.stdin.read()
        else:
            return self.fcgreq.stdin.read(n)

    def write(self, *data):
        """ Write to output stream.
        """
        self.fcgreq.out.write(self.encode(data))

    def flush(self):
        """ Flush output stream.
        """
        self.fcgreq.flush_out()

    def finish(self):
        """ Call finish method of FastCGI request to finish handling
            of this request.
        """
        RequestBase.finish(self)
        self.fcgreq.finish()

    # Accessors --------------------------------------------------------

    def getPathinfo(self):
        """ Return the remaining part of the URL. """
        pathinfo = self.path_info

        # Fix for bug in IIS/4.0
        if os.name == 'nt':
            scriptname = self.getScriptname()
            if pathinfo.startswith(scriptname):
                pathinfo = pathinfo[len(scriptname):]

        return pathinfo

    # Headers ----------------------------------------------------------
    
    def setHttpHeader(self, header):
        """ Save header for later send. """
        self.user_headers.append(header)

    def http_headers(self, more_headers=[]):
        """ Send out HTTP headers. Possibly set a default content-type.
        """
        if getattr(self, 'sent_headers', None):
            return
        self.sent_headers = 1
        have_ct = 0

        # send http headers
        for header in more_headers + getattr(self, 'user_headers', []):
            if type(header) is unicode:
                header = header.encode('ascii')
            if header.lower().startswith("content-type:"):
                # don't send content-type multiple times!
                if have_ct: continue
                have_ct = 1
            self.write("%s\r\n" % header)

        if not have_ct:
            self.write("Content-type: text/html;charset=%s\r\n" % config.charset)

        self.write('\r\n')

        #from pprint import pformat
        #sys.stderr.write(pformat(more_headers))
        #sys.stderr.write(pformat(self.user_headers))

