Anonymous avatar Anonymous committed c74b0d9

Comments (0)

Files changed (46)

+    * Check that response is a string *after* filters have been applied (ticket #28) (Remi)
+    * Removed all XML-RPC references from the core: it is now a filter (ticket #30) (Remi)
+    * Removed Generator processing for the core: it is now a filter (Remi)
+    * Removed "response encoding" from the core: it is now in a filter (ticket #31) (Remi)
+    * Implemented filters and a couple of sample filters (Remi)
+	* Fixed "cleanUpOldSessions" bug (ticket #12) (Remi)
+	* "file" storage type for sessions now works fine (Remi)
+    * Ticket #6: If a response is unicode, it gets encoded automatically. Encoding is specified in the config file unser [server].encoding (the default is UTF-8). (Remi)
+    * Added more unittest (Remi)
+    * Fixed file upload bug (Remi)
+
+2004/10/13:
+    * First alpha release of CherryPy-2 (Remi)
+
+See http://trac.cherrypy.org/cgi-bin/trac.cgi/wiki/CherryPyTeam
+ChangeLog.txt
+CherryPyTeam.txt
+Readme.txt
+setup.py
+cherrypy\__init__.py
+cherrypy\_cpconfig.py
+cherrypy\_cpdefaults.py
+cherrypy\_cphttpserver.py
+cherrypy\_cphttptools.py
+cherrypy\_cpserver.py
+cherrypy\_cputil.py
+cherrypy\cperror.py
+cherrypy\cpg.py
+cherrypy\wsgiapp.py
+cherrypy\lib\__init__.py
+cherrypy\lib\aspect.py
+cherrypy\lib\csauthenticate.py
+cherrypy\lib\httptools.py
+cherrypy\lib\filter\__init__.py
+cherrypy\lib\filter\basefilter.py
+cherrypy\lib\filter\gzipfilter.py
+cherrypy\lib\filter\logdebuginfofilter.py
+cherrypy\lib\filter\tidyfilter.py
+cherrypy\tutorial\__init__.py
+cherrypy\tutorial\bonus-sqlobject.py
+cherrypy\tutorial\readme.txt
+cherrypy\tutorial\tutorial01.py
+cherrypy\tutorial\tutorial02.py
+cherrypy\tutorial\tutorial03.py
+cherrypy\tutorial\tutorial04.py
+cherrypy\tutorial\tutorial05.py
+cherrypy\tutorial\tutorial06.py
+cherrypy\tutorial\tutorial07.py
+cherrypy\tutorial\tutorial08.py
+cherrypy\tutorial\tutorial09.py
+cherrypy\unittest\__init__.py
+cherrypy\unittest\buildInfoMap.py
+cherrypy\unittest\helper.py
+cherrypy\unittest\testGzipFilter.py
+cherrypy\unittest\testObjectMapping.py
+cherrypy\unittest\testsite.py
+cherrypy\unittest\unittest.py
+* To install, just type:
+
+    python setup.py install
+
+* To learn how to use it, look at the examples under cherrypy/tutorial/ or go to http://www.cherrypy.org for more info.
+
+* To run the unittest, just go to the cherrypy/unittest/ directory and type:
+
+    python unittest.py
+
+

cherrypy/__init__.py

+__version__ = '2.0.0a2'

cherrypy/_cpconfig.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 _cputil, ConfigParser, cpg
+
+        
+def setDefaultConfigOption():
+    """ Return an EmptyClass instance with the default config options """
+
+    cpg.configOption = _cputil.EmptyClass()
+
+    # Set default values for all options
+
+    # Parameters used for logging
+    cpg.configOption.logToScreen = 1
+    cpg.configOption.logFile = ''
+
+    # Parameters used to tell which socket the server should listen on
+    # Note that socketPort and socketFile conflict wich each
+    # other: if one has a non-null value, the other one should be null
+    cpg.configOption.socketHost = ''
+    cpg.configOption.socketPort = 8080
+    cpg.configOption.socketFile = '' # Used if server should listen on
+                                 # AF_UNIX socket
+    cpg.configOption.reverseDNS = 0
+    cpg.configOption.socketQueueSize = 5 # Size of the socket queue
+    cpg.configOption.protocolVersion = "HTTP/1.0"
+
+    # Parameters used to tell what kind of server we want
+    # Note that numberOfProcesses, threading and forking conflict
+    # wich each other: if one has a non-null value, the other
+    # ones should be null (for numberOfProcesses, null means equal to one)
+    cpg.configOption.processPool = 0 # Used if we want to fork n processes
+                                 # at the beginning. In this case, all
+                                 # processes will listen on the same
+                                 # socket (this only works on unix)
+    cpg.configOption.threading = 0 # Used if we want to create a new
+                               # thread for each request
+    cpg.configOption.forking = 0 # Used if we want to create a new process
+                             # for each request
+    cpg.configOption.threadPool = 0 # Used if we want to create a pool
+                                # of threads at the beginning
+
+    # Variables used to tell if this is an SSL server
+    cpg.configOption.sslKeyFile = ""
+    cpg.configOption.sslCertificateFile = ""
+    cpg.configOption.sslClientCertificateVerification = 0
+    cpg.configOption.sslCACertificateFile = ""
+    cpg.configOption.sslVerifyDepth = 1
+
+    # Variable used to flush cache
+    cpg.configOption.flushCacheDelay=0
+
+    # Variable used for enabling debugging
+    cpg.configOption.debugMode=0
+
+    # Variable used to serve static content
+    cpg.configOption.staticContentList = []
+
+    # Variable used for session handling
+    cpg.configOption.sessionStorageType = ""
+    cpg.configOption.sessionTimeout = 60 # In minutes
+    cpg.configOption.sessionCleanUpDelay = 60 # In minutes
+    cpg.configOption.sessionCookieName = "CherryPySession"
+    cpg.configOption.sessionStorageFileDir = ""
+
+def parseConfigFile(configFile = None, parsedConfigFile = None):
+    """
+        Parse the config file and set values in cpg.configOption
+    """
+    _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage')
+    if configFile:
+        cpg.parsedConfigFile = ConfigParser.ConfigParser()
+        if hasattr(configFile, 'read'):
+            _cpLogMessage("Reading infos from configFile stream", 'CONFIG')
+            cpg.parsedConfigFile.readfp(configFile)
+        else:
+            _cpLogMessage("Reading infos from configFile: %s" % configFile, 'CONFIG')
+            cpg.parsedConfigFile.read(configFile)
+    else:
+        cpg.parsedConfigFile = parsedConfigFile
+
+    # Read parameters from configFile
+    for sectionName, optionName, valueType in [
+            ('server', 'logToScreen', 'int'),
+            ('server', 'logFile', 'str'),
+            ('server', 'socketHost', 'str'),
+            ('server', 'protocolVersion', 'str'),
+            ('server', 'socketPort', 'int'),
+            ('server', 'socketFile', 'str'),
+            ('server', 'reverseDNS', 'int'),
+            ('server', 'processPool', 'int'),
+            ('server', 'threadPool', 'int'),
+            ('server', 'threading', 'int'),
+            ('server', 'forking', 'int'),
+            ('server', 'sslKeyFile', 'str'),
+            ('server', 'sslCertificateFile', 'str'),
+            ('server', 'sslClientCertificateVerification', 'int'),
+            ('server', 'sslCACertificateFile', 'str'),
+            ('server', 'sslVerifyDepth', 'int'),
+            ('session', 'storageType', 'str'),
+            ('session', 'timeout', 'float'),
+            ('session', 'cleanUpDelay', 'float'),
+            ('session', 'cookieName', 'str'),
+            ('session', 'storageFileDir', 'str')
+            ]:
+        try:
+            value = cpg.parsedConfigFile.get(sectionName, optionName)
+            if valueType == 'int': value = int(value)
+            elif valueType == 'float': value = float(value)
+            if sectionName == 'session':
+                optionName = 'session' + optionName[0].upper() + optionName[1:]
+            setattr(cpg.configOption, optionName, value)
+        except:
+            pass
+
+    try:
+        staticDirList = cpg.parsedConfigFile.options('staticContent')
+        for staticDir in staticDirList:
+            staticDirTarget = cpg.parsedConfigFile.get('staticContent', staticDir)
+            cpg.configOption.staticContentList.append((staticDir, staticDirTarget))
+    except: pass
+
+def outputConfigOptions():
+    _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage')
+    _cpLogMessage("Server parameters:", 'CONFIG')
+    _cpLogMessage("  logToScreen: %s" % cpg.configOption.logToScreen, 'CONFIG')
+    _cpLogMessage("  logFile: %s" % cpg.configOption.logFile, 'CONFIG')
+    _cpLogMessage("  protocolVersion: %s" % cpg.configOption.protocolVersion, 'CONFIG')
+    _cpLogMessage("  socketHost: %s" % cpg.configOption.socketHost, 'CONFIG')
+    _cpLogMessage("  socketPort: %s" % cpg.configOption.socketPort, 'CONFIG')
+    _cpLogMessage("  socketFile: %s" % cpg.configOption.socketFile, 'CONFIG')
+    _cpLogMessage("  reverseDNS: %s" % cpg.configOption.reverseDNS, 'CONFIG')
+    _cpLogMessage("  socketQueueSize: %s" % cpg.configOption.socketQueueSize, 'CONFIG')
+    _cpLogMessage("  processPool: %s" % cpg.configOption.processPool, 'CONFIG')
+    _cpLogMessage("  threadPool: %s" % cpg.configOption.threadPool, 'CONFIG')
+    _cpLogMessage("  threading: %s" % cpg.configOption.threading, 'CONFIG')
+    _cpLogMessage("  forking: %s" % cpg.configOption.forking, 'CONFIG')
+    _cpLogMessage("  sslKeyFile: %s" % cpg.configOption.sslKeyFile, 'CONFIG')
+    if cpg.configOption.sslKeyFile:
+        _cpLogMessage("  sslCertificateFile: %s" % cpg.configOption.sslCertificateFile, 'CONFIG')
+        _cpLogMessage("  sslClientCertificateVerification: %s" % cpg.configOption.sslClientCertificateVerification, 'CONFIG')
+        _cpLogMessage("  sslCACertificateFile: %s" % cpg.configOption.sslCACertificateFile, 'CONFIG')
+        _cpLogMessage("  sslVerifyDepth: %s" % cpg.configOption.sslVerifyDepth, 'CONFIG')
+        _cpLogMessage("  flushCacheDelay: %s min" % cpg.configOption.flushCacheDelay, 'CONFIG')
+    _cpLogMessage("  sessionStorageType: %s" % cpg.configOption.sessionStorageType, 'CONFIG')
+    if cpg.configOption.sessionStorageType:
+        _cpLogMessage("  sessionTimeout: %s min" % cpg.configOption.sessionTimeout, 'CONFIG')
+        _cpLogMessage("  cleanUpDelay: %s min" % cpg.configOption.sessionCleanUpDelay, 'CONFIG')
+        _cpLogMessage("  sessionCookieName: %s" % cpg.configOption.sessionCookieName, 'CONFIG')
+        _cpLogMessage("  sessionStorageFileDir: %s" % cpg.configOption.sessionStorageFileDir, 'CONFIG')
+    _cpLogMessage("  staticContent: %s" % cpg.configOption.staticContentList, 'CONFIG')
+
+def dummy():
+    # Check that parameters are correct and that they don't conflict with each other
+    if _protocolVersion not in ("HTTP/1.1", "HTTP/1.0"):
+        raise "CherryError: protocolVersion must be 'HTTP/1.1' or 'HTTP/1.0'"
+    if _reverseDNS not in (0,1): raise "CherryError: reverseDNS must be '0' or '1'"
+    if _socketFile and not hasattr(socket, 'AF_UNIX'): raise "CherryError: Configuration file has socketFile, but this is only available on Unix machines"
+    if _processPool!=1 and not hasattr(os, 'fork'): raise "CherryError: Configuration file has processPool, but forking is not available on this operating system"
+    if _forking and not hasattr(os, 'fork'): raise "CherryError: Configuration file has forking, but forking is not available on this operating system"
+    if _sslKeyFile:
+        try:
+            global SSL
+            from OpenSSL import SSL
+        except: raise "CherryError: PyOpenSSL 0.5.1 or later must be installed to use SSL. You can get it from http://pyopenssl.sourceforge.net"
+    if _socketPort and _socketFile: raise "CherryError: In configuration file: socketPort and socketFile conflict with each other"
+    if not _socketFile and not _socketPort: _socketPort=8000 # Default port
+    if _processPool==1: severalProcs=0
+    else: severalProcs=1
+    if _threadPool==1: severalThreads=0
+    else: severalThreads=1
+    if severalThreads+severalProcs+_threading+_forking>1: raise "CherryError: In configuration file: threadPool, processPool, threading and forking conflict with each other"
+    if _sslKeyFile and not _sslCertificateFile: raise "CherryError: Configuration file has sslKeyFile but no sslCertificateFile"
+    if _sslCertificateFile and not _sslKeyFile: raise "CherryError: Configuration file has sslCertificateFile but no sslKeyFile"
+    try: sys.stdout.flush()
+    except: pass
+
+    if _sessionStorageType not in ('', 'custom', 'ram', 'file', 'cookie'): raise "CherryError: Configuration file an invalid sessionStorageType: '%s'"%_sessionStorageType
+    if _sessionStorageType in ('custom', 'ram', 'cookie') and _sessionStorageFileDir!='': raise "CherryError: Configuration file has sessionStorageType set to 'custom, 'ram' or 'cookie' but a sessionStorageFileDir is specified"
+    if _sessionStorageType=='file' and _sessionStorageFileDir=='': raise "CherryError: Configuration file has sessionStorageType set to 'file' but no sessionStorageFileDir"
+    if _sessionStorageType=='ram' and (_forking or severalProcs):
+        print "CherryWarning: 'ram' sessions might be buggy when using several processes"
+

cherrypy/_cpdefaults.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.
+"""
+
+
+"""
+A module containing a few utility classes/functions used by CherryPy
+"""
+
+import time, thread, os, pickle, cpg
+
+def _cpLogMessage(msg, context = '', severity = 0):
+    """ Default method for logging messages """
+
+    nowTuple = time.localtime(time.time())
+    nowStr = '%04d/%02d/%02d %02d:%02d:%02d' % (nowTuple[:6])
+    if severity == 0: level = "INFO"
+    elif severity == 1: level = "WARNING"
+    elif severity == 2: level = "ERROR"
+    try:
+        logToScreen = int(cpg.parsedConfigFile.get('server', 'logToScreen'))
+    except:
+        logToScreen = 1
+    if logToScreen:
+        print nowStr, context, level, msg
+
+def _cpOnError():
+    """ Default _cpOnError method """
+
+    import traceback, StringIO
+    bodyFile = StringIO.StringIO()
+    traceback.print_exc(file = bodyFile)
+    cpg.response.body = bodyFile.getvalue()
+    cpg.response.headerMap['Content-Type'] = 'text/plain'
+
+def _cpInitThread(numThread): pass
+
+def _cpSaveSessionData(sessionId, sessionData, expirationTime):
+    """ Save session data if needed """
+
+    t = time.localtime(expirationTime)
+    if cpg.configOption.sessionStorageType == 'file':
+        fname=os.path.join(cpg.configOption.sessionStorageFileDir,sessionId)
+        if cpg.configOption.threadPool > 1 or cpg.configOption.threading:
+            cpg._sessionFileLock.acquire()
+        f = open(fname,"wb")
+        pickle.dump((sessionData, expirationTime), f)
+        f.close()
+        if cpg.configOption.threadPool > 1 or cpg.configOption.threading:
+            cpg._sessionFileLock.release()
+
+    elif cpg.configOption.sessionStorageType=="ram":
+        # Update expiration time
+        cpg._sessionMap[sessionId] = (sessionData, expirationTime)
+
+    """ TODO: implement cookie storage type
+    elif cpg.configOption.sessionStorageType == "cookie":
+        
+         TODO: set siteKey in _cpConfig
+            # Get site key from config file or compute it
+            try: cpg._SITE_KEY_ = configFile.get('server','siteKey')
+            except:
+                _SITE_KEY_ = ''
+                for i in range(30):
+                    _SITE_KEY_ += random.choice(string.letters)
+
+        # Update expiration time
+        sessionData = (sessionData, expirationTime)
+        dumpStr = pickle.dumps(_sessionData)
+        try: dumpStr = zlib.compress(dumpStr)
+        except: pass # zlib is not available in all python distros
+        dumpStr = binascii.hexlify(dumpStr) # Need to hexlify it because it will be stored in a cookie
+        cpg.response.simpleCookie['CSession'] = dumpStr
+        cpg.response.simpleCookie['CSession-sig'] = md5.md5(dumpStr + cpg.configOption.siteKey).hexdigest()
+        cpg.response.simpleCookie['CSession']['path'] = '/'
+        cpg.response.simpleCookie['CSession']['max-age'] = sessionTimeout * 60
+        cpg.response.simpleCookie['CSession-sig']['path'] = '/'
+        cpg.response.simpleCookie['CSession-sig']['max-age'] = sessionTimeout * 60
+    """
+
+def _cpLoadSessionData(sessionId):
+    """ Return the session data for a given sessionId.
+        The _expirationTime will be checked by the caller of this function
+    """
+    if cpg.configOption.sessionStorageType == "ram":
+        if cpg._sessionMap.has_key(sessionId):
+            return cpg._sessionMap[sessionId]
+        else: return None
+
+    elif cpg.configOption.sessionStorageType == "file":
+        fname = os.path.join(cpg.configOption.sessionStorageFileDir, sessionId)
+        if os.path.exists(fname):
+            if cpg.configOption.threadPool > 1 or cpg.configOption.threading:
+                cpg._sessionFileLock.acquire()
+            f = open(fname, "rb")
+            sessionData = pickle.load(f)
+            f.close()
+            if cpg.configOption.threadPool > 1 or cpg.configOption.threading:
+                cpg._sessionFileLock.release()
+            return sessionData
+        else: return None
+
+    """ TODO: implement cookie storage type
+    elif _sessionStorageType == "cookie":
+        if request.simpleCookie.has_key('CSession') and request.simpleCookie.has_key('CSession-sig'):
+            data = request.simpleCookie['CSession'].value
+            sig  = request.simpleCookie['CSession-sig'].value
+            if md5.md5(data + cpg.configOption.siteKey).hexdigest() == sig:
+                try:
+                    dumpStr = binascii.unhexlify(data)
+                    try: dumpStr = zlib.decompress(dumpStr)
+                    except: pass # zlib is not available in all python distros
+                    dumpStr = pickle.loads(dumpStr)
+                    return dumpStr
+                except: pass
+        return None
+    """
+
+def _cpCleanUpOldSessions():
+    """ Clean up old sessions """
+
+    # Clean up old session data
+    now = time.time()
+    if cpg.configOption.sessionStorageType == "ram":
+        sessionIdToDeleteList = []
+        for sessionId, (dummy, expirationTime) in cpg._sessionMap.items():
+            if expirationTime < now:
+                sessionIdToDeleteList.append(sessionId)
+        for sessionId in sessionIdToDeleteList:
+            del cpg._sessionMap[sessionId]
+
+    elif cpg.configOption.sessionStorageType=="file":
+        # This process is very expensive because we go through all files, parse them and then delete them if the session is expired
+        # One optimization would be to just store a list of (sessionId, expirationTime) in *one* file
+        sessionFileList = os.listdir(cpg.configOption.sessionStorageFileDir)
+        for sessionId in sessionFileList:
+            try:
+                dummy, expirationTime = _cpLoadSessionData(sessionId)
+                if expirationTime < now:
+                    os.remove(os.path.join(cpg.configOption.sessionStorageFileDir, sessionId))
+            except:
+                pass
+
+    elif cpg.configOption.sessionStorageType == "cookie":
+        # Nothing to do in this case: the session data is stored on the client
+        pass
+
+    """ TODO
+    else:
+        # custom
+        cleanUpOldSessions()
+    """
+
+_cpFilterList = []

cherrypy/_cphttpserver.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, sys, threading, SocketServer, _cphttptools, BaseHTTPServer, socket, Cookie, Queue, _cputil
+
+def start():
+    """ Prepare the HTTP server and then run it """
+
+    # TODO: SSL
+
+    # If sessions are stored in files and we
+    # use threading, we need a lock on the file
+    if (cpg.configOption.threadPool > 1 or cpg.configOption.threading) and \
+            cpg.configOption.sessionStorageType == 'file':
+        cpg._sessionFileLock = threading.RLock()
+
+
+    if cpg.configOption.socketFile:
+        # AF_UNIX socket
+        if cpg.configOption.forking:
+            class MyCherryHTTPServer(SocketServer.ForkingMixIn,CherryHTTPServer): address_family = socket.AF_UNIX
+        elif cpg.configOption.threading:
+            class MyCherryHTTPServer(CherryThreadingMixIn,CherryHTTPServer): address_family = socket.AF_UNIX
+        else:
+            class MyCherryHTTPServer(CherryHTTPServer): address_family = socket.AF_UNIX
+    else:
+        # AF_INET socket
+        if cpg.configOption.forking:
+            class MyCherryHTTPServer(SocketServer.ForkingMixIn,CherryHTTPServer): pass
+        elif cpg.configOption.threading:
+            class MyCherryHTTPServer(CherryThreadingMixIn,CherryHTTPServer):pass
+        elif cpg.configOption.threadPool > 1:
+            MyCherryHTTPServer = PooledThreadServer
+        else:
+            MyCherryHTTPServer = CherryHTTPServer
+
+    MyCherryHTTPServer.request_queue_size = cpg.configOption.socketQueueSize
+
+    # Set protocol_version
+    CherryHTTPRequestHandler.protocol_version = cpg.configOption.protocolVersion
+
+    run_server(CherryHTTPRequestHandler, MyCherryHTTPServer, \
+        (cpg.configOption.socketHost, cpg.configOption.socketPort), \
+        cpg.configOption.socketFile)
+
+def run_server(HandlerClass, ServerClass, server_address, socketFile):
+    """Run the HTTP request handler class."""
+
+    if cpg.configOption.socketFile:
+        try: os.unlink(cpg.configOption.socketFile) # So we can reuse the socket
+        except: pass
+        server_address = cpg.configOption.socketFile
+    if cpg.configOption.threadPool > 1:
+        myCherryHTTPServer = ServerClass(server_address, cpg.configOption.threadPool, HandlerClass)
+    else:
+        myCherryHTTPServer = ServerClass(server_address, HandlerClass)
+    if cpg.configOption.socketFile:
+        try: os.chmod(socketFile, 0777) # So everyone can access the socket
+        except: pass
+
+    _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage')
+
+    if cpg.configOption.sslKeyFile: servingWhat = "HTTPS"
+    else: servingWhat = "HTTP"
+    if cpg.configOption.socketPort: onWhat = "socket: ('%s', %s)" % (cpg.configOption.socketHost, cpg.configOption.socketPort)
+    else: onWhat = "socket file: %s" % cpg.configOption.socketFile
+    _cpLogMessage("Serving %s on %s" % (servingWhat, onWhat), 'HTTP')
+
+    # If configOption.processPool is more than one, create new processes
+    if cpg.configOption.processPool > 1:
+        for i in range(cpg.configOption.processPool):
+            _cpLogMessage("Forking a kid", "HTTP")
+            if not os.fork():
+                # Kid
+                # initProcess(i)
+                try: myCherryHTTPServer.serve_forever()
+                except KeyboardInterrupt:
+                    _cpLogMessage("<Ctrl-C> hit: shutting down", "HTTP")
+                    myCherryHTTPServer.shutdownCtrlC()
+    else:
+        try: myCherryHTTPServer.serve_forever()
+        except KeyboardInterrupt:
+            _cpLogMessage("<Ctrl-C> hit: shutting down", "HTTP")
+            myCherryHTTPServer.shutdownCtrlC()
+
+
+class CherryHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+
+    """CherryPy HTTP request handler with the following commands:
+
+        o  GET
+        o  HEAD
+        o  POST
+        o  HOTRELOAD
+
+    """
+
+    def address_string(self):
+        """ Try to do a reverse DNS based on [server]reverseDNS in the config file """
+        if _reverseDNS: return BaseHTTPServer.BaseHTTPRequestHandler.address_string(self)
+        else: return self.client_address[0]
+
+    def cook_headers(self):
+        """Process the headers in self.headers into the request.headerMap"""
+        cpg.request.headerMap = {}
+        cpg.request.simpleCookie = Cookie.SimpleCookie()
+        cpg.response.simpleCookie = Cookie.SimpleCookie()
+
+        # Build headerMap
+        for item in self.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)
+            _cphttptools.insertIntoHeaderMap(item[0],item[1])
+
+        # Handle cookies differently because on Konqueror, multiple cookies come on different lines with the same key
+        cookieList = self.headers.getallmatchingheaders('cookie')
+        for cookie in cookieList:
+            cpg.request.simpleCookie.load(cookie)
+
+        if not cpg.request.headerMap.has_key('Remote-Addr'):
+            try:
+                cpg.request.headerMap['Remote-Addr'] = self.client_address[0]
+                cpg.request.headerMap['Remote-Host'] = self.address_string()
+            except: pass
+
+        # 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.headerMap.get('Remote-Addr', ''), self.raw_requestline[:-2]), "HTTP")
+
+    def do_GET(self):
+        """Serve a GET request."""
+        cpg.request.method = 'GET'
+        _cphttptools.parseFirstLine(self.raw_requestline)
+        self.cook_headers()
+        _cphttptools.applyFilterList('afterRequestHeader')
+        _cphttptools.applyFilterList('afterRequestBody')
+        _cphttptools.doRequest(self.wfile)
+
+    def do_HEAD(self): # Head is not implemented
+        """Serve a HEAD request."""
+        cpg.request.method = 'HEAD'
+        _cphttptools.parseFirstLine(self.raw_requestline)
+        self.cook_headers()
+        _cphttptools.doRequest(self.wfile)
+
+    def do_POST(self):
+        """Serve a POST request."""
+        cpg.request.method = 'POST'
+        _cphttptools.parseFirstLine(self.raw_requestline)
+        self.cook_headers()
+        cpg.request.parsePostData = 1
+        cpg.request.rfile = self.rfile
+        _cphttptools.applyFilterList('afterRequestHeader')
+        if cpg.request.parsePostData:
+            _cphttptools.parsePostData(self.rfile)
+        _cphttptools.applyFilterList('afterRequestBody')
+        _cphttptools.doRequest(self.wfile)
+
+    def setup(self):
+        """ We have to override this to handle SSL
+            (socket object from the OpenSSL package don't
+            have the makefile method) """
+
+        if not cpg.configOption.sslKeyFile:
+            BaseHTTPServer.BaseHTTPRequestHandler.setup(self)
+
+        """ SSL sockets from the OpenSSL package don't have the "makefile"
+            method so we have to hack our way around this ... """
+
+        class CherrySSLFileObject(socket._fileobject):
+            """ This is used for implementing the "flush" methods
+                for SSL sockets """
+
+            def flush(self):
+                """ Some sockets have a "sendall" method, some don't """
+                if self._wbuf:
+                    if hasattr(self._sock, "sendall"):
+                        if type(self._wbuf)==type([]): # python2.3
+                            self._sock.sendall("".join(self._wbuf))
+                            self._wbuf=[]
+                        else:
+                            self._sock.sendall(self._wbuf)
+                            self._wbuf=""
+                    else:
+                        while self._wbuf:
+                            _sentChar=self._sock.send(self._wbuf)
+                            self._wbuf=self._wbuf[_sentChar:]
+
+        self.connection = self.request
+        self.rfile = CherrySSLFileObject(self.connection, 'rb', self.rbufsize)
+        self.wfile = CherrySSLFileObject(self.connection, 'wb', self.wbufsize)
+
+    def log_message(self, format, *args):
+        """ We have to override this to use our own logging mechanism """
+        _cputil.getSpecialFunction('_cpLogMessage')(format % args, "HTTP")
+
+
+class CherryHTTPServer(BaseHTTPServer.HTTPServer):
+    def __init__(self, server_address, RequestHandlerClass):
+        if not cpg.configOption.sslKeyFile:
+            return BaseHTTPServer.HTTPServer.__init__(self, server_address, RequestHandlerClass)
+
+        # I know it says "do not override", but I have to in order to implement SSL support !
+        SocketServer.BaseServer.__init__(self, server_address, RequestHandlerClass)
+        if cpg.sslKeyFile:
+            self.socket = SSL.Connection(_sslCtx, socket.socket(self.address_family, self.socket_type))
+        self.server_bind()
+        self.server_activate()
+        initAfterBind()
+
+    def server_activate(self):
+        """Override server_activate to set timeout on our listener socket"""
+        if hasattr(self.socket, 'settimeout'): self.socket.settimeout(2)
+        elif hasattr(self.socket, 'set_timeout'): self.socket.set_timeout(2)
+        BaseHTTPServer.HTTPServer.server_activate(self)
+
+    def server_bind(self):
+        # Removed getfqdn call because it was timing out on localhost when calling gethostbyaddr
+        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        self.socket.bind(self.server_address)
+
+    def get_request(self):
+        # With Python 2.3 it seems that an accept socket in timeout (nonblocking) mode
+        #  results in request sockets that are also set in nonblocking mode. Since that doesn't play
+        #  well with makefile() (where wfile and rfile are set in SocketServer.py) we explicitly set
+        #  the request socket to blocking
+
+        request, client_address = self.socket.accept()
+        if hasattr(request,'setblocking'): # Jython doesn't have setblocking
+            request.setblocking(1)
+        return request, client_address
+
+    def handle_request(self):
+        """Override handle_request to trap timeout exception."""
+        try:
+            BaseHTTPServer.HTTPServer.handle_request(self)
+        except socket.timeout: # TODO: Doesn't exist for older versions of python
+            # The only reason for the timeout is so we can notice keyboard
+            # interrupts on Win32, which don't interrupt accept() by default
+            return 1
+        except KeyboardInterrupt:
+            print "<Ctrl-C> hit: shutting down"
+            sys.exit(0)
+
+    def shutdownCtrlC(self):
+        self.shutdown()
+
+_SHUTDOWNREQUEST = (0,0)
+
+class ServerThread(threading.Thread):
+    def __init__(self, RequestHandlerClass, requestQueue, threadIndex):
+        threading.Thread.__init__(self)
+        self._RequestHandlerClass = RequestHandlerClass
+        self._requestQueue = requestQueue
+        self._threadIndex = threadIndex
+        self.setName("RUNNING")
+        
+    def run(self):
+        _cputil.getSpecialFunction('_cpInitThread')(self._threadIndex)
+        while 1:
+            request, client_address = self._requestQueue.get()
+            if (request, client_address) == _SHUTDOWNREQUEST:
+                return
+            if self.verify_request(request, client_address):            
+                try:
+                    self.process_request(request, client_address)
+                except:
+                    self.handle_error(request, client_address)
+                    self.close_request(request)
+            else:
+                self.close_request(request)
+
+    def verify_request(self, request, client_address):
+        """ Verify the request.  May be overridden.
+            Return 1 if we should proceed with this request. """
+        return 1
+
+    def process_request(self, request, client_address):
+        self._RequestHandlerClass(request, client_address, self)        
+        self.close_request(request)
+
+    def close_request(self, request):
+        """ Called to clean up an individual request. """
+        request.close()
+
+    def handle_error(self, request, client_address):
+        """ Handle an error gracefully.  May be overridden.
+            The default is to print a traceback and continue.
+        """
+        import traceback, StringIO
+        bodyFile=StringIO.StringIO()
+        traceback.print_exc(file=bodyFile)
+        errorBody=bodyFile.getvalue()
+        bodyFile.close()
+        _cputil.getSpecialFunction('_cpLogMessage')(errorBody)
+        
+
+class PooledThreadServer(SocketServer.TCPServer):
+
+    allow_reuse_address = 1
+
+    """A TCP Server using a pool of worker threads. This is superior to the
+       alternatives provided by the Python standard library, which only offer
+       (1) handling a single request at a time, (2) handling each request in
+       a separate thread (via ThreadingMixIn), or (3) handling each request in
+       a separate process (via ForkingMixIn). It's also superior in some ways
+       to the pure async approach used by Twisted because it allows a more
+       straightforward and simple programming model in the face of blocking
+       requests (i.e. you don't have to bother with Deferreds).""" 
+    def __init__(self, serverAddress, numThreads, RequestHandlerClass, ThreadClass=ServerThread):
+        assert(numThreads > 0)
+        # I know it says "do not override", but I have to in order to implement SSL support !
+        SocketServer.BaseServer.__init__(self, serverAddress, RequestHandlerClass)
+        self.socket=socket.socket(self.address_family, self.socket_type)
+        self.server_bind()
+        self.server_activate()
+
+        self._numThreads = numThreads        
+        self._RequestHandlerClass = RequestHandlerClass
+        self._ThreadClass = ThreadClass
+        self._requestQueue = Queue.Queue()
+        self._workerThreads = []
+
+    def createThread(self, threadIndex):
+        return self._ThreadClass(self._RequestHandlerClass, self._requestQueue, threadIndex)
+            
+    def start(self):
+        if self._workerThreads != []:
+            return
+        for i in xrange(self._numThreads):
+            self._workerThreads.append(self.createThread(i))        
+        for worker in self._workerThreads:
+            worker.start()
+            
+    def server_close(self):
+        """Override server_close to shutdown thread pool"""
+        SocketServer.TCPServer.server_close(self)
+        for worker in self._workerThreads:
+            self._requestQueue.put(_SHUTDOWNREQUEST)
+        for worker in self._workerThreads:
+            worker.join()
+        self._workerThreads = []
+
+    def server_activate(self):
+        """Override server_activate to set timeout on our listener socket"""
+        SocketServer.TCPServer.server_activate(self)
+
+    def server_bind(self):
+        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        self.socket.bind(self.server_address)
+
+    def shutdown(self):
+        """Gracefully shutdown a server that is serve_forever()ing."""
+        self.__running = 0
+
+    def shutdownCtrlC(self):
+        self.server_close()
+
+    def serve_forever(self):
+        """Handle one request at a time until doomsday (or shutdown is called)."""
+        if self._workerThreads == []:
+            self.start()
+        self.__running = 1
+        while self.__running:
+            if not self.handle_request():
+                break
+        self.server_close()            
+        
+    def handle_request(self):
+        """Override handle_request to enqueue requests rather than handle
+           them synchronously. Return 1 by default, 0 to shutdown the
+           server."""
+        try:
+            if 1:
+                for t in threading.enumerate():
+                    if t.getName()=="NOT RUNNING": return 0
+            request, client_address = self.get_request()
+        except KeyboardInterrupt:
+            print "<Ctrl-C> hit: shutting down"
+            return 0
+        except socket.error, e:
+            return 1
+        self._requestQueue.put((request, client_address))
+        return 1
+
+    def get_request(self):
+        # With Python 2.3 it seems that an accept socket in timeout (nonblocking) mode
+        #  results in request sockets that are also set in nonblocking mode. Since that doesn't play
+        #  well with makefile() (where wfile and rfile are set in SocketServer.py) we explicitly set
+        #  the request socket to blocking
+
+        request, client_address = self.socket.accept()
+        if hasattr(request,'setblocking'):
+            request.setblocking(1)
+        return request, client_address
+

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
+
+"""
+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']
+
+def parseFirstLine(data):
+    cpg.request.path = data.split()[1]
+    cpg.request.browserUrl = cpg.request.path
+    cpg.request.paramMap = {}
+    cpg.request.filenameMap = {}
+    cpg.request.fileTypeMap = {}
+    cpg.request.paramTuple = ()
+    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.path = cpg.request.path[:i]
+
+    if cpg.request.path and cpg.request.path[-1] == '/':
+        cpg.request.path = cpg.request.path[:-1] # Remove trailing '/' if any
+
+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):
+    filterList = _cputil.getSpecialFunction('_cpFilterList')
+    for filter in filterList:
+        method = getattr(filter, methodName)
+        method()
+
+def insertIntoHeaderMap(key,value):
+    normalizedKey = '-'.join([s.capitalize() for s in key.split('-')])
+    cpg.request.headerMap[normalizedKey] = value
+
+def doRequest(wfile):
+    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')()
+
+            # 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:
+                cpg.response.headerMap['Content-Length'] = len(cpg.response.body)
+            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')
+            wfile.write(cpg.response.body)
+        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:
+        cpg.response.headerMap['Content-Length'] = len(cpg.response.body)
+
+    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')
+
+    applyFilterList('afterResponseHeader')
+
+    applyFilterList('beforeResponseFullBody')
+
+    # Check that the response body is a string
+    if type(cpg.response.body) != types.StringType:
+        raise cperror.WrongResponseType
+
+    wfile.write(cpg.response.body)
+
+def handleRequest(wfile):
+    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={"Status": "200 OK", "protocolVersion": cpg.configOption.protocolVersion, "Content-Type": "text/html", "Server": "CherryPy/" + cpg.__version__, "Date": date, "Set-Cookie": [], "Content-Length": 0}
+
+    # Two variables used for streaming
+    cpg.response.wfile = wfile
+    cpg.response.sendResponse = 1
+
+    if cpg.configOption.sslKeyFile:
+        cpg.request.base = "https://" + cpg.request.headerMap['Host']
+    else:
+        cpg.request.base = "http://" + cpg.request.headerMap['Host']
+    cpg.request.browserUrl = cpg.request.base + '/' + cpg.request.browserUrl
+    cpg.request.isStatic = 0
+
+    # Clean up expired sessions if needed:
+    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.originalParamTuple = cpg.request.paramTuple
+
+    path = cpg.request.path
+
+    # 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': 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
+
+    path = cpg.request.path
+
+    # Traverse path:
+    # for /a/b?arg=val, we'll try:
+    #   root.a.b.index(arg='val')
+    #   root.a.b(arg='val')
+    #   root.a.b.default(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 not path:
+        pathList = []
+    else:
+        pathList = path.split('/')
+
+    pathList = ['root'] + pathList
+
+    # try root.a.b, then root.a, then root
+    func = None
+
+    obj = cpg
+    previousObj = None
+    objList = []
+    searchedPathList = []
+    myPath = ''
+    bestKnownDefaultMethod = None
+    # Successively get objects from the path: 'root', then 'a' then 'b'
+    for pathItem in pathList:
+        previousObj = obj
+        try:
+            # find contained object
+            obj = getattr(obj, pathItem)
+
+            # add object
+            objList.append(obj)
+            searchedPathList.append(pathItem)
+
+            # if found object has a default method, remember it for later
+            default = getattr(obj, 'default', None)
+            if default:
+                # TODO: check if default is callable?
+                bestKnownDefaultMethod = default
+                bestKnownDefaultMethodMyPath = '/'.join(searchedPathList[1:])
+
+        except AttributeError:
+            break
+
+    # TODO: the following code could probably be KISSed somewhat.
+
+    if len(objList) == len(pathList):
+        # root_a_b exists
+        root_a_b = objList[-1]
+
+        # Try root.a.b.index()
+        root_a_b_dot_index = getattr(root_a_b, 'index', None)
+        if root_a_b_dot_index and getattr(root_a_b_dot_index, 'exposed', None):
+            myPath = '/'.join(pathList[1:])
+            func = root_a_b_dot_index
+        else:
+            # Try root.a.b.default()
+            root_a_b_dot_default = getattr(root_a_b, 'default', None)
+            if root_a_b_dot_default and getattr(root_a_b_dot_default, 'exposed', None):
+                # XXX: I don't think this ever gets called?
+                myPath = 'root_a_b_dot_default'
+                func = root_a_b_dot_default
+            elif callable(root_a_b) and getattr(root_a_b, 'exposed', None):
+                # We use root.a.b()
+                myPath = '/'.join(pathList[1:-1])
+                func = root_a_b
+
+    if func == None:
+        # None of these exist: use the default method we found earlier
+        if bestKnownDefaultMethod:
+            func = bestKnownDefaultMethod
+            myPath = bestKnownDefaultMethodMyPath
+
+    if func == None:
+        raise cperror.NotFound
+
+    myPath += '/'
+    if len(myPath) > 1:
+        myPath = '/' + myPath
+
+    cpg.request.objectPath = myPath
+    cpg.request.virtualPath = cpg.request.path[len(myPath)-1:]
+    cpg.response.body = func(**(cpg.request.paramMap))
+
+    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()
+
+

cherrypy/_cpserver.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.
+"""
+
+"""
+Main CherryPy module:
+    - Parses config file
+    - Creates the HTTP server
+"""
+
+import cpg, thread, _cputil, _cpconfig, _cphttpserver, time
+
+def start(configFile = None, parsedConfigFile = None, configDict = {}, initOnly = 0):
+    """
+        Main function. All it does is this:
+            - read/parse config file if any
+            - create response and request objects
+            - creates HTTP server based on configFile and configDict
+            - start HTTP server
+
+        Input: There are 2 ways to pass config options:
+            - Let CherryPy parse a config file (configFile)
+            - Pass the options as a dictionary (configDict)
+    """
+
+    # cpg.configOption contains an EmptyClass instance with all the configuration option
+    _cpconfig.setDefaultConfigOption()
+
+    # cpg.parsedConfigFile contains the ConfigParser instance with the parse config file
+    cpg.parsedConfigFile = None
+
+    if configFile:
+        _cpconfig.parseConfigFile(configFile = configFile)
+    elif parsedConfigFile:
+        _cpconfig.parseConfigFile(parsedConfigFile = parsedConfigFile)
+
+    if configDict:
+        for key, value in configDict.items():
+            setattr(cpg.configOption, key, value)
+
+    # Output config options
+    _cpconfig.outputConfigOptions()
+
+    # Check the config options
+    # TODO
+    # _cpconfig.checkConfigOptions()
+
+    # Create request and response object (the same objects will be used
+    #   throughout the entire life of the webserver)
+    cpg.request = _cputil.ThreadAwareClass()
+    cpg.response = _cputil.ThreadAwareClass()
+
+    # Initialize a few global variables
+    cpg._lastCacheFlushTime = time.time()
+    cpg._lastSessionCleanUpTime = time.time()
+    cpg._sessionMap = {} # Map of "cookie" -> ("session object", "expiration time")
+
+    if not initOnly:
+        _cphttpserver.start()
+
+def stop():
+    _cphttpserver.shutdown()
+

cherrypy/_cputil.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.
+"""
+
+
+"""
+A module containing a few utility classes/functions used by CherryPy
+"""
+
+import time, thread, cpg, _cpdefaults, cperror
+
+try: import zlib
+except ImportError: pass
+
+class EmptyClass:
+    """ An empty class """
+    pass
+
+class ThreadAwareClass:
+    """ A thread-safe class for storing/retrieving
+        thread-specific variables """
+
+    def __init__(self):
+        self.__dict__['threadMap'] = {} # Used to store variables
+
+    def __setattr__(self, name, value):
+        id = thread.get_ident()
+        if not self.__dict__['threadMap'].has_key(id):
+            self.__dict__['threadMap'][id] = {}
+        self.threadMap[id][name] = value
+
+    def __getattr__(self, name):
+        id = thread.get_ident()
+        return self.__dict__['threadMap'][id][name]
+
+def getSpecialFunction(name):
+    """ Return the special function """
+
+    # First, we look in the right-most object if this special function is implemented.
+    # If not, then we try the previous object and so on until we reach cpg.root
+    # If it's still not there, we use the implementation from the
+    # "_cpdefaults.py" module
+    
+
+    moduleList = [_cpdefaults]
+    root = getattr(cpg, 'root', None)
+    if root:
+        moduleList.append(root)
+        # Try object path
+        try:
+            path = cpg.request.path
+        except:
+            path = ''
+        if path:
+            pathList = path.split('/')
+
+            obj = cpg.root
+            previousObj = None
+            # Successively get objects from the path
+            for newObj in pathList:
+                previousObj = obj
+                try:
+                    obj = getattr(obj, newObj)
+                    moduleList.append(obj)
+                except AttributeError:
+                    break
+
+    moduleList.reverse()
+    for module in moduleList:
+        func = getattr(module, name, None)
+        if func != None:
+            return func
+
+    raise cperror.InternalError, "Special function %s could not be found" % repr(name)
+

cherrypy/cperror.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.
+"""
+
+"""
+Main CherryPy module:
+    - Parses config file
+    - Creates the HTTP server
+"""
+
+class Error(Exception):
+    pass
+
+class InternalError(Error):
+    """ Error that should never happen """
+    pass
+
+class NotFound(Error):
+    """ Happens when a URL couldn't be mapped to any class.method """
+    pass
+
+class WrongResponseType(Error):
+    """ Happens when the cpg.response.body is not a string """
+    pass
+"""
+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.
+"""
+
+"""
+Global module that all modules developing with CherryPy should import.
+"""
+
+from __init__ import __version__
+
+# import server module
+import _cpserver as server
+
+# decorator function for exposing methods
+def expose(func):
+    func.exposed = True
+    return func
Add a comment to this file

cherrypy/lib/__init__.py

Empty file added.

cherrypy/lib/aspect.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.
+"""
+
+# return codes for _before and _after aspect methods
+STOP = 0
+CONTINUE = 1
+
+class Aspect(object):
+    """
+    Base class for aspects. Derive new aspect classes from this, then
+    override one or both of _before and _after.
+    """
+
+    def __getattribute__(self, methodName):
+
+        # find method specified by methodName
+        try:
+            method = object.__getattribute__(self, methodName)
+        except:
+            raise
+
+        # if requested attribute is not a method, simply return it
+        if not callable(method):
+            return method
+
+        # define wrapper function
+        def _wrapper(*k, **kw):
+            # call _before method
+            status, value = object.__getattribute__(self, '_before')(methodName, method)
+            if status == STOP:
+                return value
+
+            # call wrapped method and append results
+            result = method(*k, **kw)
+            if value:
+                result = value + result
+
+            # call _after method
+            status, value =  object.__getattribute__(self, '_after')(methodName, method)
+            if status == STOP:
+                return value
+            if value:
+                result += value
+
+            # done!
+            return result
+
+        # expose wrapper function if wrapped method is exposed
+        if getattr(method, 'exposed', None):
+            _wrapper.exposed = True
+
+        # return wrapper function. It'll get called instead of the
+        # requested method.
+        return _wrapper
+
+
+    def _before(self, methodName, method):
+        return CONTINUE, None
+
+
+    def _after(self, methodName, method):
+        return CONTINUE, None

cherrypy/lib/csauthenticate.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 time, whrandom
+from cherrypy import cpg
+
+from aspect import Aspect, STOP, CONTINUE
+
+class CSAuthenticate(Aspect):
+    timeoutMessage = "Session timed out"
+    wrongLoginPasswordMessage = "Wrong login/password"
+    noCookieMessage = "No cookie"
+    logoutMessage = "You have been logged out"
+    sessionIdCookieName = "CherrySessionId"
+    timeout = 60 # in minutes
+
+    def _before(self, methodName, method):
+        # If the method is not exposed, don't do anything
+        if not getattr(method, 'exposed', None):
+            return CONTINUE, None
+
+        cpg.request.login = ''
+        # If the method is one of these 4, do not try to find out who is logged in
+        if methodName in ["loginScreen", "logoutScreen", "doLogin", "doLogout"]:
+            return CONTINUE, None
+
+        # Check if a user is logged in:
+        #   - If they are, set request.login with the right value
+        #   - If not, return the login screen
+        if not cpg.request.simpleCookie.has_key(self.sessionIdCookieName):
+            return STOP, self.loginScreen(self.noCookieMessage, cpg.request.browserUrl)
+        sessionId = cpg.request.simpleCookie[self.sessionIdCookieName].value
+        now=time.time()
+
+        # Check that session exists and hasn't timed out
+        timeout=0
+        if not cpg.request.sessionMap.has_key(sessionId):
+            return STOP, self.loginScreen(self.noCookieMessage, cpg.request.browserUrl)
+        else:
+            login, expire = cpg.request.sessionMap[sessionId]
+            if expire < now: timeout=1
+            else:
+                expire = now + self.timeout*60
+                cpg.request.sessionMap[sessionId] = login, expire
+
+        if timeout:
+            return STOP, self.loginScreen(self.timeoutMessage, cpg.request.browserUrl)
+
+        cpg.request.login = login
+        return CONTINUE, None
+
+    def checkLoginAndPassword(self, login, password):
+        if (login,password) == ('login','password'): return ''
+        return 'Wrong login/password'
+
+    def generateSessionId(self, sessionIdList):
+        choice="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+        while 1:
+            sessionId=""
+            for dummy in range(20): sessionId += whrandom.choice(choice)
+            if sessionId not in sessionIdList: return sessionId
+
+    def doLogin(self, login, password, fromPage):
+        # Check that login/password match
+        errorMsg = self.checkLoginAndPassword(login, password)
+        if errorMsg:
+            cpg.request.login = ''
+            return self.loginScreen(errorMsg, fromPage, login)
+        cpg.request.login = login
+        # Set session
+        newSessionId = self.generateSessionId(cpg.request.sessionMap.keys())
+        cpg.request.sessionMap[newSessionId] = login, time.time()+self.timeout*60
+        
+        cpg.response.simpleCookie[self.sessionIdCookieName] = newSessionId
+        cpg.response.simpleCookie[self.sessionIdCookieName]['path'] = '/'
+        cpg.response.simpleCookie[self.sessionIdCookieName]['max-age'] = 31536000
+        cpg.response.simpleCookie[self.sessionIdCookieName]['version'] = 1
+        cpg.response.headerMap['Status'] = 302
+        cpg.response.headerMap['Location'] = fromPage
+        return ""
+    doLogin.exposed = True
+
+    def doLogout(self):
+        try:
+            sessionId = request.simpleCookie[self.sessionIdCookieName].value
+            del request.sessionMap[sessionId]
+        except: pass
+        
+        cpg.response.simpleCookie[self.sessionIdCookieName] = ""
+        cpg.response.simpleCookie[self.sessionIdCookieName]['path'] = '/'
+        cpg.response.simpleCookie[self.sessionIdCookieName]['max-age'] = 0
+        cpg.response.simpleCookie[self.sessionIdCookieName]['version'] = 1
+        cpg.request.login = ''
+        cpg.response.headerMap['Status'] = 302
+        cpg.response.headerMap['Location'] = 'logoutScreen' # TBCTBC: may not be the right URL
+        return ""
+    doLogout.exposed = True
+
+    def logoutScreen(self):
+        return self.loginScreen(self.logoutMessage, '/index') # TBC
+    logoutScreen.exposed = True
+
+    def loginScreen(self, message, fromPage, login=''):
+        return """
+        <html><body>
+            Message: %s
+            <form method="post" action="doLogin">
+                Login: <input type=text name=login value="%s" size=10><br>
+                Password: <input type=password name=password size=10><br>
+                <input type=hidden name=fromPage value="%s"><br>
+                <input type=submit>
+            </form>
+        </body></html>
+        """ % (message, login, fromPage)
+    loginScreen.exposed = True
Add a comment to this file

cherrypy/lib/filter/__init__.py

Empty file added.

cherrypy/lib/filter/basefilter.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.
+"""
+
+class BaseFilter(object):
+    """
+    Base class for filters. Derive new filter classes from this, then
+    override some of the methods to add some side-effects.
+    """
+
+    def afterRequestHeader(self):
+        """ Called after the request header has been read/parsed"""
+        pass
+
+    def afterRequestBody(self):
+        """ Called after the request body has been read/parsed"""
+        pass
+
+
+    def beforeResponse(self):
+        """ Called before starting to write response """
+        pass
+
+    def afterResponseHeader(self):
+        """ Called after writing the response header """
+        pass
+
+    def beforeResponseFullBody(self):
+        """ Called before writing the full response body """
+        pass
+
+    def beforeResponseOnTheFlyBody(self):
+        """ Called before writing a bit of the reponse (only used
+            when using "on-the-fly" response.
+        """
+        pass
+

cherrypy/lib/filter/encodingfilter.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.
+"""
+
+from basefilter import BaseFilter
+from cherrypy import cpg
+import types
+
+class EncodingFilter(BaseFilter):
+    """
+    Filter that automatically encodes the response.
+    """
+
+    def __init__(self, encoding = 'utf-8'):
+        self.encoding = encoding
+
+    def beforeResponse(self):
+        if isinstance(cpg.response.body, unicode):
+            # Encode the response
+            cpg.response.body = cpg.response.body.encode(self.encoding)
+            # Add "charset=..." to response Content-Type header
+            contentType = cpg.response.headerMap.get("Content-Type")
+            if contentType and 'charset' not in contentType:
+                cpg.response.headerMap["Content-Type"] += "; charset=%s" % self.encoding
+

cherrypy/lib/filter/generatorfilter.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.
+"""
+
+from basefilter import BaseFilter
+from cherrypy import cpg
+import types
+
+class GeneratorFilter(BaseFilter):
+    """
+    Filter that allows apps to return a generator.
+    """
+
+    def __init__(self, sendOnTheFly = False):
+        # List of mime-types to compress
+        self.sendOnTheFly = sendOnTheFly
+
+    def beforeResponse(self):
+        # TODO: Handle "sendOnTheFly" = True
+        if type(cpg.response.body) == types.GeneratorType:
+            cpg.response.body = "".join(list(cpg.response.body))
+
+

cherrypy/lib/filter/gzipfilter.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 gzip, StringIO
+from basefilter import BaseFilter
+from cherrypy import cpg
+
+class GzipFilter(BaseFilter):
+    """
+    Filter that gzips the response.
+    """
+
+    def __init__(self, mimeTypeList = ['text/html']):
+        # List of mime-types to compress
+        self.mimeTypeList = mimeTypeList
+
+    def beforeResponse(self):
+        ct = cpg.response.headerMap.get('Content-Type').split(';')[0]
+        ae = cpg.request.headerMap.get('Accept-Encoding', '')
+        if (ct in self.mimeTypeList) and ('gzip' in ae):
+            # Set header
+            cpg.response.headerMap['Content-Encoding'] = 'gzip'
+            # Compress page
+            zbuf = StringIO.StringIO()
+            zfile = gzip.GzipFile(mode='wb', fileobj = zbuf, compresslevel = 9)
+            zfile.write(cpg.response.body)
+            zfile.close()
+            cpg.response.body = zbuf.getvalue()
+
+

cherrypy/lib/filter/logdebuginfofilter.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 time, StringIO, pickle
+from basefilter import BaseFilter
+from cherrypy import cpg
+
+class LogDebugInfoFilter(BaseFilter):
+    """
+    Filter that adds debug information to the page
+    """
+
+    def __init__(self, mimeTypeList = ['text/html'], preTag = '<br><br>',
+            logBuildTime = True, logPageSize = True,
+            logSessionSize = True, logAsComment = False):
+        # List of mime-types to which this applies
+        self.mimeTypeList = mimeTypeList
+        self.preTag = preTag
+        self.logBuildTime = logBuildTime
+        self.logPageSize = logPageSize
+        self.logSessionSize = logSessionSize
+        self.logAsComment = logAsComment
+
+    def afterRequestBody(self):
+        cpg.request.startBuilTime = time.time()
+
+    def beforeResponse(self):
+        ct = cpg.response.headerMap.get('Content-Type')
+        if (ct in self.mimeTypeList):
+            if self.logAsComment:
+                cpg.response.body += '<!-- '
+            else:
+                cpg.response.body += self.preTag
+            logList = []
+            if self.logBuildTime:
+                logList.append("Build time: %.03fs" % (
+                    time.time() - cpg.request.startBuilTime))
+            if self.logPageSize:
+                logList.append("Page size: %.02fKB" % (
+                    len(cpg.response.body)/float(1024)))
+            if self.logSessionSize and cpg.configOption.sessionStorageType:
+                # Pickle session data to get its size
+                f = StringIO.StringIO()
+                pickle.dump(cpg.request.sessionMap, f, 1)
+                dumpStr = f.getvalue()
+                f.close()
+                logList.append("Session data size: %.02fKB" % (
+                    len(dumpStr)/float(1024)))
+
+            cpg.response.body += ', '.join(logList)
+            if self.logAsComment:
+                cpg.response.body += '-->'
+
+            if 'Content-Length' in cpg.response.headerMap:
+                cpg.response.headerMap['Content-Length'] = len(cpg.response.body)
+

cherrypy/lib/filter/tidyfilter.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, cgi
+from basefilter import BaseFilter
+from cherrypy import cpg
+
+class TidyFilter(BaseFilter):
+    """
+    Filter that runs the response through Tidy.
+    Note that we use the standalone Tidy tool rather than the python
+    mxTidy module. This is because this module doesn't seem to be
+    stable and it crashes on some HTML pages (which means that the
+    server would also crash)
+    """
+
+    def __init__(self, tidyPath, tmpDir, errorsToIgnore = []):
+        self.tidyPath = tidyPath
+        self.tmpDir = tmpDir
+        self.errorsToIgnore = errorsToIgnore
+
+    def beforeResponse(self):
+        ct = cpg.response.headerMap.get('Content-Type')
+        if ct == 'text/html':
+            pageFile = os.path.join(self.tmpDir, 'page.html')
+            outFile = os.path.join(self.tmpDir, 'tidy.out')
+            errFile = os.path.join(self.tmpDir, 'tidy.err')
+            f = open(pageFile, 'wb')
+            f.write(cpg.response.body)
+            f.close()
+            encoding = cpg.response.headerMap.get('Content-Encoding', '')
+            if encoding:
+                encoding = '-u' + encoding
+            os.system('"%s" %s -f %s -o %s %s' % (
+                self.tidyPath, encoding, errFile, outFile, pageFile))
+            f = open(errFile, 'rb')
+            err = f.read()
+            f.close()
+
+            errList = err.splitlines()
+            newErrList = []
+            for err in errList:
+                if (err.find('Warning') != -1 or err.find('Error') != -1):
+                    ignore = 0
+                    for errIgn in self.errorsToIgnore:
+                        if err.find(errIgn) != -1:
+                            ignore = 1
+                            break
+                    if not ignore: newErrList.append(err)
+
+            if newErrList:
+                oldHtml = cpg.response.body
+                cpg.response.body = "Wrong HTML:<br>" + cgi.escape('\n'.join(newErrList)).replace('\n','<br>')
+                cpg.response.body += '<br><br>'
+                i=0
+                for line in oldHtml.splitlines():
+                    i += 1
+                    cpg.response.body += "%03d - "%i + cgi.escape(line).replace('\t','    ').replace(' ','&nbsp;') + '<br>'
+
+                cpg.response.headerMap['Content-Length'] = len(cpg.response.body)

cherrypy/lib/httptools.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.
+"""
+
+"""
+Just a few convenient functions
+"""
+
+from cherrypy import cpg
+
+def redirect(newUrl):
+    """ Sends a redirect to the browser """
+    
+    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
+    cpg.response.headerMap['Status'] = 302
+    cpg.response.headerMap['Location'] = newUrl
+    return ""

cherrypy/tutorial/Readme.txt

+CherryPy Tutorials
+------------------------------------------------------------------------
+
+This is a series of tutorials explaining how to develop dynamic web
+applications using CherryPy. A couple of notes:
+
+  - Each of these tutorials builds on the ones before it. If you're
+    new to CherryPy, we recommend you start with tutorial01.py and
+    work your way upwards. :)
+
+  - In most of these tutorials, you will notice that all output is done
+    by returning normal Python strings, often using simple Python
+    variable substitution. In most real-world applications, you will
+    probably want to use a separate template package (like Cheetah,
+    CherryTemplate or XML/XSL).
+
Add a comment to this file

cherrypy/tutorial/__init__.py

Empty file added.

cherrypy/tutorial/bonus-sqlobject.py

+'''
+Bonus Tutorial: Using SQLObject
+
+This is a silly little contacts manager application intended to
+demonstrate how to use SQLObject from within a CherryPy2 project. It
+also shows how to use inline Cheetah templates.
+
+SQLObject is an Object/Relational Mapper that allows you to access
+data stored in an RDBMS in a pythonic fashion. You create data objects
+as Python classes and let SQLObject take care of all the nasty details.
+
+This code depends on the latest development version (0.6+) of SQLObject.
+You can get it from the SQLObject Subversion server. You can find all
+necessary information at <http://www.sqlobject.org>. This code will NOT
+work with the 0.5.x version advertised on their website!
+
+This code also depends on a recent version of Cheetah. You can find
+Cheetah at <http://www.cheetahtemplate.org>.
+
+After starting this application for the first time, you will need to
+access the /reset URI in order to create the database table and some
+sample data. Accessing /reset again will drop and re-create the table,
+so you may want to be careful. :-)
+
+This application isn't supposed to be fool-proof, it's not even supposed
+to be very GOOD. Play around with it some, browse the source code, smile.
+
+:)
+
+-- Hendrik Mans <hendrik@mans.de>
+'''
+
+from cherrypy import cpg
+from Cheetah.Template import Template
+from sqlobject import *
+
+# configure your database connection here
+__connection__ = 'mysql://root:@localhost/test'
+
+# this is our (only) data class.
+class Contact(SQLObject):
+    lastName = StringCol(length = 50, notNone = True)
+    firstName = StringCol(length = 50, notNone = True)
+    phone = StringCol(length = 30, notNone = True, default = '')
+    email = StringCol(length = 30, notNone = True, default = '')
+    url = StringCol(length = 100, notNone = True, default = '')
+