Commits

Anonymous committed d1a7bbe

converted \r\n to \n in all the .conf, .txt and .py files. '''use tools/linefeed.py before committing something''' No other change has been made.

Comments (0)

Files changed (21)

cherrypy/__init__.py

-__version__ = '2.0.0b'
+__version__ = '2.0.0b'

cherrypy/_cphttptools.py

-"""
-Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-    * 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.
-    * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 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 THE COPYRIGHT OWNER OR 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.
-"""
-
-import cpg, urllib, sys, time, traceback, types, StringIO, cgi, os
-import mimetypes, sha, random, string, _cputil, cperror, Cookie
-from lib.filter import basefilter
-
-"""
-Common Service Code for CherryPy
-"""
-
-mimetypes.types_map['.dwg']='image/x-dwg'
-mimetypes.types_map['.ico']='image/x-icon'
-
-weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
-monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
-
-class IndexRedirect(Exception): pass
-
-def parseFirstLine(data):
-    cpg.request.path = data.split()[1]
-    cpg.request.queryString = ""
-    cpg.request.browserUrl = cpg.request.path
-    cpg.request.paramMap = {}
-    cpg.request.paramList = [] # Only used for Xml-Rpc
-    cpg.request.filenameMap = {}
-    cpg.request.fileTypeMap = {}
-    i = cpg.request.path.find('?')
-    if i != -1:
-        # Parse parameters from URL
-        if cpg.request.path[i+1:]:
-            k = cpg.request.path[i+1:].find('?')
-            if k != -1:
-                j = cpg.request.path[:k].rfind('=')
-                if j != -1:
-                    cpg.request.path = cpg.request.path[:j+1] + \
-                        urllib.quote_plus(cpg.request.path[j+1:])
-            for paramStr in cpg.request.path[i+1:].split('&'):
-                sp = paramStr.split('=')
-                if len(sp) > 2:
-                    j = paramStr.find('=')
-                    sp = (paramStr[:j], paramStr[j+1:])
-                if len(sp) == 2:
-                    key, value = sp
-                    value = urllib.unquote_plus(value)
-                    if cpg.request.paramMap.has_key(key):
-                        # Already has a value: make a list out of it
-                        if type(cpg.request.paramMap[key]) == type([]):
-                            # Already is a list: append the new value to it
-                            cpg.request.paramMap[key].append(value)
-                        else:
-                            # Only had one value so far: start a list
-                            cpg.request.paramMap[key] = [cpg.request.paramMap[key], value]
-                    else:
-                        cpg.request.paramMap[key] = value
-        cpg.request.queryString = cpg.request.path[i+1:]
-        cpg.request.path = cpg.request.path[:i]
-
-def cookHeaders(clientAddress, remoteHost, headers, requestLine):
-    """Process the headers into the request.headerMap"""
-    cpg.request.headerMap = {}
-    cpg.request.requestLine = requestLine
-    cpg.request.simpleCookie = Cookie.SimpleCookie()
-
-    # Build headerMap
-    for item in headers.items():
-        # Warning: if there is more than one header entry for cookies (AFAIK, only Konqueror does that)
-        # only the last one will remain in headerMap (but they will be correctly stored in request.simpleCookie)
-        insertIntoHeaderMap(item[0],item[1])
-
-    # Handle cookies differently because on Konqueror, multiple cookies come on different lines with the same key
-    cookieList = headers.getallmatchingheaders('cookie')
-    for cookie in cookieList:
-        cpg.request.simpleCookie.load(cookie)
-
-    cpg.request.remoteAddr = clientAddress
-    cpg.request.remoteHost = remoteHost
-
-    # Set peer_certificate (in SSL mode) so the web app can examinate the client certificate
-    try: cpg.request.peerCertificate = self.request.get_peer_certificate()
-    except: pass
-
-    _cputil.getSpecialFunction('_cpLogMessage')("%s - %s" % (cpg.request.remoteAddr, requestLine[:-2]), "HTTP")
-
-
-def parsePostData(rfile):
-    # Read request body and put it in data
-    len = int(cpg.request.headerMap.get("Content-Length","0"))
-    if len: data = rfile.read(len)
-    else: data=""
-
-    # Put data in a StringIO so FieldStorage can read it
-    newRfile = StringIO.StringIO(data)
-    # Create a copy of headerMap with lowercase keys because
-    #   FieldStorage doesn't work otherwise
-    lowerHeaderMap = {}
-    for key, value in cpg.request.headerMap.items():
-        lowerHeaderMap[key.lower()] = value
-    forms = cgi.FieldStorage(fp = newRfile, headers = lowerHeaderMap, environ = {'REQUEST_METHOD':'POST'}, keep_blank_values = 1)
-    for key in forms.keys():
-        # Check if it's a list or not
-        valueList = forms[key]
-        if type(valueList) == type([]):
-            # It's a list of values
-            cpg.request.paramMap[key] = []
-            cpg.request.filenameMap[key] = []
-            cpg.request.fileTypeMap[key] = []
-            for item in valueList:
-                cpg.request.paramMap[key].append(item.value)
-                cpg.request.filenameMap[key].append(item.filename)
-                cpg.request.fileTypeMap[key].append(item.type)
-        else:
-            # It's a single value
-            # In case it's a file being uploaded, we save the filename in a map (user might need it)
-            cpg.request.paramMap[key] = valueList.value
-            cpg.request.filenameMap[key] = valueList.filename
-            cpg.request.fileTypeMap[key] = valueList.type
-
-def applyFilterList(methodName):
-    try:
-        filterList = _cputil.getSpecialFunction('_cpFilterList')
-        for filter in filterList:
-            method = getattr(filter, methodName, None)
-            if method:
-                method()
-    except basefilter.InternalRedirect:
-        # If we get an InternalRedirect, we start the filter list  
-        #   from scratch. Is cpg.request.path or cpg.request.objectPath
-        #   has been modified by the hook, then a new filter list
-        #   will be applied.  
-        # We use recursion so if there is an infinite loop, we'll  
-        #   get the regular python "recursion limit exceeded" exception.  
-        applyFilterList(methodName) 
-
-
-def insertIntoHeaderMap(key,value):
-    normalizedKey = '-'.join([s.capitalize() for s in key.split('-')])
-    cpg.request.headerMap[normalizedKey] = value
-
-def initRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile):
-    parseFirstLine(requestLine)
-    cookHeaders(clientAddress, remoteHost, headers, requestLine)
-
-    cpg.request.base = "http://" + cpg.request.headerMap['Host']
-    cpg.request.browserUrl = cpg.request.base + cpg.request.browserUrl
-    cpg.request.isStatic = False
-    cpg.request.parsePostData = True
-    cpg.request.rfile = rfile
-
-    # Change objectPath in filters to change the object that will get rendered
-    cpg.request.objectPath = None 
-
-    applyFilterList('afterRequestHeader')
-
-    if cpg.request.method == 'POST' and cpg.request.parsePostData:
-        parsePostData(rfile)
-
-    applyFilterList('afterRequestBody')
-
-def doRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile):
-    initRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile)
-
-    # Prepare response variables
-    now = time.time()
-    year, month, day, hh, mm, ss, wd, y, z = time.gmtime(now)
-    date = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (weekdayname[wd], day, monthname[month], year, hh, mm, ss)
-    cpg.response.headerMap = {
-        "protocolVersion": cpg.configOption.protocolVersion,
-        "Status": "200 OK",
-        "Content-Type": "text/html",
-        "Server": "CherryPy/" + cpg.__version__,
-        "Date": date,
-        "Set-Cookie": [],
-        "Content-Length": 0
-    }
-    cpg.response.simpleCookie = Cookie.SimpleCookie()
-    cpg.response.wfile = wfile
-    cpg.response.sendResponse = 1
-
-    try:
-        handleRequest(wfile)
-    except:
-        err = ""
-        exc_info_1 = sys.exc_info()[1]
-        if hasattr(exc_info_1, 'args') and len(exc_info_1.args) >= 1:
-            err = exc_info_1.args[0]
-
-        try:
-            _cputil.getSpecialFunction('_cpOnError')()
-
-            # Still save session data
-            if cpg.configOption.sessionStorageType and not cpg.request.isStatic:
-                sessionId = cpg.response.simpleCookie[cpg.configOption.sessionCookieName].value
-                expirationTime = time.time() + cpg.configOption.sessionTimeout * 60
-                _cputil.getSpecialFunction('_cpSaveSessionData')(sessionId, cpg.request.sessionMap, expirationTime)
-
-            wfile.write('%s %s\r\n' % (cpg.response.headerMap['protocolVersion'], cpg.response.headerMap['Status']))
-
-            if (cpg.response.headerMap.has_key('Content-Length') and
-                    cpg.response.headerMap['Content-Length']==0):
-  	 	        buf = StringIO.StringIO()
-  	 	        [buf.write(x) for x in cpg.response.body]
-  	 	        buf.seek(0)
-  	 	        cpg.response.body = [buf.read()]
-  	 	        cpg.response.headerMap['Content-Length'] = len(cpg.response.body[0])
-
-            for key, valueList in cpg.response.headerMap.items():
-                if key not in ('Status', 'protocolVersion'):
-                    if type(valueList) != type([]): valueList = [valueList]
-                    for value in valueList:
-                        wfile.write('%s: %s\r\n'%(key, value))
-            wfile.write('\r\n')
-            for line in cpg.response.body:
-                wfile.write(line)
-        except:
-            bodyFile = StringIO.StringIO()
-            traceback.print_exc(file = bodyFile)
-            body = bodyFile.getvalue()
-            wfile.write('%s 200 OK\r\n' % cpg.configOption.protocolVersion)
-            wfile.write('Content-Type: text/plain\r\n')
-            wfile.write('Content-Length: %s\r\n' % len(body))
-            wfile.write('\r\n')
-            wfile.write(body)
-
-def sendResponse(wfile):
-    applyFilterList('beforeResponse')
-
-    # Save session data
-    if cpg.configOption.sessionStorageType and not cpg.request.isStatic:
-        sessionId = cpg.response.simpleCookie[cpg.configOption.sessionCookieName].value
-        expirationTime = time.time() + cpg.configOption.sessionTimeout * 60
-        _cputil.getSpecialFunction('_cpSaveSessionData')(sessionId, cpg.request.sessionMap, expirationTime)
-
-    # Set the content-length
-    if (cpg.response.headerMap.has_key('Content-Length') and
-            cpg.response.headerMap['Content-Length']==0):
-        buf = StringIO.StringIO()
-        [buf.write(x) for x in cpg.response.body]
-        buf.seek(0)
-        cpg.response.body = [buf.read()]
-        cpg.response.headerMap['Content-Length'] = len(cpg.response.body[0])
-
-    wfile.write('%s %s\r\n' % (cpg.response.headerMap['protocolVersion'], cpg.response.headerMap['Status']))
-    for key, valueList in cpg.response.headerMap.items():
-        if key not in ('Status', 'protocolVersion'):
-            if type(valueList) != type([]): valueList = [valueList]
-            for value in valueList:
-                wfile.write('%s: %s\r\n'%(key, value))
-
-    # Send response cookies
-    cookie = cpg.response.simpleCookie.output()
-    if cookie:
-        wfile.write(cookie+'\r\n')
-    wfile.write('\r\n')
-
-    for line in cpg.response.body:
-        wfile.write(line)
-    
-    # finalization hook for filter cleanup & logging purposes
-    applyFilterList('afterResponse')
-
-def handleRequest(wfile):
-    # Clean up expired sessions if needed:
-    now = time.time()
-    if cpg.configOption.sessionStorageType and cpg.configOption.sessionCleanUpDelay and cpg._lastSessionCleanUpTime + cpg.configOption.sessionCleanUpDelay * 60 <= now:
-        cpg._lastSessionCleanUpTime = now
-        _cputil.getSpecialFunction('_cpCleanUpOldSessions')()
-
-    # Save original values (in case they get modified by filters)
-    cpg.request.originalPath = cpg.request.path
-    cpg.request.originalParamMap = cpg.request.paramMap
-    cpg.request.originalParamList = cpg.request.paramList
-
-    path = cpg.request.path
-    if path.startswith('/'): path = path[1:] # Remove leading slash
-    if path.endswith('/'): path = path[:-1] # Remove trailing slash
-
-    # Handle static directories
-    for urlDir, fsDir in cpg.configOption.staticContentList:
-        if path == urlDir or path[:len(urlDir)+1]==urlDir+'/':
-
-            cpg.request.isStatic = 1
-
-            fname = fsDir + path[len(urlDir):]
-            try:
-                stat = os.stat(fname)
-            except OSError:
-                raise cperror.NotFound
-            if type(stat) == type(()): # Python2.1
-                modifTime = stat[9]
-            else:
-                modifTime = stat.st_mtime
-
-            strModifTime = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(modifTime))
-
-            # Check if browser sent "if-modified-since" in request header
-            if cpg.request.headerMap.has_key('If-Modified-Since'):
-                # Check if if-modified-since date is the same as strModifTime
-                if cpg.request.headerMap['If-Modified-Since'] == strModifTime:
-                    cpg.response.headerMap = {'Status': 304, 'protocolVersion': cpg.configOption.protocolVersion, 'Date': cpg.response.headerMap['Date']}
-                    cpg.response.body = ''
-                    sendResponse(wfile)
-                    return
-
-            cpg.response.headerMap['Last-Modified'] = strModifTime
-            f=open(fname, 'rb')
-            cpg.response.body = f.read()
-            f.close()
-            # Set content-type based on filename extension
-            i = path.rfind('.')
-            if i != -1: ext = path[i:]
-            else: ext = ""
-            contentType = mimetypes.types_map.get(ext, "text/plain")
-            cpg.response.headerMap['Content-Type'] = contentType
-            sendResponse(wfile)
-            return
-
-    # Get session data
-    if cpg.configOption.sessionStorageType and not cpg.request.isStatic:
-        now = time.time()
-        # First, get sessionId from cookie
-        try: sessionId = cpg.request.simpleCookie[cpg.configOption.sessionCookieName].value
-        except: sessionId=None
-        if sessionId:
-            # Load session data from wherever it was stored
-            sessionData = _cputil.getSpecialFunction('_cpLoadSessionData')(sessionId)
-            if sessionData == None:
-                sessionId = None
-            else:
-                cpg.request.sessionMap, expirationTime = sessionData
-                # Check that is hasn't expired
-                if now > expirationTime:
-                    # Session expired
-                    sessionId = None
-
-        # Create a new sessionId if needed
-        if not sessionId:
-            cpg.request.sessionMap = {}
-            sessionId = generateSessionId()
-            cpg.request.sessionMap['_sessionId'] = sessionId
-
-        cpg.response.simpleCookie[cpg.configOption.sessionCookieName] = sessionId
-        cpg.response.simpleCookie[cpg.configOption.sessionCookieName]['path'] = '/'
-        cpg.response.simpleCookie[cpg.configOption.sessionCookieName]['version'] = 1
-
-    try:
-        func, objectPathList, virtualPathList = mapPathToObject()
-    except IndexRedirect, inst:
-        # For an IndexRedirect, we don't go through the regular
-        #   mechanism: we return the redirect immediately
-        newUrl = canonicalizeUrl(inst.args[0])
-        wfile.write('%s 302\r\n' % (cpg.response.headerMap['protocolVersion']))
-        cpg.response.headerMap['Location'] = newUrl
-        for key, valueList in cpg.response.headerMap.items():
-            if key not in ('Status', 'protocolVersion'):
-                if type(valueList) != type([]): valueList = [valueList]
-                for value in valueList:
-                    wfile.write('%s: %s\r\n'%(key, value))
-        wfile.write('\r\n')
-        return
-         
-    # Remove "root" from objectPathList and join it to get objectPath
-    cpg.request.objectPath = '/' + '/'.join(objectPathList[1:])
-    body = func(*(virtualPathList + cpg.request.paramList), **(cpg.request.paramMap))
-    
-    # builds a uniform return type
-    if not isinstance(body, types.GeneratorType):
-        cpg.response.body = [body]
-    else:
-        cpg.response.body = body
-
-    if cpg.response.sendResponse:
-        sendResponse(wfile)
-
-def generateSessionId():
-    s = ''
-    for i in range(50):
-        s += random.choice(string.letters+string.digits)
-    s += '%s'%time.time()
-    return sha.sha(s).hexdigest()
-
-def getObjFromPath(objPathList, objCache):
-    """ For a given objectPathList (like ['root', 'a', 'b', 'index']),
-         return the object (or None if it doesn't exist).
-         Also keep a cache for maximum efficiency
-    """
-    # Let cpg be the first valid object.
-    validObjects = ["cpg"]
-     
-    # Scan the objPathList in order from left to right
-    for index, obj in enumerate(objPathList):
-        # currentObjStr holds something like 'cpg.root.something.else'
-        currentObjStr = ".".join(validObjects)
-
-        #---------------
-        #   Cache check
-        #---------------
-        # Generate a cacheKey from the first 'index' elements of objPathList
-        cacheKey = tuple(objPathList[:index+1])
-        # Is this cacheKey in the objCache?
-        if cacheKey in objCache: 
-            # And is its value not None?
-            if objCache[cacheKey]:
-                # Yes, then add it to the list of validObjects
-                validObjects.append(obj)
-                # OK, go to the next iteration
-                continue
-            # Its value is None, so we stop
-            # (This means it is not a valid object)
-            break
-        
-        #-----------------
-        # Attribute check
-        #-----------------
-        if getattr(eval(currentObjStr), obj, None):
-            #  obj is a valid attribute of the current object
-            validObjects.append(obj)
-            #  Store it in the cache
-            objCache[cacheKey] = eval(".".join(validObjects))
-        else:
-            # obj is not a valid attribute
-            # Store None in the cache
-            objCache[cacheKey] = None
-            # Stop, we won't process the remaining objPathList
-            break
-
-    # Return the last cached object (even if its None)
-    return objCache[cacheKey]
-
-def mapPathToObject(path = None):
-    # Traverse path:
-    # for /a/b?arg=val, we'll try:
-    #   root.a.b.index -> redirect to /a/b/?arg=val
-    #   root.a.b.default(arg='val') -> redirect to /a/b/?arg=val
-    #   root.a.b(arg='val')
-    #   root.a.default('b', arg='val')
-    #   root.default('a', 'b', arg='val')
-
-    # Also, we ignore trailing slashes
-    # Also, a method has to have ".exposed = True" in order to be exposed
-
-    if path is None:
-        path = cpg.request.objectPath or cpg.request.path
-    if path.startswith('/'): path = path[1:] # Remove leading slash
-    if path.endswith('/'): path = path[:-1] # Remove trailing slash
-
-    if not path:
-        objectPathList = []
-    else:
-        objectPathList = path.split('/')
-    objectPathList = ['root'] + objectPathList + ['index']
-
-    # Try successive objects... (and also keep the remaining object list)
-    objCache = {}
-    isFirst = True
-    isDefault = False
-    foundIt = False
-    virtualPathList = []
-    while objectPathList:
-        candidate = getObjFromPath(objectPathList, objCache)
-        if callable(candidate) and getattr(candidate, 'exposed', False):
-            foundIt = True
-            break
-        # Couldn't find the object: pop one from the list and try "default"
-        lastObj = objectPathList.pop()
-        if not isFirst:
-            virtualPathList.insert(0, lastObj)
-            objectPathList.append('default')
-            candidate = getObjFromPath(objectPathList, objCache)
-            if callable(candidate) and getattr(candidate, 'exposed', False):
-                foundIt = True
-                isDefault = True
-                break
-            objectPathList.pop() # Remove "default"
-        isFirst = False
-
-    # Check results of traversal
-    if not foundIt:
-        raise cperror.NotFound # We didn't find anything
-
-    if isFirst:
-        # We found the extra ".index"
-        # Check if the original path had a trailing slash (otherwise, do
-        #   a redirect)
-        if cpg.request.path[-1] != '/':
-            newUrl = cpg.request.path + '/'
-            if cpg.request.queryString: newUrl += cpg.request.queryString
-            raise IndexRedirect(newUrl)
-
-    return candidate, objectPathList, virtualPathList
-    
-def canonicalizeUrl(newUrl):
-    if not newUrl.startswith('http://') and not newUrl.startswith('https://'):
-        # If newUrl is not canonical, we must make it canonical
-        if newUrl[0] == '/':
-            # URL was absolute: we just add the request.base in front of it
-            newUrl = cpg.request.base + newUrl
-        else:
-            # URL was relative
-            if cpg.request.browserUrl == cpg.request.base:
-                # browserUrl is request.base
-                newUrl = cpg.request.base + '/' + newUrl
-            else:
-                newUrl = cpg.request.browserUrl[:i+1] + newUrl
-    return newUrl
+"""
+Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+    * 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.
+    * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 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 THE COPYRIGHT OWNER OR 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.
+"""
+
+import cpg, urllib, sys, time, traceback, types, StringIO, cgi, os
+import mimetypes, sha, random, string, _cputil, cperror, Cookie
+from lib.filter import basefilter
+
+"""
+Common Service Code for CherryPy
+"""
+
+mimetypes.types_map['.dwg']='image/x-dwg'
+mimetypes.types_map['.ico']='image/x-icon'
+
+weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+
+class IndexRedirect(Exception): pass
+
+def parseFirstLine(data):
+    cpg.request.path = data.split()[1]
+    cpg.request.queryString = ""
+    cpg.request.browserUrl = cpg.request.path
+    cpg.request.paramMap = {}
+    cpg.request.paramList = [] # Only used for Xml-Rpc
+    cpg.request.filenameMap = {}
+    cpg.request.fileTypeMap = {}
+    i = cpg.request.path.find('?')
+    if i != -1:
+        # Parse parameters from URL
+        if cpg.request.path[i+1:]:
+            k = cpg.request.path[i+1:].find('?')
+            if k != -1:
+                j = cpg.request.path[:k].rfind('=')
+                if j != -1:
+                    cpg.request.path = cpg.request.path[:j+1] + \
+                        urllib.quote_plus(cpg.request.path[j+1:])
+            for paramStr in cpg.request.path[i+1:].split('&'):
+                sp = paramStr.split('=')
+                if len(sp) > 2:
+                    j = paramStr.find('=')
+                    sp = (paramStr[:j], paramStr[j+1:])
+                if len(sp) == 2:
+                    key, value = sp
+                    value = urllib.unquote_plus(value)
+                    if cpg.request.paramMap.has_key(key):
+                        # Already has a value: make a list out of it
+                        if type(cpg.request.paramMap[key]) == type([]):
+                            # Already is a list: append the new value to it
+                            cpg.request.paramMap[key].append(value)
+                        else:
+                            # Only had one value so far: start a list
+                            cpg.request.paramMap[key] = [cpg.request.paramMap[key], value]
+                    else:
+                        cpg.request.paramMap[key] = value
+        cpg.request.queryString = cpg.request.path[i+1:]
+        cpg.request.path = cpg.request.path[:i]
+
+def cookHeaders(clientAddress, remoteHost, headers, requestLine):
+    """Process the headers into the request.headerMap"""
+    cpg.request.headerMap = {}
+    cpg.request.requestLine = requestLine
+    cpg.request.simpleCookie = Cookie.SimpleCookie()
+
+    # Build headerMap
+    for item in headers.items():
+        # Warning: if there is more than one header entry for cookies (AFAIK, only Konqueror does that)
+        # only the last one will remain in headerMap (but they will be correctly stored in request.simpleCookie)
+        insertIntoHeaderMap(item[0],item[1])
+
+    # Handle cookies differently because on Konqueror, multiple cookies come on different lines with the same key
+    cookieList = headers.getallmatchingheaders('cookie')
+    for cookie in cookieList:
+        cpg.request.simpleCookie.load(cookie)
+
+    cpg.request.remoteAddr = clientAddress
+    cpg.request.remoteHost = remoteHost
+
+    # Set peer_certificate (in SSL mode) so the web app can examinate the client certificate
+    try: cpg.request.peerCertificate = self.request.get_peer_certificate()
+    except: pass
+
+    _cputil.getSpecialFunction('_cpLogMessage')("%s - %s" % (cpg.request.remoteAddr, requestLine[:-2]), "HTTP")
+
+
+def parsePostData(rfile):
+    # Read request body and put it in data
+    len = int(cpg.request.headerMap.get("Content-Length","0"))
+    if len: data = rfile.read(len)
+    else: data=""
+
+    # Put data in a StringIO so FieldStorage can read it
+    newRfile = StringIO.StringIO(data)
+    # Create a copy of headerMap with lowercase keys because
+    #   FieldStorage doesn't work otherwise
+    lowerHeaderMap = {}
+    for key, value in cpg.request.headerMap.items():
+        lowerHeaderMap[key.lower()] = value
+    forms = cgi.FieldStorage(fp = newRfile, headers = lowerHeaderMap, environ = {'REQUEST_METHOD':'POST'}, keep_blank_values = 1)
+    for key in forms.keys():
+        # Check if it's a list or not
+        valueList = forms[key]
+        if type(valueList) == type([]):
+            # It's a list of values
+            cpg.request.paramMap[key] = []
+            cpg.request.filenameMap[key] = []
+            cpg.request.fileTypeMap[key] = []
+            for item in valueList:
+                cpg.request.paramMap[key].append(item.value)
+                cpg.request.filenameMap[key].append(item.filename)
+                cpg.request.fileTypeMap[key].append(item.type)
+        else:
+            # It's a single value
+            # In case it's a file being uploaded, we save the filename in a map (user might need it)
+            cpg.request.paramMap[key] = valueList.value
+            cpg.request.filenameMap[key] = valueList.filename
+            cpg.request.fileTypeMap[key] = valueList.type
+
+def applyFilterList(methodName):
+    try:
+        filterList = _cputil.getSpecialFunction('_cpFilterList')
+        for filter in filterList:
+            method = getattr(filter, methodName, None)
+            if method:
+                method()
+    except basefilter.InternalRedirect:
+        # If we get an InternalRedirect, we start the filter list  
+        #   from scratch. Is cpg.request.path or cpg.request.objectPath
+        #   has been modified by the hook, then a new filter list
+        #   will be applied.  
+        # We use recursion so if there is an infinite loop, we'll  
+        #   get the regular python "recursion limit exceeded" exception.  
+        applyFilterList(methodName) 
+
+
+def insertIntoHeaderMap(key,value):
+    normalizedKey = '-'.join([s.capitalize() for s in key.split('-')])
+    cpg.request.headerMap[normalizedKey] = value
+
+def initRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile):
+    parseFirstLine(requestLine)
+    cookHeaders(clientAddress, remoteHost, headers, requestLine)
+
+    cpg.request.base = "http://" + cpg.request.headerMap['Host']
+    cpg.request.browserUrl = cpg.request.base + cpg.request.browserUrl
+    cpg.request.isStatic = False
+    cpg.request.parsePostData = True
+    cpg.request.rfile = rfile
+
+    # Change objectPath in filters to change the object that will get rendered
+    cpg.request.objectPath = None 
+
+    applyFilterList('afterRequestHeader')
+
+    if cpg.request.method == 'POST' and cpg.request.parsePostData:
+        parsePostData(rfile)
+
+    applyFilterList('afterRequestBody')
+
+def doRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile):
+    initRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile)
+
+    # Prepare response variables
+    now = time.time()
+    year, month, day, hh, mm, ss, wd, y, z = time.gmtime(now)
+    date = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (weekdayname[wd], day, monthname[month], year, hh, mm, ss)
+    cpg.response.headerMap = {
+        "protocolVersion": cpg.configOption.protocolVersion,
+        "Status": "200 OK",
+        "Content-Type": "text/html",
+        "Server": "CherryPy/" + cpg.__version__,
+        "Date": date,
+        "Set-Cookie": [],
+        "Content-Length": 0
+    }
+    cpg.response.simpleCookie = Cookie.SimpleCookie()
+    cpg.response.wfile = wfile
+    cpg.response.sendResponse = 1
+
+    try:
+        handleRequest(wfile)
+    except:
+        err = ""
+        exc_info_1 = sys.exc_info()[1]
+        if hasattr(exc_info_1, 'args') and len(exc_info_1.args) >= 1:
+            err = exc_info_1.args[0]
+
+        try:
+            _cputil.getSpecialFunction('_cpOnError')()
+
+            # Still save session data
+            if cpg.configOption.sessionStorageType and not cpg.request.isStatic:
+                sessionId = cpg.response.simpleCookie[cpg.configOption.sessionCookieName].value
+                expirationTime = time.time() + cpg.configOption.sessionTimeout * 60
+                _cputil.getSpecialFunction('_cpSaveSessionData')(sessionId, cpg.request.sessionMap, expirationTime)
+
+            wfile.write('%s %s\r\n' % (cpg.response.headerMap['protocolVersion'], cpg.response.headerMap['Status']))
+
+            if (cpg.response.headerMap.has_key('Content-Length') and
+                    cpg.response.headerMap['Content-Length']==0):
+  	 	        buf = StringIO.StringIO()
+  	 	        [buf.write(x) for x in cpg.response.body]
+  	 	        buf.seek(0)
+  	 	        cpg.response.body = [buf.read()]
+  	 	        cpg.response.headerMap['Content-Length'] = len(cpg.response.body[0])
+
+            for key, valueList in cpg.response.headerMap.items():
+                if key not in ('Status', 'protocolVersion'):
+                    if type(valueList) != type([]): valueList = [valueList]
+                    for value in valueList:
+                        wfile.write('%s: %s\r\n'%(key, value))
+            wfile.write('\r\n')
+            for line in cpg.response.body:
+                wfile.write(line)
+        except:
+            bodyFile = StringIO.StringIO()
+            traceback.print_exc(file = bodyFile)
+            body = bodyFile.getvalue()
+            wfile.write('%s 200 OK\r\n' % cpg.configOption.protocolVersion)
+            wfile.write('Content-Type: text/plain\r\n')
+            wfile.write('Content-Length: %s\r\n' % len(body))
+            wfile.write('\r\n')
+            wfile.write(body)
+
+def sendResponse(wfile):
+    applyFilterList('beforeResponse')
+
+    # Save session data
+    if cpg.configOption.sessionStorageType and not cpg.request.isStatic:
+        sessionId = cpg.response.simpleCookie[cpg.configOption.sessionCookieName].value
+        expirationTime = time.time() + cpg.configOption.sessionTimeout * 60
+        _cputil.getSpecialFunction('_cpSaveSessionData')(sessionId, cpg.request.sessionMap, expirationTime)
+
+    # Set the content-length
+    if (cpg.response.headerMap.has_key('Content-Length') and
+            cpg.response.headerMap['Content-Length']==0):
+        buf = StringIO.StringIO()
+        [buf.write(x) for x in cpg.response.body]
+        buf.seek(0)
+        cpg.response.body = [buf.read()]
+        cpg.response.headerMap['Content-Length'] = len(cpg.response.body[0])
+
+    wfile.write('%s %s\r\n' % (cpg.response.headerMap['protocolVersion'], cpg.response.headerMap['Status']))
+    for key, valueList in cpg.response.headerMap.items():
+        if key not in ('Status', 'protocolVersion'):
+            if type(valueList) != type([]): valueList = [valueList]
+            for value in valueList:
+                wfile.write('%s: %s\r\n'%(key, value))
+
+    # Send response cookies
+    cookie = cpg.response.simpleCookie.output()
+    if cookie:
+        wfile.write(cookie+'\r\n')
+    wfile.write('\r\n')
+
+    for line in cpg.response.body:
+        wfile.write(line)
+    
+    # finalization hook for filter cleanup & logging purposes
+    applyFilterList('afterResponse')
+
+def handleRequest(wfile):
+    # Clean up expired sessions if needed:
+    now = time.time()
+    if cpg.configOption.sessionStorageType and cpg.configOption.sessionCleanUpDelay and cpg._lastSessionCleanUpTime + cpg.configOption.sessionCleanUpDelay * 60 <= now:
+        cpg._lastSessionCleanUpTime = now
+        _cputil.getSpecialFunction('_cpCleanUpOldSessions')()
+
+    # Save original values (in case they get modified by filters)
+    cpg.request.originalPath = cpg.request.path
+    cpg.request.originalParamMap = cpg.request.paramMap
+    cpg.request.originalParamList = cpg.request.paramList
+
+    path = cpg.request.path
+    if path.startswith('/'): path = path[1:] # Remove leading slash
+    if path.endswith('/'): path = path[:-1] # Remove trailing slash
+
+    # Handle static directories
+    for urlDir, fsDir in cpg.configOption.staticContentList:
+        if path == urlDir or path[:len(urlDir)+1]==urlDir+'/':
+
+            cpg.request.isStatic = 1
+
+            fname = fsDir + path[len(urlDir):]
+            try:
+                stat = os.stat(fname)
+            except OSError:
+                raise cperror.NotFound
+            if type(stat) == type(()): # Python2.1
+                modifTime = stat[9]
+            else:
+                modifTime = stat.st_mtime
+
+            strModifTime = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(modifTime))
+
+            # Check if browser sent "if-modified-since" in request header
+            if cpg.request.headerMap.has_key('If-Modified-Since'):
+                # Check if if-modified-since date is the same as strModifTime
+                if cpg.request.headerMap['If-Modified-Since'] == strModifTime:
+                    cpg.response.headerMap = {'Status': 304, 'protocolVersion': cpg.configOption.protocolVersion, 'Date': cpg.response.headerMap['Date']}
+                    cpg.response.body = ''
+                    sendResponse(wfile)
+                    return
+
+            cpg.response.headerMap['Last-Modified'] = strModifTime
+            f=open(fname, 'rb')
+            cpg.response.body = f.read()
+            f.close()
+            # Set content-type based on filename extension
+            i = path.rfind('.')
+            if i != -1: ext = path[i:]
+            else: ext = ""
+            contentType = mimetypes.types_map.get(ext, "text/plain")
+            cpg.response.headerMap['Content-Type'] = contentType
+            sendResponse(wfile)
+            return
+
+    # Get session data
+    if cpg.configOption.sessionStorageType and not cpg.request.isStatic:
+        now = time.time()
+        # First, get sessionId from cookie
+        try: sessionId = cpg.request.simpleCookie[cpg.configOption.sessionCookieName].value
+        except: sessionId=None
+        if sessionId:
+            # Load session data from wherever it was stored
+            sessionData = _cputil.getSpecialFunction('_cpLoadSessionData')(sessionId)
+            if sessionData == None:
+                sessionId = None
+            else:
+                cpg.request.sessionMap, expirationTime = sessionData
+                # Check that is hasn't expired
+                if now > expirationTime:
+                    # Session expired
+                    sessionId = None
+
+        # Create a new sessionId if needed
+        if not sessionId:
+            cpg.request.sessionMap = {}
+            sessionId = generateSessionId()
+            cpg.request.sessionMap['_sessionId'] = sessionId
+
+        cpg.response.simpleCookie[cpg.configOption.sessionCookieName] = sessionId
+        cpg.response.simpleCookie[cpg.configOption.sessionCookieName]['path'] = '/'
+        cpg.response.simpleCookie[cpg.configOption.sessionCookieName]['version'] = 1
+
+    try:
+        func, objectPathList, virtualPathList = mapPathToObject()
+    except IndexRedirect, inst:
+        # For an IndexRedirect, we don't go through the regular
+        #   mechanism: we return the redirect immediately
+        newUrl = canonicalizeUrl(inst.args[0])
+        wfile.write('%s 302\r\n' % (cpg.response.headerMap['protocolVersion']))
+        cpg.response.headerMap['Location'] = newUrl
+        for key, valueList in cpg.response.headerMap.items():
+            if key not in ('Status', 'protocolVersion'):
+                if type(valueList) != type([]): valueList = [valueList]
+                for value in valueList:
+                    wfile.write('%s: %s\r\n'%(key, value))
+        wfile.write('\r\n')
+        return
+         
+    # Remove "root" from objectPathList and join it to get objectPath
+    cpg.request.objectPath = '/' + '/'.join(objectPathList[1:])
+    body = func(*(virtualPathList + cpg.request.paramList), **(cpg.request.paramMap))
+    
+    # builds a uniform return type
+    if not isinstance(body, types.GeneratorType):
+        cpg.response.body = [body]
+    else:
+        cpg.response.body = body
+
+    if cpg.response.sendResponse:
+        sendResponse(wfile)
+
+def generateSessionId():
+    s = ''
+    for i in range(50):
+        s += random.choice(string.letters+string.digits)
+    s += '%s'%time.time()
+    return sha.sha(s).hexdigest()
+
+def getObjFromPath(objPathList, objCache):
+    """ For a given objectPathList (like ['root', 'a', 'b', 'index']),
+         return the object (or None if it doesn't exist).
+         Also keep a cache for maximum efficiency
+    """
+    # Let cpg be the first valid object.
+    validObjects = ["cpg"]
+     
+    # Scan the objPathList in order from left to right
+    for index, obj in enumerate(objPathList):
+        # currentObjStr holds something like 'cpg.root.something.else'
+        currentObjStr = ".".join(validObjects)
+
+        #---------------
+        #   Cache check
+        #---------------
+        # Generate a cacheKey from the first 'index' elements of objPathList
+        cacheKey = tuple(objPathList[:index+1])
+        # Is this cacheKey in the objCache?
+        if cacheKey in objCache: 
+            # And is its value not None?
+            if objCache[cacheKey]:
+                # Yes, then add it to the list of validObjects
+                validObjects.append(obj)
+                # OK, go to the next iteration
+                continue
+            # Its value is None, so we stop
+            # (This means it is not a valid object)
+            break
+        
+        #-----------------
+        # Attribute check
+        #-----------------
+        if getattr(eval(currentObjStr), obj, None):
+            #  obj is a valid attribute of the current object
+            validObjects.append(obj)
+            #  Store it in the cache
+            objCache[cacheKey] = eval(".".join(validObjects))
+        else:
+            # obj is not a valid attribute
+            # Store None in the cache
+            objCache[cacheKey] = None
+            # Stop, we won't process the remaining objPathList
+            break
+
+    # Return the last cached object (even if its None)
+    return objCache[cacheKey]
+
+def mapPathToObject(path = None):
+    # Traverse path:
+    # for /a/b?arg=val, we'll try:
+    #   root.a.b.index -> redirect to /a/b/?arg=val
+    #   root.a.b.default(arg='val') -> redirect to /a/b/?arg=val
+    #   root.a.b(arg='val')
+    #   root.a.default('b', arg='val')
+    #   root.default('a', 'b', arg='val')
+
+    # Also, we ignore trailing slashes
+    # Also, a method has to have ".exposed = True" in order to be exposed
+
+    if path is None:
+        path = cpg.request.objectPath or cpg.request.path
+    if path.startswith('/'): path = path[1:] # Remove leading slash
+    if path.endswith('/'): path = path[:-1] # Remove trailing slash
+
+    if not path:
+        objectPathList = []
+    else:
+        objectPathList = path.split('/')
+    objectPathList = ['root'] + objectPathList + ['index']
+
+    # Try successive objects... (and also keep the remaining object list)
+    objCache = {}
+    isFirst = True
+    isDefault = False
+    foundIt = False
+    virtualPathList = []
+    while objectPathList:
+        candidate = getObjFromPath(objectPathList, objCache)
+        if callable(candidate) and getattr(candidate, 'exposed', False):
+            foundIt = True
+            break
+        # Couldn't find the object: pop one from the list and try "default"
+        lastObj = objectPathList.pop()
+        if not isFirst:
+            virtualPathList.insert(0, lastObj)
+            objectPathList.append('default')
+            candidate = getObjFromPath(objectPathList, objCache)
+            if callable(candidate) and getattr(candidate, 'exposed', False):
+                foundIt = True
+                isDefault = True
+                break
+            objectPathList.pop() # Remove "default"
+        isFirst = False
+
+    # Check results of traversal
+    if not foundIt:
+        raise cperror.NotFound # We didn't find anything
+
+    if isFirst:
+        # We found the extra ".index"
+        # Check if the original path had a trailing slash (otherwise, do
+        #   a redirect)
+        if cpg.request.path[-1] != '/':
+            newUrl = cpg.request.path + '/'
+            if cpg.request.queryString: newUrl += cpg.request.queryString
+            raise IndexRedirect(newUrl)
+
+    return candidate, objectPathList, virtualPathList
+    
+def canonicalizeUrl(newUrl):
+    if not newUrl.startswith('http://') and not newUrl.startswith('https://'):
+        # If newUrl is not canonical, we must make it canonical
+        if newUrl[0] == '/':
+            # URL was absolute: we just add the request.base in front of it
+            newUrl = cpg.request.base + newUrl
+        else:
+            # URL was relative
+            if cpg.request.browserUrl == cpg.request.base:
+                # browserUrl is request.base
+                newUrl = cpg.request.base + '/' + newUrl
+            else:
+                newUrl = cpg.request.browserUrl[:i+1] + newUrl
+    return newUrl

cherrypy/lib/filter/xmlrpcfilter.py

-"""
-Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-    * 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.
-    * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 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 THE COPYRIGHT OWNER OR 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.
-"""
-##########################################################################
-## Remco Boerma
-##
-## History:
-## 1.0.0   : 2004-12-29 Released with CP2
-## 0.0.9   : 2004-12-23 made it CP2 #59 compatible (returns an iterable)
-##           Please note: as the xmlrpc doesn't know what you would want to return
-##           (and for the logic of marshalling) it will return Generator objects, as
-##           it is.. So it'll brake on that one!!
-##           NOTE: __don't try to return a Generator object to the caller__
-##           You could of course handle the generator usage internally, before sending
-##           the result. This breaks from the general cherrypy way of handling generators...
-## 0.0.8   : 2004-12-23 cpg.request.paramList should now be a filter. 
-## 0.0.7   : 2004-12-07 inserted in the experimental branch (all remco boerma till here)
-## 0.0.6   : 2004-12-02 Converted basefilter to baseinputfileter,baseoutputfilter
-## 0.0.5   : 2004-11-22 "RPC2/" now changed to "/RPC2/" with the new mapping function
-##           Gian paolo ciceri notified me with the lack of passing parameters.
-##           Thanks Gian, it's now implemented against the latest trunk.
-##           Gian also came up with the idea of lazy content-type checking: if it's sent
-##           as a header, it should be 'text/xml', if not sent at all, it should be
-##           accepted. (While this it not the xml/rpc standard, it's handy for those
-##           xml-rpc client implementations wich don't send this header)
-## 0.0.4   : 2004-11-20 in setting the path, the dot is replaces by a slash
-##           therefore the regular CP2 routines knows how to handle things, as 
-##           dots are not allowed in object names, it's varely easily adopted. 
-##           Path + method handling. The default path is 'RPC2', this one is 
-##           stripped. In case of path 'someurl' it is used for 'someurl' + method
-##           and 'someurl/someotherurl' is mapped to someurl.someotherurl + method.
-##           this way python serverproxies initialised with an url other than 
-##           just the host are handled well. I don't hope any other service would map
-##           it to 'RPC2/someurl/someotherurl', cause then it would break i think. .
-## 0.0.3   : 2004-11-19 changed some examples (includes error checking 
-##           wich returns marshalled Fault objects if the request is an RPC call.
-##           took testing code form afterRequestHeader and put it in 
-##           testValidityOfRequest to make things a little simpler. 
-##           simply log the requested function with parameters to stdout
-## 0.0.2   : 2004-11-19 the required cgi.py patch is no longer needed
-##           (thanks remi for noticing). Webbased calls to regular objects
-##           are now possible again ;) so it's no longer a dedicated xmlrpc
-##           server. The test script is also in a ready to run file named 
-##           testRPC.py along with the test server: filterExample.py
-## 0.0.1   : 2004-11-19 informing the public, dropping loads of useless
-##           tests and debugging
-## 0.0.0   : 2004-11-19 initial alpha
-## 
-##---------------------------------------------------------------------
-## 
-## EXAMPLE CODE FOR THE SERVER:
-##    from cherrypy import cpg
-##    import cherrypy.lib.xmlrpcfilter as xmlrpcfilter
-##    class Root:
-##        _cpFilterList = [xmlrpcfilter.XmlRpcFilter()] 
-##
-##        def test(self):
-##            return `"I'm here"`
-##        test.exposed = True
-##    cpg.root = Root()
-##    cpg.server.start()
-##        
-## EXAMPLE CODE FOR THE CLIENT:
-##    import xmlrpclib
-##    server = xmlrpclib.ServerProxy('http://localhost:8080')
-##    print server.test()
-##    # results in: "I'm here"
-## 
-######################################################################
-
-from basefilter import BaseInputFilter, BaseOutputFilter
-from cherrypy import cpg
-import xmlrpclib
-
-
-class XmlRpcFilter(BaseInputFilter,BaseOutputFilter):
-    """
-    Derivative of basefilter.
-    Test to convert XMLRPC to CherryPy2 object system and reverse
-
-    PLEASE NOTE:
-
-    afterRequestHeader:
-        Unmarshalls the posted data to a methodname and parameters.
-            - These are stored in cpg.request.rpcMethod and cpg.request.rpcParams
-            - The method is also stored in cpg.request.path, so CP2 will find the right
-              method to call for you. Based on the root's position
-    beforeResponse:
-        Marshalls the result of the excecuted function (in cpg.response.body) to xmlrpc.
-            - Until resolved: the result must be a python souce string with the results,
-              this string is 'eval'ed to return the results. This will be resolved in the
-              future.
-            - the Content-Type and -Length are set according to the new (marshalled) data. 
-              
-
-    """
-    def testValidityOfRequest(self):
-        # test if the content-length was sent
-        result = int(cpg.request.headerMap.get('Content-Length',0)) > 0
-        result = result and cpg.request.headerMap.get('Content-Type','text/xml').lower() in ['text/xml']
-        return result
-        
-    def afterRequestHeader(self):
-        """ Called after the request header has been read/parsed"""
-        cpg.request.isRPC = self.testValidityOfRequest()
-        if not cpg.request.isRPC: 
-            # used for debugging or more info
-            # print 'not a valid xmlrpc call'
-            return # break this if it's not for this filter!!
-        # used for debugging, or more info:
-        # print "xmlrpcmethod...",
-        cpg.request.parsePostData = 0
-        dataLength = int(cpg.request.headerMap.get('Content-Length',0))
-        data = cpg.request.rfile.read(dataLength)
-        try:
-            params, method = xmlrpclib.loads(data)
-        except Exception,e: 
-            params, method =  ('ERROR PARAMS',),'ERRORMETHOD'
-        cpg.request.rpcMethod, cpg.request.rpcParams = method,params
-        # patch the path. .there are only a few options:
-        # - 'RPC2' + method >> method
-        # - 'someurl' + method >> someurl.method
-        # - 'someurl/someother' + method >> someurl.someother.method
-        if not cpg.request.path.endswith('/'):
-            cpg.request.path+='/'
-        if cpg.request.path.startswith('/RPC2/'):
-            cpg.request.path=cpg.request.path[5:] ## strip the irst /rpc2
-        cpg.request.path+=str(method).replace('.','/')
-        cpg.request.paramList = list(params)
-        # used for debugging and more info
-        # print "XMLRPC Filter: calling '%s' with args: '%s' " % (cpg.request.path,params)
-
-    def beforeResponse(self):
-        """ Called before starting to write response """
-        if not cpg.request.isRPC: 
-            return # it's not an RPC call, so just let it go with the normal flow
-        try:
-            # use this for debugging and more info:
-            # print 'beforeResponse: cpg.response.body ==',`cpg.response.body` 
-            cpg.response.body = xmlrpclib.dumps((cpg.response.body[0],), methodresponse=1,allow_none=1)
-        except xmlrpclib.Fault,fault:
-            cpg.response.body = xmlrpclib.dumps(fault,allow_none=1)
-        except Exception,e:
-            print 'EXCEPTION: ',e
-        cpg.response.headerMap['Content-Type']='text/xml'
-        cpg.response.headerMap['Content-Length']=`len(cpg.response.body)`
-
+"""
+Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+    * 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.
+    * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 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 THE COPYRIGHT OWNER OR 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.
+"""
+##########################################################################
+## Remco Boerma
+##
+## History:
+## 1.0.0   : 2004-12-29 Released with CP2
+## 0.0.9   : 2004-12-23 made it CP2 #59 compatible (returns an iterable)
+##           Please note: as the xmlrpc doesn't know what you would want to return
+##           (and for the logic of marshalling) it will return Generator objects, as
+##           it is.. So it'll brake on that one!!
+##           NOTE: __don't try to return a Generator object to the caller__
+##           You could of course handle the generator usage internally, before sending
+##           the result. This breaks from the general cherrypy way of handling generators...
+## 0.0.8   : 2004-12-23 cpg.request.paramList should now be a filter. 
+## 0.0.7   : 2004-12-07 inserted in the experimental branch (all remco boerma till here)
+## 0.0.6   : 2004-12-02 Converted basefilter to baseinputfileter,baseoutputfilter
+## 0.0.5   : 2004-11-22 "RPC2/" now changed to "/RPC2/" with the new mapping function
+##           Gian paolo ciceri notified me with the lack of passing parameters.
+##           Thanks Gian, it's now implemented against the latest trunk.
+##           Gian also came up with the idea of lazy content-type checking: if it's sent
+##           as a header, it should be 'text/xml', if not sent at all, it should be
+##           accepted. (While this it not the xml/rpc standard, it's handy for those
+##           xml-rpc client implementations wich don't send this header)
+## 0.0.4   : 2004-11-20 in setting the path, the dot is replaces by a slash
+##           therefore the regular CP2 routines knows how to handle things, as 
+##           dots are not allowed in object names, it's varely easily adopted. 
+##           Path + method handling. The default path is 'RPC2', this one is 
+##           stripped. In case of path 'someurl' it is used for 'someurl' + method
+##           and 'someurl/someotherurl' is mapped to someurl.someotherurl + method.
+##           this way python serverproxies initialised with an url other than 
+##           just the host are handled well. I don't hope any other service would map
+##           it to 'RPC2/someurl/someotherurl', cause then it would break i think. .
+## 0.0.3   : 2004-11-19 changed some examples (includes error checking 
+##           wich returns marshalled Fault objects if the request is an RPC call.
+##           took testing code form afterRequestHeader and put it in 
+##           testValidityOfRequest to make things a little simpler. 
+##           simply log the requested function with parameters to stdout
+## 0.0.2   : 2004-11-19 the required cgi.py patch is no longer needed
+##           (thanks remi for noticing). Webbased calls to regular objects
+##           are now possible again ;) so it's no longer a dedicated xmlrpc
+##           server. The test script is also in a ready to run file named 
+##           testRPC.py along with the test server: filterExample.py
+## 0.0.1   : 2004-11-19 informing the public, dropping loads of useless
+##           tests and debugging
+## 0.0.0   : 2004-11-19 initial alpha
+## 
+##---------------------------------------------------------------------
+## 
+## EXAMPLE CODE FOR THE SERVER:
+##    from cherrypy import cpg
+##    import cherrypy.lib.xmlrpcfilter as xmlrpcfilter
+##    class Root:
+##        _cpFilterList = [xmlrpcfilter.XmlRpcFilter()] 
+##
+##        def test(self):
+##            return `"I'm here"`
+##        test.exposed = True
+##    cpg.root = Root()
+##    cpg.server.start()
+##        
+## EXAMPLE CODE FOR THE CLIENT:
+##    import xmlrpclib
+##    server = xmlrpclib.ServerProxy('http://localhost:8080')
+##    print server.test()
+##    # results in: "I'm here"
+## 
+######################################################################
+
+from basefilter import BaseInputFilter, BaseOutputFilter
+from cherrypy import cpg
+import xmlrpclib
+
+
+class XmlRpcFilter(BaseInputFilter,BaseOutputFilter):
+    """
+    Derivative of basefilter.
+    Test to convert XMLRPC to CherryPy2 object system and reverse
+
+    PLEASE NOTE:
+
+    afterRequestHeader:
+        Unmarshalls the posted data to a methodname and parameters.
+            - These are stored in cpg.request.rpcMethod and cpg.request.rpcParams
+            - The method is also stored in cpg.request.path, so CP2 will find the right
+              method to call for you. Based on the root's position
+    beforeResponse:
+        Marshalls the result of the excecuted function (in cpg.response.body) to xmlrpc.
+            - Until resolved: the result must be a python souce string with the results,
+              this string is 'eval'ed to return the results. This will be resolved in the
+              future.
+            - the Content-Type and -Length are set according to the new (marshalled) data. 
+              
+
+    """
+    def testValidityOfRequest(self):
+        # test if the content-length was sent
+        result = int(cpg.request.headerMap.get('Content-Length',0)) > 0
+        result = result and cpg.request.headerMap.get('Content-Type','text/xml').lower() in ['text/xml']
+        return result
+        
+    def afterRequestHeader(self):
+        """ Called after the request header has been read/parsed"""
+        cpg.request.isRPC = self.testValidityOfRequest()
+        if not cpg.request.isRPC: 
+            # used for debugging or more info
+            # print 'not a valid xmlrpc call'
+            return # break this if it's not for this filter!!
+        # used for debugging, or more info:
+        # print "xmlrpcmethod...",
+        cpg.request.parsePostData = 0
+        dataLength = int(cpg.request.headerMap.get('Content-Length',0))
+        data = cpg.request.rfile.read(dataLength)
+        try:
+            params, method = xmlrpclib.loads(data)
+        except Exception,e: 
+            params, method =  ('ERROR PARAMS',),'ERRORMETHOD'
+        cpg.request.rpcMethod, cpg.request.rpcParams = method,params
+        # patch the path. .there are only a few options:
+        # - 'RPC2' + method >> method
+        # - 'someurl' + method >> someurl.method
+        # - 'someurl/someother' + method >> someurl.someother.method
+        if not cpg.request.path.endswith('/'):
+            cpg.request.path+='/'
+        if cpg.request.path.startswith('/RPC2/'):
+            cpg.request.path=cpg.request.path[5:] ## strip the irst /rpc2
+        cpg.request.path+=str(method).replace('.','/')
+        cpg.request.paramList = list(params)
+        # used for debugging and more info
+        # print "XMLRPC Filter: calling '%s' with args: '%s' " % (cpg.request.path,params)
+
+    def beforeResponse(self):
+        """ Called before starting to write response """
+        if not cpg.request.isRPC: 
+            return # it's not an RPC call, so just let it go with the normal flow
+        try:
+            # use this for debugging and more info:
+            # print 'beforeResponse: cpg.response.body ==',`cpg.response.body` 
+            cpg.response.body = xmlrpclib.dumps((cpg.response.body[0],), methodresponse=1,allow_none=1)
+        except xmlrpclib.Fault,fault:
+            cpg.response.body = xmlrpclib.dumps(fault,allow_none=1)
+        except Exception,e:
+            print 'EXCEPTION: ',e
+        cpg.response.headerMap['Content-Type']='text/xml'
+        cpg.response.headerMap['Content-Length']=`len(cpg.response.body)`
+

cherrypy/test/buildInfoMap.py

-"""
-Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-    * 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.
-    * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 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 THE COPYRIGHT OWNER OR 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.
-"""
-
-import os, sys,os
-
-def buildInfoMap(python2):
-    print "Checking which python versions are installed..."
-    for version, infoMap in python2.items():
-        # Check if this version of python is installed:
-        path=infoMap.get('path', sys.executable )
-        if path:
-            exactVersion=os.popen(
-                '%s -c "import sys,os,os.path;' % path+
-                """sys.path.insert(0,os.path.normpath(os.path.join(os.getcwd(),\'../../\')));"""+
-                'import cherrypy;'+
-                'print sys.version;'+
-                'print cherrypy.__version__"').read().strip()
-        if path and exactVersion and exactVersion.find('Traceback') == -1:
-            exactVersionShort=exactVersion.split()[0]
-            print "    Found python version %s with CherryPy version %s " % (exactVersionShort, exactVersion.split()[-1])
-            python2[version]['exactVersion']=exactVersion
-            python2[version]['exactVersionShort']=exactVersionShort
-            python2[version]['path']=path
-            if exactVersionShort.find("2.%s"%version)!=0:
-                print
-                print "*************************"
-                print "Error: the path for python2.%s appears to run python%s"%(version, exactVersionShort)
-                print 'By default, this script expects the python binaries to be in your PATH and to be called "python2.1", "python2.2", ...'
-                print "If your setup is different, please edit this script and change the path for the python binary"
-                sys.exit(-1)
-        else:
-            print "    Version 2.%s not found with cherrypy module, two directories up"%version
-            del python2[version]
-
-    if not python2:
-        print
-        print "*************************"
-        print "Error: couldn't find any python distribution on your machine."
-        print 'By default, this script expects the python binaries to be in your PATH and to be called "python2.1", "python2.2", ...'
-        print "If your setup is different, please edit this script and change the path for the python binary"
-        sys.exit(-1)
-    print
-    print "Finding out what modules are installed for these versions..."
-    for version, infoMap in python2.items():
-        print "    Checking modules for python%s..."%infoMap['exactVersionShort']
-
-        # Test if python has fork
-        res=os.popen('%s -c "import sys; sys.stderr=sys.stdout; import os; print hasattr(os,\'fork\')"'%infoMap['path']).read()
-        if res.find('1')!=-1 or res.find('True')!=-1:
-            print "        os.fork available"
-            infoMap['hasFork']=1
-        else:
-            print "        os.fork not available"
-            infoMap['hasFork']=0
-
-        # Test if threads are available
-        res=os.popen('%s -c "import sys; sys.stderr=sys.stdout; import thread"'%infoMap['path']).read()
-        if res.find("ImportError")==-1:
-            print "        thread available"
-            infoMap['hasThread']=1
-        else:
-            print "        thread not available"
-            infoMap['hasThread']=0
-
-        # Test if pyOpenSSL is available
-        res=os.popen('%s -c "import sys; sys.stderr=sys.stdout; from OpenSSL import SSL"'%infoMap['path']).read()
-        if res.find("ImportError")==-1:
-            print "        pyOpenSSL available"
-            infoMap['hasPyOpenSSL']=1
-        else:
-            print "        pyOpenSSL not available"
-            infoMap['hasPyOpenSSL']=0
-
-        # Test if xmlrpclib is available
-        res=os.popen('%s -c "import sys; sys.stderr=sys.stdout; import xmlrpclib"'%infoMap['path']).read()
-        if res.find("ImportError")==-1:
-            print "        xmlrpclib available"
-            infoMap['hasXmlrpclib']=1
-        else:
-            print "        xmlrpclib not available"
-            infoMap['hasXmlrpclib']=0
-
-    return python2
+"""
+Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+    * 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.
+    * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 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 THE COPYRIGHT OWNER OR 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.
+"""
+
+import os, sys,os
+
+def buildInfoMap(python2):
+    print "Checking which python versions are installed..."
+    for version, infoMap in python2.items():
+        # Check if this version of python is installed:
+        path=infoMap.get('path', sys.executable )
+        if path:
+            exactVersion=os.popen(
+                '%s -c "import sys,os,os.path;' % path+
+                """sys.path.insert(0,os.path.normpath(os.path.join(os.getcwd(),\'../../\')));"""+
+                'import cherrypy;'+
+                'print sys.version;'+
+                'print cherrypy.__version__"').read().strip()
+        if path and exactVersion and exactVersion.find('Traceback') == -1:
+            exactVersionShort=exactVersion.split()[0]
+            print "    Found python version %s with CherryPy version %s " % (exactVersionShort, exactVersion.split()[-1])
+            python2[version]['exactVersion']=exactVersion
+            python2[version]['exactVersionShort']=exactVersionShort
+            python2[version]['path']=path
+            if exactVersionShort.find("2.%s"%version)!=0:
+                print
+                print "*************************"
+                print "Error: the path for python2.%s appears to run python%s"%(version, exactVersionShort)
+                print 'By default, this script expects the python binaries to be in your PATH and to be called "python2.1", "python2.2", ...'
+                print "If your setup is different, please edit this script and change the path for the python binary"
+                sys.exit(-1)
+        else:
+            print "    Version 2.%s not found with cherrypy module, two directories up"%version
+            del python2[version]
+
+    if not python2:
+        print
+        print "*************************"
+        print "Error: couldn't find any python distribution on your machine."
+        print 'By default, this script expects the python binaries to be in your PATH and to be called "python2.1", "python2.2", ...'
+        print "If your setup is different, please edit this script and change the path for the python binary"
+        sys.exit(-1)
+    print
+    print "Finding out what modules are installed for these versions..."
+    for version, infoMap in python2.items():
+        print "    Checking modules for python%s..."%infoMap['exactVersionShort']
+
+        # Test if python has fork
+        res=os.popen('%s -c "import sys; sys.stderr=sys.stdout; import os; print hasattr(os,\'fork\')"'%infoMap['path']).read()
+        if res.find('1')!=-1 or res.find('True')!=-1:
+            print "        os.fork available"
+            infoMap['hasFork']=1
+        else:
+            print "        os.fork not available"
+            infoMap['hasFork']=0
+
+        # Test if threads are available
+        res=os.popen('%s -c "import sys; sys.stderr=sys.stdout; import thread"'%infoMap['path']).read()
+        if res.find("ImportError")==-1:
+            print "        thread available"
+            infoMap['hasThread']=1
+        else:
+            print "        thread not available"
+            infoMap['hasThread']=0
+
+        # Test if pyOpenSSL is available
+        res=os.popen('%s -c "import sys; sys.stderr=sys.stdout; from OpenSSL import SSL"'%infoMap['path']).read()
+        if res.find("ImportError")==-1:
+            print "        pyOpenSSL available"
+            infoMap['hasPyOpenSSL']=1
+        else:
+            print "        pyOpenSSL not available"
+            infoMap['hasPyOpenSSL']=0
+
+        # Test if xmlrpclib is available
+        res=os.popen('%s -c "import sys; sys.stderr=sys.stdout; import xmlrpclib"'%infoMap['path']).read()
+        if res.find("ImportError")==-1:
+            print "        xmlrpclib available"
+            infoMap['hasXmlrpclib']=1
+        else:
+            print "        xmlrpclib not available"
+            infoMap['hasXmlrpclib']=0
+
+    return python2

cherrypy/test/helper.py

-"""
-Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-    * 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.
-    * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 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 THE COPYRIGHT OWNER OR 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.
-"""
-import os,urllib,time,sys,signal,socket,httplib,os.path 
-
-def startServer(infoMap):
-    # Start the server in another thread
-    if not hasattr(os, "fork"): # win32 mostly
-        pid=os.spawnl(os.P_NOWAIT,infoMap['path'],infoMap['path'], '"'+os.path.join(os.getcwd(),'testsite.py')+'"')
-    else:
-        pid=os.fork()
-        if not pid:
-            os.execlp(infoMap['path'],infoMap['path'],'testsite.py')
-    return pid
-
-def getPage(url, cookies, extraRequestHeader = []):
-    data=""
-    i=0
-    response = None
-    class EmptyClass: pass
-    cpg = EmptyClass()
-    cpg.response = EmptyClass()
-    cpg.response.body = None
-    cpg.response.headerMap = {}
-    while i<10:
-        try:
-            conn=httplib.HTTPConnection('127.0.0.1:8000')
-            conn.putrequest("GET", url)
-            conn.putheader("Host", "127.0.0.1")
-            if cookies:
-                cookieList = []
-                for cookie in cookies:
-                    i = cookie.find(' ')
-                    j = cookie.find(';')
-                    cookieList.append(cookie[i+1:j])
-                cookieStr = '; '.join(cookieList)
-                conn.putheader("Cookie", cookies[:j])
-
-            for key, value in extraRequestHeader:
-                conn.putheader(key, value)
-
-            conn.endheaders()
-
-            response=conn.getresponse()
-
-            cookies=response.msg.getallmatchingheaders("Set-Cookie")
-
-            cpg=EmptyClass()
-            cpg.response = EmptyClass()
-            cpg.response.headerMap = {'Status': response.status}
-            for line in response.msg.headers:
-                line = line.strip()
-                i = line.find(':')
-                key, value = line[:i], line[i+1:].strip()
-                cpg.response.headerMap[key] = value
-
-            cpg.response.body = response.read()
-
-            conn.close()
-            break
-        except socket.error:
-            time.sleep(0.5)
-        i+=1
-    return cpg, cookies
-
-def shutdownServer(pid, mode):
-    urllib.urlopen("http://127.0.0.1:8000/shutdown/all")
-    if mode == 'tp':
-        # In thread-pool mode, it can take up to 1 sec for the server
-        #   to shutdown
-        time.sleep(1)
-    return
-
-def checkResult(testName, infoMap, serverMode, cpg, rule, failedList):
-    result = False
-    try:
-        result = eval(rule)
-        if result:
-            return result 
-    except:
-        pass 
-    if not result:
-        failedList.append(testName +
-            " for python%s" % infoMap['exactVersionShort'] + 
-            " in " + serverMode + " mode failed." + """
-* Rule:
-%s
-* cpg.response.headerMap:
-%s
-* cpg.response.body:
-%s""" % (rule, repr(cpg.response.headerMap), repr(cpg.response.body)))
-        return False
-
-def prepareCode(code):
-    f = open('testsite.py', 'w')
-    beforeStart = '''
-class Shutdown:
-    def all(self):
-        cpg.server.stop()
-    all.exposed = True
-cpg.root.shutdown = Shutdown()
-def f(*a, **kw): return ""
-cpg.root._cpLogMessage = f
-'''
-    includePathsToSysPath = """
-import sys,os,os.path
-sys.path.insert(0,os.path.normpath(os.path.join(os.getcwd(),'../../')))
-"""
-    f.write(includePathsToSysPath+code.replace('cpg.server.start', beforeStart + 'cpg.server.start'))
-    f.close()
-
-def checkPageResult(testName, infoMap, code, testList, failedList, extraConfig = '', extraRequestHeader = []):
-    response = None
-    prepareCode(code)
-    # Try it in all 2 modes (regular, threadPooling)
-    modeList=[('r',''), ('tp', 'threadPool=3')]
-    for mode,modeConfig in modeList:
-        f=open("testsite.cfg", "w")
-        f.write(extraConfig)
-        f.write('''
-[session]
-storageType=ram
-[server]
-socketPort = 8000
-''')
-        f.write(modeConfig)
-        f.close()
-
-        pid = startServer(infoMap)
-        passed=True
-        cookies=None
-        for url, rule in testList:
-            cpg, cookies = getPage(url, cookies, extraRequestHeader)
-            if not checkResult(testName, infoMap, mode, cpg, rule, failedList):
-                passed=0
-                print "*** FAILED ***"
-                break
-        shutdownServer(pid, mode)
-        if passed:
-            print mode+"...",
-            sys.stdout.flush()
-        else: break
-    if passed: print "passed"
-    sys.stdout.flush()
-    return response
-
+"""
+Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+    * 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.
+    * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 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 THE COPYRIGHT OWNER OR 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.
+"""
+import os,urllib,time,sys,signal,socket,httplib,os.path 
+
+def startServer(infoMap):
+    # Start the server in another thread
+    if not hasattr(os, "fork"): # win32 mostly
+        pid=os.spawnl(os.P_NOWAIT,infoMap['path'],infoMap['path'], '"'+os.path.join(os.getcwd(),'testsite.py')+'"')
+    else:
+        pid=os.fork()
+        if not pid:
+            os.execlp(infoMap['path'],infoMap['path'],'testsite.py')
+    return pid
+
+def getPage(url, cookies, extraRequestHeader = []):
+    data=""
+    i=0
+    response = None
+    class EmptyClass: pass
+    cpg = EmptyClass()
+    cpg.response = EmptyClass()
+    cpg.response.body = None
+    cpg.response.headerMap = {}
+    while i<10:
+        try:
+            conn=httplib.HTTPConnection('127.0.0.1:8000')
+            conn.putrequest("GET", url)
+            conn.putheader("Host", "127.0.0.1")
+            if cookies:
+                cookieList = []
+                for cookie in cookies:
+                    i = cookie.find(' ')
+                    j = cookie.find(';')
+                    cookieList.append(cookie[i+1:j])
+                cookieStr = '; '.join(cookieList)
+                conn.putheader("Cookie", cookies[:j])
+
+            for key, value in extraRequestHeader:
+                conn.putheader(key, value)
+
+            conn.endheaders()
+
+            response=conn.getresponse()
+
+            cookies=response.msg.getallmatchingheaders("Set-Cookie")
+
+            cpg=EmptyClass()
+            cpg.response = EmptyClass()
+            cpg.response.headerMap = {'Status': response.status}
+            for line in response.msg.headers:
+                line = line.strip()
+                i = line.find(':')
+                key, value = line[:i], line[i+1:].strip()
+                cpg.response.headerMap[key] = value
+
+            cpg.response.body = response.read()
+
+            conn.close()
+            break
+        except socket.error:
+            time.sleep(0.5)
+        i+=1
+    return cpg, cookies
+
+def shutdownServer(pid, mode):
+    urllib.urlopen("http://127.0.0.1:8000/shutdown/all")
+    if mode == 'tp':
+        # In thread-pool mode, it can take up to 1 sec for the server
+        #   to shutdown
+        time.sleep(1)
+    return
+
+def checkResult(testName, infoMap, serverMode, cpg, rule, failedList):
+    result = False
+    try:
+        result = eval(rule)
+        if result:
+            return result 
+    except:
+        pass 
+    if not result:
+        failedList.append(testName +
+            " for python%s" % infoMap['exactVersionShort'] + 
+            " in " + serverMode + " mode failed." + """
+* Rule:
+%s
+* cpg.response.headerMap:
+%s
+* cpg.response.body:
+%s""" % (rule, repr(cpg.response.headerMap), repr(cpg.response.body)))
+        return False
+
+def prepareCode(code):
+    f = open('testsite.py', 'w')
+    beforeStart = '''
+class Shutdown:
+    def all(self):
+        cpg.server.stop()
+    all.exposed = True
+cpg.root.shutdown = Shutdown()
+def f(*a, **kw): return ""
+cpg.root._cpLogMessage = f
+'''
+    includePathsToSysPath = """
+import sys,os,os.path
+sys.path.insert(0,os.path.normpath(os.path.join(os.getcwd(),'../../')))
+"""
+    f.write(includePathsToSysPath+code.replace('cpg.server.start', beforeStart + 'cpg.server.start'))
+    f.close()
+
+def checkPageResult(testName, infoMap, code, testList, failedList, extraConfig = '', extraRequestHeader = []):
+    response = None
+    prepareCode(code)
+    # Try it in all 2 modes (regular, threadPooling)
+    modeList=[('r',''), ('tp', 'threadPool=3')]
+    for mode,modeConfig in modeList:
+        f=open("testsite.cfg", "w")
+        f.write(extraConfig)
+        f.write('''
+[session]
+storageType=ram
+[server]
+socketPort = 8000
+''')
+        f.write(modeConfig)
+        f.close()
+
+        pid = startServer(infoMap)
+        passed=True
+        cookies=None
+        for url, rule in testList:
+            cpg, cookies = getPage(url, cookies, extraRequestHeader)
+            if not checkResult(testName, infoMap, mode, cpg, rule, failedList):
+                passed=0
+                print "*** FAILED ***"
+                break
+        shutdownServer(pid, mode)
+        if passed:
+            print mode+"...",
+            sys.stdout.flush()
+        else: break
+    if passed: print "passed"
+    sys.stdout.flush()
+    return response
+

cherrypy/test/test.py

-"""
-Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-    * 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.
-    * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 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 THE COPYRIGHT OWNER OR 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.
-"""
-
-# Regression test suite for CherryPy
-
-import sys,os,os.path
-sys.path.insert(0,os.path.normpath(os.path.join(os.getcwd(),'../../')))
-if not os.path.exists(os.path.join(os.curdir,'buildInfoMap.py')):
-    print "Run the test form the test directory (cherrypy/test)from the cherrypy you wish to test."
-    print "In no python executables are found, change this file (test.py) near line 31"
-    sys.exit(1)
-if len(sys.argv) == 2 and sys.argv[1] in ('-h', '--help'):
-    print "Usage: unittest.py [testName+]"
-    print "Run from the test directory from within cherrypy"
-    sys.exit(0)
-
-python2={}
-python2[3]={}    # Infos about python-2.3
-python2[4]={}    # Infos about python-2.4
-
-# Edit these lines to match your setup
-if sys.platform=="win32":
-    python2[3]['path']="c:\\python23\\python.exe"
-    python2[4]['path']="c:\\python24\\python.exe"
-else:
-    python2[3]['path']="python2.3"
-    python2[4]['path']="python2.4"
-
-print "Checking that port 8000 is free...",
-try:
-    import socket
-    s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-    s.connect(('127.0.0.1', 8000))
-    s.close()
-    print "\n### Error: port 8000 is busy. This port must be free to run this test script"
-    sys.exit(-1)
-except socket.error:
-    print "OK"
-
-print
-
-print "Examining your system..."
-print
-print "Python version used to run this test script:", sys.version.split()[0]
-print
-import buildInfoMap
-python2 = buildInfoMap.buildInfoMap(python2)
-
-print
-print "Checking CherryPy version..."
-import os
-try:
-    import cherrypy
-except ImportError:
-    print "Error: couln't find CherryPy !"
-    os._exit(-1)
-
-print "    Found version " + cherrypy.__version__
-print
-
-print "Testing CherryPy..."
-failedList=[]
-skippedList=[]
-
-tutorialTestList = [
-    ('01', [('/', "cpg.response.body == 'Hello world!'")]),
-    ('02', [('/showMessage', "cpg.response.body == 'Hello world!'")]),
-    ('03', [('/greetUser?name=Bob',
-            '''cpg.response.body == "Hey Bob, what's up?"''')]),
-    ('04', [('/links/extra/', r"""cpg.response.body == '\n            <p>Here are some extra useful links:</p>\n\n            <ul>\n                <li><a href="http://del.icio.us">del.icio.us</a></li>\n                <li><a href="http://www.mornography.de">Hendrik\'s weblog</a></li>\n            </ul>\n\n            <p>[<a href="../">Return to links page</a>]</p>\n        '""")]),
-    ('05', [('/another/', r"""cpg.response.body == '\n            <html>\n            <head>\n                <title>Another Page</title>\n            <head>\n            <body>\n            <h2>Another Page</h2>\n        \n            <p>\n            And this is the amazing second page!\n            </p>\n        \n            </body>\n            </html>\n        '""")]),
-    ('06', [('/', r"""cpg.response.body == '\n            <html>\n            <head>\n                <title>Tutorial 6 -- Aspect Powered!</title>\n            <head>\n            <body>\n            <h2>Tutorial 6 -- Aspect Powered!</h2>\n        \n            <p>\n            Isn\'t this exciting? There\'s\n            <a href="./another/">another page</a>, too!\n            </p>\n        \n            </body>\n            </html>\n        '""")]),
-    ('07', [('/hendrik', r"""cpg.response.body == 'Hendrik Mans, CherryPy co-developer & crazy German (<a href="./">back</a>)'""")]),
-    ('08', [('/', r'''cpg.response.body == "\n            During your current session, you've viewed this\n            page 1 times! Your life is a patio of fun!\n        "'''), ('/', r'''cpg.response.body == "\n            During your current session, you've viewed this\n            page 2 times! Your life is a patio of fun!\n        "''')]), 
-    ('09', [('/', r"""cpg.response.body == '<html><body><h2>Generators rule!</h2><h3>List of users:</h3>Remi<br/>Carlos<br/>Hendrik<br/>Lorenzo Lamas<br/></body></html>'""")]),
-]
-
-testList = [
-    'testObjectMapping',
-    'testFilter1',
-    'testVirtualHostFilter',
-]
-
-if len(sys.argv) > 1:
-    # Some specific tests were specified on the command line
-    # Limit the tests to these ones
-    newTutorialTestList = []
-    newTestList = []
-    for number, myTestList in tutorialTestList:
-        if "tutorial%s" % number in sys.argv[1:]:
-            newTutorialTestList.append((number, myTestList))
-    for t in testList:
-        if t in sys.argv[1:]:
-            newTestList.append(t)
-    tutorialTestList = newTutorialTestList
-    testList = newTestList
-
-import helper
-
-for version, infoMap in python2.items():
-    print
-    print "Running tests for python %s..."%infoMap['exactVersionShort']
-
-    # Run tests based on tutorials
-    for number, myTestList in tutorialTestList:
-        code = open('../tutorial/tutorial%s.py' % number, 'r').read()
-        code = code.replace('tutorial.conf', 'testsite.cfg')
-        print "    Testing tutorial %s..." % number,
-        #if ((version == 1 and number in ('06', '09')) or
-        #        (version == 2 and number in ('09'))):
-        #    print "skipped"
-        #    skippedList.append("Tutorial %s for python2.%s" % (number, version))
-        #    continue
-           
-        helper.checkPageResult('Tutorial %s' % number, infoMap, code, myTestList, failedList)
-
-    # Running actual unittests
-    for test in testList:
-        exec("import "+test)
-        eval(test+".test(infoMap, failedList, skippedList)")
-
-print
-print
-print "#####################################"
-print "#####################################"
-print "###          TEST RESULT          ###"
-print "#####################################"
-print "#####################################"
-if skippedList:
-    print
-    print "*** THE FOLLOWING TESTS WERE SKIPPED:"
-    for skipped in skippedList: print skipped
-
-    print "**** THE ABOVE TESTS WERE SKIPPED"
-    print
-
-if failedList:
-    print
-    print "*** THE FOLLOWING TESTS FAILED:"
-    for failed in failedList: print failed
-
-    print "**** THE ABOVE TESTS FAILED"
-    print
-    print "**** Some errors occured: please add a ticket in our Trac system (http://trac.cherrypy.org/cgi-bin/trac.cgi/newticket) with the output of this test script"
-
-else:
-    print
-    print "**** NO TEST FAILED: EVERYTHING LOOKS OK ****"
-
-############"
-# Ideas for future tests:
-#    - test if tabs and whitespaces are handled correctly in source file (option -W)
-#    - test if absolute pathnames work fine on windows
-#    - test sessions
-#    - test threading server
-#    - test forking server
-#    - test process pooling server
-#    - test SSL
-#    - test compilator errors
-#    - test abstract classes
-#    - test hidden classes
-#    ...
-
-raw_input('hit enter')
+"""
+Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+    * 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.
+    * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 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 THE COPYRIGHT OWNER OR 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.
+"""
+
+# Regression test suite for CherryPy
+
+import sys,os,os.path
+sys.path.insert(0,os.path.normpath(os.path.join(os.getcwd(),'../../')))
+if not os.path.exists(os.path.join(os.curdir,'buildInfoMap.py')):
+    print "Run the test form the test directory (cherrypy/test)from the cherrypy you wish to test."
+    print "In no python executables are found, change this file (test.py) near line 31"
+    sys.exit(1)
+if len(sys.argv) == 2 and sys.argv[1] in ('-h', '--help'):
+    print "Usage: unittest.py [testName+]"
+    print "Run from the test directory from within cherrypy"
+    sys.exit(0)
+
+python2={}
+python2[3]={}    # Infos about python-2.3
+python2[4]={}    # Infos about python-2.4
+
+# Edit these lines to match your setup
+if sys.platform=="win32":
+    python2[3]['path']="c:\\python23\\python.exe"
+    python2[4]['path']="c:\\python24\\python.exe"
+else:
+    python2[3]['path']="python2.3"
+    python2[4]['path']="python2.4"
+
+print "Checking that port 8000 is free...",
+try:
+    import socket
+    s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    s.connect(('127.0.0.1', 8000))
+    s.close()
+    print "\n### Error: port 8000 is busy. This port must be free to run this test script"
+    sys.exit(-1)
+except socket.error:
+    print "OK"
+
+print
+
+print "Examining your system..."
+print
+print "Python version used to run this test script:", sys.version.split()[0]
+print
+import buildInfoMap
+python2 = buildInfoMap.buildInfoMap(python2)
+
+print
+print "Checking CherryPy version..."
+import os
+try:
+    import cherrypy
+except ImportError:
+    print "Error: couln't find CherryPy !"
+    os._exit(-1)
+
+print "    Found version " + cherrypy.__version__
+print
+
+print "Testing CherryPy..."
+failedList=[]
+skippedList=[]
+
+tutorialTestList = [
+    ('01', [('/', "cpg.response.body == 'Hello world!'")]),
+    ('02', [('/showMessage', "cpg.response.body == 'Hello world!'")]),
+    ('03', [('/greetUser?name=Bob',
+            '''cpg.response.body == "Hey Bob, what's up?"''')]),
+    ('04', [('/links/extra/', r"""cpg.response.body == '\n            <p>Here are some extra useful links:</p>\n\n            <ul>\n                <li><a href="http://del.icio.us">del.icio.us</a></li>\n                <li><a href="http://www.mornography.de">Hendrik\'s weblog</a></li>\n            </ul>\n\n            <p>[<a href="../">Return to links page</a>]</p>\n        '""")]),
+    ('05', [('/another/', r"""cpg.response.body == '\n            <html>\n            <head>\n                <title>Another Page</title>\n            <head>\n            <body>\n            <h2>Another Page</h2>\n        \n            <p>\n            And this is the amazing second page!\n            </p>\n        \n            </body>\n            </html>\n        '""")]),
+    ('06', [('/', r"""cpg.response.body == '\n            <html>\n            <head>\n                <title>Tutorial 6 -- Aspect Powered!</title>\n            <head>\n            <body>\n            <h2>Tutorial 6 -- Aspect Powered!</h2>\n        \n            <p>\n            Isn\'t this exciting? There\'s\n            <a href="./another/">another page</a>, too!\n            </p>\n        \n            </body>\n            </html>\n        '""")]),
+    ('07', [('/hendrik', r"""cpg.response.body == 'Hendrik Mans, CherryPy co-developer & crazy German (<a href="./">back</a>)'""")]),
+    ('08', [('/', r'''cpg.response.body == "\n            During your current session, you've viewed this\n            page 1 times! Your life is a patio of fun!\n        "'''), ('/', r'''cpg.response.body == "\n            During your current session, you've viewed this\n            page 2 times! Your life is a patio of fun!\n        "''')]), 
+    ('09', [('/', r"""cpg.response.body == '<html><body><h2>Generators rule!</h2><h3>List of users:</h3>Remi<br/>Carlos<br/>Hendrik<br/>Lorenzo Lamas<br/></body></html>'""")]),
+]
+
+testList = [
+    'testObjectMapping',
+    'testFilter1',
+    'testVirtualHostFilter',
+]
+