CherryPy-2.3.0/0000755000175000017500000000000010742544024014015 5ustar christianchristianCherryPy-2.3.0/cherrypy/0000755000175000017500000000000010742544024015662 5ustar christianchristianCherryPy-2.3.0/cherrypy/filters/0000755000175000017500000000000010742544024017332 5ustar christianchristianCherryPy-2.3.0/cherrypy/filters/__init__.py0000644000175000017500000001311110742543111021434 0ustar christianchristianimport cherrypy from cherrypy import _cputil # These are in order for a reason! # Entries in the input_filters and output_filters lists # may be either a class, or the full package name of a class. input_filters = [ "cherrypy.filters.cachefilter.CacheFilter", "cherrypy.filters.logdebuginfofilter.LogDebugInfoFilter", "cherrypy.filters.baseurlfilter.BaseUrlFilter", "cherrypy.filters.virtualhostfilter.VirtualHostFilter", "cherrypy.filters.decodingfilter.DecodingFilter", "cherrypy.filters.sessionfilter.SessionFilter", "cherrypy.filters.sessionauthenticatefilter.SessionAuthenticateFilter", "cherrypy.filters.staticfilter.StaticFilter", "cherrypy.filters.nsgmlsfilter.NsgmlsFilter", "cherrypy.filters.tidyfilter.TidyFilter", "cherrypy.filters.xmlrpcfilter.XmlRpcFilter", ] output_filters = [ "cherrypy.filters.responseheadersfilter.ResponseHeadersFilter", "cherrypy.filters.xmlrpcfilter.XmlRpcFilter", "cherrypy.filters.encodingfilter.EncodingFilter", "cherrypy.filters.tidyfilter.TidyFilter", "cherrypy.filters.nsgmlsfilter.NsgmlsFilter", "cherrypy.filters.logdebuginfofilter.LogDebugInfoFilter", "cherrypy.filters.gzipfilter.GzipFilter", "cherrypy.filters.sessionfilter.SessionFilter", "cherrypy.filters.cachefilter.CacheFilter", ] _input_methods = ['on_start_resource', 'before_request_body', 'before_main'] _output_methods = ['before_finalize', 'on_end_resource', 'on_end_request', 'before_error_response', 'after_error_response'] _old_input_methods = ['onStartResource', 'beforeRequestBody', 'beforeMain'] _old_output_methods = ['beforeFinalize', 'onEndResource', 'onEndRequest', 'beforeErrorResponse', 'afterErrorResponse'] backward_compatibility_dict = { 'on_start_resource': 'onStartResource', 'before_request_body': 'beforeRequestBody', 'before_main': 'beforeMain', 'before_finalize': 'beforeFinalize', 'on_end_resource': 'onEndResource', 'on_end_request': 'onEndRequest', 'before_error_response': 'beforeErrorResponse', 'after_error_response': 'afterErrorResponse' } def init(): """Initialize the filters.""" from cherrypy.lib import cptools instances = {} inputs, outputs = [], [] conf = cherrypy.config.get for filtercls in input_filters + conf('server.input_filters', []): if isinstance(filtercls, basestring): filtercls = cptools.attributes(filtercls) f = instances.get(filtercls) if f is None: f = instances[filtercls] = filtercls() inputs.append(f) for filtercls in conf('server.output_filters', []) + output_filters: if isinstance(filtercls, basestring): filtercls = cptools.attributes(filtercls) f = instances.get(filtercls) if f is None: f = instances[filtercls] = filtercls() outputs.append(f) # Transform the instance lists into a dict of methods # in 2.2 we check the old camelCase filter names first # to provide backward compatibility with 2.1 _filterhooks.clear() for old_name, new_name in zip(_old_input_methods, _input_methods): _filterhooks[new_name] = [] for f in inputs: method = getattr(f, old_name, None) if method: _filterhooks[new_name].append(method) else: method = getattr(f, new_name, None) if method: _filterhooks[new_name].append(method) for old_name, new_name in zip(_old_output_methods, _output_methods): _filterhooks[new_name] = [] for f in outputs: method = getattr(f, old_name, None) if method: _filterhooks[new_name].append(method) else: method = getattr(f, new_name, None) if method: _filterhooks[new_name].append(method) _filterhooks = {} def applyFilters(method_name, failsafe=False): """Execute the given method for all registered filters.""" special_methods = [] for f in _cputil.get_special_attribute("_cp_filters", "_cpFilterList"): if cherrypy.lowercase_api is False: # Try old name first old_method_name = backward_compatibility_dict.get(method_name) method = getattr(f, old_method_name, None) if (method is None): method = getattr(f, method_name, None) if method: special_methods.append(method) else: # We know for sure that user uses the new lowercase API method = getattr(f, method_name, None) if method: special_methods.append(method) if method_name in _input_methods: # Run special filters after defaults. methods = _filterhooks[method_name] + special_methods else: # Run special filters before defaults. methods = special_methods + _filterhooks[method_name] for method in methods: # The on_start_resource, on_end_resource, and on_end_request methods # are guaranteed to run even if other methods of the same name fail. # We will still log the failure, but proceed on to the next method. # The only way to stop all processing from one of these methods is # to raise SystemExit and stop the whole server. So, trap your own # errors in these methods! if failsafe: try: method() except (KeyboardInterrupt, SystemExit): raise except: cherrypy.log(traceback=True) else: method() CherryPy-2.3.0/cherrypy/filters/basefilter.py0000644000175000017500000000213610742543111022022 0ustar christianchristian"""Base class for CherryPy filters.""" 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 on_start_resource(self): """Called before any request processing has been done""" pass def before_request_body(self): """Called after the request header has been read/parsed""" pass def before_main(self): """ Called after the request body has been read/parsed""" pass def before_finalize(self): """Called before final output processing""" pass def before_error_response(self): """Called before _cp_on_error and/or finalizing output""" pass def after_error_response(self): """Called after _cp_on_error and finalize""" pass def on_end_resource(self): """Called after finalizing the output (status, header, and body)""" pass def on_end_request(self): """Called when the server closes the request.""" pass CherryPy-2.3.0/cherrypy/filters/baseurlfilter.py0000644000175000017500000000202010742543111022535 0ustar christianchristianimport cherrypy from basefilter import BaseFilter class BaseUrlFilter(BaseFilter): """Filter that changes the base URL. Useful when running a CP server behind Apache. """ def before_request_body(self): if not cherrypy.config.get('base_url_filter.on', False): return request = cherrypy.request port = str(cherrypy.config.get('server.socket_port', '80')) if port == "80": defaultUrl = 'http://localhost' else: defaultUrl = 'http://localhost:%s' % port newBaseUrl = cherrypy.config.get('base_url_filter.base_url', defaultUrl) if cherrypy.config.get('base_url_filter.use_x_forwarded_host', True): newBaseUrl = request.headers.get("X-Forwarded-Host", newBaseUrl) if newBaseUrl.find("://") == -1: # add http:// or https:// if needed newBaseUrl = request.base[:request.base.find("://") + 3] + newBaseUrl request.base = newBaseUrl CherryPy-2.3.0/cherrypy/filters/cachefilter.py0000644000175000017500000002336210742543111022157 0ustar christianchristianimport datetime import Queue import threading import time import cherrypy from cherrypy.lib import httptools import basefilter class MemoryCache: def __init__(self): self.clear() self.expirationQueue = Queue.Queue() t = self.expirationThread = threading.Thread(target=self.expireCache, name='expireCache') t.setDaemon(True) t.start() def clear(self): """Reset the cache to its initial, empty state.""" self.cache = {} self.totPuts = 0 self.totGets = 0 self.totHits = 0 self.totExpires = 0 self.totNonModified = 0 self.cursize = 0 def _key(self): return cherrypy.config.get("cache_filter.key", cherrypy.request.browser_url) key = property(_key) def _maxobjsize(self): return cherrypy.config.get("cache_filter.maxobjsize", 100000) maxobjsize = property(_maxobjsize) def _maxsize(self): return cherrypy.config.get("cache_filter.maxsize", 10000000) maxsize = property(_maxsize) def _maxobjects(self): return cherrypy.config.get("cache_filter.maxobjects", 1000) maxobjects = property(_maxobjects) def expireCache(self): while True: expirationTime, objSize, objKey = self.expirationQueue.get(block=True, timeout=None) # expireCache runs in a separate thread which the servers are # not aware of. It's possible that "time" will be set to None # arbitrarily, so we check "while time" to avoid exceptions. # See tickets #99 and #180 for more information. while time and (time.time() < expirationTime): time.sleep(0.1) try: del self.cache[objKey] self.totExpires += 1 self.cursize -= objSize except KeyError: # the key may have been deleted elsewhere pass def get(self): """ If the content is in the cache, returns a tuple containing the expiration time, the lastModified response header and the object (rendered as a string); returns None if the key is not found. """ self.totGets += 1 cacheItem = self.cache.get(self.key, None) if cacheItem: self.totHits += 1 return cacheItem else: return None def put(self, lastModified, obj): # Size check no longer includes header length objSize = len(obj[2]) totalSize = self.cursize + objSize # checks if there's space for the object if ((objSize < self.maxobjsize) and (totalSize < self.maxsize) and (len(self.cache) < self.maxobjects)): # add to the expirationQueue & cache try: expirationTime = cherrypy.response.time + cherrypy.config.get("cache_filter.delay", 600) objKey = self.key self.expirationQueue.put((expirationTime, objSize, objKey)) self.cache[objKey] = (expirationTime, lastModified, obj) self.totPuts += 1 self.cursize += objSize except Queue.Full: # can't add because the queue is full return def delete(self): self.cache.pop(self.key) class CacheFilter(basefilter.BaseFilter): """If the page is already stored in the cache, serves the contents. If the page is not in the cache, caches the output. """ def __init__(self): cache_class = cherrypy.config.get("cache_filter.cacheClass", MemoryCache) cherrypy._cache = cache_class() def on_start_resource(self): cherrypy.request.cacheable = False def before_main(self): if not cherrypy.config.get('cache_filter.on', False): return request = cherrypy.request response = cherrypy.response # POST, PUT, DELETE should invalidate (delete) the cached copy. # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.10. if request.method in cherrypy.config.get("cache_filter.invalid_methods", ("POST", "PUT", "DELETE")): cherrypy._cache.delete() return cacheData = cherrypy._cache.get() if cacheData: # found a hit! check the if-modified-since request header expirationTime, lastModified, obj = cacheData s, h, b, create_time = obj modifiedSince = request.headers.get('If-Modified-Since', None) if modifiedSince is not None and modifiedSince == lastModified: cherrypy._cache.totNonModified += 1 response.status = "304 Not Modified" ct = h.get('Content-Type', None) if ct: response.headers['Content-Type'] = ct response.body = None else: # serve it & get out from the request response = cherrypy.response response.status, response.headers, response.body = s, h, b response.headers['Age'] = str(int(response.time - create_time)) request.execute_main = False else: request.cacheable = True def before_finalize(self): if not cherrypy.request.cacheable: return cherrypy.response._cachefilter_tee = [] def tee(body): """Tee response.body into response._cachefilter_tee (a list).""" for chunk in body: cherrypy.response._cachefilter_tee.append(chunk) yield chunk cherrypy.response.body = tee(cherrypy.response.body) def on_end_request(self): # Close & fix the cache entry after content was fully written if not cherrypy.request.cacheable: return response = cherrypy.response if response.headers.get('Pragma', None) != 'no-cache': lastModified = response.headers.get('Last-Modified', None) # save the cache data body = ''.join([chunk for chunk in response._cachefilter_tee]) create_time = time.time() cherrypy._cache.put(lastModified, (response.status, response.headers, body, create_time)) def percentual(n,d): """calculates the percentual, dealing with div by zeros""" if d == 0: return 0 else: return (float(n)/float(d))*100 def formatSize(n): """formats a number as a memory size, in bytes, kbytes, MB, GB)""" if n < 1024: return "%4d bytes" % n elif n < 1024*1024: return "%4d kbytes" % (n / 1024) elif n < 1024*1024*1024: return "%4d MB" % (n / (1024*1024)) else: return "%4d GB" % (n / (1024*1024*1024)) class CacheStats: def index(self): cherrypy.response.headers['Content-Type'] = 'text/plain' cherrypy.response.headers['Pragma'] = 'no-cache' cache = cherrypy._cache yield "Cache statistics\n" yield "Maximum object size: %s\n" % formatSize(cache.maxobjsize) yield "Maximum cache size: %s\n" % formatSize(cache.maxsize) yield "Maximum number of objects: %d\n" % cache.maxobjects yield "Current cache size: %s\n" % formatSize(cache.cursize) yield "Approximated expiration queue size: %d\n" % cache.expirationQueue.qsize() yield "Number of cache entries: %d\n" % len(cache.cache) yield "Total cache writes: %d\n" % cache.totPuts yield "Total cache read attempts: %d\n" % cache.totGets yield "Total hits: %d (%1.2f%%)\n" % (cache.totHits, percentual(cache.totHits, cache.totGets)) yield "Total misses: %d (%1.2f%%)\n" % (cache.totGets-cache.totHits, percentual(cache.totGets-cache.totHits, cache.totGets)) yield "Total expires: %d\n" % cache.totExpires yield "Total non-modified content: %d\n" % cache.totNonModified index.exposed = True def expires(secs=0, force=False): """Tool for influencing cache mechanisms using the 'Expires' header. 'secs' must be either an int or a datetime.timedelta, and indicates the number of seconds between response.time and when the response should expire. The 'Expires' header will be set to (response.time + secs). If 'secs' is zero, the following "cache prevention" headers are also set: 'Pragma': 'no-cache' 'Cache-Control': 'no-cache' If 'force' is False (the default), the following headers are checked: 'Etag', 'Last-Modified', 'Age', 'Expires'. If any are already present, none of the above response headers are set. """ response = cherrypy.response cacheable = False if not force: # some header names that indicate that the response can be cached for indicator in ('Etag', 'Last-Modified', 'Age', 'Expires'): if indicator in response.headers: cacheable = True break if not cacheable: if isinstance(secs, datetime.timedelta): secs = (86400 * secs.days) + secs.seconds if secs == 0: if force or ("Pragma" not in response.headers): response.headers["Pragma"] = "no-cache" if cherrypy.response.version >= "1.1": if force or ("Cache-Control" not in response.headers): response.headers["Cache-Control"] = "no-cache" expiry = httptools.HTTPDate(time.gmtime(response.time + secs)) if force or ("Expires" not in response.headers): response.headers["Expires"] = expiry CherryPy-2.3.0/cherrypy/filters/decodingfilter.py0000644000175000017500000000423010742543111022661 0ustar christianchristianimport cherrypy from basefilter import BaseFilter class DecodingFilter(BaseFilter): """Automatically decodes request parameters (except uploads).""" def before_main(self): conf = cherrypy.config.get if not conf('decoding_filter.on', False): return enc = conf('decoding_filter.encoding', None) if not enc: ct = cherrypy.request.headers.elements("Content-Type") if ct: ct = ct[0] enc = ct.params.get("charset", None) if (not enc) and ct.value.lower().startswith("text/"): # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1 # When no explicit charset parameter is provided by the # sender, media subtypes of the "text" type are defined # to have a default charset value of "ISO-8859-1" when # received via HTTP. enc = "ISO-8859-1" if not enc: enc = conf('decoding_filter.default_encoding', "utf-8") try: self.decode(enc) except UnicodeDecodeError: # IE and Firefox don't supply a charset when submitting form # params with a CT of application/x-www-form-urlencoded. # So after all our guessing, it could *still* be wrong. # Start over with ISO-8859-1, since that seems to be preferred. self.decode("ISO-8859-1") def decode(self, enc): decodedParams = {} for key, value in cherrypy.request.params.items(): if hasattr(value, 'file'): # This is a file being uploaded: skip it decodedParams[key] = value elif isinstance(value, list): # value is a list: decode each element decodedParams[key] = [v.decode(enc) for v in value] else: # value is a regular string: decode it decodedParams[key] = value.decode(enc) # Decode all or nothing, so we can try again on error. cherrypy.request.params = decodedParams CherryPy-2.3.0/cherrypy/filters/encodingfilter.py0000644000175000017500000001053310742543111022676 0ustar christianchristianimport cherrypy from basefilter import BaseFilter class EncodingFilter(BaseFilter): """Filter that automatically encodes the response.""" def before_finalize(self): conf = cherrypy.config.get if not conf('encoding_filter.on', False): return ct = cherrypy.response.headers.elements("Content-Type") if ct: ct = ct[0] if ct.value.lower().startswith("text/"): # Set "charset=..." param on response Content-Type header ct.params['charset'] = find_acceptable_charset() cherrypy.response.headers["Content-Type"] = str(ct) def encode_stream(encoding, errors='strict'): """Encode a streaming response body. Use a generator wrapper, and just pray it works as the stream is being written out. """ def encoder(body): for chunk in body: if isinstance(chunk, unicode): chunk = chunk.encode(encoding, errors) yield chunk cherrypy.response.body = encoder(cherrypy.response.body) return True def encode_string(encoding, errors='strict'): """Encode a buffered response body.""" try: body = [] for chunk in cherrypy.response.body: if isinstance(chunk, unicode): chunk = chunk.encode(encoding, errors) body.append(chunk) cherrypy.response.body = body # Delete Content-Length header so finalize() recalcs it. cherrypy.response.headers.pop("Content-Length", None) except (LookupError, UnicodeError): return False else: return True def find_acceptable_charset(): conf = cherrypy.config.get response = cherrypy.response attempted_charsets = [] stream = conf("stream_response", False) if stream: encode = encode_stream else: response.collapse_body() encode = encode_string failmsg = "The response could not be encoded with %s" errors = conf('encoding_filter.errors', 'strict') enc = conf('encoding_filter.encoding', None) if enc is not None: # If specified, force this encoding to be used, or fail. if encode(enc, errors): return enc else: raise cherrypy.HTTPError(500, failmsg % enc) # Parse the Accept_Charset request header, and try to provide one # of the requested charsets (in order of user preference). default_enc = conf('encoding_filter.default_encoding', 'utf-8') encs = cherrypy.request.headerMap.elements('Accept-Charset') if not encs: # Any character-set is acceptable. charsets = [] if encode(default_enc, errors): return default_enc else: raise cherrypy.HTTPError(500, failmsg % default_enc) else: charsets = [enc.value.lower() for enc in encs] if "*" not in charsets: # If no "*" is present in an Accept-Charset field, then all # character sets not explicitly mentioned get a quality # value of 0, except for ISO-8859-1, which gets a quality # value of 1 if not explicitly mentioned. iso = 'iso-8859-1' if iso not in charsets: attempted_charsets.append(iso) if encode(iso, errors): return iso for element in encs: if element.qvalue > 0: if element.value == "*": # Matches any charset. Try our default. if default_enc not in attempted_charsets: attempted_charsets.append(default_enc) if encode(default_enc, errors): return default_enc else: encoding = element.value if encoding not in attempted_charsets: attempted_charsets.append(encoding) if encode(encoding, errors): return encoding # No suitable encoding found. ac = cherrypy.request.headers.get('Accept-Charset') if ac is None: msg = "Your client did not send an Accept-Charset header." else: msg = "Your client sent this Accept-Charset header: %s." % ac msg += " We tried these charsets: %s." % ", ".join(attempted_charsets) raise cherrypy.HTTPError(406, msg) CherryPy-2.3.0/cherrypy/filters/gzipfilter.py0000644000175000017500000000637710742543111022074 0ustar christianchristianimport struct import time import zlib import cherrypy from basefilter import BaseFilter class GzipFilter(BaseFilter): """Filter that gzips the response.""" def before_finalize(self): if not cherrypy.config.get('gzip_filter.on', False): return response = cherrypy.response if not response.body: # Response body is empty (might be a 304 for instance) return def zipit(): # Return a generator that compresses the page varies = response.headers.get("Vary", "") varies = [x.strip() for x in varies.split(",") if x.strip()] if "Accept-Encoding" not in varies: varies.append("Accept-Encoding") response.headers['Vary'] = ", ".join(varies) response.headers['Content-Encoding'] = 'gzip' level = cherrypy.config.get('gzip_filter.compresslevel', 9) response.body = self.zip_body(response.body, level) # Delete Content-Length header so finalize() recalcs it. response.headers.pop("Content-Length", None) acceptable = cherrypy.request.headers.elements('Accept-Encoding') if not acceptable: # If no Accept-Encoding field is present in a request, # the server MAY assume that the client will accept any # content coding. In this case, if "identity" is one of # the available content-codings, then the server SHOULD use # the "identity" content-coding, unless it has additional # information that a different content-coding is meaningful # to the client. return ct = response.headers.get('Content-Type').split(';')[0] ct = ct in cherrypy.config.get('gzip_filter.mime_types', ['text/html', 'text/plain']) for coding in acceptable: if coding.value == 'identity' and coding.qvalue != 0: return if coding.value in ('gzip', 'x-gzip'): if coding.qvalue == 0: return if ct: zipit() return cherrypy.HTTPError(406, "identity, gzip").set_response() def write_gzip_header(self): """Adapted from the gzip.py standard module code""" header = '\037\213' # magic header header += '\010' # compression method header += '\0' header += struct.pack("', i) if j == -1: break original_body = original_body[:i] + original_body[j+9:] tmpdir = cherrypy.config.get('nsgmls_filter.tmp_dir') page_file = os.path.join(tmpdir, 'page.html') err_file = os.path.join(tmpdir, 'nsgmls.err') f = open(page_file, 'wb') f.write(original_body) f.close() nsgmls_path = cherrypy.config.get('nsgmls_filter.nsgmls_path') catalog_path = cherrypy.config.get('nsgmls_filter.catalog_path') command = '%s -c%s -f%s -s -E10 %s' % ( nsgmls_path, catalog_path, err_file, page_file) command = command.replace('\\', '/') os.system(command) f = open(err_file, 'rb') err = f.read() f.close() errs = err.splitlines() new_errs = [] for err in errs: ignore = False for err_ign in cherrypy.config.get('nsgmls_filter.errors_to_ignore', []): if err.find(err_ign) != -1: ignore = True break if not ignore: new_errs.append(err) if new_errs: new_body = "Wrong HTML:
" + cgi.escape('\n'.join(new_errs)).replace('\n','
') new_body += '

' i = 0 for line in original_body.splitlines(): i += 1 new_body += "%03d - "%i + cgi.escape(line).replace('\t',' ').replace(' ',' ') + '
' cherrypy.response.body = new_body # Delete Content-Length header so finalize() recalcs it. cherrypy.response.headers.pop("Content-Length", None) CherryPy-2.3.0/cherrypy/filters/responseheadersfilter.py0000644000175000017500000000123010742543111024274 0ustar christianchristianimport cherrypy from basefilter import BaseFilter class ResponseHeadersFilter(BaseFilter): """Filter that allows HTTP headers to be defined for all responses""" def before_finalize(self): conf = cherrypy.config.get if not conf('response_headers_filter.on', False): return # headers must be a list of tuples headers = conf('response_headers_filter.headers', []) for item in headers: headername = item[0] headervalue = item[1] if headername not in cherrypy.response.headerMap: cherrypy.response.headerMap[headername] = headervalue CherryPy-2.3.0/cherrypy/filters/sessionauthenticatefilter.py0000644000175000017500000001003110742543111025163 0ustar christianchristianimport cherrypy from basefilter import BaseFilter def default_login_screen(from_page, login = '', error_msg = ''): return """ Message: %s
Login:
Password:

""" % (error_msg, login, from_page) def default_check_login_and_password(login, password): # Dummy check_login_and_password function if login != 'login' or password != 'password': return u'Wrong login/password' class SessionAuthenticateFilter(BaseFilter): """ Filter allows for simple forms based authentication and access control """ def before_main(self): cherrypy.request.user = None conf = cherrypy.config.get if ((not conf('session_authenticate_filter.on', False)) or conf('static_filter.on', False)): return check_login_and_password = cherrypy.config.get('session_authenticate_filter.check_login_and_password', default_check_login_and_password) login_screen = cherrypy.config.get('session_authenticate_filter.login_screen', default_login_screen) not_logged_in = cherrypy.config.get('session_authenticate_filter.not_logged_in') load_user_by_username = cherrypy.config.get('session_authenticate_filter.load_user_by_username') session_key = cherrypy.config.get('session_authenticate_filter.session_key', 'username') on_login = cherrypy.config.get('session_authenticate_filter.on_login', None) on_logout = cherrypy.config.get('session_authenticate_filter.on_logout', None) if cherrypy.request.path.endswith('login_screen'): return elif cherrypy.request.path.endswith('do_logout'): login = cherrypy.session.get(session_key) cherrypy.session[session_key] = None cherrypy.request.user = None if login and on_logout: on_logout(login) from_page = cherrypy.request.params.get('from_page', '..') raise cherrypy.HTTPRedirect(from_page) elif cherrypy.request.path.endswith('do_login'): from_page = cherrypy.request.params.get('from_page', '..') login = cherrypy.request.params['login'] password = cherrypy.request.params['password'] error_msg = check_login_and_password(login, password) if error_msg: cherrypy.response.body = login_screen(from_page, login = login, error_msg = error_msg) # Delete Content-Length header so finalize() recalcs it. cherrypy.response.headers.pop("Content-Length", None) cherrypy.request.execute_main = False else: cherrypy.session[session_key] = login if on_login: on_login(login) if not from_page: from_page = '/' raise cherrypy.HTTPRedirect(from_page) return # Check if user is logged in temp_user = None if (not cherrypy.session.get(session_key)) and not_logged_in: # Call not_logged_in so that applications where anynymous user # is OK can handle it temp_user = not_logged_in() if (not cherrypy.session.get(session_key)) and not temp_user: cherrypy.response.body = login_screen(cherrypy.request.browser_url) # Delete Content-Length header so finalize() recalcs it. cherrypy.response.headers.pop("Content-Length", None) cherrypy.request.execute_main = False return # Everything is OK: user is logged in if load_user_by_username and not cherrypy.request.user: username = temp_user or cherrypy.session[session_key] cherrypy.request.user = load_user_by_username(username) CherryPy-2.3.0/cherrypy/filters/sessionfilter.py0000644000175000017500000004420610742543111022577 0ustar christianchristian""" Session implementation for CherryPy. We use cherrypy.request to store some convenient variables as well as data about the session for the current request. Instead of polluting cherrypy.request we use a dummy object called cherrypy.request._session (sess) to store these variables. Variables used to store config options: - sess.session_timeout: timeout delay for the session - sess.session_locking: mechanism used to lock the session ('implicit' or 'explicit') Variables used to store temporary variables: - sess.session_storage (instance of the class implementing the backend) Variables used to store the session for the current request: - sess.session_data: dictionary containing the actual session data - sess.session_id: current session ID - sess.expiration_time: date/time when the current session will expire Global variables (RAM backend only): - cherrypy._session_lock_dict: dictionary containing the locks for all session_id - cherrypy._session_data_holder: dictionary containing the data for all sessions """ import datetime import os try: import cPickle as pickle except ImportError: import pickle import random import sha import time import thread import threading import types import cherrypy import basefilter from cherrypy.lib import httptools class EmptyClass: """ An empty class """ pass class SessionDeadlockError(Exception): """ The session could not acquire a lock after a certain time """ pass class SessionNotEnabledError(Exception): """ User forgot to set session_filter.on to True """ pass class SessionStoragePathNotConfiguredError(Exception): """ User set storage_type to file but forgot to set the storage_path """ pass class SessionFilter(basefilter.BaseFilter): def on_start_resource(self): cherrypy.request._session = EmptyClass() def before_request_body(self): conf = cherrypy.config.get sess = cherrypy.request._session if not conf('session_filter.on', False): sess.session_storage = None return sess.locked = False # Not locked by default sess.to_be_loaded = True # Read config options sess.session_timeout = conf('session_filter.timeout', 60) sess.session_locking = conf('session_filter.locking', 'explicit') sess.on_create_session = conf('session_filter.on_create_session', lambda data: None) sess.on_renew_session = conf('session_filter.on_renew_session', lambda data: None) sess.on_delete_session = conf('session_filter.on_delete_session', lambda data: None) sess.generate_session_id = conf('session_filter.generate_session_id', generate_session_id) clean_up_delay = conf('session_filter.clean_up_delay', 5) clean_up_delay = datetime.timedelta(seconds = clean_up_delay * 60) cookie_name = conf('session_filter.cookie_name', 'session_id') cookie_domain = conf('session_filter.cookie_domain', None) cookie_secure = conf('session_filter.cookie_secure', False) cookie_path = conf('session_filter.cookie_path', None) if cookie_path is None: cookie_path_header = conf('session_filter.cookie_path_from_header', None) if cookie_path_header is not None: cookie_path = cherrypy.request.headerMap.get(cookie_path_header, None) if cookie_path is None: cookie_path = '/' sess.deadlock_timeout = conf('session_filter.deadlock_timeout', 30) storage = conf('session_filter.storage_type', 'Ram') storage = storage[0].upper() + storage[1:] # People can set their own custom class # through session_filter.storage_class sess.session_storage = conf('session_filter.storage_class', None) if sess.session_storage is None: sess.session_storage = globals()[storage + 'Storage']() else: sess.session_storage = sess.session_storage() now = datetime.datetime.now() # Check if we need to clean up old sessions if cherrypy._session_last_clean_up_time + clean_up_delay < now: cherrypy._session_last_clean_up_time = now # Run clean_up function in other thread to avoid blocking # this request thread.start_new_thread(sess.session_storage.clean_up, (sess,)) # Check if request came with a session ID if cookie_name in cherrypy.request.simple_cookie: # It did: we mark the data as needing to be loaded sess.session_id = cherrypy.request.simple_cookie[cookie_name].value # If using implicit locking, acquire lock if sess.session_locking == 'implicit': sess.session_data = {'_id': sess.session_id} sess.session_storage.acquire_lock() sess.to_be_loaded = True else: # No session_id yet id = None while id is None: id = sess.generate_session_id() # Assert that the generated id is not already stored. if sess.session_storage.load(id) is not None: id = None sess.session_id = id sess.session_data = {'_id': sess.session_id} sess.on_create_session(sess.session_data) # Set response cookie cookie = cherrypy.response.simple_cookie cookie[cookie_name] = sess.session_id cookie[cookie_name]['path'] = cookie_path # We'd like to use the "max-age" param as # http://www.faqs.org/rfcs/rfc2109.html indicates but IE doesn't # save it to disk and the session is lost if people close # the browser # So we have to use the old "expires" ... sigh ... #cookie[cookie_name]['max-age'] = sess.session_timeout * 60 if sess.session_timeout: gmt_expiration_time = time.gmtime(time.time() + (sess.session_timeout * 60)) cookie[cookie_name]['expires'] = httptools.HTTPDate(gmt_expiration_time) if cookie_domain is not None: cookie[cookie_name]['domain'] = cookie_domain if cookie_secure is True: cookie[cookie_name]['secure'] = 1 def before_finalize(self): def saveData(body, sess): # If the body is a generator, we have to save the data # *after* the generator has been consumed if isinstance(body, types.GeneratorType): for line in body: yield line # Save session data if sess.to_be_loaded is False: t = datetime.timedelta(seconds = sess.session_timeout * 60) expiration_time = datetime.datetime.now() + t sess.session_storage.save(sess.session_id, sess.session_data, expiration_time) else: # If session data has never been loaded then it's never been # accesses: not need to delete it pass if sess.locked: # Always release the lock if the user didn't release it sess.session_storage.release_lock() # If the body is not a generator, we save the data # before the body is returned if not isinstance(body, types.GeneratorType): for line in body: yield line sess = cherrypy.request._session if not getattr(sess, 'session_storage', None): # Sessions are not enabled: do nothing return # Make a wrapper around the body in order to save the session # either before or after the body is returned cherrypy.response.body = saveData(cherrypy.response.body, sess) def on_end_request(self): sess = cherrypy.request._session if not getattr(sess, 'session_storage', None): # Sessions are not enabled: do nothing return if getattr(sess, 'locked', None): # If the session is still locked we release the lock sess.session_storage.release_lock() if getattr(sess, 'session_storage', None): del sess.session_storage class RamStorage: """ Implementation of the RAM backend for sessions """ def load(self, id): return cherrypy._session_data_holder.get(id) def save(self, id, data, expiration_time): cherrypy._session_data_holder[id] = (data, expiration_time) def delete(self, id=None): if id is None: id = cherrypy.session.id del cherrypy._session_data_holder[id] def acquire_lock(self): sess = cherrypy.request._session id = cherrypy.session.id lock = cherrypy._session_lock_dict.get(id) if lock is None: lock = threading.Lock() cherrypy._session_lock_dict[id] = lock startTime = time.time() while True: if lock.acquire(False): break if time.time() - startTime > sess.deadlock_timeout: raise SessionDeadlockError() time.sleep(0.5) sess.locked = True def release_lock(self): sess = cherrypy.request._session id = cherrypy.session['_id'] cherrypy._session_lock_dict[id].release() sess.locked = False def clean_up(self, sess): to_be_deleted = [] now = datetime.datetime.now() for id, (data, expiration_time) in cherrypy._session_data_holder.iteritems(): if expiration_time < now: to_be_deleted.append(id) for id in to_be_deleted: try: deleted_session = cherrypy._session_data_holder[id] del cherrypy._session_data_holder[id] sess.on_delete_session(deleted_session) except KeyError: # The session probably got deleted by a concurrent thread # Safe to ignore this case pass class FileStorage: """ Implementation of the File backend for sessions """ SESSION_PREFIX = 'session-' LOCK_SUFFIX = '.lock' def load(self, id): file_path = self._get_file_path(id) try: f = open(file_path, "rb") data = pickle.load(f) f.close() return data except (IOError, EOFError): return None def save(self, id, data, expiration_time): file_path = self._get_file_path(id) f = open(file_path, "wb") pickle.dump((data, expiration_time), f) f.close() def delete(self, id=None): if id is None: id = cherrypy.session.id file_path = self._get_file_path(id) try: os.unlink(file_path) except: pass def acquire_lock(self): sess = cherrypy.request._session if not sess.locked: file_path = self._get_file_path(cherrypy.session.id) self._lock_file(file_path + self.LOCK_SUFFIX) sess.locked = True def release_lock(self): sess = cherrypy.request._session file_path = self._get_file_path(cherrypy.session.id) self._unlock_file(file_path + self.LOCK_SUFFIX) sess.locked = False def clean_up(self, sess): storage_path = cherrypy.config.get('session_filter.storage_path') if storage_path is None: return now = datetime.datetime.now() # Iterate over all files in the dir/ and exclude non session files # and lock files for fname in os.listdir(storage_path): if (fname.startswith(self.SESSION_PREFIX) and not fname.endswith(self.LOCK_SUFFIX)): # We have a session file: try to load it and check # if it's expired. If it fails, nevermind. file_path = os.path.join(storage_path, fname) try: f = open(file_path, "rb") data, expiration_time = pickle.load(f) f.close() if expiration_time < now: # Session expired: deleting it id = fname[len(self.SESSION_PREFIX):] sess.on_delete_session(data) os.unlink(file_path) except: # We can't access the file ... nevermind pass def _get_file_path(self, id): storage_path = cherrypy.config.get('session_filter.storage_path') if storage_path is None: raise SessionStoragePathNotConfiguredError() fileName = self.SESSION_PREFIX + id file_path = os.path.join(storage_path, fileName) if not os.path.normpath(file_path).startswith(storage_path): raise cherrypy.HTTPError(400, "Invalid session id in cookie.") return file_path def _lock_file(self, path): sess = cherrypy.request._session startTime = time.time() while True: try: lockfd = os.open(path, os.O_CREAT|os.O_WRONLY|os.O_EXCL) except OSError: if time.time() - startTime > sess.deadlock_timeout: raise SessionDeadlockError() time.sleep(0.5) else: os.close(lockfd) break def _unlock_file(self, path): os.unlink(path) class PostgreSQLStorage: """ Implementation of the PostgreSQL backend for sessions. It assumes a table like this: create table session ( id varchar(40), data text, expiration_time timestamp ) """ def __init__(self): self.db = cherrypy.config.get('session_filter.get_db')() self.cursor = self.db.cursor() def __del__(self): if self.cursor: self.cursor.close() self.db.commit() def load(self, id): # Select session data from table self.cursor.execute( 'select data, expiration_time from session where id=%s', (id,)) rows = self.cursor.fetchall() if not rows: return None pickled_data, expiration_time = rows[0] # Unpickle data data = pickle.loads(pickled_data) return (data, expiration_time) def delete(self, id=None): if id is None: id = cherrypy.session.id self.cursor.execute('delete from session where id=%s', (id,)) def save(self, id, data, expiration_time): # Try to delete session if it was already there self.cursor.execute( 'delete from session where id=%s', (id,)) # Pickle data pickled_data = pickle.dumps(data) # Insert new session data self.cursor.execute( 'insert into session (id, data, expiration_time) values (%s, %s, %s)', (id, pickled_data, expiration_time)) def acquire_lock(self): # We use the "for update" clause to lock the row self.cursor.execute( 'select id from session where id=%s for update', (cherrypy.session.id,)) def release_lock(self): # We just close the cursor and that will remove the lock # introduced by the "for update" clause self.cursor.close() self.cursor = None def clean_up(self, sess): now = datetime.datetime.now() self.cursor.execute( 'select data from session where expiration_time < %s', (now,)) rows = self.cursor.fetchall() for row in rows: sess.on_delete_session(row[0]) self.cursor.execute( 'delete from session where expiration_time < %s', (now,)) try: os.urandom(20) except (AttributeError, NotImplementedError): # os.urandom not available until Python 2.4. Fall back to random.random. def generate_session_id(): """Return a new session id.""" return sha.new('%s' % random.random()).hexdigest() else: def generate_session_id(): """Return a new session id.""" return os.urandom(20).encode('hex') generateSessionID = generate_session_id # Users access sessions through cherrypy.session, but we want this # to be thread-specific so we use a special wrapper that forwards # calls to cherrypy.session to a thread-specific dictionary called # cherrypy.request._session.session_data class SessionWrapper: def __getattr__(self, name): sess = cherrypy.request._session if sess.session_storage is None: raise SessionNotEnabledError() # Create thread-specific dictionary if needed session_data = getattr(sess, 'session_data', None) if session_data is None: sess.session_data = {} if name == 'acquire_lock': return sess.session_storage.acquire_lock elif name == 'release_lock': return sess.session_storage.release_lock elif name == 'id': return sess.session_id elif name == 'delete': return sess.session_storage.delete if sess.to_be_loaded: data = sess.session_storage.load(sess.session_id) # data is either None or a tuple (session_data, expiration_time) if data is None or data[1] < datetime.datetime.now(): # Expired session: # flush session data (but keep the same session_id) sess.session_data = {'_id': sess.session_id} if not (data is None): sess.on_renew_session(sess.session_data) else: sess.session_data = data[0] sess.to_be_loaded = False return getattr(sess.session_data, name) def expire(): """Expire the current session cookie.""" name = cherrypy.config.get('session_filter.cookie_name', 'session_id') one_year = 60 * 60 * 24 * 365 exp = time.gmtime(time.time() - one_year) t = time.strftime("%a, %d-%b-%Y %H:%M:%S GMT", exp) cherrypy.response.simple_cookie[name]['expires'] = t CherryPy-2.3.0/cherrypy/filters/staticfilter.py0000644000175000017500000000723710742543111022406 0ustar christianchristianimport os import urllib import cherrypy from cherrypy.lib import cptools from cherrypy.filters.basefilter import BaseFilter class StaticFilter(BaseFilter): """Filter that handles static content.""" def before_main(self): config = cherrypy.config if not config.get('static_filter.on', False): return request = cherrypy.request path = request.object_path regex = config.get('static_filter.match', '') if regex: import re if not re.search(regex, path): return root = config.get('static_filter.root', '').rstrip(r"\/") filename = config.get('static_filter.file') if filename: static_dir = None else: static_dir = config.get('static_filter.dir') if not static_dir: msg = ("StaticFilter requires either static_filter.file " "or static_filter.dir (%s)" % request.path) raise cherrypy.WrongConfigValue(msg) section = config.get('static_filter.dir', return_section = True) if section == 'global': section = "/" section = section.rstrip(r"\/") extra_path = path[len(section) + 1:] extra_path = extra_path.lstrip(r"\/") extra_path = urllib.unquote(extra_path) # If extra_path is "", filename will end in a slash filename = os.path.join(static_dir, extra_path) # If filename is relative, make absolute using "root". # Note that, if "root" isn't defined, we still may send # a relative path to serveFile. if not os.path.isabs(filename): if not root: msg = ("StaticFilter requires an absolute final path. " "Make static_filter.dir, .file, or .root absolute.") raise cherrypy.WrongConfigValue(msg) filename = os.path.join(root, filename) # If we used static_filter.dir, then there's a chance that the # extra_path pulled from the URL might have ".." or similar uplevel # attacks in it. Check that the final file is a child of static_dir. # Note that we do not check static_filter.file--that can point # anywhere (since it does not use the request URL). if static_dir: if not os.path.isabs(static_dir): static_dir = os.path.join(root, static_dir) if not os.path.normpath(filename).startswith(os.path.normpath(static_dir)): raise cherrypy.HTTPError(403) # Forbidden try: # you can set the content types for a complete directory per extension content_types = config.get('static_filter.content_types', None) content_type = None if content_types: root, ext = os.path.splitext(filename) content_type = content_types.get(ext[1:], None) cptools.serveFile(filename, contentType=content_type) request.execute_main = False except cherrypy.NotFound: # If we didn't find the static file, continue handling the # request. We might find a dynamic handler instead. # But first check for an index file if a folder was requested. if filename[-1:] in ("/", "\\"): idx = config.get('static_filter.index', '') if idx: try: cptools.serveFile(os.path.join(filename, idx)) request.execute_main = False except cherrypy.NotFound: pass CherryPy-2.3.0/cherrypy/filters/tidyfilter.py0000644000175000017500000001103510742543111022057 0ustar christianchristianimport cgi import os import StringIO import traceback import cherrypy from basefilter import BaseFilter 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 before_finalize(self): if not cherrypy.config.get('tidy_filter.on', False): return # the tidy filter, by its very nature it's not generator friendly, # so we just collect the body and work with it. originalBody = cherrypy.response.collapse_body() fct = cherrypy.response.headers.get('Content-Type', '') ct = fct.split(';')[0] encoding = '' i = fct.find('charset=') if i != -1: encoding = fct[i+8:] if ct == 'text/html': tmpdir = cherrypy.config.get('tidy_filter.tmp_dir') pageFile = os.path.join(tmpdir, 'page.html') outFile = os.path.join(tmpdir, 'tidy.out') errFile = os.path.join(tmpdir, 'tidy.err') f = open(pageFile, 'wb') f.write(originalBody) f.close() tidyEncoding = encoding.replace('-', '') if tidyEncoding: tidyEncoding = '-' + tidyEncoding strictXml = "" if cherrypy.config.get('tidy_filter.strict_xml', False): strictXml = ' -xml' os.system('"%s" %s%s -f %s -o %s %s' % (cherrypy.config.get('tidy_filter.tidy_path'), tidyEncoding, strictXml, 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 cherrypy.config.get('tidy_filter.errors_to_ignore', []): if err.find(errIgn) != -1: ignore = 1 break if not ignore: newErrList.append(err) if newErrList: newBody = "Wrong HTML:
" + cgi.escape('\n'.join(newErrList)).replace('\n','
') newBody += '

' i = 0 for line in originalBody.splitlines(): i += 1 newBody += "%03d - "%i + cgi.escape(line).replace('\t',' ').replace(' ',' ') + '
' cherrypy.response.body = newBody # Delete Content-Length header so finalize() recalcs it. cherrypy.response.headers.pop("Content-Length", None) elif strictXml: # The HTML is OK, but is it valid XML # Use elementtree to parse XML from elementtree.ElementTree import parse tagList = ['nbsp', 'quot'] for tag in tagList: originalBody = originalBody.replace( '&' + tag + ';', tag.upper()) if encoding: originalBody = """""" % encoding + originalBody f = StringIO.StringIO(originalBody) try: tree = parse(f) except: # Wrong XML bodyFile = StringIO.StringIO() traceback.print_exc(file = bodyFile) cherrypy.response.body = bodyFile.getvalue() # Delete Content-Length header so finalize() recalcs it. cherrypy.response.headers.pop("Content-Length", None) newBody = "Wrong XML:
" + cgi.escape(bodyFile.getvalue().replace('\n','
')) newBody += '

' i = 0 for line in originalBody.splitlines(): i += 1 newBody += "%03d - "%i + cgi.escape(line).replace('\t',' ').replace(' ',' ') + '
' cherrypy.response.body = newBody # Delete Content-Length header so finalize() recalcs it. cherrypy.response.headers.pop("Content-Length", None) CherryPy-2.3.0/cherrypy/filters/virtualhostfilter.py0000644000175000017500000000245010742543111023473 0ustar christianchristian""" Virtual Host Filter From http://groups.google.com/group/cherrypy-users/browse_thread/thread/f393540fe278e54d: For various reasons I need several domains to point to different parts of a single website structure as well as to their own "homepage" EG http://www.mydom1.com -> root http://www.mydom2.com -> root/mydom2/ http://www.mydom3.com -> root/mydom3/ http://www.mydom4.com -> under construction page but also to have http://www.mydom1.com/mydom2/ etc to be valid pages in their own right. """ import cherrypy from basefilter import BaseFilter class VirtualHostFilter(BaseFilter): """Filter that changes the ObjectPath based on the Host. Useful when running multiple sites within one CP server. """ def before_request_body(self): if not cherrypy.config.get('virtual_host_filter.on', False): return domain = cherrypy.request.headers.get('Host', '') if cherrypy.config.get("virtual_host_filter.use_x_forwarded_host", True): domain = cherrypy.request.headers.get("X-Forwarded-Host", domain) prefix = cherrypy.config.get("virtual_host_filter." + domain, "") if prefix: cherrypy.request.object_path = prefix + cherrypy.request.object_path CherryPy-2.3.0/cherrypy/filters/wsgiappfilter.py0000644000175000017500000001351310742543111022563 0ustar christianchristian"""a WSGI application filter for CherryPy also see cherrypy.lib.cptools.WSGIApp""" # by Christian Wyglendowski import sys import cherrypy from cherrypy.filters.basefilter import BaseFilter from cherrypy._cputil import get_object_trail # is this sufficient for start_response? def start_response(status, response_headers, exc_info=None): cherrypy.response.status = status headers_dict = dict(response_headers) cherrypy.response.headers.update(headers_dict) def get_path_components(path): """returns (script_name, path_info) determines what part of the path belongs to cp (script_name) and what part belongs to the wsgi application (path_info) """ no_parts = [''] object_trail = get_object_trail(path) root = object_trail.pop(0) if not path.endswith('/index'): object_trail.pop() script_name_parts = [""] path_info_parts = [""] for (pc,obj) in object_trail: if obj: script_name_parts.append(pc) else: path_info_parts.append(pc) script_name = "/".join(script_name_parts) path_info = "/".join(path_info_parts) if len(script_name) > 1 and path.endswith('/'): path_info = path_info + '/' if script_name and not script_name.startswith('/'): script_name = '/' + script_name if path_info and not path_info.startswith('/'): path_info = '/' + path_info return script_name, path_info def make_environ(): """grabbed some of below from _cpwsgiserver.py for hosting WSGI apps in non-WSGI environments (yikes!) """ script_name, path_info = get_path_components(cherrypy.request.path) # create and populate the wsgi environment environ = dict() environ["wsgi.version"] = (1,0) environ["wsgi.url_scheme"] = cherrypy.request.scheme environ["wsgi.input"] = cherrypy.request.rfile environ["wsgi.errors"] = sys.stderr environ["wsgi.multithread"] = True environ["wsgi.multiprocess"] = False environ["wsgi.run_once"] = False environ["REQUEST_METHOD"] = cherrypy.request.method environ["SCRIPT_NAME"] = script_name environ["PATH_INFO"] = path_info environ["QUERY_STRING"] = cherrypy.request.queryString environ["SERVER_PROTOCOL"] = cherrypy.request.version server_name = getattr(cherrypy.server.httpserver, 'server_name', "None") environ["SERVER_NAME"] = server_name environ["SERVER_PORT"] = cherrypy.config.get('server.socketPort') environ["REMOTE_HOST"] = cherrypy.request.remoteHost environ["REMOTE_ADDR"] = cherrypy.request.remoteAddr environ["REMOTE_PORT"] = cherrypy.request.remotePort # then all the http headers headers = cherrypy.request.headers environ["CONTENT_TYPE"] = headers.get("Content-type", "") environ["CONTENT_LENGTH"] = headers.get("Content-length", "") for (k, v) in headers.iteritems(): envname = "HTTP_" + k.upper().replace("-","_") environ[envname] = v return environ class WSGIAppFilter(BaseFilter): """A filter for running any WSGI middleware/application within CP. Here are the parameters: wsgi_app - any wsgi application callable env_update - a dictionary with arbitrary keys and values to be merged with the WSGI environment dictionary. Example: class Whatever: _cp_filters = [WSGIAppFilter(some_app)] """ def __init__(self, wsgi_app, env_update=None): self.app = wsgi_app self.env_update = env_update or {} def before_request_body(self): # keep the request body intact so the wsgi app # can have its way with it cherrypy.request.processRequestBody = False def before_main(self): """run the wsgi_app and set response.body to its output """ request = cherrypy.request # if the static filter is on for this path and # request.execute_main is False, assume that the # static filter has already taken care of this request staticfilter_on = cherrypy.config.get('static_filter.on', False) if staticfilter_on and not request.execute_main: return try: environ = request.wsgi_environ sn, pi = get_path_components(request.path) environ['SCRIPT_NAME'] = sn environ['PATH_INFO'] = pi except AttributeError: environ = make_environ() # update the environ with the dict passed to the filter's # constructor environ.update(self.env_update) # run the wsgi app and have it set response.body response = self.app(environ, start_response) try: cherrypy.response.body = response finally: if hasattr(response, "close"): response.close() # tell CP not to handle the request further request.execute_main = False if __name__ == '__main__': def my_app(environ, start_response): status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) yield 'Hello, world!\n' yield 'This is a wsgi app running within CherryPy!\n\n' keys = environ.keys() keys.sort() for k in keys: yield '%s: %s\n' % (k,environ[k]) class Root(object): def index(self): yield "

Hi, from CherryPy!

" yield "A non-CP WSGI app
" yield "
" yield "SCRIPT_NAME and PATH_INFO get set " yield "properly" index.exposed = True class HostedWSGI(object): _cp_filters = [WSGIAppFilter(my_app, {'cherrypy.wsgi':True,}),] # mount standard CherryPy app cherrypy.tree.mount(Root(), '/') # mount the WSGI app cherrypy.tree.mount(HostedWSGI(), '/app') cherrypy.server.start() CherryPy-2.3.0/cherrypy/filters/xmlrpcfilter.py0000644000175000017500000002374110742543111022422 0ustar christianchristian########################################################################## ## Remco Boerma ## Sylvain Hellegouarch ## ## History: ## 1.0.6 : 2005-12-04 Fixed error handling problems ## 1.0.5 : 2005-11-04 Fixed Content-Length bug (http://www.cherrypy.org/ticket/384) ## 1.0.4 : 2005-08-28 Fixed issues on input types which are not strings ## 1.0.3 : 2005-01-28 Bugfix on content-length in 1.0.2 code fixed by ## Gian Paolo Ciceri ## 1.0.2 : 2005-01-26 changed infile dox based on ticket #97 ## 1.0.1 : 2005-01-26 Speedup due to generator usage in CP2. ## The result is now converted to a list with length 1. So the complete ## xmlrpc result is written at once, and not per character. Thanks to ## Gian Paolo Ciceri for reporting the slowdown. ## 1.0.0 : 2004-12-29 Released with CP2 ## 0.0.9 : 2004-12-23 made it CP2 #59 compatible (returns an iterable) ## Please note: as the xmlrpc doesn't know what you would want to return ## (and for the logic of marshalling) it will return Generator objects, as ## it is.. So it'll brake on that one!! ## NOTE: __don't try to return a Generator object to the caller__ ## You could of course handle the generator usage internally, before sending ## the result. This breaks from the general cherrypy way of handling generators... ## 0.0.8 : 2004-12-23 cherrypy.request.paramList should now be a filter. ## 0.0.7 : 2004-12-07 inserted in the experimental branch (all remco boerma till here) ## 0.0.6 : 2004-12-02 Converted basefilter to baseinputfileter,baseoutputfilter ## 0.0.5 : 2004-11-22 "RPC2/" now changed to "/RPC2/" with the new mapping function ## Gian paolo ciceri notified me with the lack of passing parameters. ## Thanks Gian, it's now implemented against the latest trunk. ## Gian also came up with the idea of lazy content-type checking: if it's sent ## as a header, it should be 'text/xml', if not sent at all, it should be ## accepted. (While this it not the xml/rpc standard, it's handy for those ## xml-rpc client implementations wich don't send this header) ## 0.0.4 : 2004-11-20 in setting the path, the dot is replaces by a slash ## therefore the regular CP2 routines knows how to handle things, as ## dots are not allowed in object names, it's varely easily adopted. ## Path + method handling. The default path is 'RPC2', this one is ## stripped. In case of path 'someurl' it is used for 'someurl' + method ## and 'someurl/someotherurl' is mapped to someurl.someotherurl + method. ## this way python serverproxies initialised with an url other than ## just the host are handled well. I don't hope any other service would map ## it to 'RPC2/someurl/someotherurl', cause then it would break i think. . ## 0.0.3 : 2004-11-19 changed some examples (includes error checking ## wich returns marshalled Fault objects if the request is an RPC call. ## took testing code form afterRequestHeader and put it in ## testValidityOfRequest to make things a little simpler. ## simply log the requested function with parameters to stdout ## 0.0.2 : 2004-11-19 the required cgi.py patch is no longer needed ## (thanks remi for noticing). Webbased calls to regular objects ## are now possible again ;) so it's no longer a dedicated xmlrpc ## server. The test script is also in a ready to run file named ## testRPC.py along with the test server: filterExample.py ## 0.0.1 : 2004-11-19 informing the public, dropping loads of useless ## tests and debugging ## 0.0.0 : 2004-11-19 initial alpha ## ##--------------------------------------------------------------------- ## ## EXAMPLE CODE FOR THE SERVER: ## import cherrypy ## ## class Root: ## def longString(self, s, times): ## return s * times ## longString.exposed = True ## ## cherrypy.root = Root() ## cherrypy.config.update({'xmlrpc_filter.on': True, ## 'socket_port': 9001, ## 'thread_pool':10, ## 'socket_queue_size':10 }) ## if __name__=='__main__': ## cherrypy.server.start() ## ## EXAMPLE CODE FOR THE CLIENT: ## >>> import xmlrpclib ## >>> server = xmlrpclib.ServerProxy('http://localhost:9001') ## >>> assert server.longString('abc', 3) == 'abcabcabc' ## >>> ###################################################################### import sys import xmlrpclib import cherrypy from basefilter import BaseFilter class XmlRpcFilter(BaseFilter): """Converts XMLRPC to CherryPy2 object system and vice-versa. PLEASE NOTE: before_request_body: Unmarshalls the posted data to a methodname and parameters. - These are stored in cherrypy.request.rpcMethod and .rpcParams - The method is also stored in cherrypy.request.object_path, so CP2 will find the right method to call for you, based on the root's position. before_main: Marshalls cherrypy.response.body to xmlrpc. - Until resolved: cherrypy.response.body must be a python source string; this string is 'eval'ed to return the results. This will be resolved in the future. - Content-Type and Content-Length are set according to the new (marshalled) data. """ def testValidityOfRequest(self): # test if the content-length was sent length = cherrypy.request.headers.get('Content-Length') or 0 ct = cherrypy.request.headers.get('Content-Type') or 'text/xml' ct = ct.split(';')[0] return int(length) > 0 and ct.lower() in ['text/xml'] def before_request_body(self): """ Called after the request header has been read/parsed""" request = cherrypy.request request.xmlrpc_filter_on = cherrypy.config.get('xmlrpc_filter.on', False) if not request.xmlrpc_filter_on: return request.is_rpc = self.testValidityOfRequest() if not request.is_rpc: return request.processRequestBody = False dataLength = int(request.headers.get('Content-Length') or 0) data = request.rfile.read(dataLength) try: params, method = xmlrpclib.loads(data) except Exception: params, method = ('ERROR PARAMS', ), 'ERRORMETHOD' request.rpcMethod, request.rpcParams = method, params # patch the path. there are only a few options: # - 'RPC2' + method >> method # - 'someurl' + method >> someurl.method # - 'someurl/someother' + method >> someurl.someother.method if not request.object_path.endswith('/'): request.object_path += '/' if request.object_path.startswith('/RPC2/'): # strip the first /rpc2 request.object_path = request.object_path[5:] request.object_path += str(method).replace('.', '/') request.paramList = list(params) def before_main(self): """This is a variation of main() from _cphttptools. It is redone here because: 1. we want to handle responses of any type 2. we need to pass our own paramList """ if (not cherrypy.config.get('xmlrpc_filter.on', False) or not getattr(cherrypy.request, 'is_rpc', False)): return path = cherrypy.request.object_path while True: try: page_handler, object_path, virtual_path = cherrypy.request.mapPathToObject(path) # Decode any leftover %2F in the virtual_path atoms. virtual_path = [x.replace("%2F", "/") for x in virtual_path] # Remove "root" from object_path and join it to get object_path self.object_path = '/' + '/'.join(object_path[1:]) args = virtual_path + cherrypy.request.paramList body = page_handler(*args, **cherrypy.request.params) break except cherrypy.InternalRedirect, x: # Try again with the new path path = x.path except cherrypy.NotFound: # http://www.cherrypy.org/ticket/533 # if a method is not found, an xmlrpclib.Fault should be returned # raising an exception here will do that; see # cherrypy.lib.xmlrpc.on_error raise Exception('method "%s" is not supported' % cherrypy.request.rpcMethod) # See xmlrpclib documentation # Python's None value cannot be used in standard XML-RPC; # to allow using it via an extension, provide a true value for allow_none. encoding = cherrypy.config.get('xmlrpc_filter.encoding', 'utf-8') body = xmlrpclib.dumps((body,), methodresponse=1, encoding=encoding, allow_none=0) self.respond(body) cherrypy.request.execute_main = False def after_error_response(self): if (not cherrypy.config.get('xmlrpc_filter.on', False) or not getattr(cherrypy.request, 'is_rpc', False)): return # Since we got here because of an exception, # let's get its error message if any body = str(sys.exc_info()[1]) body = xmlrpclib.dumps(xmlrpclib.Fault(1, body)) self.respond(body) def respond(self, body): # The XML-RPC spec (http://www.xmlrpc.com/spec) says: # "Unless there's a lower-level error, always return 200 OK." # Since Python's xmlrpclib interprets a non-200 response # as a "Protocol Error", we'll just return 200 every time. response = cherrypy.response response.status = '200 OK' response.body = body response.headers['Content-Type'] = 'text/xml' response.headers['Content-Length'] = len(body) CherryPy-2.3.0/cherrypy/lib/0000755000175000017500000000000010742544024016430 5ustar christianchristianCherryPy-2.3.0/cherrypy/lib/filter/0000755000175000017500000000000010742544024017715 5ustar christianchristianCherryPy-2.3.0/cherrypy/lib/filter/__init__.py0000644000175000017500000000143510742543110022024 0ustar christianchristianimport warnings warnings.warn("cherrypy.lib.filter has been superseded by cherrypy.filters and will be removed in CP 2.3", DeprecationWarning, stacklevel = 2) from cherrypy.filters import * import sys builtin_filters = ("basefilter", "baseurlfilter", "cachefilter", "decodingfilter", "encodingfilter", "gzipfilter", "logdebuginfofilter", "nsgmlsfilter", "responseheadersfilter", "sessionauthenticatefilter", "sessionfilter", "staticfilter", "tidyfilter", "virtualhostfilter", "xmlrpcfilter") for name in builtin_filters: newlocation = "cherrypy.filters." + name m = __import__(newlocation, globals(), locals(), ['']) sys.modules["cherrypy.lib.filter." + name] = m globals()[name] = m CherryPy-2.3.0/cherrypy/lib/__init__.py0000644000175000017500000000004310742543110020531 0ustar christianchristian""" CherryPy Standard Library """ CherryPy-2.3.0/cherrypy/lib/autoreload.py0000644000175000017500000000542510742543110021142 0ustar christianchristian# autoreloading launcher # stolen a lot from Ian Bicking's WSGIKit (www.wsgikit.org) import errno import os import re import sys import time import thread RUN_RELOADER = True reloadFiles = [] ignoreFiles = [''] match = ".*" def reloader_thread(freq): mtimes = {} while RUN_RELOADER: sysfiles = [] for k, m in sys.modules.items(): if re.match(match, k): if hasattr(m, "__loader__"): if hasattr(m.__loader__, "archive"): k = m.__loader__.archive k = getattr(m, "__file__", None) sysfiles.append(k) for filename in sysfiles + reloadFiles: if filename and filename not in ignoreFiles: orig = filename if filename.endswith(".pyc"): filename = filename[:-1] # Get the last-modified time of the source file. try: mtime = os.stat(filename).st_mtime except OSError, e: if orig.endswith('.pyc') and e[0] == errno.ENOENT: # This prevents us from endlessly restarting if # there is an old .pyc lying around after a .py # file has been deleted. Note that TG's solution # actually deletes the .pyc, but we just ignore it. # See http://www.cherrypy.org/ticket/438. continue sys.exit(3) # force reload if filename not in mtimes: mtimes[filename] = mtime continue if mtime > mtimes[filename]: sys.exit(3) # force reload time.sleep(freq) def restart_with_reloader(): while True: args = [sys.executable] + sys.argv if sys.platform == "win32": args = ['"%s"' % arg for arg in args] new_environ = os.environ.copy() new_environ["RUN_MAIN"] = 'true' exit_code = os.spawnve(os.P_WAIT, sys.executable, args, new_environ) if exit_code != 3: return exit_code def main(main_func, args=None, kwargs=None, freq=1): if os.environ.get("RUN_MAIN") == "true": if args is None: args = () if kwargs is None: kwargs = {} thread.start_new_thread(main_func, args, kwargs) # If KeyboardInterrupt is raised within reloader_thread, # let it propagate out to the caller. reloader_thread(freq) else: # If KeyboardInterrupt is raised within restart_with_reloader, # let it propagate out to the caller. sys.exit(restart_with_reloader()) CherryPy-2.3.0/cherrypy/lib/covercp.py0000644000175000017500000002516610742543110020450 0ustar christianchristian"""Code-coverage tools for CherryPy. To use this module, or the coverage tools in the test suite, you need to download 'coverage.py', either Gareth Rees' original implementation: http://www.garethrees.org/2001/12/04/python-coverage/ or Ned Batchelder's enhanced version: http://www.nedbatchelder.com/code/modules/coverage.html Set "cherrypy.codecoverage = True" to turn on coverage tracing. Then, use the serve() function to browse the results in a web browser. If you run this module from the command line, it will call serve() for you. """ import re import sys import cgi import urllib import os, os.path localFile = os.path.join(os.path.dirname(__file__), "coverage.cache") try: import cStringIO as StringIO except ImportError: import StringIO try: from coverage import the_coverage as coverage def start(): coverage.start() except ImportError: # Setting coverage to None will raise errors # that need to be trapped downstream. coverage = None import warnings warnings.warn("No code coverage will be performed; coverage.py could not be imported.") def start(): pass # Guess initial depth to hide FIXME this doesn't work for non-cherrypy stuff import cherrypy initial_base = os.path.dirname(cherrypy.__file__) TEMPLATE_MENU = """ CherryPy Coverage Menu

CherryPy Coverage

""" TEMPLATE_FORM = """
Show percentages
Hide files over %%
Exclude files matching

""" TEMPLATE_FRAMESET = """ CherryPy coverage data """ % initial_base.lower() TEMPLATE_COVERAGE = """ Coverage for %(name)s

%(name)s

%(fullpath)s

Coverage: %(pc)s%%

""" TEMPLATE_LOC_COVERED = """ %s  %s \n""" TEMPLATE_LOC_NOT_COVERED = """ %s  %s \n""" TEMPLATE_LOC_EXCLUDED = """ %s  %s \n""" TEMPLATE_ITEM = "%s%s%s\n" def _percent(statements, missing): s = len(statements) e = s - len(missing) if s > 0: return int(round(100.0 * e / s)) return 0 def _show_branch(root, base, path, pct=0, showpct=False, exclude=""): # Show the directory name and any of our children dirs = [k for k, v in root.iteritems() if v] dirs.sort() for name in dirs: newpath = os.path.join(path, name) if newpath.startswith(base): relpath = newpath[len(base):] yield "| " * relpath.count(os.sep) yield "%s\n" % \ (newpath, urllib.quote_plus(exclude), name) for chunk in _show_branch(root[name], base, newpath, pct, showpct, exclude): yield chunk # Now list the files if path.startswith(base): relpath = path[len(base):] files = [k for k, v in root.iteritems() if not v] files.sort() for name in files: newpath = os.path.join(path, name) pc_str = "" if showpct: try: _, statements, _, missing, _ = coverage.analysis2(newpath) except: # Yes, we really want to pass on all errors. pass else: pc = _percent(statements, missing) pc_str = ("%3d%% " % pc).replace(' ',' ') if pc < float(pct) or pc == -1: pc_str = "%s" % pc_str else: pc_str = "%s" % pc_str yield TEMPLATE_ITEM % ("| " * (relpath.count(os.sep) + 1), pc_str, newpath, name) def _skip_file(path, exclude): if exclude: return bool(re.search(exclude, path)) def _graft(path, tree): d = tree p = path atoms = [] while True: p, tail = os.path.split(p) if not tail: break atoms.append(tail) atoms.append(p) if p != "/": atoms.append("/") atoms.reverse() for node in atoms: if node: d = d.setdefault(node, {}) def get_tree(base, exclude): """Return covered module names as a nested dict.""" tree = {} coverage.get_ready() runs = coverage.cexecuted.keys() if runs: for path in runs: if not _skip_file(path, exclude) and not os.path.isdir(path): _graft(path, tree) return tree class CoverStats(object): def index(self): return TEMPLATE_FRAMESET index.exposed = True def menu(self, base="/", pct="50", showpct="", exclude=r'python\d\.\d|test|tut\d|tutorial'): # The coverage module uses all-lower-case names. base = base.lower().rstrip(os.sep) yield TEMPLATE_MENU yield TEMPLATE_FORM % locals() # Start by showing links for parent paths yield "
" path = "" atoms = base.split(os.sep) atoms.pop() for atom in atoms: path += atom + os.sep yield ("%s %s" % (path, urllib.quote_plus(exclude), atom, os.sep)) yield "
" yield "
" # Then display the tree tree = get_tree(base, exclude) if not tree: yield "

No modules covered.

" else: for chunk in _show_branch(tree, base, "/", pct, showpct=='checked', exclude): yield chunk yield "
" yield "" menu.exposed = True def annotated_file(self, filename, statements, excluded, missing): source = open(filename, 'r') buffer = [] for lineno, line in enumerate(source.readlines()): lineno += 1 line = line.strip("\n\r") empty_the_buffer = True if lineno in excluded: template = TEMPLATE_LOC_EXCLUDED elif lineno in missing: template = TEMPLATE_LOC_NOT_COVERED elif lineno in statements: template = TEMPLATE_LOC_COVERED else: empty_the_buffer = False buffer.append((lineno, line)) if empty_the_buffer: for lno, pastline in buffer: yield template % (lno, cgi.escape(pastline)) buffer = [] yield template % (lineno, cgi.escape(line)) def report(self, name): coverage.get_ready() filename, statements, excluded, missing, _ = coverage.analysis2(name) pc = _percent(statements, missing) yield TEMPLATE_COVERAGE % dict(name=os.path.basename(name), fullpath=name, pc=pc) yield '\n' for line in self.annotated_file(filename, statements, excluded, missing): yield line yield '
' yield '' yield '' report.exposed = True def serve(path=localFile, port=8080): if coverage is None: raise ImportError("The coverage module could not be imported.") coverage.cache_default = path import cherrypy cherrypy.root = CoverStats() cherrypy.config.update({'server.socket_port': port, 'server.thread_pool': 10, 'server.environment': "production", }) cherrypy.server.start() if __name__ == "__main__": serve(*tuple(sys.argv[1:])) CherryPy-2.3.0/cherrypy/lib/cptools.py0000644000175000017500000004512310742543110020465 0ustar christianchristian"""Tools which both CherryPy and application developers may invoke.""" import md5 import mimetools import mimetypes mimetypes.init() mimetypes.types_map['.dwg']='image/x-dwg' mimetypes.types_map['.ico']='image/x-icon' import os import re import stat as _stat import sys import time import cherrypy import httptools from cherrypy.filters.wsgiappfilter import WSGIAppFilter def decorate(func, decorator): """ Return the decorated func. This will automatically copy all non-standard attributes (like exposed) to the newly decorated function. """ newfunc = decorator(func) for key in dir(func): if not hasattr(newfunc, key): setattr(newfunc, key, getattr(func, key)) return newfunc def decorateAll(obj, decorator): """ Recursively decorate all exposed functions of obj and all of its children, grandchildren, etc. If you used to use aspects, you might want to look into these. This function modifies obj; there is no return value. """ obj_type = type(obj) for key in dir(obj): # only deal with user-defined attributes if hasattr(obj_type, key): value = getattr(obj, key) if callable(value) and getattr(value, "exposed", False): setattr(obj, key, decorate(value, decorator)) decorateAll(value, decorator) class ExposeItems: """ Utility class that exposes a getitem-aware object. It does not provide index() or default() methods, and it does not expose the individual item objects - just the list or dict that contains them. User-specific index() and default() methods can be implemented by inheriting from this class. Use case: from cherrypy.lib.cptools import ExposeItems ... cherrypy.root.foo = ExposeItems(mylist) cherrypy.root.bar = ExposeItems(mydict) """ exposed = True def __init__(self, items): self.items = items def __getattr__(self, key): return self.items[key] # Conditional HTTP request support # def validate_etags(autotags=False): """Validate the current ETag against If-Match, If-None-Match headers. If autotags is True, an ETag response-header value will be provided from an MD5 hash of the response body (unless some other code has already provided an ETag header). If False (the default), the ETag will not be automatic. WARNING: the autotags feature is not designed for URL's which allow methods other than GET. For example, if a POST to the same URL returns no content, the automatic ETag will be incorrect, breaking a fundamental use for entity tags in a possibly destructive fashion. Likewise, if you raise 304 Not Modified, the response body will be empty, the ETag hash will be incorrect, and your application will break. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.24 """ response = cherrypy.response # Guard against being run twice. if hasattr(response, "ETag"): return status, reason, msg = httptools.validStatus(response.status) etag = response.headers.get('ETag') # Automatic ETag generation. See warning in docstring. if (not etag) and autotags: if status == 200: etag = response.collapse_body() etag = '"%s"' % md5.new(etag).hexdigest() response.headers['ETag'] = etag response.ETag = etag # "If the request would, without the If-Match header field, result in # anything other than a 2xx or 412 status, then the If-Match header # MUST be ignored." if status >= 200 and status <= 299: request = cherrypy.request conditions = request.headers.elements('If-Match') or [] conditions = [str(x) for x in conditions] if conditions and not (conditions == ["*"] or etag in conditions): raise cherrypy.HTTPError(412, "If-Match failed: ETag %r did " "not match %r" % (etag, conditions)) conditions = request.headers.elements('If-None-Match') or [] conditions = [str(x) for x in conditions] if conditions == ["*"] or etag in conditions: if request.method in ("GET", "HEAD"): raise cherrypy.HTTPRedirect([], 304) else: raise cherrypy.HTTPError(412, "If-None-Match failed: ETag %r " "matched %r" % (etag, conditions)) def validate_since(): """Validate the current Last-Modified against If-Modified-Since headers. If no code has set the Last-Modified response header, then no validation will be performed. """ response = cherrypy.response lastmod = response.headers.get('Last-Modified') if lastmod: status, reason, msg = httptools.validStatus(response.status) request = cherrypy.request since = request.headers.get('If-Unmodified-Since') if since and since != lastmod: if (status >= 200 and status <= 299) or status == 412: raise cherrypy.HTTPError(412) since = request.headers.get('If-Modified-Since') if since and since == lastmod: if (status >= 200 and status <= 299) or status == 304: if request.method in ("GET", "HEAD"): raise cherrypy.HTTPRedirect([], 304) else: raise cherrypy.HTTPError(412) def modified_since(path, stat=None): """Check whether a file has been modified since the date provided in 'If-Modified-Since' It doesn't check if the file exists or not Return True if has been modified, False otherwise """ # serveFile already creates a stat object so let's not # waste our energy to do it again if not stat: try: stat = os.stat(path) except OSError: if cherrypy.config.get('server.log_file_not_found', False): cherrypy.log(" NOT FOUND file: %s" % path, "DEBUG") raise cherrypy.NotFound() response = cherrypy.response strModifTime = httptools.HTTPDate(time.gmtime(stat.st_mtime)) if cherrypy.request.headers.has_key('If-Modified-Since'): if cherrypy.request.headers['If-Modified-Since'] == strModifTime: raise cherrypy.HTTPRedirect([], 304) response.headers['Last-Modified'] = strModifTime return True def serveFile(path, contentType=None, disposition=None, name=None): """Set status, headers, and body in order to serve the given file. The Content-Type header will be set to the contentType arg, if provided. If not provided, the Content-Type will be guessed by its extension. If disposition is not None, the Content-Disposition header will be set to "; filename=". If name is None, it will be set to the basename of path. If disposition is None, no Content-Disposition header will be written. """ response = cherrypy.response # If path is relative, users should fix it by making path absolute. # That is, CherryPy should not guess where the application root is. # It certainly should *not* use cwd (since CP may be invoked from a # variety of paths). If using static_filter, you can make your relative # paths become absolute by supplying a value for "static_filter.root". if not os.path.isabs(path): raise ValueError("'%s' is not an absolute path." % path) try: stat = os.stat(path) except OSError: if cherrypy.config.get('server.log_file_not_found', False): cherrypy.log(" NOT FOUND file: %s" % path, "DEBUG") raise cherrypy.NotFound() # Check if path is a directory. if _stat.S_ISDIR(stat.st_mode): # Let the caller deal with it as they like. raise cherrypy.NotFound() if contentType is None: # Set content-type based on filename extension ext = "" i = path.rfind('.') if i != -1: ext = path[i:].lower() contentType = mimetypes.types_map.get(ext, "text/plain") response.headers['Content-Type'] = contentType # Set the Last-Modified response header, so that # modified-since validation code can work. response.headers['Last-Modified'] = httptools.HTTPDate(time.gmtime(stat.st_mtime)) validate_since() if disposition is not None: if name is None: name = os.path.basename(path) cd = '%s; filename="%s"' % (disposition, name) response.headers["Content-Disposition"] = cd # Set Content-Length and use an iterable (file object) # this way CP won't load the whole file in memory c_len = stat.st_size bodyfile = open(path, 'rb') if getattr(cherrypy, "debug", None): cherrypy.log(" Found file: %s" % path, "DEBUG") # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code if cherrypy.response.version >= "1.1": response.headers["Accept-Ranges"] = "bytes" r = httptools.getRanges(cherrypy.request.headers.get('Range'), c_len) if r == []: response.headers['Content-Range'] = "bytes */%s" % c_len message = "Invalid Range (first-byte-pos greater than Content-Length)" raise cherrypy.HTTPError(416, message) if r: if len(r) == 1: # Return a single-part response. start, stop = r[0] r_len = stop - start response.status = "206 Partial Content" response.headers['Content-Range'] = ("bytes %s-%s/%s" % (start, stop - 1, c_len)) response.headers['Content-Length'] = r_len bodyfile.seek(start) response.body = bodyfile.read(r_len) else: # Return a multipart/byteranges response. response.status = "206 Partial Content" boundary = mimetools.choose_boundary() ct = "multipart/byteranges; boundary=%s" % boundary response.headers['Content-Type'] = ct ## del response.headers['Content-Length'] def fileRanges(): # Apache compatibility: yield "\r\n" for start, stop in r: yield "--" + boundary yield "\r\nContent-type: %s" % contentType yield ("\r\nContent-range: bytes %s-%s/%s\r\n\r\n" % (start, stop - 1, c_len)) bodyfile.seek(start) yield bodyfile.read(stop - start) yield "\r\n" # Final boundary yield "--" + boundary + "--" # Apache compatibility: yield "\r\n" response.body = fileRanges() else: response.headers['Content-Length'] = c_len response.body = bodyfile else: response.headers['Content-Length'] = c_len response.body = bodyfile return response.body serve_file = serveFile def serve_download(path, name=None): """Serve 'path' as an application/x-download attachment.""" # This is such a common idiom I felt it deserved its own wrapper. return serve_file(path, "application/x-download", "attachment", name) def fileGenerator(input, chunkSize=65536): """Yield the given input (a file object) in chunks (default 64k).""" chunk = input.read(chunkSize) while chunk: yield chunk chunk = input.read(chunkSize) input.close() def modules(modulePath): """Load a module and retrieve a reference to that module.""" try: mod = sys.modules[modulePath] if mod is None: raise KeyError() except KeyError: # The last [''] is important. mod = __import__(modulePath, globals(), locals(), ['']) return mod def attributes(fullAttributeName): """Load a module and retrieve an attribute of that module.""" # Parse out the path, module, and attribute lastDot = fullAttributeName.rfind(u".") attrName = fullAttributeName[lastDot + 1:] modPath = fullAttributeName[:lastDot] aMod = modules(modPath) # Let an AttributeError propagate outward. try: attr = getattr(aMod, attrName) except AttributeError: raise AttributeError("'%s' object has no attribute '%s'" % (modPath, attrName)) # Return a reference to the attribute. return attr class WSGIApp(object): """a convenience class that uses the WSGIAppFilter to easily add a WSGI application to the CP object tree. example: cherrypy.tree.mount(SomeRoot(), '/') cherrypy.tree.mount(WSGIApp(other_wsgi_app), '/ext_app') """ def __init__(self, app, env_update=None): self._cpFilterList = [WSGIAppFilter(app, env_update)] # public domain "unrepr" implementation, found on the web and then improved. def getObj(s): try: import compiler except ImportError: # Fallback to eval when compiler package is not available, # e.g. IronPython 1.0. return eval(s) s = "a=" + s p = compiler.parse(s) return p.getChildren()[1].getChildren()[0].getChildren()[1] class UnknownType(Exception): pass class Builder: def build(self, o): m = getattr(self, 'build_' + o.__class__.__name__, None) if m is None: raise UnknownType(o.__class__.__name__) return m(o) def build_CallFunc(self, o): callee, args, starargs, kwargs = map(self.build, o.getChildren()) return callee(args, *(starargs or ()), **(kwargs or {})) def build_List(self, o): return map(self.build, o.getChildren()) def build_Const(self, o): return o.value def build_Dict(self, o): d = {} i = iter(map(self.build, o.getChildren())) for el in i: d[el] = i.next() return d def build_Tuple(self, o): return tuple(self.build_List(o)) def build_Name(self, o): if o.name == 'None': return None if o.name == 'True': return True if o.name == 'False': return False # See if the Name is a package or module try: return modules(o.name) except ImportError: pass raise UnknownType(o.name) def build_Add(self, o): real, imag = map(self.build_Const, o.getChildren()) try: real = float(real) except TypeError: raise UnknownType('Add') if not isinstance(imag, complex) or imag.real != 0.0: raise UnknownType('Add') return real+imag def build_Getattr(self, o): parent = self.build(o.expr) return getattr(parent, o.attrname) def build_NoneType(self, o): return None def build_UnarySub(self, o): return -self.build_Const(o.getChildren()[0]) def build_UnaryAdd(self, o): return self.build_Const(o.getChildren()[0]) def unrepr(s): if not s: return s return Builder().build(getObj(s)) def referer(pattern, accept=True, accept_missing=False, error=403, message='Forbidden Referer header.'): """Raise HTTPError if Referer header does not pass our test. pattern: a regular expression pattern to test against the Referer. accept: if True, the Referer must match the pattern; if False, the Referer must NOT match the pattern. accept_missing: if True, permit requests with no Referer header. error: the HTTP error code to return to the client on failure. message: a string to include in the response body on failure. """ try: match = bool(re.match(pattern, cherrypy.request.headers['Referer'])) if accept == match: return except KeyError: if accept_missing: return raise cherrypy.HTTPError(error, message) def accept(media=None): """Return the client's preferred media-type (from the given Content-Types). If 'media' is None (the default), no test will be performed. If 'media' is provided, it should be the Content-Type value (as a string) or values (as a list or tuple of strings) which the current request can emit. The client's acceptable media ranges (as declared in the Accept request header) will be matched in order to these Content-Type values; the first such string is returned. That is, the return value will always be one of the strings provided in the 'media' arg (or None if 'media' is None). If no match is found, then HTTPError 406 (Not Acceptable) is raised. Note that most web browsers send */* as a (low-quality) acceptable media range, which should match any Content-Type. In addition, "...if no Accept header field is present, then it is assumed that the client accepts all media types." Matching types are checked in order of client preference first, and then in the order of the given 'media' values. Note that this function does not honor accept-params (other than "q"). """ if not media: return if isinstance(media, basestring): media = [media] # Parse the Accept request header, and try to match one # of the requested media-ranges (in order of preference). ranges = cherrypy.request.headers.elements('Accept') if not ranges: # Any media type is acceptable. return media[0] else: # Note that 'ranges' is sorted in order of preference for element in ranges: if element.qvalue > 0: if element.value == "*/*": # Matches any type or subtype return media[0] elif element.value.endswith("/*"): # Matches any subtype mtype = element.value[:-1] # Keep the slash for m in media: if m.startswith(mtype): return m else: # Matches exact value if element.value in media: return element.value # No suitable media-range found. ah = cherrypy.request.headers.get('Accept') if ah is None: msg = "Your client did not send an Accept header." else: msg = "Your client sent this Accept header: %s." % ah msg += (" But this resource only emits these media types: %s." % ", ".join(media)) raise cherrypy.HTTPError(406, msg) CherryPy-2.3.0/cherrypy/lib/defaultformmask.py0000644000175000017500000000736710742543110022176 0ustar christianchristian"""Default mask for the form.py module""" import warnings warnings.warn("cherrypy.lib.defaultformmask is deprecated and might disappear in future versions of CherryPy", DeprecationWarning, stacklevel = 2) from xml.sax.saxutils import quoteattr as q def selected(value): """If value is True, return a valid XHTML 'selected' attribute, else ''.""" if value: return ' selected="selected" ' return '' def checked(value): """If value is True, return a valid XHTML 'checked' attribute, else ''.""" if value: return ' checked="checked" ' return '' def defaultMask(field): res = ["", "%s" % field.label] if field.typ == 'text': res.append('' % (q(field.name), q(field.currentValue), q(field.size))) elif field.typ == 'forced': res.append('%s' % (q(field.name), q(field.currentValue), field.currentValue)) elif field.typ == 'password': res.append('' % (q(field.name), q(field.currentValue))) elif field.typ == 'select': res.append('') elif field.typ == 'textarea': # Size is cols x rows if field.size == 15: size = "15x15" else: size = field.size cols, rows = size.split('x') res.append('' % (q(field.name), rows, cols, field.currentValue)) elif field.typ == 'submit': res.append('' % q(field.name)) elif field.typ == 'hidden': if isinstance(field.currentValue, list): vals = field.currentValue else: vals = [field.currentValue] i = '' % q(field.name) return ''.join([i % q(v) for v in vals]) elif field.typ in ('checkbox', 'radio'): res.append('') for option in field.optionList: if isinstance(option, tuple): val, label = option else: val, label = option, option if isinstance(field.currentValue, list): c = checked(val in field.currentValue) else: c = checked(val == field.currentValue) res.append('  %s
' % (field.typ, q(field.name), q(val), c, label)) res.append('') if field.errorMessage: res.append("%s" % field.errorMessage) else: res.append(" ") res.append("") return "\n".join(res) def hiddenMask(field): if isinstance(field.currentValue, list): currentValue = field.currentValue else: currentValue = [field.currentValue] return "\n".join(['' % (q(field.name), q(value)) for value in currentValue]) def defaultHeader(label): return "" def defaultFooter(label): return "
" def echoMask(label): return label CherryPy-2.3.0/cherrypy/lib/form.py0000644000175000017500000001106110742543110017737 0ustar christianchristian"""Simple form handling module.""" import warnings warnings.warn("cherrypy.lib.form is deprecated and might disappear in future versions of CherryPy", DeprecationWarning, stacklevel = 2) import cherrypy import defaultformmask class FormField: def __init__(self, label, name, typ, mask=None, mandatory=0, size='15', optionList=[], defaultValue='', defaultMessage='', validate=None): self.isField = 1 self.label = label self.name = name self.typ = typ if mask is None: self.mask = defaultformmask.defaultMask else: self.mask = mask self.mandatory = mandatory self.size = size self.optionList = optionList self.defaultValue = defaultValue self.defaultMessage = defaultMessage self.validate = validate self.errorMessage = "" def render(self, leaveValues): if leaveValues: if self.typ !='submit': self.currentValue = cherrypy.request.params.get(self.name, "") else: self.currentValue = self.defaultValue else: self.currentValue = self.defaultValue self.errorMessage = self.defaultMessage return self.mask(self) class FormSeparator: def __init__(self, label, mask): self.isField = 0 self.label = label self.mask = mask def render(self, dummy): return self.mask(self.label) class Form: method = "post" enctype = "" def __init__(self, action = "postForm", method = "post", enctype = "", header = defaultformmask.defaultHeader, footer = defaultformmask.defaultFooter, headerLabel = "", footerLabel = ""): self.action = action self.method = method self.enctype = enctype self.header = header self.footer = footer self.headerLabel = headerLabel self.footerLabel = footerLabel def formView(self, leaveValues=0): if self.enctype: enctypeTag = 'enctype="%s"' % self.enctype else: enctypeTag = "" res = ['
' % (self.method, enctypeTag, self.action)] res.append(self.header(self.headerLabel)) for field in self.fieldList: res.append(field.render(leaveValues)) res.append(self.footer(self.footerLabel)) res.append("
") return "".join(res) def validateFields(self): # Should be subclassed # Update field's errorMessage value to set an error pass def validateForm(self): # Reset errorMesage for each field for field in self.fieldList: if field.isField: field.errorMessage = "" # Validate mandatory fields for field in self.fieldList: if (field.isField and field.mandatory and not cherrypy.request.params.get(field.name)): field.errorMessage = "Missing" # Validate fields one by one for field in self.fieldList: if field.isField and field.validate and not field.errorMessage: value = cherrypy.request.params.get(field.name, "") field.errorMessage = field.validate(value) # Validate all fields together (ie: check that passwords match) self.validateFields() for field in self.fieldList: if field.isField and field.errorMessage: return 0 return 1 def setFieldErrorMessage(self, fieldName, errorMessage): for field in self.fieldList: if field.isField and field.name == fieldName: field.errorMessage = errorMessage def getFieldOptionList(self, fieldName): for field in self.fieldList: if field.isField and field.name == fieldName: return field.optionList def getFieldDefaultValue(self, fieldName): for field in self.fieldList: if field.isField and field.name == fieldName: return field.defaultValue def setFieldDefaultValue(self, fieldName, defaultValue): for field in self.fieldList: if field.isField and field.name == fieldName: field.defaultValue = defaultValue def getFieldNameList(self, exceptList=[]): fieldNameList = [] for field in self.fieldList: if field.isField and field.name and field.name not in exceptList: fieldNameList.append(field.name) return fieldNameList CherryPy-2.3.0/cherrypy/lib/httptools.py0000644000175000017500000004651410742543110021047 0ustar christianchristian"""HTTP library functions and tools.""" # This module contains functions and tools for building an HTTP application # framework: any one, not just one whose name starts with "Ch". ;) If you # reference any modules from some popular framework inside *this* module, # FuManChu will personally hang you up by your thumbs and submit you # to a public caning. from BaseHTTPServer import BaseHTTPRequestHandler responseCodes = BaseHTTPRequestHandler.responses.copy() # From http://www.cherrypy.org/ticket/361 responseCodes[500] = ('Internal error', 'The server encountered an unexpected condition ' 'which prevented it from fulfilling the request.') import cgi from email.Header import Header, decode_header import re import time import urllib from urlparse import urlparse def urljoin(*atoms): url = "/".join(atoms) while "//" in url: url = url.replace("//", "/") return url weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] def HTTPDate(dt=None): """Return the given time.struct_time as a string in RFC 1123 format. If no arguments are provided, the current time (as determined by time.gmtime() is used). RFC 2616: "[Concerning RFC 1123, RFC 850, asctime date formats]... HTTP/1.1 clients and servers that parse the date value MUST accept all three formats (for compatibility with HTTP/1.0), though they MUST only generate the RFC 1123 format for representing HTTP-date values in header fields." RFC 1945 (HTTP/1.0) requires the same. """ if dt is None: dt = time.gmtime() year, month, day, hh, mm, ss, wd, y, z = dt # Is "%a, %d %b %Y %H:%M:%S GMT" better or worse? return ("%s, %02d %3s %4d %02d:%02d:%02d GMT" % (weekdayname[wd], day, monthname[month], year, hh, mm, ss)) class Version(object): """A version, such as "2.1 beta 3", which can be compared atom-by-atom. If a string is provided to the constructor, it will be split on word boundaries; that is, "1.4.13 beta 9" -> ["1", "4", "13", "beta", "9"]. Comparisons are performed atom-by-atom, numerically if both atoms are numeric. Therefore, "2.12" is greater than "2.4", and "3.0 beta" is greater than "3.0 alpha" (only because "b" > "a"). If an atom is provided in one Version and not another, the longer Version is greater than the shorter, that is: "4.8 alpha" > "4.8". """ def __init__(self, atoms): """A Version object. atoms: if a str, it will be split on word boundaries; if a float or int, it will be split at the decimal point. """ if isinstance(atoms, (int, float)): atoms = str(atoms) if isinstance(atoms, basestring): self.atoms = re.split(r'\W', atoms) else: self.atoms = [str(x) for x in atoms] def from_http(cls, version_str): """Return a Version object from the given 'HTTP/x.y' string.""" return cls(version_str[5:]) from_http = classmethod(from_http) def to_http(self): """Return a 'HTTP/x.y' string for this Version object.""" return "HTTP/%s.%s" % tuple(self.atoms[:2]) def __str__(self): return ".".join([str(x) for x in self.atoms]) def __cmp__(self, other): cls = self.__class__ if not isinstance(other, cls): # Try to coerce other to a Version instance. other = cls(other) index = 0 while index < len(self.atoms) and index < len(other.atoms): mine, theirs = self.atoms[index], other.atoms[index] if mine.isdigit() and theirs.isdigit(): mine, theirs = int(mine), int(theirs) if mine < theirs: return -1 if mine > theirs: return 1 index += 1 if index < len(other.atoms): return -1 if index < len(self.atoms): return 1 return 0 def getRanges(headervalue, content_length): """Return a list of (start, stop) indices from a Range header, or None. Each (start, stop) tuple will be composed of two ints, which are suitable for use in a slicing operation. That is, the header "Range: bytes=3-6", if applied against a Python string, is requesting resource[3:7]. This function will return the list [(3, 7)]. """ if not headervalue: return None result = [] bytesunit, byteranges = headervalue.split("=", 1) for brange in byteranges.split(","): start, stop = [x.strip() for x in brange.split("-", 1)] if start: if not stop: stop = content_length - 1 start, stop = map(int, (start, stop)) if start >= content_length: # From rfc 2616 sec 14.16: # "If the server receives a request (other than one # including an If-Range request-header field) with an # unsatisfiable Range request-header field (that is, # all of whose byte-range-spec values have a first-byte-pos # value greater than the current length of the selected # resource), it SHOULD return a response code of 416 # (Requested range not satisfiable)." continue if stop < start: # From rfc 2616 sec 14.16: # "If the server ignores a byte-range-spec because it # is syntactically invalid, the server SHOULD treat # the request as if the invalid Range header field # did not exist. (Normally, this means return a 200 # response containing the full entity)." return None result.append((start, stop + 1)) else: if not stop: # See rfc quote above. return None # Negative subscript (last N bytes) result.append((content_length - int(stop), content_length)) return result class HeaderElement(object): """An element (with parameters) from an HTTP header's element list.""" def __init__(self, value, params=None): self.value = value if params is None: params = {} self.params = params def __str__(self): p = [";%s=%s" % (k, v) for k, v in self.params.iteritems()] return "%s%s" % (self.value, "".join(p)) def parse(elementstr): """Transform 'token;key=val' to ('token', {'key': 'val'}).""" # Split the element into a value and parameters. The 'value' may # be of the form, "token=token", but we don't split that here. atoms = [x.strip() for x in elementstr.split(";")] initial_value = atoms.pop(0).strip() params = {} for atom in atoms: atom = [x.strip() for x in atom.split("=", 1) if x.strip()] key = atom.pop(0) if atom: val = atom[0] else: val = "" params[key] = val return initial_value, params parse = staticmethod(parse) def from_str(cls, elementstr): """Construct an instance from a string of the form 'token;key=val'.""" ival, params = cls.parse(elementstr) return cls(ival, params) from_str = classmethod(from_str) class AcceptElement(HeaderElement): """An element (with parameters) from an Accept-* header's element list.""" def from_str(cls, elementstr): qvalue = None # The first "q" parameter (if any) separates the initial # parameter(s) (if any) from the accept-params. atoms = re.split(r'; *q *=', elementstr, 1) initial_value = atoms.pop(0).strip() if atoms: # The qvalue for an Accept header can have extensions. The other # headers cannot, but it's easier to parse them as if they did. qvalue = HeaderElement.from_str(atoms[0].strip()) ival, params = cls.parse(initial_value) if qvalue is not None: params["q"] = qvalue return cls(ival, params) from_str = classmethod(from_str) def qvalue(self): val = self.params.get("q", "1") if isinstance(val, HeaderElement): val = val.value return float(val) qvalue = property(qvalue, doc="The qvalue, or priority, of this value.") def __cmp__(self, other): # If you sort a list of AcceptElement objects, they will be listed # in priority order; the most preferred value will be first. diff = cmp(other.qvalue, self.qvalue) if diff == 0: diff = cmp(str(other), str(self)) return diff def header_elements(fieldname, fieldvalue): """Return a HeaderElement list from a comma-separated header str.""" if not fieldvalue: return None headername = fieldname.lower() result = [] for element in fieldvalue.split(","): if headername.startswith("accept") or headername == 'te': hv = AcceptElement.from_str(element) else: hv = HeaderElement.from_str(element) result.append(hv) result.sort() return result def decode_TEXT(value): """Decode RFC-2047 TEXT (e.g. "=?utf-8?q?f=C3=BCr?=" -> u"f\xfcr").""" atoms = decode_header(value) decodedvalue = "" for atom, charset in atoms: if charset is not None: atom = atom.decode(charset) decodedvalue += atom return decodedvalue def validStatus(status): """Return legal HTTP status Code, Reason-phrase and Message. The status arg must be an int, or a str that begins with an int. If status is an int, or a str and no reason-phrase is supplied, a default reason-phrase will be provided. """ if not status: status = 200 status = str(status) parts = status.split(" ", 1) if len(parts) == 1: # No reason supplied. code, = parts reason = None else: code, reason = parts reason = reason.strip() try: code = int(code) except ValueError: raise ValueError("Illegal response status from server " "(%s is non-numeric)." % repr(code)) if code < 100 or code > 599: raise ValueError("Illegal response status from server " "(%s is out of range)." % repr(code)) if code not in responseCodes: # code is unknown but not illegal defaultReason, message = "", "" else: defaultReason, message = responseCodes[code] if reason is None: reason = defaultReason return code, reason, message def parseRequestLine(requestLine): """Return (method, path, querystring, protocol) from a requestLine.""" method, path, protocol = requestLine.split() # path may be an abs_path (including "http://host.domain.tld"); # Ignore scheme, location, and fragments (so config lookups work). # [Therefore, this assumes all hosts are valid for this server.] scheme, location, path, params, qs, frag = urlparse(path) if path == "*": # "...the request does not apply to a particular resource, # but to the server itself". See # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 pass else: if params: params = ";" + params path = path + params # Unquote the path (e.g. "/this%20path" -> "this path"). # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 # # But note that "...a URI must be separated into its components # before the escaped characters within those components can be # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2 # # Note also that cgi.parse_qs will decode the querystring for us. atoms = [urllib.unquote(x) for x in re.split("(?i)%2F", path)] path = "%2F".join(atoms) return method, path, qs, protocol def parseQueryString(query_string, keep_blank_values=True): """Build a paramMap dictionary from a query_string.""" if re.match(r"[0-9]+,[0-9]+", query_string): # Server-side image map. Map the coords to 'x' and 'y' # (like CGI::Request does). pm = query_string.split(",") pm = {'x': int(pm[0]), 'y': int(pm[1])} else: pm = cgi.parse_qs(query_string, keep_blank_values) for key, val in pm.items(): if len(val) == 1: pm[key] = val[0] return pm def paramsFromCGIForm(form): paramMap = {} for key in form.keys(): valueList = form[key] if isinstance(valueList, list): paramMap[key] = [] for item in valueList: if item.filename is not None: value = item # It's a file upload else: value = item.value # It's a regular field paramMap[key].append(value) else: if valueList.filename is not None: value = valueList # It's a file upload else: value = valueList.value # It's a regular field paramMap[key] = value return paramMap class CaseInsensitiveDict(dict): """A case-insensitive dict subclass. Each key is changed on entry to str(key).title(). """ def __getitem__(self, key): return dict.__getitem__(self, str(key).title()) def __setitem__(self, key, value): dict.__setitem__(self, str(key).title(), value) def __delitem__(self, key): dict.__delitem__(self, str(key).title()) def __contains__(self, key): return dict.__contains__(self, str(key).title()) def get(self, key, default=None): return dict.get(self, str(key).title(), default) def has_key(self, key): return dict.has_key(self, str(key).title()) def update(self, E): for k in E.keys(): self[str(k).title()] = E[k] def fromkeys(cls, seq, value=None): newdict = cls() for k in seq: newdict[str(k).title()] = value return newdict fromkeys = classmethod(fromkeys) def setdefault(self, key, x=None): key = str(key).title() try: return self[key] except KeyError: self[key] = x return x def pop(self, key, default): return dict.pop(self, str(key).title(), default) class HeaderMap(CaseInsensitiveDict): """A dict subclass for HTTP request and response headers. Each key is changed on entry to str(key).title(). This allows headers to be case-insensitive and avoid duplicates. """ def elements(self, key): """Return a list of HeaderElements for the given header (or None).""" key = str(key).title() h = self.get(key) if h is None: return [] return header_elements(key, h) general_fields = ["Cache-Control", "Connection", "Date", "Pragma", "Trailer", "Transfer-Encoding", "Upgrade", "Via", "Warning"] response_fields = ["Accept-Ranges", "Age", "ETag", "Location", "Proxy-Authenticate", "Retry-After", "Server", "Vary", "WWW-Authenticate"] entity_fields = ["Allow", "Content-Encoding", "Content-Language", "Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type", "Expires", "Last-Modified"] order_map = {} for _ in general_fields: order_map[_] = 0 for _ in response_fields: order_map[_] = 1 for _ in entity_fields: order_map[_] = 2 def sorted_list(self, protocol=(1, 0)): """Transform self into a sorted list of (name, value) tuples. From http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 '... it is "good practice" to send general-header fields first, followed by request-header or response-header fields, and ending with the entity-header fields.' """ header_list = [] for key, valueList in self.iteritems(): order = self.order_map.get(key, 3) if not isinstance(valueList, list): valueList = [valueList] for v in valueList: if isinstance(v, unicode): # HTTP/1.0 says, "Words of *TEXT may contain octets # from character sets other than US-ASCII." and # "Recipients of header field TEXT containing octets # outside the US-ASCII character set may assume that # they represent ISO-8859-1 characters." try: v = v.encode("iso-8859-1") except UnicodeEncodeError: if protocol >= (1, 1): # Encode RFC-2047 TEXT # (e.g. u"\u8200" -> "=?utf-8?b?6IiA?="). v = Header(v, 'utf-8').encode() else: raise else: # This coercion should not take any time at all # if value is already of type "str". v = str(v) header_list.append((order, (key, v))) header_list.sort() return [item[1] for item in header_list] class MaxSizeExceeded(Exception): pass class SizeCheckWrapper(object): """Wraps a file-like object, raising MaxSizeExceeded if too large.""" def __init__(self, rfile, maxlen): self.rfile = rfile self.maxlen = maxlen self.bytes_read = 0 def _check_length(self): if self.maxlen and self.bytes_read > self.maxlen: raise MaxSizeExceeded() def read(self, size = None): data = self.rfile.read(size) self.bytes_read += len(data) self._check_length() return data def readline(self, size = None): if size is not None: data = self.rfile.readline(size) self.bytes_read += len(data) self._check_length() return data # User didn't specify a size ... # We read the line in chunks to make sure it's not a 100MB line ! res = [] while True: data = self.rfile.readline(256) self.bytes_read += len(data) self._check_length() res.append(data) # See http://www.cherrypy.org/ticket/421 if len(data) < 256 or data[-1:] == "\n": return ''.join(res) def readlines(self, sizehint = 0): # Shamelessly stolen from StringIO total = 0 lines = [] line = self.readline() while line: lines.append(line) total += len(line) if 0 < sizehint <= total: break line = self.readline() return lines def close(self): self.rfile.close() def __iter__(self): return self def next(self): data = self.rfile.next() self.bytes_read += len(data) self._check_length() return data CherryPy-2.3.0/cherrypy/lib/profiler.py0000644000175000017500000001016410742543110020621 0ustar christianchristian"""Profiler tools for CherryPy. CherryPy users ============== You can profile any of your pages as follows: from cherrypy.lib import profile class Root: p = profile.Profiler("/path/to/profile/dir") def index(self): self.p.run(self._index) index.exposed = True def _index(self): return "Hello, world!" cherrypy.root = Root() CherryPy developers =================== This module can be used whenever you make changes to CherryPy, to get a quick sanity-check on overall CP performance. Set the config entry: "profiling.on = True" to turn on profiling. Then, use the serve() function to browse the results in a web browser. If you run this module from the command line, it will call serve() for you. """ # Make profiler output more readable by adding __init__ modules' parents. def new_func_strip_path(func_name): filename, line, name = func_name if filename.endswith("__init__.py"): return os.path.basename(filename[:-12]) + filename[-12:], line, name return os.path.basename(filename), line, name try: import profile import pstats pstats.func_strip_path = new_func_strip_path except ImportError: profile = None pstats = None import warnings msg = ("Your installation of Python doesn't have a profile module. " "If you're on Debian, you can apt-get python2.4-profiler from " "non-free in a separate step. See http://www.cherrypy.org/wiki/" "ProfilingOnDebian for details.") warnings.warn(msg) import os, os.path import sys try: import cStringIO as StringIO except ImportError: import StringIO class Profiler(object): def __init__(self, path=None): if not path: path = os.path.join(os.path.dirname(__file__), "profile") self.path = path if not os.path.exists(path): os.makedirs(path) self.count = 0 def run(self, func, *args): """run(func, *args). Run func, dumping profile data into self.path.""" self.count += 1 path = os.path.join(self.path, "cp_%04d.prof" % self.count) prof = profile.Profile() prof.runcall(func, *args) prof.dump_stats(path) def statfiles(self): """statfiles() -> list of available profiles.""" return [f for f in os.listdir(self.path) if f.startswith("cp_") and f.endswith(".prof")] def stats(self, filename, sortby='cumulative'): """stats(index) -> output of print_stats() for the given profile.""" s = pstats.Stats(os.path.join(self.path, filename)) s.strip_dirs() s.sort_stats(sortby) oldout = sys.stdout try: sys.stdout = sio = StringIO.StringIO() s.print_stats() finally: sys.stdout = oldout response = sio.getvalue() sio.close() return response def index(self): return """ CherryPy profile data """ index.exposed = True def menu(self): yield "

Profiling runs

" yield "

Click on one of the runs below to see profiling data.

" runs = self.statfiles() runs.sort() for i in runs: yield "%s
" % (i, i) menu.exposed = True def report(self, filename): import cherrypy cherrypy.response.headers['Content-Type'] = 'text/plain' return self.stats(filename) report.exposed = True def serve(path=None, port=8080): import cherrypy cherrypy.root = Profiler(path) cherrypy.config.update({'server.socket_port': int(port), 'server.thread_pool': 10, 'server.environment': "production", 'session.storageType': "ram", }) cherrypy.server.start() if __name__ == "__main__": serve(*tuple(sys.argv[1:])) CherryPy-2.3.0/cherrypy/test/0000755000175000017500000000000010742544024016641 5ustar christianchristianCherryPy-2.3.0/cherrypy/test/static/0000755000175000017500000000000010742544024020130 5ustar christianchristianCherryPy-2.3.0/cherrypy/test/static/dirback.jpg0000644000175000017500000004347610742543107022250 0ustar christianchristianJFIFHHC  !"$"$C d">!1A"2Qaq#3BRbrCS$4c!1A ?bt`p=t@o]ƶAcrZ=VH$8DU ӡJ~msTkCd *8Åj%M7UP. JVQ^$ B8q_2wu>yov:Q B$>e1)nae1Prx9'p0W:a Rs".ͷUl NOJ`09[N =U-Z35ާW=Oe:q}D+|" %Ӣz-OsM|bܓ]fsךh NpM blr),٦<t#LĜ 곗֋2}U0 | =g? DIgoz-CfsRv\iK|.lNZA6r_Dd-CM+FǩVdj_/2Q 8 ,Y]qHp6YinWUhh&kLg{' }6E@ .mw4B|GqaEcEz)fqK]p6Ӛ*/X|)$Cps# 8IU**34BuǨ\i82zU#8T(AɱkXe(fx'J#Au^nJqF ViDb\PT0v^rO@v-YK]#4*<$~U{mDc\;t-hvZj1`@Qu:aҰl#r Qn<tpwB&M&u[o,ip:e`-=D3Z0LhgRl< uT 0D#❆8LZ<۝0UZMW\Pkv#Qѧ+ԓgNl4ZL\I+ʩH8|ф@9vrgwGkF T7Piy0XaI f{G"{vk*6# 1zujAZ[w6ANXU\G5OJV¹Կf0rDU7yK> z C6k㓇}'}b{-2'>|oϺ$hTVֶ枍Jkzalk`›FiXk0d2ep]tn\y*[fk]'`$ kg[D`dgk\t SuO&/kh Ti62I\-2 GM7T6tAV144 9D`%G8Z[ZK75H3$%VTy̱3E 㸚s̺2h3Y& ܆xs6x6Ps-AdD٣@H@<'X7s3qUw/jAۀ͹+h$ICxlc=&eRr&Dp"R4#*%CPLPINM &H hRi}Nihtm )Nj>[2U [mL1MN!Ĩñ~ IDƜ>*bp[ /q-= j 30z9û*H\MFy?*I0b1n 9,xѼÄ Fq]8$Ʉzo D8%(Ei)S٭<.as7͕d4嶌NS!цUUZ'Tb١q%TE"N햑M;oc|?. CI∌w@ L6Oyn mrYNO.NE*esEEXO2ʷW6姢#LOuBAljw?x[\+l@8e;i<EWG\8XKFM@㔄qs ).HjvZR^*;N"]P$1T"r#qAf#TDԼE&fIE6mKf:n@PgS0b*O7vRF4:vj lweHu rǺ]ך?qꊥ+G#1o5O }R18~<0x am09:m& UDlD$k8MYFtv CHp XN)p=앸@Qi= 4 b3ꟺYv\m*ucPR@L8 { \|9iZ5\^ߊH3dRZYiUꌏO%ŗ7Q0oU1U-lM%Ax'R1"3pv[6fqJ-}õ58]qq}UN(ɆY]s'I=zO@\$?gjXo&c 1 ;QJpn oe'5kbݑ5jÚ1uF'B V`3~nL^.3ת)֌.؉Jz NIcdM@Ehq0W 9YnFrL}#(7%wovHi Φ mZl.칭4!YP;Vyv:-,u . XIx>I.kYAɨo VT19A=SU~'p$%ےj֘."z,aIq-ijmy*'1H[2 '= '=QsYiY)3crƖqQ2Tmj0ҸE D\* 19l-+I?:bNQ*B<%y{̙.H8N΂~`.Px8^ټII3&m91Fz~իMu,cV9+oArV&^`aud6)- )ItHw~lf+o+nڞfL8-}Vkх 㒯k|3! ֡}doecJ  t6a4$S-C'xLFr$N5M[\GewR&B|z}ak" @Y96zNfD )N0H; |UD5Ц'z9JdwVˏu*I$[^HBDn9X>t⒓3't#ǥ,V~W??\ܟjMpA,|o7ZCd\ʁV08`|R s LZ18[Uh8ɽB:ceQ( ϢKVah) ًENuUzg>Oɒ$N֒`Q)&f3]Uᮽd!HsdE?6_%@8$YRzd{~  N>8k8^y kbB#EUpꝯZ'@'.hE!TCdŮD $2b52Gܦ%Ǻ0x^ˏ{.\gi:9_eD 23?22K$4]P2geMBPRp l Pڍ LM@]$ V#h{$rB9+8'Tl,ٴ[qkmvz* @fD~$E[%Aa245j`Qk#g$ u01]|0U܃c= rslpZ|2a/Ld4ZגƖ%Ga{@Ӱ%%O'ἓ䵊 )Nj'X 8u)5g!'{wdx) ||*1'2 K2D= ,Sy'K#-si m)TNM&.-Z'p1)S1U2) E2%zk8OtwQ ٌ',PMmQ2Ukf. H1)QO()y#bkYɴ1IU&M`0#j"4DЮ:À l3ZS%]M^Nl x|X5Q1pWg2-)8UZ ;$j{:B|b/Dc}9 QftY?t+CJПC~y`2-Tq!4i4QEd_]d>l S}A nRa=Uhَ~YtSnb^.]rR$:_%Z·uRsԀy!͜;%a"B-tzHܑ`sxG$O]%'V u(gmr+83(GA9)y FFC@#EJ yn) L'I0!F+Cf^:^jЧcp8H` bڤh84?2(q R e;Dޫ?D®=Ҩ $uPp2T&yڋ !Fb9 G4$GZ׃S)5i`fZNuǘ=[@eZiifdouqYA՟ ،uȷ-.X)2\`DOn-CZke[$hNi.)b"߇$QEX!3xS8upsS.8ZK_EJDFC,㕝˪amGac:wON֋Vm<-E25b(R&hRkLȺryHX\(,fyj#wbum)P2E8^HיT"z' 2nI!agL,/SL0&M60 0沜@4LhGRɦIict@: <уZG+Ya`6&t IȘVa`20yh8lddy_B׏E wƽUC2^ y,sOCa9-2hIXIB\G\}CIP\vggff5䊁zBq|r=,$UwDKCJ۵hvYٸ{ G|tH -^VqpYv<"bH\z*5}DT QلZD9%RC  NdTr"E@H ?%ןʜ$uUxI:mb􇉞GIBvh 4bNP`}(o$}=@P6HOEdU6θhlGѣz?7i2(ѽA OY \nfT7"O|G4e nWhtSA#f}Smz@rY{\7\ymn5vopq`>dzh!a~Y&>wՖ,4O諾[m3zU_8d3{8+aMx쩅r!@b߅^8TN![x̶IG1H-;[I"3w&O4cG #NȆ A卹s4 쑀0ol//3(1}Ty"!W2kWô$tMW*D/Nm8TSU!b\y'eG$?)aM L]Nݬ>i[ = 4:903q=ҊNq+3Fاw!i#d&ڕagU 5'8.CVaa gl:iӂA0֏{N%Q-vFwPkchViQׂvMjCA]ˀNx;5MӲrԬm =h9,0OOA%ŰE1pw(.;MQpr8"uW@^NDuCI-OR/}EN}ҝU{{:ӑhGQ;?nۥ]suF׎' &xq^9}-+8hvY @oڤ6ҴR,p"x,;N3O?8& Xӣ&~U&qUj}r3D Qş BE%S &b8)vc5w4ቂѼ'gx*xs3y\o"1AOMg5aMmf/˜$亩p%i&vn2 i4Caqi rЄ8h# RP{47>=U MxENdAܯtԠ^ I BdiyH#g 69f))V*KGt@Q ?`⴩ Ȱa1pT,5VԡuY ._-0dÂjk\boDtyu$j8&Q1%諮ats]`8&vFp;f"^؎ I't, ;c.7r,-þ˖g*3 nTdbLe*6!MΆ&SuI-kAP?S1x34qA"JR1U9Ó% #٤n`;+,Irf"?ԲΜHJ]Zb~f9q/DHUyah5 -u]{(#\4c+$oӚ퓖,֦!s{iĜvgWc[*8m*.*al9Mh1J=C#˪qIdzsY塢%ܺJBoW.,Vlz%oTa$wM~N[[C֐ Uu.isfSVĆIQ)ɞ<$0NU;.qkj;b9=$JU-2yAMYhgq(L}҄ ppuRr=^%g^={Rtc6芡\ݲK'8 a . v762㢳sQ$;g> $;[*ќFRqbjSÑ>k"HAn|<0"Ép']mtI|dA; }*-8,>Tu(UCЅUF"@#.(RiT8ODNFV7iS͹'Cl&3f@9^@ŇhA ُ7"wB?q~ߙU:/(.ýZ(MӞfE⚕َ(4L"&|Pp+/@sM! ]IhH!!Q [$Yo Z|4:$A9\TxZ4(^^x&zg !;He+JtM .L9Ӈ )F foʶ9<13ـLG<р9 M3ȦHփM#dlpphןUNd֘ `qt}J" :]U#vaP3% ;(ZFSk٤˪͚ئdė*9S:\I6Ic' #9'Sfe(h뺃t0c#ik~s+;d4cןUndM4' #h!mrZ<%4t3YL,\5Ų? =4 sGIy@{ا"x0#u(Hm8 u&95T@$ܸ)K@JIq,n\Ԇ 3XD N/bxYK$ at}B W\O <.ݫE<"u8zʓǽyU {=P"g`zi`6!˹N :c,qq!ڠ?<4sasR9j(av5zO0rkS7שUPʸe˧UouU&xy+C[ꨪD~ .8ickY/Bv&o'T CDAbeVtZTay´2CfBJj4Z*h4fI8CJLTKNJv[\%trS%}IOPl G65ZFeMs)R1PݾDzn26gYI f8ќ5qF3!NADb> tC%'wCḼ΅DTv(ϊLSpX2$ ̨tiئ9J8AۈR2 Z%M%c HQdɋ|8;&`S M0JrŬe&sB!`uWQpCv@4~`o\HmO9QkHA×EAњ%spIk@!RcZmD&.5--F[(pSʮpKuQQG>Y RjcuQXqW }NxYIWL 0`,!O4|HxG[1rJFX,}y)fUN]f< hЏQb-C:h=8UT ,,U/Lؗai6IEFD "r= }.{hkQuf7kʼnU$[6@E2=Vj~ӒrEk͢yY\ai7h} Ѓq-JnqN |sLƀaCJ*KFLsa<GX9-@.ͿT "lt+QVI71@Lfw\T|'l-Qt<2>;ͥ3rָ֟} y6qZWT굞a0Ԓ439ςAO=\L&փh*@@X4qPLUD!Όt2itYh#g%"tF{5R1v[9IuB"r*CR[ ,VYүStPo{,iHٿ oDbgB$D  )tk" Ju18p9iˢA2;͛ic sD0AR$@!Rv,IS}go99D:`:‘ t0=!IM" }S4d/kH##KasԨq.'fy3|9J#l ג4ᴢci:J1f74ၬ*xiyLU-TcOuUFa.;r*qB9;29BAMNp0ٹwgoĜ=]f@.vF;F~(898uCC o*A 1*zˆn^;IvG`xQ{$ȧ9}I@4 b]<ʙLSwthb Hq[3ed8zF?rӑY\Ӄ{.w'Kgֲg hStzY7T*:Wd68sR3uE㖋c-?UщĊ6XY2t~ʌseϧxRap-RX'qSJs3|O"œ*lip~Y⫂2O,҃IdVk]%E66NW[b/.3e8תqfm'fb!h 1:+x C1h3 Sh!< GI3Dz+9|yGo[jӨsE2@1.-k7&Djt 3H.6kuA8red'])+:ǐq"eyA2MEyyvC$0,]9prT 2qcahdy+֪u=\L0\2)]uϺC if CD2摌l2iN={cwJA0 "]T`y qZ I'R@#Ș9@d4Lv2q;0aZY#WT?-p &:@RBI1X6EL @!#D]}ES%l,jet+*T:g;?QN[=7ǐDG\pꞑIdui2"50DB y7U|Đۋ] fͷU-b[5DPH`>Ƞ-l rJЅ v姊qRQ0K4!u `Q*DQpȣJqD /q_l}Pe Lq>pGT>biت@Hte9M3; Sh95YH'R`@u)ݖL 5xDT ū&ktfowmUԈ h4(a+DQԜ@ֵ (,Rs4`uZNY0CD)8鑔i]2.n?݆\ m&k\})4}SiiEocohLpw6 D0R"f>tZfR3H?bѮ|?,HCZim<֏"΁# L*E1Rr5&99mS̐ qRb6p֡Tio#)0 3Y0-=+ZNBfI>9I`;6*YMʼnBY7YkKZ2SԛOTzϺP AD'H\Rsixvy90-EPd:\Q=T߈NRT187iXSn !m?=L)CZhb31bܘu,9pPpꔽMn%[8n >:G-Vh2Fu ?\`\Ѧckw!veQ yyRTk #!5c/v ^E^$YR1} ^ZNZ+ÆA-Z[>l ܓSFj2ïe01hk"/:5R"A\V{*Pm @m.}akĴDV4lͨLh )dᩱcLʤLMGEwi)gy9Qț\LJ*:2z42C% @l|:IHr2.ZlԠSGČ{TT7h06s*xF|;^$q-gϥzg ez賓Ლ轥MķWqCZ3a,^^湠egeT- GʱFXH!⩅n6JsOq sF-S%\*~csОRܙFѼ%='baZD)Y@M@-i+%p7WKH- 6r&T8XIuF=W58CA"YwRS qH ;uG r )B]DT*  ͻ[ Xe5:/FT[a5+=%%^\_Iiyw'C$YF]pISOȻ٩32DNnq"<𪃶6HR0ѶۋljsdCD‹'}g5F8li}%1R@J0х!+[8 {5lQ5j` O U߇F;DmA2F]LQ*45nfz+R]D88Y\ >95< o`TX//ޥ~"aQ$3F F&# l9 . g Jn9M$.NO+V#4Hi.d+ ;hfyſ LGz;A;C34L!T=,d]kr(DBbA8] -O7S>Au.S8"^Eqo830c\21~viBƛ&m!?ܬ4 Y\4F)!?rS`? ٛUG0u=QpgwD,c)kpl9&ypggVvJxs5YoLk5RF&2[r2/c $%6Ul}Ɓf ؏,G$ d{Zh6CGЩ0-7ԩ8Ouܕ+ $M9) 7YJN&`ODb CFOsOL%& NhD4\r ) I0̌/d]QM ,B͂A>YhL[Q* J)NqJPY(3\$Yaϊ> (ҁU+Z@s^IxZiGSO"ĉ#eQ aЩm#2;ki( ltŢ#I.@[# - 6t|u yvgE`g󍛟`IL3^FKE| fDPHd΅ )Ė!L(ӖE64qĭ40 m*lNGtZL/ sM=Ҵ%E \ UQh #G,25E@N7ap7wdC_~I䏨Zeبd}Rq~&j:z <4of yU#Z|]@) @3 y-"X$&aVYgYfUZ&soe@nK&#׊,V*L@fs K[P8T9fi Zb8mN-˂vvx;KwPUccZ 5KyyeWsD; 'MTMW bj&6`E8Z(4;*;&ri@BsLeim:4^ŀ;#LS#--mqꂨy!939AI)I79*qliLd K"9sqpq&xJ8Hn EK[y!VfrJa*6@5$`-EDժ"24A <ɃZ\mS6 Rʮ*;$GwY*0aWˆqYU+=Sf6sZktY1 >xzHqHYiFHظ 9+1-wBq4O{G#dTiqtn+R(r̻.I0'$C7u:Ή덖 짆Z&_XBs-xQ 6QsFNVm~I-p7O)mZUP("sD{$D7AwdSt˚d\Bz0oŁa8óEN~y&*iDrME/]3T;D,?[0ٹ6I/ *D B'S'Y䅆OE\\9u0bsE'ݧSvZ z-:d{5'0 dfӌM&CߪVSt+:Ҿ/1mL(66l+x †H%_ʨ"HR t6LIPTľ7/QitIiሓ-Tގ=`6:>n>XRP~w\N%I#9˱:s\Zkڟ*<,nޫB\j43'R! aT@ʘZfFVɔ&g?UbL5aksODboa+x-y+x?1Eceh!oH#M /p L ]<-VZ; g% Hm0Mb?075