##############################################################################
# 
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
# 
# Copyright (c) Digital Creations.  All rights reserved.
# 
# This license has been certified as Open Source(tm).
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# 
# 1. Redistributions in source code must retain the above copyright
#    notice, this list of conditions, and the following disclaimer.
# 
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions, and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
# 
# 3. Digital Creations requests that attribution be given to Zope
#    in any manner possible. Zope includes a "Powered by Zope"
#    button that is installed by default. While it is not a license
#    violation to remove this button, it is requested that the
#    attribution remain. A significant investment has been put
#    into Zope, and this effort will continue if the Zope community
#    continues to grow. This is one way to assure that growth.
# 
# 4. All advertising materials and documentation mentioning
#    features derived from or use of this software must display
#    the following acknowledgement:
# 
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
#      (http://www.zope.org/)."
# 
#    In the event that the product being advertised includes an
#    intact Zope distribution (with copyright and license included)
#    then this clause is waived.
# 
# 5. Names associated with Zope or Digital Creations must not be used to
#    endorse or promote products derived from this software without
#    prior written permission from Digital Creations.
# 
# 6. Modified redistributions of any form whatsoever must retain
#    the following acknowledgment:
# 
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
#      (http://www.zope.org/)."
# 
#    Intact (re-)distributions of any official Zope release do not
#    require an external acknowledgement.
# 
# 7. Modifications are encouraged but must be packaged separately as
#    patches to official Zope releases.  Distributions that do not
#    clearly separate the patches from the original work must be clearly
#    labeled as unofficial distributions.  Modifications which do not
#    carry the name Zope may be packaged in any form, as long as they
#    conform to all of the clauses above.
# 
# 
# Disclaimer
# 
#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
#   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
#   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
#   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
#   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
#   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
#   SUCH DAMAGE.
# 
# 
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations.  Specific
# attributions are listed in the accompanying credits file.
# 
##############################################################################

__version__='$Revision: 1.11 $'[11:-2]

import regex, re, sys, os, string
from string import lower, atoi, rfind, split, strip, join, upper, find
from BaseRequest import BaseRequest
from HTTPResponse import HTTPResponse
from cgi import FieldStorage
from urllib import quote, unquote
xmlrpc=None # Placeholder for module that we'll import if we have to.

isCGI_NAME = {
        'SERVER_SOFTWARE' : 1, 
        'SERVER_NAME' : 1, 
        'GATEWAY_INTERFACE' : 1, 
        'SERVER_PROTOCOL' : 1, 
        'SERVER_PORT' : 1, 
        'REQUEST_METHOD' : 1, 
        'PATH_INFO' : 1, 
        'PATH_TRANSLATED' : 1, 
        'SCRIPT_NAME' : 1, 
        'QUERY_STRING' : 1, 
        'REMOTE_HOST' : 1, 
        'REMOTE_ADDR' : 1, 
        'AUTH_TYPE' : 1, 
        'REMOTE_USER' : 1, 
        'REMOTE_IDENT' : 1, 
        'CONTENT_TYPE' : 1, 
        'CONTENT_LENGTH' : 1,
        'SERVER_URL': 1,
        }.has_key

hide_key={'HTTP_AUTHORIZATION':1,
          'HTTP_CGI_AUTHORIZATION': 1,
          }.has_key

_marker=[]
class HTTPRequest(BaseRequest):
    """\
    Model HTTP request data.
    
    This object provides access to request data.  This includes, the
    input headers, form data, server data, and cookies.

    Request objects are created by the object publisher and will be
    passed to published objects through the argument name, REQUEST.

    The request object is a mapping object that represents a
    collection of variable to value mappings.  In addition, variables
    are divided into four categories:

      - Environment variables

        These variables include input headers, server data, and other
        request-related data.  The variable names are as <a
        href="http://hoohoo.ncsa.uiuc.edu/cgi/env.html">specified</a>
        in the <a
        href="http://hoohoo.ncsa.uiuc.edu/cgi/interface.html">CGI
        specification</a>

      - Form data

        These are data extracted from either a URL-encoded query
        string or body, if present.

      - Cookies

        These are the cookie data, if present.

      - Other

        Data that may be set by an application object.

    The form attribute of a request is actually a Field Storage
    object.  When file uploads are used, this provides a richer and
    more complex interface than is provided by accessing form data as
    items of the request.  See the FieldStorage class documentation
    for more details.

    The request object may be used as a mapping object, in which case
    values will be looked up in the order: environment variables,
    other variables, form data, and then cookies.
    """
    _hacked_path=None
    args=()
    _file=None
    _urls = ()

    retry_max_count=3
    def supports_retry(self): return self.retry_count < self.retry_max_count

    def retry(self):
        self.retry_count=self.retry_count+1
        self.stdin.seek(0)
        r=self.__class__(stdin=self.stdin,
                         environ=self._orig_env,
                         response=self.response.retry()
                         )
        return r


    def setSite(self, base=None):
        """ blah """
        if base is not None:
            self.script = self.base = base

        # reset the URL
        self.other['URL'] = self.script
        # clear the 'cache' of URLx and BASEx
        for x in self._urls:
            del self.other[x]
            
        del self.other['PARENTS'][:]


    def __init__(self, stdin, environ, response):
        self._orig_env=environ

        if environ.has_key('HTTP_AUTHORIZATION'):
            self._auth=environ['HTTP_AUTHORIZATION']
            response._auth=1
            del environ['HTTP_AUTHORIZATION']

        self.stdin=stdin
        self.environ=environ
        have_env=environ.has_key
        get_env=environ.get
        self.response=response
        other=self.other={'RESPONSE': response}
        self.form={}
        self.steps=[]

        ################################################################
        # Get base info first. This isn't likely to cause
        # errors and might be useful to error handlers.
        b=script=strip(get_env('SCRIPT_NAME',''))
        while b and b[-1]=='/': b=b[:-1]
        p = rfind(b,'/')
        if p >= 0: b=b[:p+1]
        else: b=''
        while b and b[0]=='/': b=b[1:]

        server_url=get_env('SERVER_URL',None)
        if server_url is not None:
             server_url=strip(server_url)
        else:
             if have_env('HTTPS') and (
                 environ['HTTPS'] == "on" or environ['HTTPS'] == "ON"):
                 server_url='https://'
             elif (have_env('SERVER_PORT_SECURE') and 
                   environ['SERVER_PORT_SECURE'] == "1"):
                 server_url='https://'
             else: server_url='http://'

             if have_env('HTTP_HOST'):
                 server_url=server_url+strip(environ['HTTP_HOST'])
             else:
                 server_url=server_url+strip(environ['SERVER_NAME'])
                 server_port=environ['SERVER_PORT']
                 if server_port!='80': server_url=server_url+':'+server_port
             other['SERVER_URL']=server_url
             
        if server_url[-1:]=='/': server_url=server_url[:-1]
                        
        if b: self.base="%s/%s" % (server_url,b)
        else: self.base=server_url
        while script[:1]=='/': script=script[1:]
        if script: script="%s/%s" % (server_url,script)
        else:      script=server_url
        other['URL']=self.script=script

        ################################################################
        # Cookie values should *not* be appended to existing form
        # vars with the same name - they are more like default values
        # for names not otherwise specified in the form.
        cookies={}
        k=get_env('HTTP_COOKIE','')
        if k:
            parse_cookie(k, cookies)
            for k,item in cookies.items():
                if not other.has_key(k):
                    other[k]=item
        self.cookies=cookies

        self.session = None
        
    def processInputs(
        self,
        # "static" variables that we want to be local for speed
        SEQUENCE=1,
        DEFAULT=2,
        RECORD=4,
        RECORDS=8,
        REC=12, # RECORD|RECORDS
        EMPTY=16,
        CONVERTED=32,
        hasattr=hasattr,
        getattr=getattr,
        setattr=setattr,
        search_type=regex.compile(':[a-zA-Z][a-zA-Z0-9_]+$').search,
        rfind=string.rfind,
        ):
        """Process request inputs

        We need to delay input parsing so that it is done under
        publisher control for error handling purposes.
        """
        response=self.response
        environ=self.environ
        method=environ.get('REQUEST_METHOD','GET')
        
        if method != 'GET': fp=self.stdin
        else:               fp=None

        form=self.form
        other=self.other

        meth=None
        fs=FieldStorage(fp=fp,environ=environ,keep_blank_values=1)
        if not hasattr(fs,'list') or fs.list is None:
            # Hm, maybe it's an XML-RPC
            if (fs.headers.has_key('content-type') and
                fs.headers['content-type'] == 'text/xml' and
                method == 'POST'):
                # Ye haaa, XML-RPC!
                global xmlrpc
                if xmlrpc is None: import xmlrpc
                meth, self.args = xmlrpc.parse_input(fs.value)
                response=xmlrpc.response(response)
                other['RESPONSE']=self.response=response
                other['REQUEST_METHOD']='' # We don't want index_html!
            else:
                self._file=fs.file
        else:
            fslist=fs.list
            tuple_items={}
            lt=type([])
            CGI_name=isCGI_NAME
            defaults={}
            converter=seqf=None
            
            for item in fslist:
                
                key=item.name
                if (hasattr(item,'file') and hasattr(item,'filename')
                    and hasattr(item,'headers')):
                    if (item.file and
                        (item.filename is not None
                         # RFC 1867 says that all fields get a content-type.
                         # or 'content-type' in map(lower, item.headers.keys())
                         )):
                        item=FileUpload(item)
                    else:
                        item=item.value

                flags=0

                # Loop through the different types and set
                # the appropriate flags

                # We'll search from the back to the front.
                # We'll do the search in two steps.  First, we'll
                # do a string search, and then we'll check it with
                # a regex search.
                
                l=rfind(key,':')
                if l >= 0:
                    l=search_type(key,l)
                    while l >= 0:
                        type_name=key[l+1:]
                        key=key[:l]
                        if type_name == 'list':
                            seqf=list
                            flags=flags|SEQUENCE
                        elif type_name == 'tuple':
                            seqf=tuple
                            tuple_items[key]=1
                            flags=flags|SEQUENCE
                        elif type_name == 'method':
                            if l: meth=key
                            else: meth=item
                        elif type_name == 'default_method':
                            if not meth:
                                if l: meth=key
                                else: meth=item
                        elif type_name == 'default':
                            flags=flags|DEFAULT
                        elif type_name == 'record':
                            flags=flags|RECORD
                        elif type_name == 'records':
                            flags=flags|RECORDS
                        elif type_name == 'ignore_empty':
                            if not item: flags=flags|EMPTY
    
                        l=rfind(key,':')
                        if l < 0: break
                        l=search_type(key,l)
             
                # Filter out special names from form:
                if CGI_name(key) or key[:5]=='HTTP_': continue

                if flags:

                    # skip over empty fields    
                    if flags&EMPTY: continue

                    #Split the key and its attribute
                    if flags&REC:
                        key=split(key,".")
                        key, attr=join(key[:-1],"."), key[-1]
                       
                    # defer conversion
                    if flags&CONVERTED:
                        try:
                            item=converter(item)
                        except:
                            if (not item and not (flags&DEFAULT) and
                                defaults.has_key(key)):
                                item = defaults[key]
                                if flags&RECORD:
                                    item=getattr(item,attr)
                                if flags&RECORDS:
                                    item.reverse()
                                    item = item[0]
                                    item=getattr(item,attr)
                            else:
                                raise                            
                         
                    #Determine which dictionary to use
                    if flags&DEFAULT:
                       mapping_object = defaults
                    else:
                       mapping_object = form

                    #Insert in dictionary
                    if mapping_object.has_key(key):
                       if flags&RECORDS:
                           #Get the list and the last record
                           #in the list
                           reclist = mapping_object[key]
                           reclist.reverse()
                           x=reclist[0]
                           reclist.reverse()
                           if not hasattr(x,attr):
                               #If the attribute does not
                               #exist, set it
                               if flags&SEQUENCE: item=[item]
                               reclist.remove(x)
                               setattr(x,attr,item)
                               reclist.append(x)
                               mapping_object[key] = reclist
                           else:
                               if flags&SEQUENCE:
                                   # If the attribute is a
                                   # sequence, append the item
                                   # to the existing attribute
                                   reclist.remove(x)
                                   y = getattr(x, attr)
                                   y.append(item)
                                   setattr(x, attr, y)
                                   reclist.append(x)
                                   mapping_object[key] = reclist
                               else:
                                   # Create a new record and add
                                   # it to the list
                                   n=record()
                                   setattr(n,attr,item)
                                   reclist.append(n)
                                   mapping_object[key]=reclist
                       elif flags&RECORD:
                           b=mapping_object[key]
                           if flags&SEQUENCE:
                              item=[item]
                              if not hasattr(b,attr):
                                  # if it does not have the
                                  # attribute, set it
                                  setattr(b,attr,item)
                              else:
                                  # it has the attribute so
                                  # append the item to it
                                  setattr(b,attr,getattr(b,attr)+item)
                           else:
                              # it is not a sequence so
                              # set the attribute
                              setattr(b,attr,item)        
                       else:
                          # it is not a record or list of records
                           found=mapping_object[key]
                           if type(found) is lt:
                               found.append(item)
                           else:
                               found=[found,item]
                               mapping_object[key]=found
                    else:
                       # The dictionary does not have the key
                       if flags&RECORDS:
                           # Create a new record, set its attribute
                           # and put it in the dictionary as a list
                           a = record()
                           if flags&SEQUENCE: item=[item]
                           setattr(a,attr,item)
                           mapping_object[key]=[a]
                       elif flags&RECORD:
                           # Create a new record, set its attribute
                           # and put it in the dictionary
                           if flags&SEQUENCE: item=[item]
                           r = mapping_object[key]=record()
                           setattr(r,attr,item)
                       else:
                           # it is not a record or list of records
                           if flags&SEQUENCE: item=[item]
                           mapping_object[key]=item

                else:
                    # This branch is for case when no type was specified.
                    mapping_object = form
    
                    #Insert in dictionary
                    if mapping_object.has_key(key):
                        # it is not a record or list of records
                        found=mapping_object[key]
                        if type(found) is lt:
                            found.append(item)
                        else:
                            found=[found,item]
                            mapping_object[key]=found
                    else:
                        mapping_object[key]=item

            #insert defaults into form dictionary
            if defaults:
                for keys, values in defaults.items():
                    if not form.has_key(keys):
                        # if the form does not have the key,
                        # set the default
                        form[keys]=values
                    else:
                        #The form has the key
                        if getattr(values, '__class__',0) is record:
                           # if the key is mapped to a record, get the
                           # record
                           r = form[keys]
                           for k, v in values.__dict__.items():
                              # loop through the attributes and values
                              # in the default dictionary
                              if not hasattr(r, k):
                                 # if the form dictionary doesn't have
                                 # the attribute, set it to the default
                                 setattr(r,k,v)
                                 form[keys] = r    
                        elif values == type([]):
                               # the key is mapped to a list
                               l = form[keys]
                               for x in values:
                                   # for each x in the list
                                   if getattr(x, '__class__',0) is record:
                                       # if the x is a record
                                       for k, v in x.__dict__.items():
                                           
                                           # loop through each
                                           # attribute and value in
                                           # the record
                                           
                                           for y in l:
                                               
                                               # loop through each
                                               # record in the form
                                               # list if it doesn't
                                               # have the attributes
                                               # in the default
                                               # dictionary, set them
                                               
                                               if not hasattr(y, k):
                                                   setattr(y, k, v)
                                   else:
                                       # x is not a record
                                       if not a in l:
                                           l.append(a)
                               form[keys] = l
                        else:
                            # The form has the key, the key is not mapped
                            # to a record or sequence so do nothing
                            pass
                                
            # Convert to tuples
            if tuple_items:
                for key in tuple_items.keys():
                   # Split the key and get the attr
                   k=split(key, ".")
                   k,attr=join(k[:-1], "."), k[-1]
                   a = attr
                   # remove any type_names in the attr
                   while not a=='':
                      a=split(a, ":")
                      a,new=join(a[:-1], ":"), a[-1]
                   attr = new
                   if form.has_key(k):
                      # If the form has the split key get its value
                      item =form[k]
                      if (hasattr(item, '__class__') and
                          item.__class__ is record):
                         # if the value is mapped to a record, check if it
                         # has the attribute, if it has it, convert it to
                         # a tuple and set it
                         if hasattr(item,attr):
                            value=tuple(getattr(item,attr))
                            setattr(item,attr,value)
                      else:
                         # It is mapped to a list of  records
                         for x in item:
                            # loop through the records
                            if hasattr(x, attr):
                               # If the record has the attribute
                               # convert it to a tuple and set it
                               value=tuple(getattr(x,attr))
                               setattr(x,attr,value)          
                   else:
                      # the form does not have the split key 
                      if form.has_key(key):
                         # if it has the original key, get the item
                         # convert it to a tuple
                         item=form[key]  
                         item=tuple(form[key])
                         form[key]=item
                     
        other.update(form)
        if meth:
            if environ.has_key('PATH_INFO'):
                path=environ['PATH_INFO']
                while path[-1:]=='/': path=path[:-1]
            else: path=''
            other['PATH_INFO']=path="%s/%s" % (path,meth)
            self._hacked_path=1

    def get_header(self, name, default=None):
        """Return the named HTTP header, or an optional default
        argument or None if the header is not found. Note that
        both original and CGI-ified header names are recognized,
        e.g. 'Content-Type', 'CONTENT_TYPE' and 'HTTP_CONTENT_TYPE'
        should all return the Content-Type header, if available.
        """
        environ=self.environ
        name=upper(join(split(name,"-"),"_"))
        val=environ.get(name, None)
        if val is not None:
            return val
        if name[:5] != 'HTTP_':
            name='HTTP_%s' % name
        return environ.get(name, default)


    def __getitem__(self,key,
                    default=_marker, # Any special internal marker will do
                    URLmatch=regex.compile('URL[0-9]+$').match,
                    BASEmatch=regex.compile('BASE[0-9]+$').match,
                    ):
        """Get a variable value

        Return a value for the required variable name.
        The value will be looked up from one of the request data
        categories. The search order is environment variables,
        other variables, form data, and then cookies. 
        
        """ #"
        other=self.other
        if other.has_key(key):
            return other[key]

        if key[:1]=='U' and URLmatch(key) >= 0:
            n=atoi(key[3:])
            URL=other['URL']
            for i in range(0,n):
                l=rfind(URL,'/')
                if l >= 0: URL=URL[:l]
                else: raise KeyError, key
                if len(URL) < len(self.base) and n > 1: raise KeyError, key
            other[key]=URL
            self._urls = self._urls + (key,)
            return URL

        if isCGI_NAME(key) or key[:5] == 'HTTP_':
            environ=self.environ
            if environ.has_key(key) and (not hide_key(key)):
                return environ[key]
            return ''

        if key[:1]=='B':
            if BASEmatch(key) >= 0:
                n=atoi(key[4:])
                if n:
                    n=n-1
                    
                    if len(self.steps) < n:
                        raise KeyError, key

                    v=self.script
                    while v[-1:]=='/': v=v[:-1]
                    v=join([v]+self.steps[:n],'/')
                else:
                    v=self.base
                    while v[-1:]=='/': v=v[:-1]
                other[key]=v
                self._urls = self._urls + (key,)
                return v

            if key=='BODY' and self._file is not None:
                p=self._file.tell()
                self._file.seek(0)
                v=self._file.read()
                self._file.seek(p)
                self.other[key]=v
                return v

            if key=='BODYFILE' and self._file is not None:
                v=self._file
                self.other[key]=v
                return v

        if default is not _marker: return default

        raise KeyError, key

    __getattr__=__getitem__

    def get(self, key, default=None):
        return self.__getitem__(key, default)

    def has_key(self, key):
        try: self[key]
        except: return 0
        else: return 1

    def keys(self):
        keys = {}

        for key in self.environ.keys():
            if (isCGI_NAME(key) or key[:5] == 'HTTP_') and \
               (not hide_key(key)):
                    keys[key] = 1

        n=0
        while 1:
            n=n+1
            key = "URL%s" % n
            if not self.has_key(key): break

        n=0
        while 1:
            n=n+1
            key = "BASE%s" % n
            if not self.has_key(key): break

        keys.update(self.other)

        keys=keys.keys()
        keys.sort()

        return keys

    def dump_html(self):
        result="<h3>form</h3><table>"
        row='<tr valign="top" align="left"><th>%s</th><td>%s</td></tr>'
        for k,v in self.form.items():
            result=result + row % (html_quote(k), html_quote(v))
        result=result+"</table><h3>cookies</h3><table>"
        for k,v in self.cookies.items():
            result=result + row % (html_quote(k), html_quote(v))
        result=result+"</table><h3>other</h3><table>"
        for k,v in self.other.items():
            if k in ('PARENTS','RESPONSE'): continue
            result=result + row % (html_quote(k), html_quote(v))
    
        for n in "0123456789":
            key = "URL%s"%n
            try: result=result + row % (key, html_quote(self[key])) 
            except KeyError: pass
        for n in "0123456789":
            key = "BASE%s"%n
            try: result=result + row % (key, html_quote(self[key])) 
            except KeyError: pass

        result=result+"</table><h3>environ</h3><table>"
        for k,v in self.environ.items():
            if not hide_key(k):
                result=result + row % (html_quote(k), html_quote(v))
        return result+"</table>"

    def dump(self):
        result=""
        row='%-15s %s\n'
        L = self.form.items() ; L.sort()
        for k,v in L:
            result=result + row % (k,v)
        result=result+"\nCookies:\n"
        L = self.cookies.items() ; L.sort()
        for k,v in L:
            result=result + row % (k,v)
        result=result+"\nOther:\n"
        L = self.other.items() ; L.sort()
        for k,v in L:
            if k in ('PARENTS','RESPONSE'): continue
            result=result + row % (k,v)

        for n in "0123456789":
            key = "URL%s"%n
            try: result=result + row % (key,self[key])
            except KeyError: pass
        for n in "0123456789":
            key = "BASE%s"%n
            try: result=result + row % (key,self[key])
            except KeyError: pass

        result=result+"\nEnvironment:\n"
        L = self.environ.items() ; L.sort()
        for k,v in L:
            if not hide_key(k):
                result=result + row % (k,v)
        return result

    def browser_version (self):
        """Examine the User-agent request header to try to figure out
           what the current browser is.  Returns a (name,version) tuple
           where each element is a string or None if we couldn't parse the
           User-agent header.  Note that Netscape {Communicator,Navigator}
           and Internet Explorer advertise themselves as, respectively,
           "Mozilla" and "MSIE"."""

        ua = self.get_header ('user-agent')
        if ua is None:
            return (None, None)

        # Looks like Netscape...
        moz_match = re.search (r'(Mozilla)/([\w\.]+)', ua)
        if moz_match:
            # ...but smells like Microsoft
            ie_match = re.search (r'(MSIE)\s+([\w\.]+)', ua)
            if ie_match:
                return ie_match.group (1, 2)

            # nope, it's really Netscape
            else:
                return moz_match.group (1, 2)

        # neither Netscape or IE -- try a broader regex
        else:
            match = re.search (r'([^\s\/]+)/([\w\.]+)', ua)
            if match:
                return match.group (1, 2)
            else:
                return (None, None)

    # browser_version ()
    
    def _authUserPW(self):
        global base64
        auth=self._auth
        if auth:
            if lower(auth[:6]) == 'basic ':
                if base64 is None: import base64
                [name,password] = split(
                    base64.decodestring(split(auth)[-1]), ':')
                return name, password




base64=None


# This is duplicated from DocumentTemplate.DT_Util to
# prevent a dependency on the DocumentTemplate package.
# Some folks still use the ZPublisher package as a
# standalone publisher without DocumentTemplate.
def html_quote(value, character_entities=(
                      (('&'), '&amp;'),
                      (("<"), '&lt;' ),
                      ((">"), '&gt;' ),
                      (('"'), '&quot;'))): #"
        text=str(value)
        for re, name in character_entities:
            if find(text, re) >= 0: text=join(split(text, re), name)
        return text


def str_field(v):
    if type(v) is ListType:
        return map(str_field,v)

    if hasattr(v,'__class__') and v.__class__ is FieldStorage:
        v=v.value
    elif type(v) is not StringType:
        if hasattr(v,'file') and v.file: v=v.file
        elif hasattr(v,'value'): v=v.value
    return v


class FileUpload:
    '''\
    File upload objects

    File upload objects are used to represent file-uploaded data.

    File upload objects can be used just like files.

    In addition, they have a 'headers' attribute that is a dictionary
    containing the file-upload headers, and a 'filename' attribute
    containing the name of the uploaded file.
    '''

    def __init__(self, aFieldStorage):

        file=aFieldStorage.file
        if hasattr(file, '__methods__'): methods=file.__methods__
        else: methods= ['close', 'fileno', 'flush', 'isatty',
                        'read', 'readline', 'readlines', 'seek',
                        'tell', 'truncate', 'write', 'writelines']

        d=self.__dict__
        for m in methods:
            if hasattr(file,m): d[m]=getattr(file,m)

        self.headers=aFieldStorage.headers
        self.filename=aFieldStorage.filename
    

def parse_cookie(text,
                 result=None,
                 qparmre=regex.compile(
                     '\([\0- ]*'
                     '\([^\0- ;,=\"]+\)="\([^"]*\)\"'
                     '\([\0- ]*[;,]\)?[\0- ]*\)'
                     ),
                 parmre=regex.compile(
                     '\([\0- ]*'
                     '\([^\0- ;,=\"]+\)=\([^\0- ;,\"]*\)'
                     '\([\0- ]*[;,]\)?[\0- ]*\)'
                     ),
                 ):

    if result is None: result={}
    already_have=result.has_key

    try:
        if qparmre.match(text) >= 0:
            # Match quoted correct cookies
            name=qparmre.group(2)
            value=qparmre.group(3)
            l=len(qparmre.group(1))
        elif parmre.match(text) >= 0:
            # Match evil MSIE cookies ;)
            name=parmre.group(2)
            value=parmre.group(3)
            l=len(parmre.group(1))
        else:
            # this may be an invalid cookie.
            # We'll simply bail without raising an error
            # if the cookie is invalid.
            return result
            
    finally: pass

    if not already_have(name): result[name]=value

    return apply(parse_cookie,(text[l:],result))

import sys

# add class
class record:
    def __getattr__(self, key, default=None):
        if key in ('get', 'keys', 'items', 'values', 'copy', 'has_key'):
            return getattr(self.__dict__, key)
        raise AttributeError, key

    def __getitem__(self, key):
        return self.__dict__[key]
            
    def __str__(self):
        L1 = self.__dict__.items()
        L1.sort()
        return join(map(lambda item: "%s: %s" %item, L1), ", ") 

    __repr__ = __str__

# Flags
SEQUENCE=1
DEFAULT=2
RECORD=4
RECORDS=8
REC=RECORD|RECORDS
EMPTY=16
CONVERTED=32

