mirror of
https://github.com/djohnlewis/stackdump
synced 2025-12-07 00:13:33 +00:00
Added PowerShell equivalents to launch and manage Stackdump on Windows.
This commit is contained in:
30
python/packages/EGG-INFO/PKG-INFO
Normal file
30
python/packages/EGG-INFO/PKG-INFO
Normal file
@@ -0,0 +1,30 @@
|
||||
Metadata-Version: 1.0
|
||||
Name: SQLObject
|
||||
Version: 1.1.3
|
||||
Summary: Object-Relational Manager, aka database wrapper
|
||||
Home-page: http://sqlobject.org/
|
||||
Author: Ian Bicking
|
||||
Author-email: ianb@colorstudy.com
|
||||
License: LGPL
|
||||
Download-URL: http://cheeseshop.python.org/pypi/SQLObject/1.1.3
|
||||
Description: SQLObject is a popular *Object Relational Manager* for providing an
|
||||
object interface to your database, with tables as classes, rows as
|
||||
instances, and columns as attributes.
|
||||
|
||||
SQLObject includes a Python-object-based query language that makes SQL
|
||||
more abstract, and provides substantial database independence for
|
||||
applications.
|
||||
|
||||
Supports MySQL, PostgreSQL, SQLite, Firebird, Sybase, MSSQL and MaxDB (SAPDB).
|
||||
|
||||
For development see the `subversion repository
|
||||
<http://svn.colorstudy.com/SQLObject/branches/1.1/>`_
|
||||
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Topic :: Database
|
||||
Classifier: Topic :: Database :: Front-Ends
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
189
python/packages/EGG-INFO/SOURCES.txt
Normal file
189
python/packages/EGG-INFO/SOURCES.txt
Normal file
@@ -0,0 +1,189 @@
|
||||
MANIFEST.in
|
||||
README.txt
|
||||
setup.cfg
|
||||
setup.py
|
||||
SQLObject.egg-info/PKG-INFO
|
||||
SQLObject.egg-info/SOURCES.txt
|
||||
SQLObject.egg-info/dependency_links.txt
|
||||
SQLObject.egg-info/entry_points.txt
|
||||
SQLObject.egg-info/requires.txt
|
||||
SQLObject.egg-info/top_level.txt
|
||||
debian/changelog
|
||||
debian/control
|
||||
debian/copyright
|
||||
debian/docs
|
||||
debian/examples
|
||||
debian/rules
|
||||
docs/Authors.txt
|
||||
docs/DeveloperGuide.txt
|
||||
docs/FAQ.txt
|
||||
docs/Inheritance.txt
|
||||
docs/News.txt
|
||||
docs/SQLBuilder.txt
|
||||
docs/SQLObject.txt
|
||||
docs/SelectResults.txt
|
||||
docs/TODO.txt
|
||||
docs/Versioning.txt
|
||||
docs/Views.txt
|
||||
docs/community.txt
|
||||
docs/default.css
|
||||
docs/download.txt
|
||||
docs/index.txt
|
||||
docs/interface.py
|
||||
docs/links.txt
|
||||
docs/rebuild
|
||||
docs/sqlobject-admin.txt
|
||||
docs/test.py
|
||||
docs/europython/europython_sqlobj.py
|
||||
docs/europython/main.css
|
||||
docs/europython/person.py
|
||||
docs/presentation-2004-11/sqlobject-and-database-programming.html
|
||||
docs/presentation-2004-11/ui/bodybg.gif
|
||||
docs/presentation-2004-11/ui/custom.css
|
||||
docs/presentation-2004-11/ui/framing.css
|
||||
docs/presentation-2004-11/ui/opera.css
|
||||
docs/presentation-2004-11/ui/pretty.css
|
||||
docs/presentation-2004-11/ui/print.css
|
||||
docs/presentation-2004-11/ui/s5-core.css
|
||||
docs/presentation-2004-11/ui/slides.css
|
||||
docs/presentation-2004-11/ui/slides.js
|
||||
ez_setup/README.txt
|
||||
ez_setup/__init__.py
|
||||
scripts/sqlobject-admin
|
||||
scripts/sqlobject-convertOldURI
|
||||
sqlobject/__init__.py
|
||||
sqlobject/__version__.py
|
||||
sqlobject/boundattributes.py
|
||||
sqlobject/cache.py
|
||||
sqlobject/classregistry.py
|
||||
sqlobject/col.py
|
||||
sqlobject/conftest.py
|
||||
sqlobject/constraints.py
|
||||
sqlobject/converters.py
|
||||
sqlobject/dbconnection.py
|
||||
sqlobject/dberrors.py
|
||||
sqlobject/declarative.py
|
||||
sqlobject/events.py
|
||||
sqlobject/index.py
|
||||
sqlobject/joins.py
|
||||
sqlobject/main.py
|
||||
sqlobject/sqlbuilder.py
|
||||
sqlobject/sresults.py
|
||||
sqlobject/styles.py
|
||||
sqlobject/views.py
|
||||
sqlobject/wsgi_middleware.py
|
||||
sqlobject/firebird/__init__.py
|
||||
sqlobject/firebird/firebirdconnection.py
|
||||
sqlobject/include/__init__.py
|
||||
sqlobject/include/hashcol.py
|
||||
sqlobject/include/pydispatch/README.txt
|
||||
sqlobject/include/pydispatch/__init__.py
|
||||
sqlobject/include/pydispatch/dispatcher.py
|
||||
sqlobject/include/pydispatch/errors.py
|
||||
sqlobject/include/pydispatch/robust.py
|
||||
sqlobject/include/pydispatch/robustapply.py
|
||||
sqlobject/include/pydispatch/saferef.py
|
||||
sqlobject/inheritance/__init__.py
|
||||
sqlobject/inheritance/iteration.py
|
||||
sqlobject/inheritance/tests/__init__.py
|
||||
sqlobject/inheritance/tests/testDestroyCascade.py
|
||||
sqlobject/inheritance/tests/test_aggregates.py
|
||||
sqlobject/inheritance/tests/test_asdict.py
|
||||
sqlobject/inheritance/tests/test_deep_inheritance.py
|
||||
sqlobject/inheritance/tests/test_foreignKey.py
|
||||
sqlobject/inheritance/tests/test_indexes.py
|
||||
sqlobject/inheritance/tests/test_inheritance.py
|
||||
sqlobject/inheritance/tests/test_inheritance_tree.py
|
||||
sqlobject/manager/__init__.py
|
||||
sqlobject/manager/command.py
|
||||
sqlobject/maxdb/__init__.py
|
||||
sqlobject/maxdb/maxdbconnection.py
|
||||
sqlobject/maxdb/readme.txt
|
||||
sqlobject/mssql/__init__.py
|
||||
sqlobject/mssql/mssqlconnection.py
|
||||
sqlobject/mysql/__init__.py
|
||||
sqlobject/mysql/mysqlconnection.py
|
||||
sqlobject/postgres/__init__.py
|
||||
sqlobject/postgres/pgconnection.py
|
||||
sqlobject/rdbhost/__init__.py
|
||||
sqlobject/rdbhost/rdbhostconnection.py
|
||||
sqlobject/sqlite/__init__.py
|
||||
sqlobject/sqlite/sqliteconnection.py
|
||||
sqlobject/sybase/__init__.py
|
||||
sqlobject/sybase/sybaseconnection.py
|
||||
sqlobject/tests/__init__.py
|
||||
sqlobject/tests/dbtest.py
|
||||
sqlobject/tests/test_NoneValuedResultItem.py
|
||||
sqlobject/tests/test_SQLMultipleJoin.py
|
||||
sqlobject/tests/test_SQLRelatedJoin.py
|
||||
sqlobject/tests/test_SingleJoin.py
|
||||
sqlobject/tests/test_aggregates.py
|
||||
sqlobject/tests/test_aliases.py
|
||||
sqlobject/tests/test_asdict.py
|
||||
sqlobject/tests/test_auto.py
|
||||
sqlobject/tests/test_basic.py
|
||||
sqlobject/tests/test_blob.py
|
||||
sqlobject/tests/test_boundattributes.py
|
||||
sqlobject/tests/test_cache.py
|
||||
sqlobject/tests/test_columns_order.py
|
||||
sqlobject/tests/test_combining_joins.py
|
||||
sqlobject/tests/test_comparison.py
|
||||
sqlobject/tests/test_constraints.py
|
||||
sqlobject/tests/test_converters.py
|
||||
sqlobject/tests/test_create_drop.py
|
||||
sqlobject/tests/test_csvexport.py
|
||||
sqlobject/tests/test_cyclic_reference.py
|
||||
sqlobject/tests/test_datetime.py
|
||||
sqlobject/tests/test_decimal.py
|
||||
sqlobject/tests/test_declarative.py
|
||||
sqlobject/tests/test_default_style.py
|
||||
sqlobject/tests/test_delete.py
|
||||
sqlobject/tests/test_distinct.py
|
||||
sqlobject/tests/test_empty.py
|
||||
sqlobject/tests/test_enum.py
|
||||
sqlobject/tests/test_events.py
|
||||
sqlobject/tests/test_exceptions.py
|
||||
sqlobject/tests/test_expire.py
|
||||
sqlobject/tests/test_foreignKey.py
|
||||
sqlobject/tests/test_groupBy.py
|
||||
sqlobject/tests/test_indexes.py
|
||||
sqlobject/tests/test_inheritance.py
|
||||
sqlobject/tests/test_joins.py
|
||||
sqlobject/tests/test_joins_conditional.py
|
||||
sqlobject/tests/test_lazy.py
|
||||
sqlobject/tests/test_new_joins.py
|
||||
sqlobject/tests/test_parse_uri.py
|
||||
sqlobject/tests/test_paste.py
|
||||
sqlobject/tests/test_perConnection.py
|
||||
sqlobject/tests/test_pickle.py
|
||||
sqlobject/tests/test_picklecol.py
|
||||
sqlobject/tests/test_psycopg_sslmode.py
|
||||
sqlobject/tests/test_reparent_sqlmeta.py
|
||||
sqlobject/tests/test_schema.py
|
||||
sqlobject/tests/test_select.py
|
||||
sqlobject/tests/test_select_through.py
|
||||
sqlobject/tests/test_setters.py
|
||||
sqlobject/tests/test_slice.py
|
||||
sqlobject/tests/test_sorting.py
|
||||
sqlobject/tests/test_sqlbuilder.py
|
||||
sqlobject/tests/test_sqlbuilder_dbspecific.py
|
||||
sqlobject/tests/test_sqlbuilder_importproxy.py
|
||||
sqlobject/tests/test_sqlbuilder_joins_instances.py
|
||||
sqlobject/tests/test_sqlite.py
|
||||
sqlobject/tests/test_sqlmeta_idName.py
|
||||
sqlobject/tests/test_sqlobject_admin.py
|
||||
sqlobject/tests/test_string_id.py
|
||||
sqlobject/tests/test_style.py
|
||||
sqlobject/tests/test_subqueries.py
|
||||
sqlobject/tests/test_transactions.py
|
||||
sqlobject/tests/test_unicode.py
|
||||
sqlobject/tests/test_validation.py
|
||||
sqlobject/tests/test_views.py
|
||||
sqlobject/util/__init__.py
|
||||
sqlobject/util/csvexport.py
|
||||
sqlobject/util/csvimport.py
|
||||
sqlobject/util/moduleloader.py
|
||||
sqlobject/util/threadinglocal.py
|
||||
sqlobject/versioning/__init__.py
|
||||
sqlobject/versioning/test/__init__.py
|
||||
sqlobject/versioning/test/test_version.py
|
||||
1
python/packages/EGG-INFO/dependency_links.txt
Normal file
1
python/packages/EGG-INFO/dependency_links.txt
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
4
python/packages/EGG-INFO/entry_points.txt
Normal file
4
python/packages/EGG-INFO/entry_points.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
[paste.filter_app_factory]
|
||||
main = sqlobject.wsgi_middleware:make_middleware
|
||||
|
||||
1
python/packages/EGG-INFO/not-zip-safe
Normal file
1
python/packages/EGG-INFO/not-zip-safe
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
22
python/packages/EGG-INFO/requires.txt
Normal file
22
python/packages/EGG-INFO/requires.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
FormEncode>=1.1.1
|
||||
|
||||
[sqlite]
|
||||
pysqlite
|
||||
|
||||
[sapdb]
|
||||
sapdb
|
||||
|
||||
[postgresql]
|
||||
psycopg
|
||||
|
||||
[firebird]
|
||||
kinterbasdb
|
||||
|
||||
[sybase]
|
||||
Sybase
|
||||
|
||||
[mysql]
|
||||
MySQLdb
|
||||
|
||||
[mssql]
|
||||
adodbapi
|
||||
35
python/packages/EGG-INFO/scripts/sqlobject-admin
Executable file
35
python/packages/EGG-INFO/scripts/sqlobject-admin
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/python2.5
|
||||
import sys
|
||||
import os
|
||||
|
||||
try:
|
||||
import pkg_resources
|
||||
pkg_resources.require('SQLObject>0.6.1')
|
||||
except (ImportError, pkg_resources.DistributionNotFound):
|
||||
# Oh well, we tried...
|
||||
pass
|
||||
|
||||
try:
|
||||
import sqlobject.manager
|
||||
except ImportError:
|
||||
try:
|
||||
here = __file__
|
||||
except NameError:
|
||||
here = sys.argv[0]
|
||||
updir = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(here))),
|
||||
'sqlobject')
|
||||
if os.path.exists(updir):
|
||||
sys.path.insert(0, os.path.dirname(updir))
|
||||
else:
|
||||
print 'I cannot find the sqlobject module'
|
||||
print 'If SQLObject is installed, you may need to set $PYTHONPATH'
|
||||
sys.exit(3)
|
||||
# Now we have to get rid of possibly stale modules from that import
|
||||
# up there
|
||||
for name, value in sys.modules.items():
|
||||
if name.startswith('sqlobject'):
|
||||
del sys.modules[name]
|
||||
|
||||
from sqlobject.manager import command
|
||||
command.the_runner.run(sys.argv)
|
||||
18
python/packages/EGG-INFO/scripts/sqlobject-convertOldURI
Executable file
18
python/packages/EGG-INFO/scripts/sqlobject-convertOldURI
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/python2.5
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
uri = sys.argv[1]
|
||||
except IndexError:
|
||||
sys.exit("Usage: %s old-style-URI" % sys.argv[0])
|
||||
|
||||
try:
|
||||
import pkg_resources
|
||||
pkg_resources.require('SQLObject>=1.0.0')
|
||||
except (ImportError, pkg_resources.DistributionNotFound):
|
||||
pass
|
||||
|
||||
from sqlobject import connectionForURI
|
||||
conn = connectionForURI(uri, oldUri=True)
|
||||
print conn.uri()
|
||||
1
python/packages/EGG-INFO/top_level.txt
Normal file
1
python/packages/EGG-INFO/top_level.txt
Normal file
@@ -0,0 +1 @@
|
||||
sqlobject
|
||||
2966
python/packages/bottle.py
Executable file
2966
python/packages/bottle.py
Executable file
File diff suppressed because it is too large
Load Diff
25
python/packages/cherrypy/LICENSE.txt
Normal file
25
python/packages/cherrypy/LICENSE.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
Copyright (c) 2004-2011, CherryPy Team (team@cherrypy.org)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of the CherryPy Team nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
624
python/packages/cherrypy/__init__.py
Normal file
624
python/packages/cherrypy/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
327
python/packages/cherrypy/_cpchecker.py
Normal file
327
python/packages/cherrypy/_cpchecker.py
Normal file
@@ -0,0 +1,327 @@
|
||||
import os
|
||||
import warnings
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import iteritems, copykeys, builtins
|
||||
|
||||
|
||||
class Checker(object):
|
||||
"""A checker for CherryPy sites and their mounted applications.
|
||||
|
||||
When this object is called at engine startup, it executes each
|
||||
of its own methods whose names start with ``check_``. If you wish
|
||||
to disable selected checks, simply add a line in your global
|
||||
config which sets the appropriate method to False::
|
||||
|
||||
[global]
|
||||
checker.check_skipped_app_config = False
|
||||
|
||||
You may also dynamically add or replace ``check_*`` methods in this way.
|
||||
"""
|
||||
|
||||
on = True
|
||||
"""If True (the default), run all checks; if False, turn off all checks."""
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self._populate_known_types()
|
||||
|
||||
def __call__(self):
|
||||
"""Run all check_* methods."""
|
||||
if self.on:
|
||||
oldformatwarning = warnings.formatwarning
|
||||
warnings.formatwarning = self.formatwarning
|
||||
try:
|
||||
for name in dir(self):
|
||||
if name.startswith("check_"):
|
||||
method = getattr(self, name)
|
||||
if method and hasattr(method, '__call__'):
|
||||
method()
|
||||
finally:
|
||||
warnings.formatwarning = oldformatwarning
|
||||
|
||||
def formatwarning(self, message, category, filename, lineno, line=None):
|
||||
"""Function to format a warning."""
|
||||
return "CherryPy Checker:\n%s\n\n" % message
|
||||
|
||||
# This value should be set inside _cpconfig.
|
||||
global_config_contained_paths = False
|
||||
|
||||
def check_app_config_entries_dont_start_with_script_name(self):
|
||||
"""Check for Application config with sections that repeat script_name."""
|
||||
for sn, app in cherrypy.tree.apps.items():
|
||||
if not isinstance(app, cherrypy.Application):
|
||||
continue
|
||||
if not app.config:
|
||||
continue
|
||||
if sn == '':
|
||||
continue
|
||||
sn_atoms = sn.strip("/").split("/")
|
||||
for key in app.config.keys():
|
||||
key_atoms = key.strip("/").split("/")
|
||||
if key_atoms[:len(sn_atoms)] == sn_atoms:
|
||||
warnings.warn(
|
||||
"The application mounted at %r has config " \
|
||||
"entries that start with its script name: %r" % (sn, key))
|
||||
|
||||
def check_site_config_entries_in_app_config(self):
|
||||
"""Check for mounted Applications that have site-scoped config."""
|
||||
for sn, app in iteritems(cherrypy.tree.apps):
|
||||
if not isinstance(app, cherrypy.Application):
|
||||
continue
|
||||
|
||||
msg = []
|
||||
for section, entries in iteritems(app.config):
|
||||
if section.startswith('/'):
|
||||
for key, value in iteritems(entries):
|
||||
for n in ("engine.", "server.", "tree.", "checker."):
|
||||
if key.startswith(n):
|
||||
msg.append("[%s] %s = %s" % (section, key, value))
|
||||
if msg:
|
||||
msg.insert(0,
|
||||
"The application mounted at %r contains the following "
|
||||
"config entries, which are only allowed in site-wide "
|
||||
"config. Move them to a [global] section and pass them "
|
||||
"to cherrypy.config.update() instead of tree.mount()." % sn)
|
||||
warnings.warn(os.linesep.join(msg))
|
||||
|
||||
def check_skipped_app_config(self):
|
||||
"""Check for mounted Applications that have no config."""
|
||||
for sn, app in cherrypy.tree.apps.items():
|
||||
if not isinstance(app, cherrypy.Application):
|
||||
continue
|
||||
if not app.config:
|
||||
msg = "The Application mounted at %r has an empty config." % sn
|
||||
if self.global_config_contained_paths:
|
||||
msg += (" It looks like the config you passed to "
|
||||
"cherrypy.config.update() contains application-"
|
||||
"specific sections. You must explicitly pass "
|
||||
"application config via "
|
||||
"cherrypy.tree.mount(..., config=app_config)")
|
||||
warnings.warn(msg)
|
||||
return
|
||||
|
||||
def check_app_config_brackets(self):
|
||||
"""Check for Application config with extraneous brackets in section names."""
|
||||
for sn, app in cherrypy.tree.apps.items():
|
||||
if not isinstance(app, cherrypy.Application):
|
||||
continue
|
||||
if not app.config:
|
||||
continue
|
||||
for key in app.config.keys():
|
||||
if key.startswith("[") or key.endswith("]"):
|
||||
warnings.warn(
|
||||
"The application mounted at %r has config " \
|
||||
"section names with extraneous brackets: %r. "
|
||||
"Config *files* need brackets; config *dicts* "
|
||||
"(e.g. passed to tree.mount) do not." % (sn, key))
|
||||
|
||||
def check_static_paths(self):
|
||||
"""Check Application config for incorrect static paths."""
|
||||
# Use the dummy Request object in the main thread.
|
||||
request = cherrypy.request
|
||||
for sn, app in cherrypy.tree.apps.items():
|
||||
if not isinstance(app, cherrypy.Application):
|
||||
continue
|
||||
request.app = app
|
||||
for section in app.config:
|
||||
# get_resource will populate request.config
|
||||
request.get_resource(section + "/dummy.html")
|
||||
conf = request.config.get
|
||||
|
||||
if conf("tools.staticdir.on", False):
|
||||
msg = ""
|
||||
root = conf("tools.staticdir.root")
|
||||
dir = conf("tools.staticdir.dir")
|
||||
if dir is None:
|
||||
msg = "tools.staticdir.dir is not set."
|
||||
else:
|
||||
fulldir = ""
|
||||
if os.path.isabs(dir):
|
||||
fulldir = dir
|
||||
if root:
|
||||
msg = ("dir is an absolute path, even "
|
||||
"though a root is provided.")
|
||||
testdir = os.path.join(root, dir[1:])
|
||||
if os.path.exists(testdir):
|
||||
msg += ("\nIf you meant to serve the "
|
||||
"filesystem folder at %r, remove "
|
||||
"the leading slash from dir." % testdir)
|
||||
else:
|
||||
if not root:
|
||||
msg = "dir is a relative path and no root provided."
|
||||
else:
|
||||
fulldir = os.path.join(root, dir)
|
||||
if not os.path.isabs(fulldir):
|
||||
msg = "%r is not an absolute path." % fulldir
|
||||
|
||||
if fulldir and not os.path.exists(fulldir):
|
||||
if msg:
|
||||
msg += "\n"
|
||||
msg += ("%r (root + dir) is not an existing "
|
||||
"filesystem path." % fulldir)
|
||||
|
||||
if msg:
|
||||
warnings.warn("%s\nsection: [%s]\nroot: %r\ndir: %r"
|
||||
% (msg, section, root, dir))
|
||||
|
||||
|
||||
# -------------------------- Compatibility -------------------------- #
|
||||
|
||||
obsolete = {
|
||||
'server.default_content_type': 'tools.response_headers.headers',
|
||||
'log_access_file': 'log.access_file',
|
||||
'log_config_options': None,
|
||||
'log_file': 'log.error_file',
|
||||
'log_file_not_found': None,
|
||||
'log_request_headers': 'tools.log_headers.on',
|
||||
'log_to_screen': 'log.screen',
|
||||
'show_tracebacks': 'request.show_tracebacks',
|
||||
'throw_errors': 'request.throw_errors',
|
||||
'profiler.on': ('cherrypy.tree.mount(profiler.make_app('
|
||||
'cherrypy.Application(Root())))'),
|
||||
}
|
||||
|
||||
deprecated = {}
|
||||
|
||||
def _compat(self, config):
|
||||
"""Process config and warn on each obsolete or deprecated entry."""
|
||||
for section, conf in config.items():
|
||||
if isinstance(conf, dict):
|
||||
for k, v in conf.items():
|
||||
if k in self.obsolete:
|
||||
warnings.warn("%r is obsolete. Use %r instead.\n"
|
||||
"section: [%s]" %
|
||||
(k, self.obsolete[k], section))
|
||||
elif k in self.deprecated:
|
||||
warnings.warn("%r is deprecated. Use %r instead.\n"
|
||||
"section: [%s]" %
|
||||
(k, self.deprecated[k], section))
|
||||
else:
|
||||
if section in self.obsolete:
|
||||
warnings.warn("%r is obsolete. Use %r instead."
|
||||
% (section, self.obsolete[section]))
|
||||
elif section in self.deprecated:
|
||||
warnings.warn("%r is deprecated. Use %r instead."
|
||||
% (section, self.deprecated[section]))
|
||||
|
||||
def check_compatibility(self):
|
||||
"""Process config and warn on each obsolete or deprecated entry."""
|
||||
self._compat(cherrypy.config)
|
||||
for sn, app in cherrypy.tree.apps.items():
|
||||
if not isinstance(app, cherrypy.Application):
|
||||
continue
|
||||
self._compat(app.config)
|
||||
|
||||
|
||||
# ------------------------ Known Namespaces ------------------------ #
|
||||
|
||||
extra_config_namespaces = []
|
||||
|
||||
def _known_ns(self, app):
|
||||
ns = ["wsgi"]
|
||||
ns.extend(copykeys(app.toolboxes))
|
||||
ns.extend(copykeys(app.namespaces))
|
||||
ns.extend(copykeys(app.request_class.namespaces))
|
||||
ns.extend(copykeys(cherrypy.config.namespaces))
|
||||
ns += self.extra_config_namespaces
|
||||
|
||||
for section, conf in app.config.items():
|
||||
is_path_section = section.startswith("/")
|
||||
if is_path_section and isinstance(conf, dict):
|
||||
for k, v in conf.items():
|
||||
atoms = k.split(".")
|
||||
if len(atoms) > 1:
|
||||
if atoms[0] not in ns:
|
||||
# Spit out a special warning if a known
|
||||
# namespace is preceded by "cherrypy."
|
||||
if (atoms[0] == "cherrypy" and atoms[1] in ns):
|
||||
msg = ("The config entry %r is invalid; "
|
||||
"try %r instead.\nsection: [%s]"
|
||||
% (k, ".".join(atoms[1:]), section))
|
||||
else:
|
||||
msg = ("The config entry %r is invalid, because "
|
||||
"the %r config namespace is unknown.\n"
|
||||
"section: [%s]" % (k, atoms[0], section))
|
||||
warnings.warn(msg)
|
||||
elif atoms[0] == "tools":
|
||||
if atoms[1] not in dir(cherrypy.tools):
|
||||
msg = ("The config entry %r may be invalid, "
|
||||
"because the %r tool was not found.\n"
|
||||
"section: [%s]" % (k, atoms[1], section))
|
||||
warnings.warn(msg)
|
||||
|
||||
def check_config_namespaces(self):
|
||||
"""Process config and warn on each unknown config namespace."""
|
||||
for sn, app in cherrypy.tree.apps.items():
|
||||
if not isinstance(app, cherrypy.Application):
|
||||
continue
|
||||
self._known_ns(app)
|
||||
|
||||
|
||||
|
||||
|
||||
# -------------------------- Config Types -------------------------- #
|
||||
|
||||
known_config_types = {}
|
||||
|
||||
def _populate_known_types(self):
|
||||
b = [x for x in vars(builtins).values()
|
||||
if type(x) is type(str)]
|
||||
|
||||
def traverse(obj, namespace):
|
||||
for name in dir(obj):
|
||||
# Hack for 3.2's warning about body_params
|
||||
if name == 'body_params':
|
||||
continue
|
||||
vtype = type(getattr(obj, name, None))
|
||||
if vtype in b:
|
||||
self.known_config_types[namespace + "." + name] = vtype
|
||||
|
||||
traverse(cherrypy.request, "request")
|
||||
traverse(cherrypy.response, "response")
|
||||
traverse(cherrypy.server, "server")
|
||||
traverse(cherrypy.engine, "engine")
|
||||
traverse(cherrypy.log, "log")
|
||||
|
||||
def _known_types(self, config):
|
||||
msg = ("The config entry %r in section %r is of type %r, "
|
||||
"which does not match the expected type %r.")
|
||||
|
||||
for section, conf in config.items():
|
||||
if isinstance(conf, dict):
|
||||
for k, v in conf.items():
|
||||
if v is not None:
|
||||
expected_type = self.known_config_types.get(k, None)
|
||||
vtype = type(v)
|
||||
if expected_type and vtype != expected_type:
|
||||
warnings.warn(msg % (k, section, vtype.__name__,
|
||||
expected_type.__name__))
|
||||
else:
|
||||
k, v = section, conf
|
||||
if v is not None:
|
||||
expected_type = self.known_config_types.get(k, None)
|
||||
vtype = type(v)
|
||||
if expected_type and vtype != expected_type:
|
||||
warnings.warn(msg % (k, section, vtype.__name__,
|
||||
expected_type.__name__))
|
||||
|
||||
def check_config_types(self):
|
||||
"""Assert that config values are of the same type as default values."""
|
||||
self._known_types(cherrypy.config)
|
||||
for sn, app in cherrypy.tree.apps.items():
|
||||
if not isinstance(app, cherrypy.Application):
|
||||
continue
|
||||
self._known_types(app.config)
|
||||
|
||||
|
||||
# -------------------- Specific config warnings -------------------- #
|
||||
|
||||
def check_localhost(self):
|
||||
"""Warn if any socket_host is 'localhost'. See #711."""
|
||||
for k, v in cherrypy.config.items():
|
||||
if k == 'server.socket_host' and v == 'localhost':
|
||||
warnings.warn("The use of 'localhost' as a socket host can "
|
||||
"cause problems on newer systems, since 'localhost' can "
|
||||
"map to either an IPv4 or an IPv6 address. You should "
|
||||
"use '127.0.0.1' or '[::1]' instead.")
|
||||
318
python/packages/cherrypy/_cpcompat.py
Normal file
318
python/packages/cherrypy/_cpcompat.py
Normal file
@@ -0,0 +1,318 @@
|
||||
"""Compatibility code for using CherryPy with various versions of Python.
|
||||
|
||||
CherryPy 3.2 is compatible with Python versions 2.3+. This module provides a
|
||||
useful abstraction over the differences between Python versions, sometimes by
|
||||
preferring a newer idiom, sometimes an older one, and sometimes a custom one.
|
||||
|
||||
In particular, Python 2 uses str and '' for byte strings, while Python 3
|
||||
uses str and '' for unicode strings. We will call each of these the 'native
|
||||
string' type for each version. Because of this major difference, this module
|
||||
provides new 'bytestr', 'unicodestr', and 'nativestr' attributes, as well as
|
||||
two functions: 'ntob', which translates native strings (of type 'str') into
|
||||
byte strings regardless of Python version, and 'ntou', which translates native
|
||||
strings to unicode strings. This also provides a 'BytesIO' name for dealing
|
||||
specifically with bytes, and a 'StringIO' name for dealing with native strings.
|
||||
It also provides a 'base64_decode' function with native strings as input and
|
||||
output.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
py3k = True
|
||||
bytestr = bytes
|
||||
unicodestr = str
|
||||
nativestr = unicodestr
|
||||
basestring = (bytes, str)
|
||||
def ntob(n, encoding='ISO-8859-1'):
|
||||
"""Return the given native string as a byte string in the given encoding."""
|
||||
# In Python 3, the native string type is unicode
|
||||
return n.encode(encoding)
|
||||
def ntou(n, encoding='ISO-8859-1'):
|
||||
"""Return the given native string as a unicode string with the given encoding."""
|
||||
# In Python 3, the native string type is unicode
|
||||
return n
|
||||
def tonative(n, encoding='ISO-8859-1'):
|
||||
"""Return the given string as a native string in the given encoding."""
|
||||
# In Python 3, the native string type is unicode
|
||||
if isinstance(n, bytes):
|
||||
return n.decode(encoding)
|
||||
return n
|
||||
# type("")
|
||||
from io import StringIO
|
||||
# bytes:
|
||||
from io import BytesIO as BytesIO
|
||||
else:
|
||||
# Python 2
|
||||
py3k = False
|
||||
bytestr = str
|
||||
unicodestr = unicode
|
||||
nativestr = bytestr
|
||||
basestring = basestring
|
||||
def ntob(n, encoding='ISO-8859-1'):
|
||||
"""Return the given native string as a byte string in the given encoding."""
|
||||
# In Python 2, the native string type is bytes. Assume it's already
|
||||
# in the given encoding, which for ISO-8859-1 is almost always what
|
||||
# was intended.
|
||||
return n
|
||||
def ntou(n, encoding='ISO-8859-1'):
|
||||
"""Return the given native string as a unicode string with the given encoding."""
|
||||
# In Python 2, the native string type is bytes.
|
||||
# First, check for the special encoding 'escape'. The test suite uses this
|
||||
# to signal that it wants to pass a string with embedded \uXXXX escapes,
|
||||
# but without having to prefix it with u'' for Python 2, but no prefix
|
||||
# for Python 3.
|
||||
if encoding == 'escape':
|
||||
return unicode(
|
||||
re.sub(r'\\u([0-9a-zA-Z]{4})',
|
||||
lambda m: unichr(int(m.group(1), 16)),
|
||||
n.decode('ISO-8859-1')))
|
||||
# Assume it's already in the given encoding, which for ISO-8859-1 is almost
|
||||
# always what was intended.
|
||||
return n.decode(encoding)
|
||||
def tonative(n, encoding='ISO-8859-1'):
|
||||
"""Return the given string as a native string in the given encoding."""
|
||||
# In Python 2, the native string type is bytes.
|
||||
if isinstance(n, unicode):
|
||||
return n.encode(encoding)
|
||||
return n
|
||||
try:
|
||||
# type("")
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
# type("")
|
||||
from StringIO import StringIO
|
||||
# bytes:
|
||||
BytesIO = StringIO
|
||||
|
||||
try:
|
||||
set = set
|
||||
except NameError:
|
||||
from sets import Set as set
|
||||
|
||||
try:
|
||||
# Python 3.1+
|
||||
from base64 import decodebytes as _base64_decodebytes
|
||||
except ImportError:
|
||||
# Python 3.0-
|
||||
# since CherryPy claims compability with Python 2.3, we must use
|
||||
# the legacy API of base64
|
||||
from base64 import decodestring as _base64_decodebytes
|
||||
|
||||
def base64_decode(n, encoding='ISO-8859-1'):
|
||||
"""Return the native string base64-decoded (as a native string)."""
|
||||
if isinstance(n, unicodestr):
|
||||
b = n.encode(encoding)
|
||||
else:
|
||||
b = n
|
||||
b = _base64_decodebytes(b)
|
||||
if nativestr is unicodestr:
|
||||
return b.decode(encoding)
|
||||
else:
|
||||
return b
|
||||
|
||||
try:
|
||||
# Python 2.5+
|
||||
from hashlib import md5
|
||||
except ImportError:
|
||||
from md5 import new as md5
|
||||
|
||||
try:
|
||||
# Python 2.5+
|
||||
from hashlib import sha1 as sha
|
||||
except ImportError:
|
||||
from sha import new as sha
|
||||
|
||||
try:
|
||||
sorted = sorted
|
||||
except NameError:
|
||||
def sorted(i):
|
||||
i = i[:]
|
||||
i.sort()
|
||||
return i
|
||||
|
||||
try:
|
||||
reversed = reversed
|
||||
except NameError:
|
||||
def reversed(x):
|
||||
i = len(x)
|
||||
while i > 0:
|
||||
i -= 1
|
||||
yield x[i]
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
from urllib.parse import urljoin, urlencode
|
||||
from urllib.parse import quote, quote_plus
|
||||
from urllib.request import unquote, urlopen
|
||||
from urllib.request import parse_http_list, parse_keqv_list
|
||||
except ImportError:
|
||||
# Python 2
|
||||
from urlparse import urljoin
|
||||
from urllib import urlencode, urlopen
|
||||
from urllib import quote, quote_plus
|
||||
from urllib import unquote
|
||||
from urllib2 import parse_http_list, parse_keqv_list
|
||||
|
||||
try:
|
||||
from threading import local as threadlocal
|
||||
except ImportError:
|
||||
from cherrypy._cpthreadinglocal import local as threadlocal
|
||||
|
||||
try:
|
||||
dict.iteritems
|
||||
# Python 2
|
||||
iteritems = lambda d: d.iteritems()
|
||||
copyitems = lambda d: d.items()
|
||||
except AttributeError:
|
||||
# Python 3
|
||||
iteritems = lambda d: d.items()
|
||||
copyitems = lambda d: list(d.items())
|
||||
|
||||
try:
|
||||
dict.iterkeys
|
||||
# Python 2
|
||||
iterkeys = lambda d: d.iterkeys()
|
||||
copykeys = lambda d: d.keys()
|
||||
except AttributeError:
|
||||
# Python 3
|
||||
iterkeys = lambda d: d.keys()
|
||||
copykeys = lambda d: list(d.keys())
|
||||
|
||||
try:
|
||||
dict.itervalues
|
||||
# Python 2
|
||||
itervalues = lambda d: d.itervalues()
|
||||
copyvalues = lambda d: d.values()
|
||||
except AttributeError:
|
||||
# Python 3
|
||||
itervalues = lambda d: d.values()
|
||||
copyvalues = lambda d: list(d.values())
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
import builtins
|
||||
except ImportError:
|
||||
# Python 2
|
||||
import __builtin__ as builtins
|
||||
|
||||
try:
|
||||
# Python 2. We have to do it in this order so Python 2 builds
|
||||
# don't try to import the 'http' module from cherrypy.lib
|
||||
from Cookie import SimpleCookie, CookieError
|
||||
from httplib import BadStatusLine, HTTPConnection, HTTPSConnection, IncompleteRead, NotConnected
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||
except ImportError:
|
||||
# Python 3
|
||||
from http.cookies import SimpleCookie, CookieError
|
||||
from http.client import BadStatusLine, HTTPConnection, HTTPSConnection, IncompleteRead, NotConnected
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
|
||||
try:
|
||||
# Python 2. We have to do it in this order so Python 2 builds
|
||||
# don't try to import the 'http' module from cherrypy.lib
|
||||
from httplib import HTTPSConnection
|
||||
except ImportError:
|
||||
try:
|
||||
# Python 3
|
||||
from http.client import HTTPSConnection
|
||||
except ImportError:
|
||||
# Some platforms which don't have SSL don't expose HTTPSConnection
|
||||
HTTPSConnection = None
|
||||
|
||||
try:
|
||||
# Python 2
|
||||
xrange = xrange
|
||||
except NameError:
|
||||
# Python 3
|
||||
xrange = range
|
||||
|
||||
import threading
|
||||
if hasattr(threading.Thread, "daemon"):
|
||||
# Python 2.6+
|
||||
def get_daemon(t):
|
||||
return t.daemon
|
||||
def set_daemon(t, val):
|
||||
t.daemon = val
|
||||
else:
|
||||
def get_daemon(t):
|
||||
return t.isDaemon()
|
||||
def set_daemon(t, val):
|
||||
t.setDaemon(val)
|
||||
|
||||
try:
|
||||
from email.utils import formatdate
|
||||
def HTTPDate(timeval=None):
|
||||
return formatdate(timeval, usegmt=True)
|
||||
except ImportError:
|
||||
from rfc822 import formatdate as HTTPDate
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
from urllib.parse import unquote as parse_unquote
|
||||
def unquote_qs(atom, encoding, errors='strict'):
|
||||
return parse_unquote(atom.replace('+', ' '), encoding=encoding, errors=errors)
|
||||
except ImportError:
|
||||
# Python 2
|
||||
from urllib import unquote as parse_unquote
|
||||
def unquote_qs(atom, encoding, errors='strict'):
|
||||
return parse_unquote(atom.replace('+', ' ')).decode(encoding, errors)
|
||||
|
||||
try:
|
||||
# Prefer simplejson, which is usually more advanced than the builtin module.
|
||||
import simplejson as json
|
||||
json_decode = json.JSONDecoder().decode
|
||||
json_encode = json.JSONEncoder().iterencode
|
||||
except ImportError:
|
||||
if py3k:
|
||||
# Python 3.0: json is part of the standard library,
|
||||
# but outputs unicode. We need bytes.
|
||||
import json
|
||||
json_decode = json.JSONDecoder().decode
|
||||
_json_encode = json.JSONEncoder().iterencode
|
||||
def json_encode(value):
|
||||
for chunk in _json_encode(value):
|
||||
yield chunk.encode('utf8')
|
||||
elif sys.version_info >= (2, 6):
|
||||
# Python 2.6: json is part of the standard library
|
||||
import json
|
||||
json_decode = json.JSONDecoder().decode
|
||||
json_encode = json.JSONEncoder().iterencode
|
||||
else:
|
||||
json = None
|
||||
def json_decode(s):
|
||||
raise ValueError('No JSON library is available')
|
||||
def json_encode(s):
|
||||
raise ValueError('No JSON library is available')
|
||||
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
# In Python 2, pickle is a Python version.
|
||||
# In Python 3, pickle is the sped-up C version.
|
||||
import pickle
|
||||
|
||||
try:
|
||||
os.urandom(20)
|
||||
import binascii
|
||||
def random20():
|
||||
return binascii.hexlify(os.urandom(20)).decode('ascii')
|
||||
except (AttributeError, NotImplementedError):
|
||||
import random
|
||||
# os.urandom not available until Python 2.4. Fall back to random.random.
|
||||
def random20():
|
||||
return sha('%s' % random.random()).hexdigest()
|
||||
|
||||
try:
|
||||
from _thread import get_ident as get_thread_ident
|
||||
except ImportError:
|
||||
from thread import get_ident as get_thread_ident
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
next = next
|
||||
except NameError:
|
||||
# Python 2
|
||||
def next(i):
|
||||
return i.next()
|
||||
295
python/packages/cherrypy/_cpconfig.py
Normal file
295
python/packages/cherrypy/_cpconfig.py
Normal file
@@ -0,0 +1,295 @@
|
||||
"""
|
||||
Configuration system for CherryPy.
|
||||
|
||||
Configuration in CherryPy is implemented via dictionaries. Keys are strings
|
||||
which name the mapped value, which may be of any type.
|
||||
|
||||
|
||||
Architecture
|
||||
------------
|
||||
|
||||
CherryPy Requests are part of an Application, which runs in a global context,
|
||||
and configuration data may apply to any of those three scopes:
|
||||
|
||||
Global
|
||||
Configuration entries which apply everywhere are stored in
|
||||
cherrypy.config.
|
||||
|
||||
Application
|
||||
Entries which apply to each mounted application are stored
|
||||
on the Application object itself, as 'app.config'. This is a two-level
|
||||
dict where each key is a path, or "relative URL" (for example, "/" or
|
||||
"/path/to/my/page"), and each value is a config dict. Usually, this
|
||||
data is provided in the call to tree.mount(root(), config=conf),
|
||||
although you may also use app.merge(conf).
|
||||
|
||||
Request
|
||||
Each Request object possesses a single 'Request.config' dict.
|
||||
Early in the request process, this dict is populated by merging global
|
||||
config entries, Application entries (whose path equals or is a parent
|
||||
of Request.path_info), and any config acquired while looking up the
|
||||
page handler (see next).
|
||||
|
||||
|
||||
Declaration
|
||||
-----------
|
||||
|
||||
Configuration data may be supplied as a Python dictionary, as a filename,
|
||||
or as an open file object. When you supply a filename or file, CherryPy
|
||||
uses Python's builtin ConfigParser; you declare Application config by
|
||||
writing each path as a section header::
|
||||
|
||||
[/path/to/my/page]
|
||||
request.stream = True
|
||||
|
||||
To declare global configuration entries, place them in a [global] section.
|
||||
|
||||
You may also declare config entries directly on the classes and methods
|
||||
(page handlers) that make up your CherryPy application via the ``_cp_config``
|
||||
attribute. For example::
|
||||
|
||||
class Demo:
|
||||
_cp_config = {'tools.gzip.on': True}
|
||||
|
||||
def index(self):
|
||||
return "Hello world"
|
||||
index.exposed = True
|
||||
index._cp_config = {'request.show_tracebacks': False}
|
||||
|
||||
.. note::
|
||||
|
||||
This behavior is only guaranteed for the default dispatcher.
|
||||
Other dispatchers may have different restrictions on where
|
||||
you can attach _cp_config attributes.
|
||||
|
||||
|
||||
Namespaces
|
||||
----------
|
||||
|
||||
Configuration keys are separated into namespaces by the first "." in the key.
|
||||
Current namespaces:
|
||||
|
||||
engine
|
||||
Controls the 'application engine', including autoreload.
|
||||
These can only be declared in the global config.
|
||||
|
||||
tree
|
||||
Grafts cherrypy.Application objects onto cherrypy.tree.
|
||||
These can only be declared in the global config.
|
||||
|
||||
hooks
|
||||
Declares additional request-processing functions.
|
||||
|
||||
log
|
||||
Configures the logging for each application.
|
||||
These can only be declared in the global or / config.
|
||||
|
||||
request
|
||||
Adds attributes to each Request.
|
||||
|
||||
response
|
||||
Adds attributes to each Response.
|
||||
|
||||
server
|
||||
Controls the default HTTP server via cherrypy.server.
|
||||
These can only be declared in the global config.
|
||||
|
||||
tools
|
||||
Runs and configures additional request-processing packages.
|
||||
|
||||
wsgi
|
||||
Adds WSGI middleware to an Application's "pipeline".
|
||||
These can only be declared in the app's root config ("/").
|
||||
|
||||
checker
|
||||
Controls the 'checker', which looks for common errors in
|
||||
app state (including config) when the engine starts.
|
||||
Global config only.
|
||||
|
||||
The only key that does not exist in a namespace is the "environment" entry.
|
||||
This special entry 'imports' other config entries from a template stored in
|
||||
cherrypy._cpconfig.environments[environment]. It only applies to the global
|
||||
config, and only when you use cherrypy.config.update.
|
||||
|
||||
You can define your own namespaces to be called at the Global, Application,
|
||||
or Request level, by adding a named handler to cherrypy.config.namespaces,
|
||||
app.namespaces, or app.request_class.namespaces. The name can
|
||||
be any string, and the handler must be either a callable or a (Python 2.5
|
||||
style) context manager.
|
||||
"""
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import set, basestring
|
||||
from cherrypy.lib import reprconf
|
||||
|
||||
# Deprecated in CherryPy 3.2--remove in 3.3
|
||||
NamespaceSet = reprconf.NamespaceSet
|
||||
|
||||
def merge(base, other):
|
||||
"""Merge one app config (from a dict, file, or filename) into another.
|
||||
|
||||
If the given config is a filename, it will be appended to
|
||||
the list of files to monitor for "autoreload" changes.
|
||||
"""
|
||||
if isinstance(other, basestring):
|
||||
cherrypy.engine.autoreload.files.add(other)
|
||||
|
||||
# Load other into base
|
||||
for section, value_map in reprconf.as_dict(other).items():
|
||||
if not isinstance(value_map, dict):
|
||||
raise ValueError(
|
||||
"Application config must include section headers, but the "
|
||||
"config you tried to merge doesn't have any sections. "
|
||||
"Wrap your config in another dict with paths as section "
|
||||
"headers, for example: {'/': config}.")
|
||||
base.setdefault(section, {}).update(value_map)
|
||||
|
||||
|
||||
class Config(reprconf.Config):
|
||||
"""The 'global' configuration data for the entire CherryPy process."""
|
||||
|
||||
def update(self, config):
|
||||
"""Update self from a dict, file or filename."""
|
||||
if isinstance(config, basestring):
|
||||
# Filename
|
||||
cherrypy.engine.autoreload.files.add(config)
|
||||
reprconf.Config.update(self, config)
|
||||
|
||||
def _apply(self, config):
|
||||
"""Update self from a dict."""
|
||||
if isinstance(config.get("global", None), dict):
|
||||
if len(config) > 1:
|
||||
cherrypy.checker.global_config_contained_paths = True
|
||||
config = config["global"]
|
||||
if 'tools.staticdir.dir' in config:
|
||||
config['tools.staticdir.section'] = "global"
|
||||
reprconf.Config._apply(self, config)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""Decorator for page handlers to set _cp_config."""
|
||||
if args:
|
||||
raise TypeError(
|
||||
"The cherrypy.config decorator does not accept positional "
|
||||
"arguments; you must use keyword arguments.")
|
||||
def tool_decorator(f):
|
||||
if not hasattr(f, "_cp_config"):
|
||||
f._cp_config = {}
|
||||
for k, v in kwargs.items():
|
||||
f._cp_config[k] = v
|
||||
return f
|
||||
return tool_decorator
|
||||
|
||||
|
||||
Config.environments = environments = {
|
||||
"staging": {
|
||||
'engine.autoreload_on': False,
|
||||
'checker.on': False,
|
||||
'tools.log_headers.on': False,
|
||||
'request.show_tracebacks': False,
|
||||
'request.show_mismatched_params': False,
|
||||
},
|
||||
"production": {
|
||||
'engine.autoreload_on': False,
|
||||
'checker.on': False,
|
||||
'tools.log_headers.on': False,
|
||||
'request.show_tracebacks': False,
|
||||
'request.show_mismatched_params': False,
|
||||
'log.screen': False,
|
||||
},
|
||||
"embedded": {
|
||||
# For use with CherryPy embedded in another deployment stack.
|
||||
'engine.autoreload_on': False,
|
||||
'checker.on': False,
|
||||
'tools.log_headers.on': False,
|
||||
'request.show_tracebacks': False,
|
||||
'request.show_mismatched_params': False,
|
||||
'log.screen': False,
|
||||
'engine.SIGHUP': None,
|
||||
'engine.SIGTERM': None,
|
||||
},
|
||||
"test_suite": {
|
||||
'engine.autoreload_on': False,
|
||||
'checker.on': False,
|
||||
'tools.log_headers.on': False,
|
||||
'request.show_tracebacks': True,
|
||||
'request.show_mismatched_params': True,
|
||||
'log.screen': False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _server_namespace_handler(k, v):
|
||||
"""Config handler for the "server" namespace."""
|
||||
atoms = k.split(".", 1)
|
||||
if len(atoms) > 1:
|
||||
# Special-case config keys of the form 'server.servername.socket_port'
|
||||
# to configure additional HTTP servers.
|
||||
if not hasattr(cherrypy, "servers"):
|
||||
cherrypy.servers = {}
|
||||
|
||||
servername, k = atoms
|
||||
if servername not in cherrypy.servers:
|
||||
from cherrypy import _cpserver
|
||||
cherrypy.servers[servername] = _cpserver.Server()
|
||||
# On by default, but 'on = False' can unsubscribe it (see below).
|
||||
cherrypy.servers[servername].subscribe()
|
||||
|
||||
if k == 'on':
|
||||
if v:
|
||||
cherrypy.servers[servername].subscribe()
|
||||
else:
|
||||
cherrypy.servers[servername].unsubscribe()
|
||||
else:
|
||||
setattr(cherrypy.servers[servername], k, v)
|
||||
else:
|
||||
setattr(cherrypy.server, k, v)
|
||||
Config.namespaces["server"] = _server_namespace_handler
|
||||
|
||||
def _engine_namespace_handler(k, v):
|
||||
"""Backward compatibility handler for the "engine" namespace."""
|
||||
engine = cherrypy.engine
|
||||
if k == 'autoreload_on':
|
||||
if v:
|
||||
engine.autoreload.subscribe()
|
||||
else:
|
||||
engine.autoreload.unsubscribe()
|
||||
elif k == 'autoreload_frequency':
|
||||
engine.autoreload.frequency = v
|
||||
elif k == 'autoreload_match':
|
||||
engine.autoreload.match = v
|
||||
elif k == 'reload_files':
|
||||
engine.autoreload.files = set(v)
|
||||
elif k == 'deadlock_poll_freq':
|
||||
engine.timeout_monitor.frequency = v
|
||||
elif k == 'SIGHUP':
|
||||
engine.listeners['SIGHUP'] = set([v])
|
||||
elif k == 'SIGTERM':
|
||||
engine.listeners['SIGTERM'] = set([v])
|
||||
elif "." in k:
|
||||
plugin, attrname = k.split(".", 1)
|
||||
plugin = getattr(engine, plugin)
|
||||
if attrname == 'on':
|
||||
if v and hasattr(getattr(plugin, 'subscribe', None), '__call__'):
|
||||
plugin.subscribe()
|
||||
return
|
||||
elif (not v) and hasattr(getattr(plugin, 'unsubscribe', None), '__call__'):
|
||||
plugin.unsubscribe()
|
||||
return
|
||||
setattr(plugin, attrname, v)
|
||||
else:
|
||||
setattr(engine, k, v)
|
||||
Config.namespaces["engine"] = _engine_namespace_handler
|
||||
|
||||
|
||||
def _tree_namespace_handler(k, v):
|
||||
"""Namespace handler for the 'tree' config namespace."""
|
||||
if isinstance(v, dict):
|
||||
for script_name, app in v.items():
|
||||
cherrypy.tree.graft(app, script_name)
|
||||
cherrypy.engine.log("Mounted: %s on %s" % (app, script_name or "/"))
|
||||
else:
|
||||
cherrypy.tree.graft(v, v.script_name)
|
||||
cherrypy.engine.log("Mounted: %s on %s" % (v, v.script_name or "/"))
|
||||
Config.namespaces["tree"] = _tree_namespace_handler
|
||||
|
||||
|
||||
636
python/packages/cherrypy/_cpdispatch.py
Normal file
636
python/packages/cherrypy/_cpdispatch.py
Normal file
File diff suppressed because it is too large
Load Diff
556
python/packages/cherrypy/_cperror.py
Normal file
556
python/packages/cherrypy/_cperror.py
Normal file
File diff suppressed because it is too large
Load Diff
440
python/packages/cherrypy/_cplogging.py
Normal file
440
python/packages/cherrypy/_cplogging.py
Normal file
@@ -0,0 +1,440 @@
|
||||
"""
|
||||
Simple config
|
||||
=============
|
||||
|
||||
Although CherryPy uses the :mod:`Python logging module <logging>`, it does so
|
||||
behind the scenes so that simple logging is simple, but complicated logging
|
||||
is still possible. "Simple" logging means that you can log to the screen
|
||||
(i.e. console/stdout) or to a file, and that you can easily have separate
|
||||
error and access log files.
|
||||
|
||||
Here are the simplified logging settings. You use these by adding lines to
|
||||
your config file or dict. You should set these at either the global level or
|
||||
per application (see next), but generally not both.
|
||||
|
||||
* ``log.screen``: Set this to True to have both "error" and "access" messages
|
||||
printed to stdout.
|
||||
* ``log.access_file``: Set this to an absolute filename where you want
|
||||
"access" messages written.
|
||||
* ``log.error_file``: Set this to an absolute filename where you want "error"
|
||||
messages written.
|
||||
|
||||
Many events are automatically logged; to log your own application events, call
|
||||
:func:`cherrypy.log`.
|
||||
|
||||
Architecture
|
||||
============
|
||||
|
||||
Separate scopes
|
||||
---------------
|
||||
|
||||
CherryPy provides log managers at both the global and application layers.
|
||||
This means you can have one set of logging rules for your entire site,
|
||||
and another set of rules specific to each application. The global log
|
||||
manager is found at :func:`cherrypy.log`, and the log manager for each
|
||||
application is found at :attr:`app.log<cherrypy._cptree.Application.log>`.
|
||||
If you're inside a request, the latter is reachable from
|
||||
``cherrypy.request.app.log``; if you're outside a request, you'll have to obtain
|
||||
a reference to the ``app``: either the return value of
|
||||
:func:`tree.mount()<cherrypy._cptree.Tree.mount>` or, if you used
|
||||
:func:`quickstart()<cherrypy.quickstart>` instead, via ``cherrypy.tree.apps['/']``.
|
||||
|
||||
By default, the global logs are named "cherrypy.error" and "cherrypy.access",
|
||||
and the application logs are named "cherrypy.error.2378745" and
|
||||
"cherrypy.access.2378745" (the number is the id of the Application object).
|
||||
This means that the application logs "bubble up" to the site logs, so if your
|
||||
application has no log handlers, the site-level handlers will still log the
|
||||
messages.
|
||||
|
||||
Errors vs. Access
|
||||
-----------------
|
||||
|
||||
Each log manager handles both "access" messages (one per HTTP request) and
|
||||
"error" messages (everything else). Note that the "error" log is not just for
|
||||
errors! The format of access messages is highly formalized, but the error log
|
||||
isn't--it receives messages from a variety of sources (including full error
|
||||
tracebacks, if enabled).
|
||||
|
||||
|
||||
Custom Handlers
|
||||
===============
|
||||
|
||||
The simple settings above work by manipulating Python's standard :mod:`logging`
|
||||
module. So when you need something more complex, the full power of the standard
|
||||
module is yours to exploit. You can borrow or create custom handlers, formats,
|
||||
filters, and much more. Here's an example that skips the standard FileHandler
|
||||
and uses a RotatingFileHandler instead:
|
||||
|
||||
::
|
||||
|
||||
#python
|
||||
log = app.log
|
||||
|
||||
# Remove the default FileHandlers if present.
|
||||
log.error_file = ""
|
||||
log.access_file = ""
|
||||
|
||||
maxBytes = getattr(log, "rot_maxBytes", 10000000)
|
||||
backupCount = getattr(log, "rot_backupCount", 1000)
|
||||
|
||||
# Make a new RotatingFileHandler for the error log.
|
||||
fname = getattr(log, "rot_error_file", "error.log")
|
||||
h = handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount)
|
||||
h.setLevel(DEBUG)
|
||||
h.setFormatter(_cplogging.logfmt)
|
||||
log.error_log.addHandler(h)
|
||||
|
||||
# Make a new RotatingFileHandler for the access log.
|
||||
fname = getattr(log, "rot_access_file", "access.log")
|
||||
h = handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount)
|
||||
h.setLevel(DEBUG)
|
||||
h.setFormatter(_cplogging.logfmt)
|
||||
log.access_log.addHandler(h)
|
||||
|
||||
|
||||
The ``rot_*`` attributes are pulled straight from the application log object.
|
||||
Since "log.*" config entries simply set attributes on the log object, you can
|
||||
add custom attributes to your heart's content. Note that these handlers are
|
||||
used ''instead'' of the default, simple handlers outlined above (so don't set
|
||||
the "log.error_file" config entry, for example).
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
# Silence the no-handlers "warning" (stderr write!) in stdlib logging
|
||||
logging.Logger.manager.emittedNoHandlerWarning = 1
|
||||
logfmt = logging.Formatter("%(message)s")
|
||||
import os
|
||||
import sys
|
||||
|
||||
import cherrypy
|
||||
from cherrypy import _cperror
|
||||
from cherrypy._cpcompat import ntob, py3k
|
||||
|
||||
|
||||
class NullHandler(logging.Handler):
|
||||
"""A no-op logging handler to silence the logging.lastResort handler."""
|
||||
|
||||
def handle(self, record):
|
||||
pass
|
||||
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
def createLock(self):
|
||||
self.lock = None
|
||||
|
||||
|
||||
class LogManager(object):
|
||||
"""An object to assist both simple and advanced logging.
|
||||
|
||||
``cherrypy.log`` is an instance of this class.
|
||||
"""
|
||||
|
||||
appid = None
|
||||
"""The id() of the Application object which owns this log manager. If this
|
||||
is a global log manager, appid is None."""
|
||||
|
||||
error_log = None
|
||||
"""The actual :class:`logging.Logger` instance for error messages."""
|
||||
|
||||
access_log = None
|
||||
"""The actual :class:`logging.Logger` instance for access messages."""
|
||||
|
||||
if py3k:
|
||||
access_log_format = \
|
||||
'{h} {l} {u} {t} "{r}" {s} {b} "{f}" "{a}"'
|
||||
else:
|
||||
access_log_format = \
|
||||
'%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
|
||||
|
||||
logger_root = None
|
||||
"""The "top-level" logger name.
|
||||
|
||||
This string will be used as the first segment in the Logger names.
|
||||
The default is "cherrypy", for example, in which case the Logger names
|
||||
will be of the form::
|
||||
|
||||
cherrypy.error.<appid>
|
||||
cherrypy.access.<appid>
|
||||
"""
|
||||
|
||||
def __init__(self, appid=None, logger_root="cherrypy"):
|
||||
self.logger_root = logger_root
|
||||
self.appid = appid
|
||||
if appid is None:
|
||||
self.error_log = logging.getLogger("%s.error" % logger_root)
|
||||
self.access_log = logging.getLogger("%s.access" % logger_root)
|
||||
else:
|
||||
self.error_log = logging.getLogger("%s.error.%s" % (logger_root, appid))
|
||||
self.access_log = logging.getLogger("%s.access.%s" % (logger_root, appid))
|
||||
self.error_log.setLevel(logging.INFO)
|
||||
self.access_log.setLevel(logging.INFO)
|
||||
|
||||
# Silence the no-handlers "warning" (stderr write!) in stdlib logging
|
||||
self.error_log.addHandler(NullHandler())
|
||||
self.access_log.addHandler(NullHandler())
|
||||
|
||||
cherrypy.engine.subscribe('graceful', self.reopen_files)
|
||||
|
||||
def reopen_files(self):
|
||||
"""Close and reopen all file handlers."""
|
||||
for log in (self.error_log, self.access_log):
|
||||
for h in log.handlers:
|
||||
if isinstance(h, logging.FileHandler):
|
||||
h.acquire()
|
||||
h.stream.close()
|
||||
h.stream = open(h.baseFilename, h.mode)
|
||||
h.release()
|
||||
|
||||
def error(self, msg='', context='', severity=logging.INFO, traceback=False):
|
||||
"""Write the given ``msg`` to the error log.
|
||||
|
||||
This is not just for errors! Applications may call this at any time
|
||||
to log application-specific information.
|
||||
|
||||
If ``traceback`` is True, the traceback of the current exception
|
||||
(if any) will be appended to ``msg``.
|
||||
"""
|
||||
if traceback:
|
||||
msg += _cperror.format_exc()
|
||||
self.error_log.log(severity, ' '.join((self.time(), context, msg)))
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""An alias for ``error``."""
|
||||
return self.error(*args, **kwargs)
|
||||
|
||||
def access(self):
|
||||
"""Write to the access log (in Apache/NCSA Combined Log format).
|
||||
|
||||
See http://httpd.apache.org/docs/2.0/logs.html#combined for format
|
||||
details.
|
||||
|
||||
CherryPy calls this automatically for you. Note there are no arguments;
|
||||
it collects the data itself from
|
||||
:class:`cherrypy.request<cherrypy._cprequest.Request>`.
|
||||
|
||||
Like Apache started doing in 2.0.46, non-printable and other special
|
||||
characters in %r (and we expand that to all parts) are escaped using
|
||||
\\xhh sequences, where hh stands for the hexadecimal representation
|
||||
of the raw byte. Exceptions from this rule are " and \\, which are
|
||||
escaped by prepending a backslash, and all whitespace characters,
|
||||
which are written in their C-style notation (\\n, \\t, etc).
|
||||
"""
|
||||
request = cherrypy.serving.request
|
||||
remote = request.remote
|
||||
response = cherrypy.serving.response
|
||||
outheaders = response.headers
|
||||
inheaders = request.headers
|
||||
if response.output_status is None:
|
||||
status = "-"
|
||||
else:
|
||||
status = response.output_status.split(ntob(" "), 1)[0]
|
||||
if py3k:
|
||||
status = status.decode('ISO-8859-1')
|
||||
|
||||
atoms = {'h': remote.name or remote.ip,
|
||||
'l': '-',
|
||||
'u': getattr(request, "login", None) or "-",
|
||||
't': self.time(),
|
||||
'r': request.request_line,
|
||||
's': status,
|
||||
'b': dict.get(outheaders, 'Content-Length', '') or "-",
|
||||
'f': dict.get(inheaders, 'Referer', ''),
|
||||
'a': dict.get(inheaders, 'User-Agent', ''),
|
||||
}
|
||||
if py3k:
|
||||
for k, v in atoms.items():
|
||||
if not isinstance(v, str):
|
||||
v = str(v)
|
||||
v = v.replace('"', '\\"').encode('utf8')
|
||||
# Fortunately, repr(str) escapes unprintable chars, \n, \t, etc
|
||||
# and backslash for us. All we have to do is strip the quotes.
|
||||
v = repr(v)[2:-1]
|
||||
|
||||
# in python 3.0 the repr of bytes (as returned by encode)
|
||||
# uses double \'s. But then the logger escapes them yet, again
|
||||
# resulting in quadruple slashes. Remove the extra one here.
|
||||
v = v.replace('\\\\', '\\')
|
||||
|
||||
# Escape double-quote.
|
||||
atoms[k] = v
|
||||
|
||||
try:
|
||||
self.access_log.log(logging.INFO, self.access_log_format.format(**atoms))
|
||||
except:
|
||||
self(traceback=True)
|
||||
else:
|
||||
for k, v in atoms.items():
|
||||
if isinstance(v, unicode):
|
||||
v = v.encode('utf8')
|
||||
elif not isinstance(v, str):
|
||||
v = str(v)
|
||||
# Fortunately, repr(str) escapes unprintable chars, \n, \t, etc
|
||||
# and backslash for us. All we have to do is strip the quotes.
|
||||
v = repr(v)[1:-1]
|
||||
# Escape double-quote.
|
||||
atoms[k] = v.replace('"', '\\"')
|
||||
|
||||
try:
|
||||
self.access_log.log(logging.INFO, self.access_log_format % atoms)
|
||||
except:
|
||||
self(traceback=True)
|
||||
|
||||
def time(self):
|
||||
"""Return now() in Apache Common Log Format (no timezone)."""
|
||||
now = datetime.datetime.now()
|
||||
monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun',
|
||||
'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
|
||||
month = monthnames[now.month - 1].capitalize()
|
||||
return ('[%02d/%s/%04d:%02d:%02d:%02d]' %
|
||||
(now.day, month, now.year, now.hour, now.minute, now.second))
|
||||
|
||||
def _get_builtin_handler(self, log, key):
|
||||
for h in log.handlers:
|
||||
if getattr(h, "_cpbuiltin", None) == key:
|
||||
return h
|
||||
|
||||
|
||||
# ------------------------- Screen handlers ------------------------- #
|
||||
|
||||
def _set_screen_handler(self, log, enable, stream=None):
|
||||
h = self._get_builtin_handler(log, "screen")
|
||||
if enable:
|
||||
if not h:
|
||||
if stream is None:
|
||||
stream=sys.stderr
|
||||
h = logging.StreamHandler(stream)
|
||||
h.setFormatter(logfmt)
|
||||
h._cpbuiltin = "screen"
|
||||
log.addHandler(h)
|
||||
elif h:
|
||||
log.handlers.remove(h)
|
||||
|
||||
def _get_screen(self):
|
||||
h = self._get_builtin_handler
|
||||
has_h = h(self.error_log, "screen") or h(self.access_log, "screen")
|
||||
return bool(has_h)
|
||||
|
||||
def _set_screen(self, newvalue):
|
||||
self._set_screen_handler(self.error_log, newvalue, stream=sys.stderr)
|
||||
self._set_screen_handler(self.access_log, newvalue, stream=sys.stdout)
|
||||
screen = property(_get_screen, _set_screen,
|
||||
doc="""Turn stderr/stdout logging on or off.
|
||||
|
||||
If you set this to True, it'll add the appropriate StreamHandler for
|
||||
you. If you set it to False, it will remove the handler.
|
||||
""")
|
||||
|
||||
# -------------------------- File handlers -------------------------- #
|
||||
|
||||
def _add_builtin_file_handler(self, log, fname):
|
||||
h = logging.FileHandler(fname)
|
||||
h.setFormatter(logfmt)
|
||||
h._cpbuiltin = "file"
|
||||
log.addHandler(h)
|
||||
|
||||
def _set_file_handler(self, log, filename):
|
||||
h = self._get_builtin_handler(log, "file")
|
||||
if filename:
|
||||
if h:
|
||||
if h.baseFilename != os.path.abspath(filename):
|
||||
h.close()
|
||||
log.handlers.remove(h)
|
||||
self._add_builtin_file_handler(log, filename)
|
||||
else:
|
||||
self._add_builtin_file_handler(log, filename)
|
||||
else:
|
||||
if h:
|
||||
h.close()
|
||||
log.handlers.remove(h)
|
||||
|
||||
def _get_error_file(self):
|
||||
h = self._get_builtin_handler(self.error_log, "file")
|
||||
if h:
|
||||
return h.baseFilename
|
||||
return ''
|
||||
def _set_error_file(self, newvalue):
|
||||
self._set_file_handler(self.error_log, newvalue)
|
||||
error_file = property(_get_error_file, _set_error_file,
|
||||
doc="""The filename for self.error_log.
|
||||
|
||||
If you set this to a string, it'll add the appropriate FileHandler for
|
||||
you. If you set it to ``None`` or ``''``, it will remove the handler.
|
||||
""")
|
||||
|
||||
def _get_access_file(self):
|
||||
h = self._get_builtin_handler(self.access_log, "file")
|
||||
if h:
|
||||
return h.baseFilename
|
||||
return ''
|
||||
def _set_access_file(self, newvalue):
|
||||
self._set_file_handler(self.access_log, newvalue)
|
||||
access_file = property(_get_access_file, _set_access_file,
|
||||
doc="""The filename for self.access_log.
|
||||
|
||||
If you set this to a string, it'll add the appropriate FileHandler for
|
||||
you. If you set it to ``None`` or ``''``, it will remove the handler.
|
||||
""")
|
||||
|
||||
# ------------------------- WSGI handlers ------------------------- #
|
||||
|
||||
def _set_wsgi_handler(self, log, enable):
|
||||
h = self._get_builtin_handler(log, "wsgi")
|
||||
if enable:
|
||||
if not h:
|
||||
h = WSGIErrorHandler()
|
||||
h.setFormatter(logfmt)
|
||||
h._cpbuiltin = "wsgi"
|
||||
log.addHandler(h)
|
||||
elif h:
|
||||
log.handlers.remove(h)
|
||||
|
||||
def _get_wsgi(self):
|
||||
return bool(self._get_builtin_handler(self.error_log, "wsgi"))
|
||||
|
||||
def _set_wsgi(self, newvalue):
|
||||
self._set_wsgi_handler(self.error_log, newvalue)
|
||||
wsgi = property(_get_wsgi, _set_wsgi,
|
||||
doc="""Write errors to wsgi.errors.
|
||||
|
||||
If you set this to True, it'll add the appropriate
|
||||
:class:`WSGIErrorHandler<cherrypy._cplogging.WSGIErrorHandler>` for you
|
||||
(which writes errors to ``wsgi.errors``).
|
||||
If you set it to False, it will remove the handler.
|
||||
""")
|
||||
|
||||
|
||||
class WSGIErrorHandler(logging.Handler):
|
||||
"A handler class which writes logging records to environ['wsgi.errors']."
|
||||
|
||||
def flush(self):
|
||||
"""Flushes the stream."""
|
||||
try:
|
||||
stream = cherrypy.serving.request.wsgi_environ.get('wsgi.errors')
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
else:
|
||||
stream.flush()
|
||||
|
||||
def emit(self, record):
|
||||
"""Emit a record."""
|
||||
try:
|
||||
stream = cherrypy.serving.request.wsgi_environ.get('wsgi.errors')
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
msg = self.format(record)
|
||||
fs = "%s\n"
|
||||
import types
|
||||
if not hasattr(types, "UnicodeType"): #if no unicode support...
|
||||
stream.write(fs % msg)
|
||||
else:
|
||||
try:
|
||||
stream.write(fs % msg)
|
||||
except UnicodeError:
|
||||
stream.write(fs % msg.encode("UTF-8"))
|
||||
self.flush()
|
||||
except:
|
||||
self.handleError(record)
|
||||
344
python/packages/cherrypy/_cpmodpy.py
Normal file
344
python/packages/cherrypy/_cpmodpy.py
Normal file
@@ -0,0 +1,344 @@
|
||||
"""Native adapter for serving CherryPy via mod_python
|
||||
|
||||
Basic usage:
|
||||
|
||||
##########################################
|
||||
# Application in a module called myapp.py
|
||||
##########################################
|
||||
|
||||
import cherrypy
|
||||
|
||||
class Root:
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
return 'Hi there, Ho there, Hey there'
|
||||
|
||||
|
||||
# We will use this method from the mod_python configuration
|
||||
# as the entry point to our application
|
||||
def setup_server():
|
||||
cherrypy.tree.mount(Root())
|
||||
cherrypy.config.update({'environment': 'production',
|
||||
'log.screen': False,
|
||||
'show_tracebacks': False})
|
||||
|
||||
##########################################
|
||||
# mod_python settings for apache2
|
||||
# This should reside in your httpd.conf
|
||||
# or a file that will be loaded at
|
||||
# apache startup
|
||||
##########################################
|
||||
|
||||
# Start
|
||||
DocumentRoot "/"
|
||||
Listen 8080
|
||||
LoadModule python_module /usr/lib/apache2/modules/mod_python.so
|
||||
|
||||
<Location "/">
|
||||
PythonPath "sys.path+['/path/to/my/application']"
|
||||
SetHandler python-program
|
||||
PythonHandler cherrypy._cpmodpy::handler
|
||||
PythonOption cherrypy.setup myapp::setup_server
|
||||
PythonDebug On
|
||||
</Location>
|
||||
# End
|
||||
|
||||
The actual path to your mod_python.so is dependent on your
|
||||
environment. In this case we suppose a global mod_python
|
||||
installation on a Linux distribution such as Ubuntu.
|
||||
|
||||
We do set the PythonPath configuration setting so that
|
||||
your application can be found by from the user running
|
||||
the apache2 instance. Of course if your application
|
||||
resides in the global site-package this won't be needed.
|
||||
|
||||
Then restart apache2 and access http://127.0.0.1:8080
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import BytesIO, copyitems, ntob
|
||||
from cherrypy._cperror import format_exc, bare_error
|
||||
from cherrypy.lib import httputil
|
||||
|
||||
|
||||
# ------------------------------ Request-handling
|
||||
|
||||
|
||||
|
||||
def setup(req):
|
||||
from mod_python import apache
|
||||
|
||||
# Run any setup functions defined by a "PythonOption cherrypy.setup" directive.
|
||||
options = req.get_options()
|
||||
if 'cherrypy.setup' in options:
|
||||
for function in options['cherrypy.setup'].split():
|
||||
atoms = function.split('::', 1)
|
||||
if len(atoms) == 1:
|
||||
mod = __import__(atoms[0], globals(), locals())
|
||||
else:
|
||||
modname, fname = atoms
|
||||
mod = __import__(modname, globals(), locals(), [fname])
|
||||
func = getattr(mod, fname)
|
||||
func()
|
||||
|
||||
cherrypy.config.update({'log.screen': False,
|
||||
"tools.ignore_headers.on": True,
|
||||
"tools.ignore_headers.headers": ['Range'],
|
||||
})
|
||||
|
||||
engine = cherrypy.engine
|
||||
if hasattr(engine, "signal_handler"):
|
||||
engine.signal_handler.unsubscribe()
|
||||
if hasattr(engine, "console_control_handler"):
|
||||
engine.console_control_handler.unsubscribe()
|
||||
engine.autoreload.unsubscribe()
|
||||
cherrypy.server.unsubscribe()
|
||||
|
||||
def _log(msg, level):
|
||||
newlevel = apache.APLOG_ERR
|
||||
if logging.DEBUG >= level:
|
||||
newlevel = apache.APLOG_DEBUG
|
||||
elif logging.INFO >= level:
|
||||
newlevel = apache.APLOG_INFO
|
||||
elif logging.WARNING >= level:
|
||||
newlevel = apache.APLOG_WARNING
|
||||
# On Windows, req.server is required or the msg will vanish. See
|
||||
# http://www.modpython.org/pipermail/mod_python/2003-October/014291.html.
|
||||
# Also, "When server is not specified...LogLevel does not apply..."
|
||||
apache.log_error(msg, newlevel, req.server)
|
||||
engine.subscribe('log', _log)
|
||||
|
||||
engine.start()
|
||||
|
||||
def cherrypy_cleanup(data):
|
||||
engine.exit()
|
||||
try:
|
||||
# apache.register_cleanup wasn't available until 3.1.4.
|
||||
apache.register_cleanup(cherrypy_cleanup)
|
||||
except AttributeError:
|
||||
req.server.register_cleanup(req, cherrypy_cleanup)
|
||||
|
||||
|
||||
class _ReadOnlyRequest:
|
||||
expose = ('read', 'readline', 'readlines')
|
||||
def __init__(self, req):
|
||||
for method in self.expose:
|
||||
self.__dict__[method] = getattr(req, method)
|
||||
|
||||
|
||||
recursive = False
|
||||
|
||||
_isSetUp = False
|
||||
def handler(req):
|
||||
from mod_python import apache
|
||||
try:
|
||||
global _isSetUp
|
||||
if not _isSetUp:
|
||||
setup(req)
|
||||
_isSetUp = True
|
||||
|
||||
# Obtain a Request object from CherryPy
|
||||
local = req.connection.local_addr
|
||||
local = httputil.Host(local[0], local[1], req.connection.local_host or "")
|
||||
remote = req.connection.remote_addr
|
||||
remote = httputil.Host(remote[0], remote[1], req.connection.remote_host or "")
|
||||
|
||||
scheme = req.parsed_uri[0] or 'http'
|
||||
req.get_basic_auth_pw()
|
||||
|
||||
try:
|
||||
# apache.mpm_query only became available in mod_python 3.1
|
||||
q = apache.mpm_query
|
||||
threaded = q(apache.AP_MPMQ_IS_THREADED)
|
||||
forked = q(apache.AP_MPMQ_IS_FORKED)
|
||||
except AttributeError:
|
||||
bad_value = ("You must provide a PythonOption '%s', "
|
||||
"either 'on' or 'off', when running a version "
|
||||
"of mod_python < 3.1")
|
||||
|
||||
threaded = options.get('multithread', '').lower()
|
||||
if threaded == 'on':
|
||||
threaded = True
|
||||
elif threaded == 'off':
|
||||
threaded = False
|
||||
else:
|
||||
raise ValueError(bad_value % "multithread")
|
||||
|
||||
forked = options.get('multiprocess', '').lower()
|
||||
if forked == 'on':
|
||||
forked = True
|
||||
elif forked == 'off':
|
||||
forked = False
|
||||
else:
|
||||
raise ValueError(bad_value % "multiprocess")
|
||||
|
||||
sn = cherrypy.tree.script_name(req.uri or "/")
|
||||
if sn is None:
|
||||
send_response(req, '404 Not Found', [], '')
|
||||
else:
|
||||
app = cherrypy.tree.apps[sn]
|
||||
method = req.method
|
||||
path = req.uri
|
||||
qs = req.args or ""
|
||||
reqproto = req.protocol
|
||||
headers = copyitems(req.headers_in)
|
||||
rfile = _ReadOnlyRequest(req)
|
||||
prev = None
|
||||
|
||||
try:
|
||||
redirections = []
|
||||
while True:
|
||||
request, response = app.get_serving(local, remote, scheme,
|
||||
"HTTP/1.1")
|
||||
request.login = req.user
|
||||
request.multithread = bool(threaded)
|
||||
request.multiprocess = bool(forked)
|
||||
request.app = app
|
||||
request.prev = prev
|
||||
|
||||
# Run the CherryPy Request object and obtain the response
|
||||
try:
|
||||
request.run(method, path, qs, reqproto, headers, rfile)
|
||||
break
|
||||
except cherrypy.InternalRedirect:
|
||||
ir = sys.exc_info()[1]
|
||||
app.release_serving()
|
||||
prev = request
|
||||
|
||||
if not recursive:
|
||||
if ir.path in redirections:
|
||||
raise RuntimeError("InternalRedirector visited the "
|
||||
"same URL twice: %r" % ir.path)
|
||||
else:
|
||||
# Add the *previous* path_info + qs to redirections.
|
||||
if qs:
|
||||
qs = "?" + qs
|
||||
redirections.append(sn + path + qs)
|
||||
|
||||
# Munge environment and try again.
|
||||
method = "GET"
|
||||
path = ir.path
|
||||
qs = ir.query_string
|
||||
rfile = BytesIO()
|
||||
|
||||
send_response(req, response.output_status, response.header_list,
|
||||
response.body, response.stream)
|
||||
finally:
|
||||
app.release_serving()
|
||||
except:
|
||||
tb = format_exc()
|
||||
cherrypy.log(tb, 'MOD_PYTHON', severity=logging.ERROR)
|
||||
s, h, b = bare_error()
|
||||
send_response(req, s, h, b)
|
||||
return apache.OK
|
||||
|
||||
|
||||
def send_response(req, status, headers, body, stream=False):
|
||||
# Set response status
|
||||
req.status = int(status[:3])
|
||||
|
||||
# Set response headers
|
||||
req.content_type = "text/plain"
|
||||
for header, value in headers:
|
||||
if header.lower() == 'content-type':
|
||||
req.content_type = value
|
||||
continue
|
||||
req.headers_out.add(header, value)
|
||||
|
||||
if stream:
|
||||
# Flush now so the status and headers are sent immediately.
|
||||
req.flush()
|
||||
|
||||
# Set response body
|
||||
if isinstance(body, basestring):
|
||||
req.write(body)
|
||||
else:
|
||||
for seg in body:
|
||||
req.write(seg)
|
||||
|
||||
|
||||
|
||||
# --------------- Startup tools for CherryPy + mod_python --------------- #
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
try:
|
||||
import subprocess
|
||||
def popen(fullcmd):
|
||||
p = subprocess.Popen(fullcmd, shell=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
close_fds=True)
|
||||
return p.stdout
|
||||
except ImportError:
|
||||
def popen(fullcmd):
|
||||
pipein, pipeout = os.popen4(fullcmd)
|
||||
return pipeout
|
||||
|
||||
|
||||
def read_process(cmd, args=""):
|
||||
fullcmd = "%s %s" % (cmd, args)
|
||||
pipeout = popen(fullcmd)
|
||||
try:
|
||||
firstline = pipeout.readline()
|
||||
if (re.search(ntob("(not recognized|No such file|not found)"), firstline,
|
||||
re.IGNORECASE)):
|
||||
raise IOError('%s must be on your system path.' % cmd)
|
||||
output = firstline + pipeout.read()
|
||||
finally:
|
||||
pipeout.close()
|
||||
return output
|
||||
|
||||
|
||||
class ModPythonServer(object):
|
||||
|
||||
template = """
|
||||
# Apache2 server configuration file for running CherryPy with mod_python.
|
||||
|
||||
DocumentRoot "/"
|
||||
Listen %(port)s
|
||||
LoadModule python_module modules/mod_python.so
|
||||
|
||||
<Location %(loc)s>
|
||||
SetHandler python-program
|
||||
PythonHandler %(handler)s
|
||||
PythonDebug On
|
||||
%(opts)s
|
||||
</Location>
|
||||
"""
|
||||
|
||||
def __init__(self, loc="/", port=80, opts=None, apache_path="apache",
|
||||
handler="cherrypy._cpmodpy::handler"):
|
||||
self.loc = loc
|
||||
self.port = port
|
||||
self.opts = opts
|
||||
self.apache_path = apache_path
|
||||
self.handler = handler
|
||||
|
||||
def start(self):
|
||||
opts = "".join([" PythonOption %s %s\n" % (k, v)
|
||||
for k, v in self.opts])
|
||||
conf_data = self.template % {"port": self.port,
|
||||
"loc": self.loc,
|
||||
"opts": opts,
|
||||
"handler": self.handler,
|
||||
}
|
||||
|
||||
mpconf = os.path.join(os.path.dirname(__file__), "cpmodpy.conf")
|
||||
f = open(mpconf, 'wb')
|
||||
try:
|
||||
f.write(conf_data)
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
response = read_process(self.apache_path, "-k start -f %s" % mpconf)
|
||||
self.ready = True
|
||||
return response
|
||||
|
||||
def stop(self):
|
||||
os.popen("apache -k stop")
|
||||
self.ready = False
|
||||
|
||||
149
python/packages/cherrypy/_cpnative_server.py
Normal file
149
python/packages/cherrypy/_cpnative_server.py
Normal file
@@ -0,0 +1,149 @@
|
||||
"""Native adapter for serving CherryPy via its builtin server."""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import BytesIO
|
||||
from cherrypy._cperror import format_exc, bare_error
|
||||
from cherrypy.lib import httputil
|
||||
from cherrypy import wsgiserver
|
||||
|
||||
|
||||
class NativeGateway(wsgiserver.Gateway):
|
||||
|
||||
recursive = False
|
||||
|
||||
def respond(self):
|
||||
req = self.req
|
||||
try:
|
||||
# Obtain a Request object from CherryPy
|
||||
local = req.server.bind_addr
|
||||
local = httputil.Host(local[0], local[1], "")
|
||||
remote = req.conn.remote_addr, req.conn.remote_port
|
||||
remote = httputil.Host(remote[0], remote[1], "")
|
||||
|
||||
scheme = req.scheme
|
||||
sn = cherrypy.tree.script_name(req.uri or "/")
|
||||
if sn is None:
|
||||
self.send_response('404 Not Found', [], [''])
|
||||
else:
|
||||
app = cherrypy.tree.apps[sn]
|
||||
method = req.method
|
||||
path = req.path
|
||||
qs = req.qs or ""
|
||||
headers = req.inheaders.items()
|
||||
rfile = req.rfile
|
||||
prev = None
|
||||
|
||||
try:
|
||||
redirections = []
|
||||
while True:
|
||||
request, response = app.get_serving(
|
||||
local, remote, scheme, "HTTP/1.1")
|
||||
request.multithread = True
|
||||
request.multiprocess = False
|
||||
request.app = app
|
||||
request.prev = prev
|
||||
|
||||
# Run the CherryPy Request object and obtain the response
|
||||
try:
|
||||
request.run(method, path, qs, req.request_protocol, headers, rfile)
|
||||
break
|
||||
except cherrypy.InternalRedirect:
|
||||
ir = sys.exc_info()[1]
|
||||
app.release_serving()
|
||||
prev = request
|
||||
|
||||
if not self.recursive:
|
||||
if ir.path in redirections:
|
||||
raise RuntimeError("InternalRedirector visited the "
|
||||
"same URL twice: %r" % ir.path)
|
||||
else:
|
||||
# Add the *previous* path_info + qs to redirections.
|
||||
if qs:
|
||||
qs = "?" + qs
|
||||
redirections.append(sn + path + qs)
|
||||
|
||||
# Munge environment and try again.
|
||||
method = "GET"
|
||||
path = ir.path
|
||||
qs = ir.query_string
|
||||
rfile = BytesIO()
|
||||
|
||||
self.send_response(
|
||||
response.output_status, response.header_list,
|
||||
response.body)
|
||||
finally:
|
||||
app.release_serving()
|
||||
except:
|
||||
tb = format_exc()
|
||||
#print tb
|
||||
cherrypy.log(tb, 'NATIVE_ADAPTER', severity=logging.ERROR)
|
||||
s, h, b = bare_error()
|
||||
self.send_response(s, h, b)
|
||||
|
||||
def send_response(self, status, headers, body):
|
||||
req = self.req
|
||||
|
||||
# Set response status
|
||||
req.status = str(status or "500 Server Error")
|
||||
|
||||
# Set response headers
|
||||
for header, value in headers:
|
||||
req.outheaders.append((header, value))
|
||||
if (req.ready and not req.sent_headers):
|
||||
req.sent_headers = True
|
||||
req.send_headers()
|
||||
|
||||
# Set response body
|
||||
for seg in body:
|
||||
req.write(seg)
|
||||
|
||||
|
||||
class CPHTTPServer(wsgiserver.HTTPServer):
|
||||
"""Wrapper for wsgiserver.HTTPServer.
|
||||
|
||||
wsgiserver has been designed to not reference CherryPy in any way,
|
||||
so that it can be used in other frameworks and applications.
|
||||
Therefore, we wrap it here, so we can apply some attributes
|
||||
from config -> cherrypy.server -> HTTPServer.
|
||||
"""
|
||||
|
||||
def __init__(self, server_adapter=cherrypy.server):
|
||||
self.server_adapter = server_adapter
|
||||
|
||||
server_name = (self.server_adapter.socket_host or
|
||||
self.server_adapter.socket_file or
|
||||
None)
|
||||
|
||||
wsgiserver.HTTPServer.__init__(
|
||||
self, server_adapter.bind_addr, NativeGateway,
|
||||
minthreads=server_adapter.thread_pool,
|
||||
maxthreads=server_adapter.thread_pool_max,
|
||||
server_name=server_name)
|
||||
|
||||
self.max_request_header_size = self.server_adapter.max_request_header_size or 0
|
||||
self.max_request_body_size = self.server_adapter.max_request_body_size or 0
|
||||
self.request_queue_size = self.server_adapter.socket_queue_size
|
||||
self.timeout = self.server_adapter.socket_timeout
|
||||
self.shutdown_timeout = self.server_adapter.shutdown_timeout
|
||||
self.protocol = self.server_adapter.protocol_version
|
||||
self.nodelay = self.server_adapter.nodelay
|
||||
|
||||
ssl_module = self.server_adapter.ssl_module or 'pyopenssl'
|
||||
if self.server_adapter.ssl_context:
|
||||
adapter_class = wsgiserver.get_ssl_adapter_class(ssl_module)
|
||||
self.ssl_adapter = adapter_class(
|
||||
self.server_adapter.ssl_certificate,
|
||||
self.server_adapter.ssl_private_key,
|
||||
self.server_adapter.ssl_certificate_chain)
|
||||
self.ssl_adapter.context = self.server_adapter.ssl_context
|
||||
elif self.server_adapter.ssl_certificate:
|
||||
adapter_class = wsgiserver.get_ssl_adapter_class(ssl_module)
|
||||
self.ssl_adapter = adapter_class(
|
||||
self.server_adapter.ssl_certificate,
|
||||
self.server_adapter.ssl_private_key,
|
||||
self.server_adapter.ssl_certificate_chain)
|
||||
|
||||
|
||||
965
python/packages/cherrypy/_cpreqbody.py
Normal file
965
python/packages/cherrypy/_cpreqbody.py
Normal file
File diff suppressed because it is too large
Load Diff
956
python/packages/cherrypy/_cprequest.py
Normal file
956
python/packages/cherrypy/_cprequest.py
Normal file
File diff suppressed because it is too large
Load Diff
205
python/packages/cherrypy/_cpserver.py
Normal file
205
python/packages/cherrypy/_cpserver.py
Normal file
@@ -0,0 +1,205 @@
|
||||
"""Manage HTTP servers with CherryPy."""
|
||||
|
||||
import warnings
|
||||
|
||||
import cherrypy
|
||||
from cherrypy.lib import attributes
|
||||
from cherrypy._cpcompat import basestring, py3k
|
||||
|
||||
# We import * because we want to export check_port
|
||||
# et al as attributes of this module.
|
||||
from cherrypy.process.servers import *
|
||||
|
||||
|
||||
class Server(ServerAdapter):
|
||||
"""An adapter for an HTTP server.
|
||||
|
||||
You can set attributes (like socket_host and socket_port)
|
||||
on *this* object (which is probably cherrypy.server), and call
|
||||
quickstart. For example::
|
||||
|
||||
cherrypy.server.socket_port = 80
|
||||
cherrypy.quickstart()
|
||||
"""
|
||||
|
||||
socket_port = 8080
|
||||
"""The TCP port on which to listen for connections."""
|
||||
|
||||
_socket_host = '127.0.0.1'
|
||||
def _get_socket_host(self):
|
||||
return self._socket_host
|
||||
def _set_socket_host(self, value):
|
||||
if value == '':
|
||||
raise ValueError("The empty string ('') is not an allowed value. "
|
||||
"Use '0.0.0.0' instead to listen on all active "
|
||||
"interfaces (INADDR_ANY).")
|
||||
self._socket_host = value
|
||||
socket_host = property(_get_socket_host, _set_socket_host,
|
||||
doc="""The hostname or IP address on which to listen for connections.
|
||||
|
||||
Host values may be any IPv4 or IPv6 address, or any valid hostname.
|
||||
The string 'localhost' is a synonym for '127.0.0.1' (or '::1', if
|
||||
your hosts file prefers IPv6). The string '0.0.0.0' is a special
|
||||
IPv4 entry meaning "any active interface" (INADDR_ANY), and '::'
|
||||
is the similar IN6ADDR_ANY for IPv6. The empty string or None are
|
||||
not allowed.""")
|
||||
|
||||
socket_file = None
|
||||
"""If given, the name of the UNIX socket to use instead of TCP/IP.
|
||||
|
||||
When this option is not None, the `socket_host` and `socket_port` options
|
||||
are ignored."""
|
||||
|
||||
socket_queue_size = 5
|
||||
"""The 'backlog' argument to socket.listen(); specifies the maximum number
|
||||
of queued connections (default 5)."""
|
||||
|
||||
socket_timeout = 10
|
||||
"""The timeout in seconds for accepted connections (default 10)."""
|
||||
|
||||
shutdown_timeout = 5
|
||||
"""The time to wait for HTTP worker threads to clean up."""
|
||||
|
||||
protocol_version = 'HTTP/1.1'
|
||||
"""The version string to write in the Status-Line of all HTTP responses,
|
||||
for example, "HTTP/1.1" (the default). Depending on the HTTP server used,
|
||||
this should also limit the supported features used in the response."""
|
||||
|
||||
thread_pool = 10
|
||||
"""The number of worker threads to start up in the pool."""
|
||||
|
||||
thread_pool_max = -1
|
||||
"""The maximum size of the worker-thread pool. Use -1 to indicate no limit."""
|
||||
|
||||
max_request_header_size = 500 * 1024
|
||||
"""The maximum number of bytes allowable in the request headers. If exceeded,
|
||||
the HTTP server should return "413 Request Entity Too Large"."""
|
||||
|
||||
max_request_body_size = 100 * 1024 * 1024
|
||||
"""The maximum number of bytes allowable in the request body. If exceeded,
|
||||
the HTTP server should return "413 Request Entity Too Large"."""
|
||||
|
||||
instance = None
|
||||
"""If not None, this should be an HTTP server instance (such as
|
||||
CPWSGIServer) which cherrypy.server will control. Use this when you need
|
||||
more control over object instantiation than is available in the various
|
||||
configuration options."""
|
||||
|
||||
ssl_context = None
|
||||
"""When using PyOpenSSL, an instance of SSL.Context."""
|
||||
|
||||
ssl_certificate = None
|
||||
"""The filename of the SSL certificate to use."""
|
||||
|
||||
ssl_certificate_chain = None
|
||||
"""When using PyOpenSSL, the certificate chain to pass to
|
||||
Context.load_verify_locations."""
|
||||
|
||||
ssl_private_key = None
|
||||
"""The filename of the private key to use with SSL."""
|
||||
|
||||
if py3k:
|
||||
ssl_module = 'builtin'
|
||||
"""The name of a registered SSL adaptation module to use with the builtin
|
||||
WSGI server. Builtin options are: 'builtin' (to use the SSL library built
|
||||
into recent versions of Python). You may also register your
|
||||
own classes in the wsgiserver.ssl_adapters dict."""
|
||||
else:
|
||||
ssl_module = 'pyopenssl'
|
||||
"""The name of a registered SSL adaptation module to use with the builtin
|
||||
WSGI server. Builtin options are 'builtin' (to use the SSL library built
|
||||
into recent versions of Python) and 'pyopenssl' (to use the PyOpenSSL
|
||||
project, which you must install separately). You may also register your
|
||||
own classes in the wsgiserver.ssl_adapters dict."""
|
||||
|
||||
statistics = False
|
||||
"""Turns statistics-gathering on or off for aware HTTP servers."""
|
||||
|
||||
nodelay = True
|
||||
"""If True (the default since 3.1), sets the TCP_NODELAY socket option."""
|
||||
|
||||
wsgi_version = (1, 0)
|
||||
"""The WSGI version tuple to use with the builtin WSGI server.
|
||||
The provided options are (1, 0) [which includes support for PEP 3333,
|
||||
which declares it covers WSGI version 1.0.1 but still mandates the
|
||||
wsgi.version (1, 0)] and ('u', 0), an experimental unicode version.
|
||||
You may create and register your own experimental versions of the WSGI
|
||||
protocol by adding custom classes to the wsgiserver.wsgi_gateways dict."""
|
||||
|
||||
def __init__(self):
|
||||
self.bus = cherrypy.engine
|
||||
self.httpserver = None
|
||||
self.interrupt = None
|
||||
self.running = False
|
||||
|
||||
def httpserver_from_self(self, httpserver=None):
|
||||
"""Return a (httpserver, bind_addr) pair based on self attributes."""
|
||||
if httpserver is None:
|
||||
httpserver = self.instance
|
||||
if httpserver is None:
|
||||
from cherrypy import _cpwsgi_server
|
||||
httpserver = _cpwsgi_server.CPWSGIServer(self)
|
||||
if isinstance(httpserver, basestring):
|
||||
# Is anyone using this? Can I add an arg?
|
||||
httpserver = attributes(httpserver)(self)
|
||||
return httpserver, self.bind_addr
|
||||
|
||||
def start(self):
|
||||
"""Start the HTTP server."""
|
||||
if not self.httpserver:
|
||||
self.httpserver, self.bind_addr = self.httpserver_from_self()
|
||||
ServerAdapter.start(self)
|
||||
start.priority = 75
|
||||
|
||||
def _get_bind_addr(self):
|
||||
if self.socket_file:
|
||||
return self.socket_file
|
||||
if self.socket_host is None and self.socket_port is None:
|
||||
return None
|
||||
return (self.socket_host, self.socket_port)
|
||||
def _set_bind_addr(self, value):
|
||||
if value is None:
|
||||
self.socket_file = None
|
||||
self.socket_host = None
|
||||
self.socket_port = None
|
||||
elif isinstance(value, basestring):
|
||||
self.socket_file = value
|
||||
self.socket_host = None
|
||||
self.socket_port = None
|
||||
else:
|
||||
try:
|
||||
self.socket_host, self.socket_port = value
|
||||
self.socket_file = None
|
||||
except ValueError:
|
||||
raise ValueError("bind_addr must be a (host, port) tuple "
|
||||
"(for TCP sockets) or a string (for Unix "
|
||||
"domain sockets), not %r" % value)
|
||||
bind_addr = property(_get_bind_addr, _set_bind_addr,
|
||||
doc='A (host, port) tuple for TCP sockets or a str for Unix domain sockets.')
|
||||
|
||||
def base(self):
|
||||
"""Return the base (scheme://host[:port] or sock file) for this server."""
|
||||
if self.socket_file:
|
||||
return self.socket_file
|
||||
|
||||
host = self.socket_host
|
||||
if host in ('0.0.0.0', '::'):
|
||||
# 0.0.0.0 is INADDR_ANY and :: is IN6ADDR_ANY.
|
||||
# Look up the host name, which should be the
|
||||
# safest thing to spit out in a URL.
|
||||
import socket
|
||||
host = socket.gethostname()
|
||||
|
||||
port = self.socket_port
|
||||
|
||||
if self.ssl_certificate:
|
||||
scheme = "https"
|
||||
if port != 443:
|
||||
host += ":%s" % port
|
||||
else:
|
||||
scheme = "http"
|
||||
if port != 80:
|
||||
host += ":%s" % port
|
||||
|
||||
return "%s://%s" % (scheme, host)
|
||||
|
||||
239
python/packages/cherrypy/_cpthreadinglocal.py
Normal file
239
python/packages/cherrypy/_cpthreadinglocal.py
Normal file
@@ -0,0 +1,239 @@
|
||||
# This is a backport of Python-2.4's threading.local() implementation
|
||||
|
||||
"""Thread-local objects
|
||||
|
||||
(Note that this module provides a Python version of thread
|
||||
threading.local class. Depending on the version of Python you're
|
||||
using, there may be a faster one available. You should always import
|
||||
the local class from threading.)
|
||||
|
||||
Thread-local objects support the management of thread-local data.
|
||||
If you have data that you want to be local to a thread, simply create
|
||||
a thread-local object and use its attributes:
|
||||
|
||||
>>> mydata = local()
|
||||
>>> mydata.number = 42
|
||||
>>> mydata.number
|
||||
42
|
||||
|
||||
You can also access the local-object's dictionary:
|
||||
|
||||
>>> mydata.__dict__
|
||||
{'number': 42}
|
||||
>>> mydata.__dict__.setdefault('widgets', [])
|
||||
[]
|
||||
>>> mydata.widgets
|
||||
[]
|
||||
|
||||
What's important about thread-local objects is that their data are
|
||||
local to a thread. If we access the data in a different thread:
|
||||
|
||||
>>> log = []
|
||||
>>> def f():
|
||||
... items = mydata.__dict__.items()
|
||||
... items.sort()
|
||||
... log.append(items)
|
||||
... mydata.number = 11
|
||||
... log.append(mydata.number)
|
||||
|
||||
>>> import threading
|
||||
>>> thread = threading.Thread(target=f)
|
||||
>>> thread.start()
|
||||
>>> thread.join()
|
||||
>>> log
|
||||
[[], 11]
|
||||
|
||||
we get different data. Furthermore, changes made in the other thread
|
||||
don't affect data seen in this thread:
|
||||
|
||||
>>> mydata.number
|
||||
42
|
||||
|
||||
Of course, values you get from a local object, including a __dict__
|
||||
attribute, are for whatever thread was current at the time the
|
||||
attribute was read. For that reason, you generally don't want to save
|
||||
these values across threads, as they apply only to the thread they
|
||||
came from.
|
||||
|
||||
You can create custom local objects by subclassing the local class:
|
||||
|
||||
>>> class MyLocal(local):
|
||||
... number = 2
|
||||
... initialized = False
|
||||
... def __init__(self, **kw):
|
||||
... if self.initialized:
|
||||
... raise SystemError('__init__ called too many times')
|
||||
... self.initialized = True
|
||||
... self.__dict__.update(kw)
|
||||
... def squared(self):
|
||||
... return self.number ** 2
|
||||
|
||||
This can be useful to support default values, methods and
|
||||
initialization. Note that if you define an __init__ method, it will be
|
||||
called each time the local object is used in a separate thread. This
|
||||
is necessary to initialize each thread's dictionary.
|
||||
|
||||
Now if we create a local object:
|
||||
|
||||
>>> mydata = MyLocal(color='red')
|
||||
|
||||
Now we have a default number:
|
||||
|
||||
>>> mydata.number
|
||||
2
|
||||
|
||||
an initial color:
|
||||
|
||||
>>> mydata.color
|
||||
'red'
|
||||
>>> del mydata.color
|
||||
|
||||
And a method that operates on the data:
|
||||
|
||||
>>> mydata.squared()
|
||||
4
|
||||
|
||||
As before, we can access the data in a separate thread:
|
||||
|
||||
>>> log = []
|
||||
>>> thread = threading.Thread(target=f)
|
||||
>>> thread.start()
|
||||
>>> thread.join()
|
||||
>>> log
|
||||
[[('color', 'red'), ('initialized', True)], 11]
|
||||
|
||||
without affecting this thread's data:
|
||||
|
||||
>>> mydata.number
|
||||
2
|
||||
>>> mydata.color
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'MyLocal' object has no attribute 'color'
|
||||
|
||||
Note that subclasses can define slots, but they are not thread
|
||||
local. They are shared across threads:
|
||||
|
||||
>>> class MyLocal(local):
|
||||
... __slots__ = 'number'
|
||||
|
||||
>>> mydata = MyLocal()
|
||||
>>> mydata.number = 42
|
||||
>>> mydata.color = 'red'
|
||||
|
||||
So, the separate thread:
|
||||
|
||||
>>> thread = threading.Thread(target=f)
|
||||
>>> thread.start()
|
||||
>>> thread.join()
|
||||
|
||||
affects what we see:
|
||||
|
||||
>>> mydata.number
|
||||
11
|
||||
|
||||
>>> del mydata
|
||||
"""
|
||||
|
||||
# Threading import is at end
|
||||
|
||||
class _localbase(object):
|
||||
__slots__ = '_local__key', '_local__args', '_local__lock'
|
||||
|
||||
def __new__(cls, *args, **kw):
|
||||
self = object.__new__(cls)
|
||||
key = 'thread.local.' + str(id(self))
|
||||
object.__setattr__(self, '_local__key', key)
|
||||
object.__setattr__(self, '_local__args', (args, kw))
|
||||
object.__setattr__(self, '_local__lock', RLock())
|
||||
|
||||
if args or kw and (cls.__init__ is object.__init__):
|
||||
raise TypeError("Initialization arguments are not supported")
|
||||
|
||||
# We need to create the thread dict in anticipation of
|
||||
# __init__ being called, to make sure we don't call it
|
||||
# again ourselves.
|
||||
dict = object.__getattribute__(self, '__dict__')
|
||||
currentThread().__dict__[key] = dict
|
||||
|
||||
return self
|
||||
|
||||
def _patch(self):
|
||||
key = object.__getattribute__(self, '_local__key')
|
||||
d = currentThread().__dict__.get(key)
|
||||
if d is None:
|
||||
d = {}
|
||||
currentThread().__dict__[key] = d
|
||||
object.__setattr__(self, '__dict__', d)
|
||||
|
||||
# we have a new instance dict, so call out __init__ if we have
|
||||
# one
|
||||
cls = type(self)
|
||||
if cls.__init__ is not object.__init__:
|
||||
args, kw = object.__getattribute__(self, '_local__args')
|
||||
cls.__init__(self, *args, **kw)
|
||||
else:
|
||||
object.__setattr__(self, '__dict__', d)
|
||||
|
||||
class local(_localbase):
|
||||
|
||||
def __getattribute__(self, name):
|
||||
lock = object.__getattribute__(self, '_local__lock')
|
||||
lock.acquire()
|
||||
try:
|
||||
_patch(self)
|
||||
return object.__getattribute__(self, name)
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
lock = object.__getattribute__(self, '_local__lock')
|
||||
lock.acquire()
|
||||
try:
|
||||
_patch(self)
|
||||
return object.__setattr__(self, name, value)
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def __delattr__(self, name):
|
||||
lock = object.__getattribute__(self, '_local__lock')
|
||||
lock.acquire()
|
||||
try:
|
||||
_patch(self)
|
||||
return object.__delattr__(self, name)
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
|
||||
def __del__():
|
||||
threading_enumerate = enumerate
|
||||
__getattribute__ = object.__getattribute__
|
||||
|
||||
def __del__(self):
|
||||
key = __getattribute__(self, '_local__key')
|
||||
|
||||
try:
|
||||
threads = list(threading_enumerate())
|
||||
except:
|
||||
# if enumerate fails, as it seems to do during
|
||||
# shutdown, we'll skip cleanup under the assumption
|
||||
# that there is nothing to clean up
|
||||
return
|
||||
|
||||
for thread in threads:
|
||||
try:
|
||||
__dict__ = thread.__dict__
|
||||
except AttributeError:
|
||||
# Thread is dying, rest in peace
|
||||
continue
|
||||
|
||||
if key in __dict__:
|
||||
try:
|
||||
del __dict__[key]
|
||||
except KeyError:
|
||||
pass # didn't have anything in this thread
|
||||
|
||||
return __del__
|
||||
__del__ = __del__()
|
||||
|
||||
from threading import currentThread, enumerate, RLock
|
||||
510
python/packages/cherrypy/_cptools.py
Normal file
510
python/packages/cherrypy/_cptools.py
Normal file
File diff suppressed because it is too large
Load Diff
290
python/packages/cherrypy/_cptree.py
Normal file
290
python/packages/cherrypy/_cptree.py
Normal file
@@ -0,0 +1,290 @@
|
||||
"""CherryPy Application and Tree objects."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import ntou, py3k
|
||||
from cherrypy import _cpconfig, _cplogging, _cprequest, _cpwsgi, tools
|
||||
from cherrypy.lib import httputil
|
||||
|
||||
|
||||
class Application(object):
|
||||
"""A CherryPy Application.
|
||||
|
||||
Servers and gateways should not instantiate Request objects directly.
|
||||
Instead, they should ask an Application object for a request object.
|
||||
|
||||
An instance of this class may also be used as a WSGI callable
|
||||
(WSGI application object) for itself.
|
||||
"""
|
||||
|
||||
root = None
|
||||
"""The top-most container of page handlers for this app. Handlers should
|
||||
be arranged in a hierarchy of attributes, matching the expected URI
|
||||
hierarchy; the default dispatcher then searches this hierarchy for a
|
||||
matching handler. When using a dispatcher other than the default,
|
||||
this value may be None."""
|
||||
|
||||
config = {}
|
||||
"""A dict of {path: pathconf} pairs, where 'pathconf' is itself a dict
|
||||
of {key: value} pairs."""
|
||||
|
||||
namespaces = _cpconfig.NamespaceSet()
|
||||
toolboxes = {'tools': cherrypy.tools}
|
||||
|
||||
log = None
|
||||
"""A LogManager instance. See _cplogging."""
|
||||
|
||||
wsgiapp = None
|
||||
"""A CPWSGIApp instance. See _cpwsgi."""
|
||||
|
||||
request_class = _cprequest.Request
|
||||
response_class = _cprequest.Response
|
||||
|
||||
relative_urls = False
|
||||
|
||||
def __init__(self, root, script_name="", config=None):
|
||||
self.log = _cplogging.LogManager(id(self), cherrypy.log.logger_root)
|
||||
self.root = root
|
||||
self.script_name = script_name
|
||||
self.wsgiapp = _cpwsgi.CPWSGIApp(self)
|
||||
|
||||
self.namespaces = self.namespaces.copy()
|
||||
self.namespaces["log"] = lambda k, v: setattr(self.log, k, v)
|
||||
self.namespaces["wsgi"] = self.wsgiapp.namespace_handler
|
||||
|
||||
self.config = self.__class__.config.copy()
|
||||
if config:
|
||||
self.merge(config)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s.%s(%r, %r)" % (self.__module__, self.__class__.__name__,
|
||||
self.root, self.script_name)
|
||||
|
||||
script_name_doc = """The URI "mount point" for this app. A mount point is that portion of
|
||||
the URI which is constant for all URIs that are serviced by this
|
||||
application; it does not include scheme, host, or proxy ("virtual host")
|
||||
portions of the URI.
|
||||
|
||||
For example, if script_name is "/my/cool/app", then the URL
|
||||
"http://www.example.com/my/cool/app/page1" might be handled by a
|
||||
"page1" method on the root object.
|
||||
|
||||
The value of script_name MUST NOT end in a slash. If the script_name
|
||||
refers to the root of the URI, it MUST be an empty string (not "/").
|
||||
|
||||
If script_name is explicitly set to None, then the script_name will be
|
||||
provided for each call from request.wsgi_environ['SCRIPT_NAME'].
|
||||
"""
|
||||
def _get_script_name(self):
|
||||
if self._script_name is None:
|
||||
# None signals that the script name should be pulled from WSGI environ.
|
||||
return cherrypy.serving.request.wsgi_environ['SCRIPT_NAME'].rstrip("/")
|
||||
return self._script_name
|
||||
def _set_script_name(self, value):
|
||||
if value:
|
||||
value = value.rstrip("/")
|
||||
self._script_name = value
|
||||
script_name = property(fget=_get_script_name, fset=_set_script_name,
|
||||
doc=script_name_doc)
|
||||
|
||||
def merge(self, config):
|
||||
"""Merge the given config into self.config."""
|
||||
_cpconfig.merge(self.config, config)
|
||||
|
||||
# Handle namespaces specified in config.
|
||||
self.namespaces(self.config.get("/", {}))
|
||||
|
||||
def find_config(self, path, key, default=None):
|
||||
"""Return the most-specific value for key along path, or default."""
|
||||
trail = path or "/"
|
||||
while trail:
|
||||
nodeconf = self.config.get(trail, {})
|
||||
|
||||
if key in nodeconf:
|
||||
return nodeconf[key]
|
||||
|
||||
lastslash = trail.rfind("/")
|
||||
if lastslash == -1:
|
||||
break
|
||||
elif lastslash == 0 and trail != "/":
|
||||
trail = "/"
|
||||
else:
|
||||
trail = trail[:lastslash]
|
||||
|
||||
return default
|
||||
|
||||
def get_serving(self, local, remote, scheme, sproto):
|
||||
"""Create and return a Request and Response object."""
|
||||
req = self.request_class(local, remote, scheme, sproto)
|
||||
req.app = self
|
||||
|
||||
for name, toolbox in self.toolboxes.items():
|
||||
req.namespaces[name] = toolbox
|
||||
|
||||
resp = self.response_class()
|
||||
cherrypy.serving.load(req, resp)
|
||||
cherrypy.engine.publish('acquire_thread')
|
||||
cherrypy.engine.publish('before_request')
|
||||
|
||||
return req, resp
|
||||
|
||||
def release_serving(self):
|
||||
"""Release the current serving (request and response)."""
|
||||
req = cherrypy.serving.request
|
||||
|
||||
cherrypy.engine.publish('after_request')
|
||||
|
||||
try:
|
||||
req.close()
|
||||
except:
|
||||
cherrypy.log(traceback=True, severity=40)
|
||||
|
||||
cherrypy.serving.clear()
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
return self.wsgiapp(environ, start_response)
|
||||
|
||||
|
||||
class Tree(object):
|
||||
"""A registry of CherryPy applications, mounted at diverse points.
|
||||
|
||||
An instance of this class may also be used as a WSGI callable
|
||||
(WSGI application object), in which case it dispatches to all
|
||||
mounted apps.
|
||||
"""
|
||||
|
||||
apps = {}
|
||||
"""
|
||||
A dict of the form {script name: application}, where "script name"
|
||||
is a string declaring the URI mount point (no trailing slash), and
|
||||
"application" is an instance of cherrypy.Application (or an arbitrary
|
||||
WSGI callable if you happen to be using a WSGI server)."""
|
||||
|
||||
def __init__(self):
|
||||
self.apps = {}
|
||||
|
||||
def mount(self, root, script_name="", config=None):
|
||||
"""Mount a new app from a root object, script_name, and config.
|
||||
|
||||
root
|
||||
An instance of a "controller class" (a collection of page
|
||||
handler methods) which represents the root of the application.
|
||||
This may also be an Application instance, or None if using
|
||||
a dispatcher other than the default.
|
||||
|
||||
script_name
|
||||
A string containing the "mount point" of the application.
|
||||
This should start with a slash, and be the path portion of the
|
||||
URL at which to mount the given root. For example, if root.index()
|
||||
will handle requests to "http://www.example.com:8080/dept/app1/",
|
||||
then the script_name argument would be "/dept/app1".
|
||||
|
||||
It MUST NOT end in a slash. If the script_name refers to the
|
||||
root of the URI, it MUST be an empty string (not "/").
|
||||
|
||||
config
|
||||
A file or dict containing application config.
|
||||
"""
|
||||
if script_name is None:
|
||||
raise TypeError(
|
||||
"The 'script_name' argument may not be None. Application "
|
||||
"objects may, however, possess a script_name of None (in "
|
||||
"order to inpect the WSGI environ for SCRIPT_NAME upon each "
|
||||
"request). You cannot mount such Applications on this Tree; "
|
||||
"you must pass them to a WSGI server interface directly.")
|
||||
|
||||
# Next line both 1) strips trailing slash and 2) maps "/" -> "".
|
||||
script_name = script_name.rstrip("/")
|
||||
|
||||
if isinstance(root, Application):
|
||||
app = root
|
||||
if script_name != "" and script_name != app.script_name:
|
||||
raise ValueError("Cannot specify a different script name and "
|
||||
"pass an Application instance to cherrypy.mount")
|
||||
script_name = app.script_name
|
||||
else:
|
||||
app = Application(root, script_name)
|
||||
|
||||
# If mounted at "", add favicon.ico
|
||||
if (script_name == "" and root is not None
|
||||
and not hasattr(root, "favicon_ico")):
|
||||
favicon = os.path.join(os.getcwd(), os.path.dirname(__file__),
|
||||
"favicon.ico")
|
||||
root.favicon_ico = tools.staticfile.handler(favicon)
|
||||
|
||||
if config:
|
||||
app.merge(config)
|
||||
|
||||
self.apps[script_name] = app
|
||||
|
||||
return app
|
||||
|
||||
def graft(self, wsgi_callable, script_name=""):
|
||||
"""Mount a wsgi callable at the given script_name."""
|
||||
# Next line both 1) strips trailing slash and 2) maps "/" -> "".
|
||||
script_name = script_name.rstrip("/")
|
||||
self.apps[script_name] = wsgi_callable
|
||||
|
||||
def script_name(self, path=None):
|
||||
"""The script_name of the app at the given path, or None.
|
||||
|
||||
If path is None, cherrypy.request is used.
|
||||
"""
|
||||
if path is None:
|
||||
try:
|
||||
request = cherrypy.serving.request
|
||||
path = httputil.urljoin(request.script_name,
|
||||
request.path_info)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
while True:
|
||||
if path in self.apps:
|
||||
return path
|
||||
|
||||
if path == "":
|
||||
return None
|
||||
|
||||
# Move one node up the tree and try again.
|
||||
path = path[:path.rfind("/")]
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
# If you're calling this, then you're probably setting SCRIPT_NAME
|
||||
# to '' (some WSGI servers always set SCRIPT_NAME to '').
|
||||
# Try to look up the app using the full path.
|
||||
env1x = environ
|
||||
if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
|
||||
env1x = _cpwsgi.downgrade_wsgi_ux_to_1x(environ)
|
||||
path = httputil.urljoin(env1x.get('SCRIPT_NAME', ''),
|
||||
env1x.get('PATH_INFO', ''))
|
||||
sn = self.script_name(path or "/")
|
||||
if sn is None:
|
||||
start_response('404 Not Found', [])
|
||||
return []
|
||||
|
||||
app = self.apps[sn]
|
||||
|
||||
# Correct the SCRIPT_NAME and PATH_INFO environ entries.
|
||||
environ = environ.copy()
|
||||
if not py3k:
|
||||
if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
|
||||
# Python 2/WSGI u.0: all strings MUST be of type unicode
|
||||
enc = environ[ntou('wsgi.url_encoding')]
|
||||
environ[ntou('SCRIPT_NAME')] = sn.decode(enc)
|
||||
environ[ntou('PATH_INFO')] = path[len(sn.rstrip("/")):].decode(enc)
|
||||
else:
|
||||
# Python 2/WSGI 1.x: all strings MUST be of type str
|
||||
environ['SCRIPT_NAME'] = sn
|
||||
environ['PATH_INFO'] = path[len(sn.rstrip("/")):]
|
||||
else:
|
||||
if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
|
||||
# Python 3/WSGI u.0: all strings MUST be full unicode
|
||||
environ['SCRIPT_NAME'] = sn
|
||||
environ['PATH_INFO'] = path[len(sn.rstrip("/")):]
|
||||
else:
|
||||
# Python 3/WSGI 1.x: all strings MUST be ISO-8859-1 str
|
||||
environ['SCRIPT_NAME'] = sn.encode('utf-8').decode('ISO-8859-1')
|
||||
environ['PATH_INFO'] = path[len(sn.rstrip("/")):].encode('utf-8').decode('ISO-8859-1')
|
||||
return app(environ, start_response)
|
||||
408
python/packages/cherrypy/_cpwsgi.py
Normal file
408
python/packages/cherrypy/_cpwsgi.py
Normal file
@@ -0,0 +1,408 @@
|
||||
"""WSGI interface (see PEP 333 and 3333).
|
||||
|
||||
Note that WSGI environ keys and values are 'native strings'; that is,
|
||||
whatever the type of "" is. For Python 2, that's a byte string; for Python 3,
|
||||
it's a unicode string. But PEP 3333 says: "even if Python's str type is
|
||||
actually Unicode "under the hood", the content of native strings must
|
||||
still be translatable to bytes via the Latin-1 encoding!"
|
||||
"""
|
||||
|
||||
import sys as _sys
|
||||
|
||||
import cherrypy as _cherrypy
|
||||
from cherrypy._cpcompat import BytesIO, bytestr, ntob, ntou, py3k, unicodestr
|
||||
from cherrypy import _cperror
|
||||
from cherrypy.lib import httputil
|
||||
|
||||
|
||||
def downgrade_wsgi_ux_to_1x(environ):
|
||||
"""Return a new environ dict for WSGI 1.x from the given WSGI u.x environ."""
|
||||
env1x = {}
|
||||
|
||||
url_encoding = environ[ntou('wsgi.url_encoding')]
|
||||
for k, v in list(environ.items()):
|
||||
if k in [ntou('PATH_INFO'), ntou('SCRIPT_NAME'), ntou('QUERY_STRING')]:
|
||||
v = v.encode(url_encoding)
|
||||
elif isinstance(v, unicodestr):
|
||||
v = v.encode('ISO-8859-1')
|
||||
env1x[k.encode('ISO-8859-1')] = v
|
||||
|
||||
return env1x
|
||||
|
||||
|
||||
class VirtualHost(object):
|
||||
"""Select a different WSGI application based on the Host header.
|
||||
|
||||
This can be useful when running multiple sites within one CP server.
|
||||
It allows several domains to point to different applications. For example::
|
||||
|
||||
root = Root()
|
||||
RootApp = cherrypy.Application(root)
|
||||
Domain2App = cherrypy.Application(root)
|
||||
SecureApp = cherrypy.Application(Secure())
|
||||
|
||||
vhost = cherrypy._cpwsgi.VirtualHost(RootApp,
|
||||
domains={'www.domain2.example': Domain2App,
|
||||
'www.domain2.example:443': SecureApp,
|
||||
})
|
||||
|
||||
cherrypy.tree.graft(vhost)
|
||||
"""
|
||||
default = None
|
||||
"""Required. The default WSGI application."""
|
||||
|
||||
use_x_forwarded_host = True
|
||||
"""If True (the default), any "X-Forwarded-Host"
|
||||
request header will be used instead of the "Host" header. This
|
||||
is commonly added by HTTP servers (such as Apache) when proxying."""
|
||||
|
||||
domains = {}
|
||||
"""A dict of {host header value: application} pairs.
|
||||
The incoming "Host" request header is looked up in this dict,
|
||||
and, if a match is found, the corresponding WSGI application
|
||||
will be called instead of the default. Note that you often need
|
||||
separate entries for "example.com" and "www.example.com".
|
||||
In addition, "Host" headers may contain the port number.
|
||||
"""
|
||||
|
||||
def __init__(self, default, domains=None, use_x_forwarded_host=True):
|
||||
self.default = default
|
||||
self.domains = domains or {}
|
||||
self.use_x_forwarded_host = use_x_forwarded_host
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
domain = environ.get('HTTP_HOST', '')
|
||||
if self.use_x_forwarded_host:
|
||||
domain = environ.get("HTTP_X_FORWARDED_HOST", domain)
|
||||
|
||||
nextapp = self.domains.get(domain)
|
||||
if nextapp is None:
|
||||
nextapp = self.default
|
||||
return nextapp(environ, start_response)
|
||||
|
||||
|
||||
class InternalRedirector(object):
|
||||
"""WSGI middleware that handles raised cherrypy.InternalRedirect."""
|
||||
|
||||
def __init__(self, nextapp, recursive=False):
|
||||
self.nextapp = nextapp
|
||||
self.recursive = recursive
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
redirections = []
|
||||
while True:
|
||||
environ = environ.copy()
|
||||
try:
|
||||
return self.nextapp(environ, start_response)
|
||||
except _cherrypy.InternalRedirect:
|
||||
ir = _sys.exc_info()[1]
|
||||
sn = environ.get('SCRIPT_NAME', '')
|
||||
path = environ.get('PATH_INFO', '')
|
||||
qs = environ.get('QUERY_STRING', '')
|
||||
|
||||
# Add the *previous* path_info + qs to redirections.
|
||||
old_uri = sn + path
|
||||
if qs:
|
||||
old_uri += "?" + qs
|
||||
redirections.append(old_uri)
|
||||
|
||||
if not self.recursive:
|
||||
# Check to see if the new URI has been redirected to already
|
||||
new_uri = sn + ir.path
|
||||
if ir.query_string:
|
||||
new_uri += "?" + ir.query_string
|
||||
if new_uri in redirections:
|
||||
ir.request.close()
|
||||
raise RuntimeError("InternalRedirector visited the "
|
||||
"same URL twice: %r" % new_uri)
|
||||
|
||||
# Munge the environment and try again.
|
||||
environ['REQUEST_METHOD'] = "GET"
|
||||
environ['PATH_INFO'] = ir.path
|
||||
environ['QUERY_STRING'] = ir.query_string
|
||||
environ['wsgi.input'] = BytesIO()
|
||||
environ['CONTENT_LENGTH'] = "0"
|
||||
environ['cherrypy.previous_request'] = ir.request
|
||||
|
||||
|
||||
class ExceptionTrapper(object):
|
||||
"""WSGI middleware that traps exceptions."""
|
||||
|
||||
def __init__(self, nextapp, throws=(KeyboardInterrupt, SystemExit)):
|
||||
self.nextapp = nextapp
|
||||
self.throws = throws
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
return _TrappedResponse(self.nextapp, environ, start_response, self.throws)
|
||||
|
||||
|
||||
class _TrappedResponse(object):
|
||||
|
||||
response = iter([])
|
||||
|
||||
def __init__(self, nextapp, environ, start_response, throws):
|
||||
self.nextapp = nextapp
|
||||
self.environ = environ
|
||||
self.start_response = start_response
|
||||
self.throws = throws
|
||||
self.started_response = False
|
||||
self.response = self.trap(self.nextapp, self.environ, self.start_response)
|
||||
self.iter_response = iter(self.response)
|
||||
|
||||
def __iter__(self):
|
||||
self.started_response = True
|
||||
return self
|
||||
|
||||
if py3k:
|
||||
def __next__(self):
|
||||
return self.trap(next, self.iter_response)
|
||||
else:
|
||||
def next(self):
|
||||
return self.trap(self.iter_response.next)
|
||||
|
||||
def close(self):
|
||||
if hasattr(self.response, 'close'):
|
||||
self.response.close()
|
||||
|
||||
def trap(self, func, *args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except self.throws:
|
||||
raise
|
||||
except StopIteration:
|
||||
raise
|
||||
except:
|
||||
tb = _cperror.format_exc()
|
||||
#print('trapped (started %s):' % self.started_response, tb)
|
||||
_cherrypy.log(tb, severity=40)
|
||||
if not _cherrypy.request.show_tracebacks:
|
||||
tb = ""
|
||||
s, h, b = _cperror.bare_error(tb)
|
||||
if py3k:
|
||||
# What fun.
|
||||
s = s.decode('ISO-8859-1')
|
||||
h = [(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
|
||||
for k, v in h]
|
||||
if self.started_response:
|
||||
# Empty our iterable (so future calls raise StopIteration)
|
||||
self.iter_response = iter([])
|
||||
else:
|
||||
self.iter_response = iter(b)
|
||||
|
||||
try:
|
||||
self.start_response(s, h, _sys.exc_info())
|
||||
except:
|
||||
# "The application must not trap any exceptions raised by
|
||||
# start_response, if it called start_response with exc_info.
|
||||
# Instead, it should allow such exceptions to propagate
|
||||
# back to the server or gateway."
|
||||
# But we still log and call close() to clean up ourselves.
|
||||
_cherrypy.log(traceback=True, severity=40)
|
||||
raise
|
||||
|
||||
if self.started_response:
|
||||
return ntob("").join(b)
|
||||
else:
|
||||
return b
|
||||
|
||||
|
||||
# WSGI-to-CP Adapter #
|
||||
|
||||
|
||||
class AppResponse(object):
|
||||
"""WSGI response iterable for CherryPy applications."""
|
||||
|
||||
def __init__(self, environ, start_response, cpapp):
|
||||
self.cpapp = cpapp
|
||||
try:
|
||||
if not py3k:
|
||||
if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
|
||||
environ = downgrade_wsgi_ux_to_1x(environ)
|
||||
self.environ = environ
|
||||
self.run()
|
||||
|
||||
r = _cherrypy.serving.response
|
||||
|
||||
outstatus = r.output_status
|
||||
if not isinstance(outstatus, bytestr):
|
||||
raise TypeError("response.output_status is not a byte string.")
|
||||
|
||||
outheaders = []
|
||||
for k, v in r.header_list:
|
||||
if not isinstance(k, bytestr):
|
||||
raise TypeError("response.header_list key %r is not a byte string." % k)
|
||||
if not isinstance(v, bytestr):
|
||||
raise TypeError("response.header_list value %r is not a byte string." % v)
|
||||
outheaders.append((k, v))
|
||||
|
||||
if py3k:
|
||||
# According to PEP 3333, when using Python 3, the response status
|
||||
# and headers must be bytes masquerading as unicode; that is, they
|
||||
# must be of type "str" but are restricted to code points in the
|
||||
# "latin-1" set.
|
||||
outstatus = outstatus.decode('ISO-8859-1')
|
||||
outheaders = [(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
|
||||
for k, v in outheaders]
|
||||
|
||||
self.iter_response = iter(r.body)
|
||||
self.write = start_response(outstatus, outheaders)
|
||||
except:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
if py3k:
|
||||
def __next__(self):
|
||||
return next(self.iter_response)
|
||||
else:
|
||||
def next(self):
|
||||
return self.iter_response.next()
|
||||
|
||||
def close(self):
|
||||
"""Close and de-reference the current request and response. (Core)"""
|
||||
self.cpapp.release_serving()
|
||||
|
||||
def run(self):
|
||||
"""Create a Request object using environ."""
|
||||
env = self.environ.get
|
||||
|
||||
local = httputil.Host('', int(env('SERVER_PORT', 80)),
|
||||
env('SERVER_NAME', ''))
|
||||
remote = httputil.Host(env('REMOTE_ADDR', ''),
|
||||
int(env('REMOTE_PORT', -1) or -1),
|
||||
env('REMOTE_HOST', ''))
|
||||
scheme = env('wsgi.url_scheme')
|
||||
sproto = env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1")
|
||||
request, resp = self.cpapp.get_serving(local, remote, scheme, sproto)
|
||||
|
||||
# LOGON_USER is served by IIS, and is the name of the
|
||||
# user after having been mapped to a local account.
|
||||
# Both IIS and Apache set REMOTE_USER, when possible.
|
||||
request.login = env('LOGON_USER') or env('REMOTE_USER') or None
|
||||
request.multithread = self.environ['wsgi.multithread']
|
||||
request.multiprocess = self.environ['wsgi.multiprocess']
|
||||
request.wsgi_environ = self.environ
|
||||
request.prev = env('cherrypy.previous_request', None)
|
||||
|
||||
meth = self.environ['REQUEST_METHOD']
|
||||
|
||||
path = httputil.urljoin(self.environ.get('SCRIPT_NAME', ''),
|
||||
self.environ.get('PATH_INFO', ''))
|
||||
qs = self.environ.get('QUERY_STRING', '')
|
||||
|
||||
if py3k:
|
||||
# This isn't perfect; if the given PATH_INFO is in the wrong encoding,
|
||||
# it may fail to match the appropriate config section URI. But meh.
|
||||
old_enc = self.environ.get('wsgi.url_encoding', 'ISO-8859-1')
|
||||
new_enc = self.cpapp.find_config(self.environ.get('PATH_INFO', ''),
|
||||
"request.uri_encoding", 'utf-8')
|
||||
if new_enc.lower() != old_enc.lower():
|
||||
# Even though the path and qs are unicode, the WSGI server is
|
||||
# required by PEP 3333 to coerce them to ISO-8859-1 masquerading
|
||||
# as unicode. So we have to encode back to bytes and then decode
|
||||
# again using the "correct" encoding.
|
||||
try:
|
||||
u_path = path.encode(old_enc).decode(new_enc)
|
||||
u_qs = qs.encode(old_enc).decode(new_enc)
|
||||
except (UnicodeEncodeError, UnicodeDecodeError):
|
||||
# Just pass them through without transcoding and hope.
|
||||
pass
|
||||
else:
|
||||
# Only set transcoded values if they both succeed.
|
||||
path = u_path
|
||||
qs = u_qs
|
||||
|
||||
rproto = self.environ.get('SERVER_PROTOCOL')
|
||||
headers = self.translate_headers(self.environ)
|
||||
rfile = self.environ['wsgi.input']
|
||||
request.run(meth, path, qs, rproto, headers, rfile)
|
||||
|
||||
headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization',
|
||||
'CONTENT_LENGTH': 'Content-Length',
|
||||
'CONTENT_TYPE': 'Content-Type',
|
||||
'REMOTE_HOST': 'Remote-Host',
|
||||
'REMOTE_ADDR': 'Remote-Addr',
|
||||
}
|
||||
|
||||
def translate_headers(self, environ):
|
||||
"""Translate CGI-environ header names to HTTP header names."""
|
||||
for cgiName in environ:
|
||||
# We assume all incoming header keys are uppercase already.
|
||||
if cgiName in self.headerNames:
|
||||
yield self.headerNames[cgiName], environ[cgiName]
|
||||
elif cgiName[:5] == "HTTP_":
|
||||
# Hackish attempt at recovering original header names.
|
||||
translatedHeader = cgiName[5:].replace("_", "-")
|
||||
yield translatedHeader, environ[cgiName]
|
||||
|
||||
|
||||
class CPWSGIApp(object):
|
||||
"""A WSGI application object for a CherryPy Application."""
|
||||
|
||||
pipeline = [('ExceptionTrapper', ExceptionTrapper),
|
||||
('InternalRedirector', InternalRedirector),
|
||||
]
|
||||
"""A list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a
|
||||
constructor that takes an initial, positional 'nextapp' argument,
|
||||
plus optional keyword arguments, and returns a WSGI application
|
||||
(that takes environ and start_response arguments). The 'name' can
|
||||
be any you choose, and will correspond to keys in self.config."""
|
||||
|
||||
head = None
|
||||
"""Rather than nest all apps in the pipeline on each call, it's only
|
||||
done the first time, and the result is memoized into self.head. Set
|
||||
this to None again if you change self.pipeline after calling self."""
|
||||
|
||||
config = {}
|
||||
"""A dict whose keys match names listed in the pipeline. Each
|
||||
value is a further dict which will be passed to the corresponding
|
||||
named WSGI callable (from the pipeline) as keyword arguments."""
|
||||
|
||||
response_class = AppResponse
|
||||
"""The class to instantiate and return as the next app in the WSGI chain."""
|
||||
|
||||
def __init__(self, cpapp, pipeline=None):
|
||||
self.cpapp = cpapp
|
||||
self.pipeline = self.pipeline[:]
|
||||
if pipeline:
|
||||
self.pipeline.extend(pipeline)
|
||||
self.config = self.config.copy()
|
||||
|
||||
def tail(self, environ, start_response):
|
||||
"""WSGI application callable for the actual CherryPy application.
|
||||
|
||||
You probably shouldn't call this; call self.__call__ instead,
|
||||
so that any WSGI middleware in self.pipeline can run first.
|
||||
"""
|
||||
return self.response_class(environ, start_response, self.cpapp)
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
head = self.head
|
||||
if head is None:
|
||||
# Create and nest the WSGI apps in our pipeline (in reverse order).
|
||||
# Then memoize the result in self.head.
|
||||
head = self.tail
|
||||
for name, callable in self.pipeline[::-1]:
|
||||
conf = self.config.get(name, {})
|
||||
head = callable(head, **conf)
|
||||
self.head = head
|
||||
return head(environ, start_response)
|
||||
|
||||
def namespace_handler(self, k, v):
|
||||
"""Config handler for the 'wsgi' namespace."""
|
||||
if k == "pipeline":
|
||||
# Note this allows multiple 'wsgi.pipeline' config entries
|
||||
# (but each entry will be processed in a 'random' order).
|
||||
# It should also allow developers to set default middleware
|
||||
# in code (passed to self.__init__) that deployers can add to
|
||||
# (but not remove) via config.
|
||||
self.pipeline.extend(v)
|
||||
elif k == "response_class":
|
||||
self.response_class = v
|
||||
else:
|
||||
name, arg = k.split(".", 1)
|
||||
bucket = self.config.setdefault(name, {})
|
||||
bucket[arg] = v
|
||||
|
||||
63
python/packages/cherrypy/_cpwsgi_server.py
Normal file
63
python/packages/cherrypy/_cpwsgi_server.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""WSGI server interface (see PEP 333). This adds some CP-specific bits to
|
||||
the framework-agnostic wsgiserver package.
|
||||
"""
|
||||
import sys
|
||||
|
||||
import cherrypy
|
||||
from cherrypy import wsgiserver
|
||||
|
||||
|
||||
class CPWSGIServer(wsgiserver.CherryPyWSGIServer):
|
||||
"""Wrapper for wsgiserver.CherryPyWSGIServer.
|
||||
|
||||
wsgiserver has been designed to not reference CherryPy in any way,
|
||||
so that it can be used in other frameworks and applications. Therefore,
|
||||
we wrap it here, so we can set our own mount points from cherrypy.tree
|
||||
and apply some attributes from config -> cherrypy.server -> wsgiserver.
|
||||
"""
|
||||
|
||||
def __init__(self, server_adapter=cherrypy.server):
|
||||
self.server_adapter = server_adapter
|
||||
self.max_request_header_size = self.server_adapter.max_request_header_size or 0
|
||||
self.max_request_body_size = self.server_adapter.max_request_body_size or 0
|
||||
|
||||
server_name = (self.server_adapter.socket_host or
|
||||
self.server_adapter.socket_file or
|
||||
None)
|
||||
|
||||
self.wsgi_version = self.server_adapter.wsgi_version
|
||||
s = wsgiserver.CherryPyWSGIServer
|
||||
s.__init__(self, server_adapter.bind_addr, cherrypy.tree,
|
||||
self.server_adapter.thread_pool,
|
||||
server_name,
|
||||
max = self.server_adapter.thread_pool_max,
|
||||
request_queue_size = self.server_adapter.socket_queue_size,
|
||||
timeout = self.server_adapter.socket_timeout,
|
||||
shutdown_timeout = self.server_adapter.shutdown_timeout,
|
||||
)
|
||||
self.protocol = self.server_adapter.protocol_version
|
||||
self.nodelay = self.server_adapter.nodelay
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
ssl_module = self.server_adapter.ssl_module or 'builtin'
|
||||
else:
|
||||
ssl_module = self.server_adapter.ssl_module or 'pyopenssl'
|
||||
if self.server_adapter.ssl_context:
|
||||
adapter_class = wsgiserver.get_ssl_adapter_class(ssl_module)
|
||||
self.ssl_adapter = adapter_class(
|
||||
self.server_adapter.ssl_certificate,
|
||||
self.server_adapter.ssl_private_key,
|
||||
self.server_adapter.ssl_certificate_chain)
|
||||
self.ssl_adapter.context = self.server_adapter.ssl_context
|
||||
elif self.server_adapter.ssl_certificate:
|
||||
adapter_class = wsgiserver.get_ssl_adapter_class(ssl_module)
|
||||
self.ssl_adapter = adapter_class(
|
||||
self.server_adapter.ssl_certificate,
|
||||
self.server_adapter.ssl_private_key,
|
||||
self.server_adapter.ssl_certificate_chain)
|
||||
|
||||
self.stats['Enabled'] = getattr(self.server_adapter, 'statistics', False)
|
||||
|
||||
def error_log(self, msg="", level=20, traceback=False):
|
||||
cherrypy.engine.log(msg, level, traceback)
|
||||
|
||||
109
python/packages/cherrypy/cherryd
Executable file
109
python/packages/cherrypy/cherryd
Executable file
@@ -0,0 +1,109 @@
|
||||
#! /usr/bin/env python
|
||||
"""The CherryPy daemon."""
|
||||
|
||||
import sys
|
||||
|
||||
import cherrypy
|
||||
from cherrypy.process import plugins, servers
|
||||
from cherrypy import Application
|
||||
|
||||
def start(configfiles=None, daemonize=False, environment=None,
|
||||
fastcgi=False, scgi=False, pidfile=None, imports=None,
|
||||
cgi=False):
|
||||
"""Subscribe all engine plugins and start the engine."""
|
||||
sys.path = [''] + sys.path
|
||||
for i in imports or []:
|
||||
exec("import %s" % i)
|
||||
|
||||
for c in configfiles or []:
|
||||
cherrypy.config.update(c)
|
||||
# If there's only one app mounted, merge config into it.
|
||||
if len(cherrypy.tree.apps) == 1:
|
||||
for app in cherrypy.tree.apps.values():
|
||||
if isinstance(app, Application):
|
||||
app.merge(c)
|
||||
|
||||
engine = cherrypy.engine
|
||||
|
||||
if environment is not None:
|
||||
cherrypy.config.update({'environment': environment})
|
||||
|
||||
# Only daemonize if asked to.
|
||||
if daemonize:
|
||||
# Don't print anything to stdout/sterr.
|
||||
cherrypy.config.update({'log.screen': False})
|
||||
plugins.Daemonizer(engine).subscribe()
|
||||
|
||||
if pidfile:
|
||||
plugins.PIDFile(engine, pidfile).subscribe()
|
||||
|
||||
if hasattr(engine, "signal_handler"):
|
||||
engine.signal_handler.subscribe()
|
||||
if hasattr(engine, "console_control_handler"):
|
||||
engine.console_control_handler.subscribe()
|
||||
|
||||
if (fastcgi and (scgi or cgi)) or (scgi and cgi):
|
||||
cherrypy.log.error("You may only specify one of the cgi, fastcgi, and "
|
||||
"scgi options.", 'ENGINE')
|
||||
sys.exit(1)
|
||||
elif fastcgi or scgi or cgi:
|
||||
# Turn off autoreload when using *cgi.
|
||||
cherrypy.config.update({'engine.autoreload_on': False})
|
||||
# Turn off the default HTTP server (which is subscribed by default).
|
||||
cherrypy.server.unsubscribe()
|
||||
|
||||
addr = cherrypy.server.bind_addr
|
||||
if fastcgi:
|
||||
f = servers.FlupFCGIServer(application=cherrypy.tree,
|
||||
bindAddress=addr)
|
||||
elif scgi:
|
||||
f = servers.FlupSCGIServer(application=cherrypy.tree,
|
||||
bindAddress=addr)
|
||||
else:
|
||||
f = servers.FlupCGIServer(application=cherrypy.tree,
|
||||
bindAddress=addr)
|
||||
s = servers.ServerAdapter(engine, httpserver=f, bind_addr=addr)
|
||||
s.subscribe()
|
||||
|
||||
# Always start the engine; this will start all other services
|
||||
try:
|
||||
engine.start()
|
||||
except:
|
||||
# Assume the error has been logged already via bus.log.
|
||||
sys.exit(1)
|
||||
else:
|
||||
engine.block()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from optparse import OptionParser
|
||||
|
||||
p = OptionParser()
|
||||
p.add_option('-c', '--config', action="append", dest='config',
|
||||
help="specify config file(s)")
|
||||
p.add_option('-d', action="store_true", dest='daemonize',
|
||||
help="run the server as a daemon")
|
||||
p.add_option('-e', '--environment', dest='environment', default=None,
|
||||
help="apply the given config environment")
|
||||
p.add_option('-f', action="store_true", dest='fastcgi',
|
||||
help="start a fastcgi server instead of the default HTTP server")
|
||||
p.add_option('-s', action="store_true", dest='scgi',
|
||||
help="start a scgi server instead of the default HTTP server")
|
||||
p.add_option('-x', action="store_true", dest='cgi',
|
||||
help="start a cgi server instead of the default HTTP server")
|
||||
p.add_option('-i', '--import', action="append", dest='imports',
|
||||
help="specify modules to import")
|
||||
p.add_option('-p', '--pidfile', dest='pidfile', default=None,
|
||||
help="store the process id in the given file")
|
||||
p.add_option('-P', '--Path', action="append", dest='Path',
|
||||
help="add the given paths to sys.path")
|
||||
options, args = p.parse_args()
|
||||
|
||||
if options.Path:
|
||||
for p in options.Path:
|
||||
sys.path.insert(0, p)
|
||||
|
||||
start(options.config, options.daemonize,
|
||||
options.environment, options.fastcgi, options.scgi,
|
||||
options.pidfile, options.imports, options.cgi)
|
||||
|
||||
BIN
python/packages/cherrypy/favicon.ico
Normal file
BIN
python/packages/cherrypy/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
45
python/packages/cherrypy/lib/__init__.py
Normal file
45
python/packages/cherrypy/lib/__init__.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""CherryPy Library"""
|
||||
|
||||
# Deprecated in CherryPy 3.2 -- remove in CherryPy 3.3
|
||||
from cherrypy.lib.reprconf import unrepr, modules, attributes
|
||||
|
||||
class file_generator(object):
|
||||
"""Yield the given input (a file object) in chunks (default 64k). (Core)"""
|
||||
|
||||
def __init__(self, input, chunkSize=65536):
|
||||
self.input = input
|
||||
self.chunkSize = chunkSize
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
chunk = self.input.read(self.chunkSize)
|
||||
if chunk:
|
||||
return chunk
|
||||
else:
|
||||
if hasattr(self.input, 'close'):
|
||||
self.input.close()
|
||||
raise StopIteration()
|
||||
next = __next__
|
||||
|
||||
def file_generator_limited(fileobj, count, chunk_size=65536):
|
||||
"""Yield the given file object in chunks, stopping after `count`
|
||||
bytes has been emitted. Default chunk size is 64kB. (Core)
|
||||
"""
|
||||
remaining = count
|
||||
while remaining > 0:
|
||||
chunk = fileobj.read(min(chunk_size, remaining))
|
||||
chunklen = len(chunk)
|
||||
if chunklen == 0:
|
||||
return
|
||||
remaining -= chunklen
|
||||
yield chunk
|
||||
|
||||
def set_vary_header(response, header_name):
|
||||
"Add a Vary header to a response"
|
||||
varies = response.headers.get("Vary", "")
|
||||
varies = [x.strip() for x in varies.split(",") if x.strip()]
|
||||
if header_name not in varies:
|
||||
varies.append(header_name)
|
||||
response.headers['Vary'] = ", ".join(varies)
|
||||
87
python/packages/cherrypy/lib/auth.py
Normal file
87
python/packages/cherrypy/lib/auth.py
Normal file
@@ -0,0 +1,87 @@
|
||||
import cherrypy
|
||||
from cherrypy.lib import httpauth
|
||||
|
||||
|
||||
def check_auth(users, encrypt=None, realm=None):
|
||||
"""If an authorization header contains credentials, return True, else False."""
|
||||
request = cherrypy.serving.request
|
||||
if 'authorization' in request.headers:
|
||||
# make sure the provided credentials are correctly set
|
||||
ah = httpauth.parseAuthorization(request.headers['authorization'])
|
||||
if ah is None:
|
||||
raise cherrypy.HTTPError(400, 'Bad Request')
|
||||
|
||||
if not encrypt:
|
||||
encrypt = httpauth.DIGEST_AUTH_ENCODERS[httpauth.MD5]
|
||||
|
||||
if hasattr(users, '__call__'):
|
||||
try:
|
||||
# backward compatibility
|
||||
users = users() # expect it to return a dictionary
|
||||
|
||||
if not isinstance(users, dict):
|
||||
raise ValueError("Authentication users must be a dictionary")
|
||||
|
||||
# fetch the user password
|
||||
password = users.get(ah["username"], None)
|
||||
except TypeError:
|
||||
# returns a password (encrypted or clear text)
|
||||
password = users(ah["username"])
|
||||
else:
|
||||
if not isinstance(users, dict):
|
||||
raise ValueError("Authentication users must be a dictionary")
|
||||
|
||||
# fetch the user password
|
||||
password = users.get(ah["username"], None)
|
||||
|
||||
# validate the authorization by re-computing it here
|
||||
# and compare it with what the user-agent provided
|
||||
if httpauth.checkResponse(ah, password, method=request.method,
|
||||
encrypt=encrypt, realm=realm):
|
||||
request.login = ah["username"]
|
||||
return True
|
||||
|
||||
request.login = False
|
||||
return False
|
||||
|
||||
def basic_auth(realm, users, encrypt=None, debug=False):
|
||||
"""If auth fails, raise 401 with a basic authentication header.
|
||||
|
||||
realm
|
||||
A string containing the authentication realm.
|
||||
|
||||
users
|
||||
A dict of the form: {username: password} or a callable returning a dict.
|
||||
|
||||
encrypt
|
||||
callable used to encrypt the password returned from the user-agent.
|
||||
if None it defaults to a md5 encryption.
|
||||
|
||||
"""
|
||||
if check_auth(users, encrypt):
|
||||
if debug:
|
||||
cherrypy.log('Auth successful', 'TOOLS.BASIC_AUTH')
|
||||
return
|
||||
|
||||
# inform the user-agent this path is protected
|
||||
cherrypy.serving.response.headers['www-authenticate'] = httpauth.basicAuth(realm)
|
||||
|
||||
raise cherrypy.HTTPError(401, "You are not authorized to access that resource")
|
||||
|
||||
def digest_auth(realm, users, debug=False):
|
||||
"""If auth fails, raise 401 with a digest authentication header.
|
||||
|
||||
realm
|
||||
A string containing the authentication realm.
|
||||
users
|
||||
A dict of the form: {username: password} or a callable returning a dict.
|
||||
"""
|
||||
if check_auth(users, realm=realm):
|
||||
if debug:
|
||||
cherrypy.log('Auth successful', 'TOOLS.DIGEST_AUTH')
|
||||
return
|
||||
|
||||
# inform the user-agent this path is protected
|
||||
cherrypy.serving.response.headers['www-authenticate'] = httpauth.digestAuth(realm)
|
||||
|
||||
raise cherrypy.HTTPError(401, "You are not authorized to access that resource")
|
||||
87
python/packages/cherrypy/lib/auth_basic.py
Normal file
87
python/packages/cherrypy/lib/auth_basic.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# This file is part of CherryPy <http://www.cherrypy.org/>
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:ts=4:sw=4:expandtab:fileencoding=utf-8
|
||||
|
||||
__doc__ = """This module provides a CherryPy 3.x tool which implements
|
||||
the server-side of HTTP Basic Access Authentication, as described in :rfc:`2617`.
|
||||
|
||||
Example usage, using the built-in checkpassword_dict function which uses a dict
|
||||
as the credentials store::
|
||||
|
||||
userpassdict = {'bird' : 'bebop', 'ornette' : 'wayout'}
|
||||
checkpassword = cherrypy.lib.auth_basic.checkpassword_dict(userpassdict)
|
||||
basic_auth = {'tools.auth_basic.on': True,
|
||||
'tools.auth_basic.realm': 'earth',
|
||||
'tools.auth_basic.checkpassword': checkpassword,
|
||||
}
|
||||
app_config = { '/' : basic_auth }
|
||||
|
||||
"""
|
||||
|
||||
__author__ = 'visteya'
|
||||
__date__ = 'April 2009'
|
||||
|
||||
import binascii
|
||||
from cherrypy._cpcompat import base64_decode
|
||||
import cherrypy
|
||||
|
||||
|
||||
def checkpassword_dict(user_password_dict):
|
||||
"""Returns a checkpassword function which checks credentials
|
||||
against a dictionary of the form: {username : password}.
|
||||
|
||||
If you want a simple dictionary-based authentication scheme, use
|
||||
checkpassword_dict(my_credentials_dict) as the value for the
|
||||
checkpassword argument to basic_auth().
|
||||
"""
|
||||
def checkpassword(realm, user, password):
|
||||
p = user_password_dict.get(user)
|
||||
return p and p == password or False
|
||||
|
||||
return checkpassword
|
||||
|
||||
|
||||
def basic_auth(realm, checkpassword, debug=False):
|
||||
"""A CherryPy tool which hooks at before_handler to perform
|
||||
HTTP Basic Access Authentication, as specified in :rfc:`2617`.
|
||||
|
||||
If the request has an 'authorization' header with a 'Basic' scheme, this
|
||||
tool attempts to authenticate the credentials supplied in that header. If
|
||||
the request has no 'authorization' header, or if it does but the scheme is
|
||||
not 'Basic', or if authentication fails, the tool sends a 401 response with
|
||||
a 'WWW-Authenticate' Basic header.
|
||||
|
||||
realm
|
||||
A string containing the authentication realm.
|
||||
|
||||
checkpassword
|
||||
A callable which checks the authentication credentials.
|
||||
Its signature is checkpassword(realm, username, password). where
|
||||
username and password are the values obtained from the request's
|
||||
'authorization' header. If authentication succeeds, checkpassword
|
||||
returns True, else it returns False.
|
||||
|
||||
"""
|
||||
|
||||
if '"' in realm:
|
||||
raise ValueError('Realm cannot contain the " (quote) character.')
|
||||
request = cherrypy.serving.request
|
||||
|
||||
auth_header = request.headers.get('authorization')
|
||||
if auth_header is not None:
|
||||
try:
|
||||
scheme, params = auth_header.split(' ', 1)
|
||||
if scheme.lower() == 'basic':
|
||||
username, password = base64_decode(params).split(':', 1)
|
||||
if checkpassword(realm, username, password):
|
||||
if debug:
|
||||
cherrypy.log('Auth succeeded', 'TOOLS.AUTH_BASIC')
|
||||
request.login = username
|
||||
return # successful authentication
|
||||
except (ValueError, binascii.Error): # split() error, base64.decodestring() error
|
||||
raise cherrypy.HTTPError(400, 'Bad Request')
|
||||
|
||||
# Respond with 401 status and a WWW-Authenticate header
|
||||
cherrypy.serving.response.headers['www-authenticate'] = 'Basic realm="%s"' % realm
|
||||
raise cherrypy.HTTPError(401, "You are not authorized to access that resource")
|
||||
|
||||
365
python/packages/cherrypy/lib/auth_digest.py
Normal file
365
python/packages/cherrypy/lib/auth_digest.py
Normal file
@@ -0,0 +1,365 @@
|
||||
# This file is part of CherryPy <http://www.cherrypy.org/>
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:ts=4:sw=4:expandtab:fileencoding=utf-8
|
||||
|
||||
__doc__ = """An implementation of the server-side of HTTP Digest Access
|
||||
Authentication, which is described in :rfc:`2617`.
|
||||
|
||||
Example usage, using the built-in get_ha1_dict_plain function which uses a dict
|
||||
of plaintext passwords as the credentials store::
|
||||
|
||||
userpassdict = {'alice' : '4x5istwelve'}
|
||||
get_ha1 = cherrypy.lib.auth_digest.get_ha1_dict_plain(userpassdict)
|
||||
digest_auth = {'tools.auth_digest.on': True,
|
||||
'tools.auth_digest.realm': 'wonderland',
|
||||
'tools.auth_digest.get_ha1': get_ha1,
|
||||
'tools.auth_digest.key': 'a565c27146791cfb',
|
||||
}
|
||||
app_config = { '/' : digest_auth }
|
||||
"""
|
||||
|
||||
__author__ = 'visteya'
|
||||
__date__ = 'April 2009'
|
||||
|
||||
|
||||
import time
|
||||
from cherrypy._cpcompat import parse_http_list, parse_keqv_list
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import md5, ntob
|
||||
md5_hex = lambda s: md5(ntob(s)).hexdigest()
|
||||
|
||||
qop_auth = 'auth'
|
||||
qop_auth_int = 'auth-int'
|
||||
valid_qops = (qop_auth, qop_auth_int)
|
||||
|
||||
valid_algorithms = ('MD5', 'MD5-sess')
|
||||
|
||||
|
||||
def TRACE(msg):
|
||||
cherrypy.log(msg, context='TOOLS.AUTH_DIGEST')
|
||||
|
||||
# Three helper functions for users of the tool, providing three variants
|
||||
# of get_ha1() functions for three different kinds of credential stores.
|
||||
def get_ha1_dict_plain(user_password_dict):
|
||||
"""Returns a get_ha1 function which obtains a plaintext password from a
|
||||
dictionary of the form: {username : password}.
|
||||
|
||||
If you want a simple dictionary-based authentication scheme, with plaintext
|
||||
passwords, use get_ha1_dict_plain(my_userpass_dict) as the value for the
|
||||
get_ha1 argument to digest_auth().
|
||||
"""
|
||||
def get_ha1(realm, username):
|
||||
password = user_password_dict.get(username)
|
||||
if password:
|
||||
return md5_hex('%s:%s:%s' % (username, realm, password))
|
||||
return None
|
||||
|
||||
return get_ha1
|
||||
|
||||
def get_ha1_dict(user_ha1_dict):
|
||||
"""Returns a get_ha1 function which obtains a HA1 password hash from a
|
||||
dictionary of the form: {username : HA1}.
|
||||
|
||||
If you want a dictionary-based authentication scheme, but with
|
||||
pre-computed HA1 hashes instead of plain-text passwords, use
|
||||
get_ha1_dict(my_userha1_dict) as the value for the get_ha1
|
||||
argument to digest_auth().
|
||||
"""
|
||||
def get_ha1(realm, username):
|
||||
return user_ha1_dict.get(user)
|
||||
|
||||
return get_ha1
|
||||
|
||||
def get_ha1_file_htdigest(filename):
|
||||
"""Returns a get_ha1 function which obtains a HA1 password hash from a
|
||||
flat file with lines of the same format as that produced by the Apache
|
||||
htdigest utility. For example, for realm 'wonderland', username 'alice',
|
||||
and password '4x5istwelve', the htdigest line would be::
|
||||
|
||||
alice:wonderland:3238cdfe91a8b2ed8e39646921a02d4c
|
||||
|
||||
If you want to use an Apache htdigest file as the credentials store,
|
||||
then use get_ha1_file_htdigest(my_htdigest_file) as the value for the
|
||||
get_ha1 argument to digest_auth(). It is recommended that the filename
|
||||
argument be an absolute path, to avoid problems.
|
||||
"""
|
||||
def get_ha1(realm, username):
|
||||
result = None
|
||||
f = open(filename, 'r')
|
||||
for line in f:
|
||||
u, r, ha1 = line.rstrip().split(':')
|
||||
if u == username and r == realm:
|
||||
result = ha1
|
||||
break
|
||||
f.close()
|
||||
return result
|
||||
|
||||
return get_ha1
|
||||
|
||||
|
||||
def synthesize_nonce(s, key, timestamp=None):
|
||||
"""Synthesize a nonce value which resists spoofing and can be checked for staleness.
|
||||
Returns a string suitable as the value for 'nonce' in the www-authenticate header.
|
||||
|
||||
s
|
||||
A string related to the resource, such as the hostname of the server.
|
||||
|
||||
key
|
||||
A secret string known only to the server.
|
||||
|
||||
timestamp
|
||||
An integer seconds-since-the-epoch timestamp
|
||||
|
||||
"""
|
||||
if timestamp is None:
|
||||
timestamp = int(time.time())
|
||||
h = md5_hex('%s:%s:%s' % (timestamp, s, key))
|
||||
nonce = '%s:%s' % (timestamp, h)
|
||||
return nonce
|
||||
|
||||
|
||||
def H(s):
|
||||
"""The hash function H"""
|
||||
return md5_hex(s)
|
||||
|
||||
|
||||
class HttpDigestAuthorization (object):
|
||||
"""Class to parse a Digest Authorization header and perform re-calculation
|
||||
of the digest.
|
||||
"""
|
||||
|
||||
def errmsg(self, s):
|
||||
return 'Digest Authorization header: %s' % s
|
||||
|
||||
def __init__(self, auth_header, http_method, debug=False):
|
||||
self.http_method = http_method
|
||||
self.debug = debug
|
||||
scheme, params = auth_header.split(" ", 1)
|
||||
self.scheme = scheme.lower()
|
||||
if self.scheme != 'digest':
|
||||
raise ValueError('Authorization scheme is not "Digest"')
|
||||
|
||||
self.auth_header = auth_header
|
||||
|
||||
# make a dict of the params
|
||||
items = parse_http_list(params)
|
||||
paramsd = parse_keqv_list(items)
|
||||
|
||||
self.realm = paramsd.get('realm')
|
||||
self.username = paramsd.get('username')
|
||||
self.nonce = paramsd.get('nonce')
|
||||
self.uri = paramsd.get('uri')
|
||||
self.method = paramsd.get('method')
|
||||
self.response = paramsd.get('response') # the response digest
|
||||
self.algorithm = paramsd.get('algorithm', 'MD5')
|
||||
self.cnonce = paramsd.get('cnonce')
|
||||
self.opaque = paramsd.get('opaque')
|
||||
self.qop = paramsd.get('qop') # qop
|
||||
self.nc = paramsd.get('nc') # nonce count
|
||||
|
||||
# perform some correctness checks
|
||||
if self.algorithm not in valid_algorithms:
|
||||
raise ValueError(self.errmsg("Unsupported value for algorithm: '%s'" % self.algorithm))
|
||||
|
||||
has_reqd = self.username and \
|
||||
self.realm and \
|
||||
self.nonce and \
|
||||
self.uri and \
|
||||
self.response
|
||||
if not has_reqd:
|
||||
raise ValueError(self.errmsg("Not all required parameters are present."))
|
||||
|
||||
if self.qop:
|
||||
if self.qop not in valid_qops:
|
||||
raise ValueError(self.errmsg("Unsupported value for qop: '%s'" % self.qop))
|
||||
if not (self.cnonce and self.nc):
|
||||
raise ValueError(self.errmsg("If qop is sent then cnonce and nc MUST be present"))
|
||||
else:
|
||||
if self.cnonce or self.nc:
|
||||
raise ValueError(self.errmsg("If qop is not sent, neither cnonce nor nc can be present"))
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return 'authorization : %s' % self.auth_header
|
||||
|
||||
def validate_nonce(self, s, key):
|
||||
"""Validate the nonce.
|
||||
Returns True if nonce was generated by synthesize_nonce() and the timestamp
|
||||
is not spoofed, else returns False.
|
||||
|
||||
s
|
||||
A string related to the resource, such as the hostname of the server.
|
||||
|
||||
key
|
||||
A secret string known only to the server.
|
||||
|
||||
Both s and key must be the same values which were used to synthesize the nonce
|
||||
we are trying to validate.
|
||||
"""
|
||||
try:
|
||||
timestamp, hashpart = self.nonce.split(':', 1)
|
||||
s_timestamp, s_hashpart = synthesize_nonce(s, key, timestamp).split(':', 1)
|
||||
is_valid = s_hashpart == hashpart
|
||||
if self.debug:
|
||||
TRACE('validate_nonce: %s' % is_valid)
|
||||
return is_valid
|
||||
except ValueError: # split() error
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def is_nonce_stale(self, max_age_seconds=600):
|
||||
"""Returns True if a validated nonce is stale. The nonce contains a
|
||||
timestamp in plaintext and also a secure hash of the timestamp. You should
|
||||
first validate the nonce to ensure the plaintext timestamp is not spoofed.
|
||||
"""
|
||||
try:
|
||||
timestamp, hashpart = self.nonce.split(':', 1)
|
||||
if int(timestamp) + max_age_seconds > int(time.time()):
|
||||
return False
|
||||
except ValueError: # int() error
|
||||
pass
|
||||
if self.debug:
|
||||
TRACE("nonce is stale")
|
||||
return True
|
||||
|
||||
|
||||
def HA2(self, entity_body=''):
|
||||
"""Returns the H(A2) string. See :rfc:`2617` section 3.2.2.3."""
|
||||
# RFC 2617 3.2.2.3
|
||||
# If the "qop" directive's value is "auth" or is unspecified, then A2 is:
|
||||
# A2 = method ":" digest-uri-value
|
||||
#
|
||||
# If the "qop" value is "auth-int", then A2 is:
|
||||
# A2 = method ":" digest-uri-value ":" H(entity-body)
|
||||
if self.qop is None or self.qop == "auth":
|
||||
a2 = '%s:%s' % (self.http_method, self.uri)
|
||||
elif self.qop == "auth-int":
|
||||
a2 = "%s:%s:%s" % (self.http_method, self.uri, H(entity_body))
|
||||
else:
|
||||
# in theory, this should never happen, since I validate qop in __init__()
|
||||
raise ValueError(self.errmsg("Unrecognized value for qop!"))
|
||||
return H(a2)
|
||||
|
||||
|
||||
def request_digest(self, ha1, entity_body=''):
|
||||
"""Calculates the Request-Digest. See :rfc:`2617` section 3.2.2.1.
|
||||
|
||||
ha1
|
||||
The HA1 string obtained from the credentials store.
|
||||
|
||||
entity_body
|
||||
If 'qop' is set to 'auth-int', then A2 includes a hash
|
||||
of the "entity body". The entity body is the part of the
|
||||
message which follows the HTTP headers. See :rfc:`2617` section
|
||||
4.3. This refers to the entity the user agent sent in the request which
|
||||
has the Authorization header. Typically GET requests don't have an entity,
|
||||
and POST requests do.
|
||||
|
||||
"""
|
||||
ha2 = self.HA2(entity_body)
|
||||
# Request-Digest -- RFC 2617 3.2.2.1
|
||||
if self.qop:
|
||||
req = "%s:%s:%s:%s:%s" % (self.nonce, self.nc, self.cnonce, self.qop, ha2)
|
||||
else:
|
||||
req = "%s:%s" % (self.nonce, ha2)
|
||||
|
||||
# RFC 2617 3.2.2.2
|
||||
#
|
||||
# If the "algorithm" directive's value is "MD5" or is unspecified, then A1 is:
|
||||
# A1 = unq(username-value) ":" unq(realm-value) ":" passwd
|
||||
#
|
||||
# If the "algorithm" directive's value is "MD5-sess", then A1 is
|
||||
# calculated only once - on the first request by the client following
|
||||
# receipt of a WWW-Authenticate challenge from the server.
|
||||
# A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd )
|
||||
# ":" unq(nonce-value) ":" unq(cnonce-value)
|
||||
if self.algorithm == 'MD5-sess':
|
||||
ha1 = H('%s:%s:%s' % (ha1, self.nonce, self.cnonce))
|
||||
|
||||
digest = H('%s:%s' % (ha1, req))
|
||||
return digest
|
||||
|
||||
|
||||
|
||||
def www_authenticate(realm, key, algorithm='MD5', nonce=None, qop=qop_auth, stale=False):
|
||||
"""Constructs a WWW-Authenticate header for Digest authentication."""
|
||||
if qop not in valid_qops:
|
||||
raise ValueError("Unsupported value for qop: '%s'" % qop)
|
||||
if algorithm not in valid_algorithms:
|
||||
raise ValueError("Unsupported value for algorithm: '%s'" % algorithm)
|
||||
|
||||
if nonce is None:
|
||||
nonce = synthesize_nonce(realm, key)
|
||||
s = 'Digest realm="%s", nonce="%s", algorithm="%s", qop="%s"' % (
|
||||
realm, nonce, algorithm, qop)
|
||||
if stale:
|
||||
s += ', stale="true"'
|
||||
return s
|
||||
|
||||
|
||||
def digest_auth(realm, get_ha1, key, debug=False):
|
||||
"""A CherryPy tool which hooks at before_handler to perform
|
||||
HTTP Digest Access Authentication, as specified in :rfc:`2617`.
|
||||
|
||||
If the request has an 'authorization' header with a 'Digest' scheme, this
|
||||
tool authenticates the credentials supplied in that header. If
|
||||
the request has no 'authorization' header, or if it does but the scheme is
|
||||
not "Digest", or if authentication fails, the tool sends a 401 response with
|
||||
a 'WWW-Authenticate' Digest header.
|
||||
|
||||
realm
|
||||
A string containing the authentication realm.
|
||||
|
||||
get_ha1
|
||||
A callable which looks up a username in a credentials store
|
||||
and returns the HA1 string, which is defined in the RFC to be
|
||||
MD5(username : realm : password). The function's signature is:
|
||||
``get_ha1(realm, username)``
|
||||
where username is obtained from the request's 'authorization' header.
|
||||
If username is not found in the credentials store, get_ha1() returns
|
||||
None.
|
||||
|
||||
key
|
||||
A secret string known only to the server, used in the synthesis of nonces.
|
||||
|
||||
"""
|
||||
request = cherrypy.serving.request
|
||||
|
||||
auth_header = request.headers.get('authorization')
|
||||
nonce_is_stale = False
|
||||
if auth_header is not None:
|
||||
try:
|
||||
auth = HttpDigestAuthorization(auth_header, request.method, debug=debug)
|
||||
except ValueError:
|
||||
raise cherrypy.HTTPError(400, "The Authorization header could not be parsed.")
|
||||
|
||||
if debug:
|
||||
TRACE(str(auth))
|
||||
|
||||
if auth.validate_nonce(realm, key):
|
||||
ha1 = get_ha1(realm, auth.username)
|
||||
if ha1 is not None:
|
||||
# note that for request.body to be available we need to hook in at
|
||||
# before_handler, not on_start_resource like 3.1.x digest_auth does.
|
||||
digest = auth.request_digest(ha1, entity_body=request.body)
|
||||
if digest == auth.response: # authenticated
|
||||
if debug:
|
||||
TRACE("digest matches auth.response")
|
||||
# Now check if nonce is stale.
|
||||
# The choice of ten minutes' lifetime for nonce is somewhat arbitrary
|
||||
nonce_is_stale = auth.is_nonce_stale(max_age_seconds=600)
|
||||
if not nonce_is_stale:
|
||||
request.login = auth.username
|
||||
if debug:
|
||||
TRACE("authentication of %s successful" % auth.username)
|
||||
return
|
||||
|
||||
# Respond with 401 status and a WWW-Authenticate header
|
||||
header = www_authenticate(realm, key, stale=nonce_is_stale)
|
||||
if debug:
|
||||
TRACE(header)
|
||||
cherrypy.serving.response.headers['WWW-Authenticate'] = header
|
||||
raise cherrypy.HTTPError(401, "You are not authorized to access that resource")
|
||||
|
||||
465
python/packages/cherrypy/lib/caching.py
Normal file
465
python/packages/cherrypy/lib/caching.py
Normal file
@@ -0,0 +1,465 @@
|
||||
"""
|
||||
CherryPy implements a simple caching system as a pluggable Tool. This tool tries
|
||||
to be an (in-process) HTTP/1.1-compliant cache. It's not quite there yet, but
|
||||
it's probably good enough for most sites.
|
||||
|
||||
In general, GET responses are cached (along with selecting headers) and, if
|
||||
another request arrives for the same resource, the caching Tool will return 304
|
||||
Not Modified if possible, or serve the cached response otherwise. It also sets
|
||||
request.cached to True if serving a cached representation, and sets
|
||||
request.cacheable to False (so it doesn't get cached again).
|
||||
|
||||
If POST, PUT, or DELETE requests are made for a cached resource, they invalidate
|
||||
(delete) any cached response.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Configuration file example::
|
||||
|
||||
[/]
|
||||
tools.caching.on = True
|
||||
tools.caching.delay = 3600
|
||||
|
||||
You may use a class other than the default
|
||||
:class:`MemoryCache<cherrypy.lib.caching.MemoryCache>` by supplying the config
|
||||
entry ``cache_class``; supply the full dotted name of the replacement class
|
||||
as the config value. It must implement the basic methods ``get``, ``put``,
|
||||
``delete``, and ``clear``.
|
||||
|
||||
You may set any attribute, including overriding methods, on the cache
|
||||
instance by providing them in config. The above sets the
|
||||
:attr:`delay<cherrypy.lib.caching.MemoryCache.delay>` attribute, for example.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
import cherrypy
|
||||
from cherrypy.lib import cptools, httputil
|
||||
from cherrypy._cpcompat import copyitems, ntob, set_daemon, sorted
|
||||
|
||||
|
||||
class Cache(object):
|
||||
"""Base class for Cache implementations."""
|
||||
|
||||
def get(self):
|
||||
"""Return the current variant if in the cache, else None."""
|
||||
raise NotImplemented
|
||||
|
||||
def put(self, obj, size):
|
||||
"""Store the current variant in the cache."""
|
||||
raise NotImplemented
|
||||
|
||||
def delete(self):
|
||||
"""Remove ALL cached variants of the current resource."""
|
||||
raise NotImplemented
|
||||
|
||||
def clear(self):
|
||||
"""Reset the cache to its initial, empty state."""
|
||||
raise NotImplemented
|
||||
|
||||
|
||||
|
||||
# ------------------------------- Memory Cache ------------------------------- #
|
||||
|
||||
|
||||
class AntiStampedeCache(dict):
|
||||
"""A storage system for cached items which reduces stampede collisions."""
|
||||
|
||||
def wait(self, key, timeout=5, debug=False):
|
||||
"""Return the cached value for the given key, or None.
|
||||
|
||||
If timeout is not None, and the value is already
|
||||
being calculated by another thread, wait until the given timeout has
|
||||
elapsed. If the value is available before the timeout expires, it is
|
||||
returned. If not, None is returned, and a sentinel placed in the cache
|
||||
to signal other threads to wait.
|
||||
|
||||
If timeout is None, no waiting is performed nor sentinels used.
|
||||
"""
|
||||
value = self.get(key)
|
||||
if isinstance(value, threading._Event):
|
||||
if timeout is None:
|
||||
# Ignore the other thread and recalc it ourselves.
|
||||
if debug:
|
||||
cherrypy.log('No timeout', 'TOOLS.CACHING')
|
||||
return None
|
||||
|
||||
# Wait until it's done or times out.
|
||||
if debug:
|
||||
cherrypy.log('Waiting up to %s seconds' % timeout, 'TOOLS.CACHING')
|
||||
value.wait(timeout)
|
||||
if value.result is not None:
|
||||
# The other thread finished its calculation. Use it.
|
||||
if debug:
|
||||
cherrypy.log('Result!', 'TOOLS.CACHING')
|
||||
return value.result
|
||||
# Timed out. Stick an Event in the slot so other threads wait
|
||||
# on this one to finish calculating the value.
|
||||
if debug:
|
||||
cherrypy.log('Timed out', 'TOOLS.CACHING')
|
||||
e = threading.Event()
|
||||
e.result = None
|
||||
dict.__setitem__(self, key, e)
|
||||
|
||||
return None
|
||||
elif value is None:
|
||||
# Stick an Event in the slot so other threads wait
|
||||
# on this one to finish calculating the value.
|
||||
if debug:
|
||||
cherrypy.log('Timed out', 'TOOLS.CACHING')
|
||||
e = threading.Event()
|
||||
e.result = None
|
||||
dict.__setitem__(self, key, e)
|
||||
return value
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""Set the cached value for the given key."""
|
||||
existing = self.get(key)
|
||||
dict.__setitem__(self, key, value)
|
||||
if isinstance(existing, threading._Event):
|
||||
# Set Event.result so other threads waiting on it have
|
||||
# immediate access without needing to poll the cache again.
|
||||
existing.result = value
|
||||
existing.set()
|
||||
|
||||
|
||||
class MemoryCache(Cache):
|
||||
"""An in-memory cache for varying response content.
|
||||
|
||||
Each key in self.store is a URI, and each value is an AntiStampedeCache.
|
||||
The response for any given URI may vary based on the values of
|
||||
"selecting request headers"; that is, those named in the Vary
|
||||
response header. We assume the list of header names to be constant
|
||||
for each URI throughout the lifetime of the application, and store
|
||||
that list in ``self.store[uri].selecting_headers``.
|
||||
|
||||
The items contained in ``self.store[uri]`` have keys which are tuples of
|
||||
request header values (in the same order as the names in its
|
||||
selecting_headers), and values which are the actual responses.
|
||||
"""
|
||||
|
||||
maxobjects = 1000
|
||||
"""The maximum number of cached objects; defaults to 1000."""
|
||||
|
||||
maxobj_size = 100000
|
||||
"""The maximum size of each cached object in bytes; defaults to 100 KB."""
|
||||
|
||||
maxsize = 10000000
|
||||
"""The maximum size of the entire cache in bytes; defaults to 10 MB."""
|
||||
|
||||
delay = 600
|
||||
"""Seconds until the cached content expires; defaults to 600 (10 minutes)."""
|
||||
|
||||
antistampede_timeout = 5
|
||||
"""Seconds to wait for other threads to release a cache lock."""
|
||||
|
||||
expire_freq = 0.1
|
||||
"""Seconds to sleep between cache expiration sweeps."""
|
||||
|
||||
debug = False
|
||||
|
||||
def __init__(self):
|
||||
self.clear()
|
||||
|
||||
# Run self.expire_cache in a separate daemon thread.
|
||||
t = threading.Thread(target=self.expire_cache, name='expire_cache')
|
||||
self.expiration_thread = t
|
||||
set_daemon(t, True)
|
||||
t.start()
|
||||
|
||||
def clear(self):
|
||||
"""Reset the cache to its initial, empty state."""
|
||||
self.store = {}
|
||||
self.expirations = {}
|
||||
self.tot_puts = 0
|
||||
self.tot_gets = 0
|
||||
self.tot_hist = 0
|
||||
self.tot_expires = 0
|
||||
self.tot_non_modified = 0
|
||||
self.cursize = 0
|
||||
|
||||
def expire_cache(self):
|
||||
"""Continuously examine cached objects, expiring stale ones.
|
||||
|
||||
This function is designed to be run in its own daemon thread,
|
||||
referenced at ``self.expiration_thread``.
|
||||
"""
|
||||
# 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:
|
||||
now = time.time()
|
||||
# Must make a copy of expirations so it doesn't change size
|
||||
# during iteration
|
||||
for expiration_time, objects in copyitems(self.expirations):
|
||||
if expiration_time <= now:
|
||||
for obj_size, uri, sel_header_values in objects:
|
||||
try:
|
||||
del self.store[uri][tuple(sel_header_values)]
|
||||
self.tot_expires += 1
|
||||
self.cursize -= obj_size
|
||||
except KeyError:
|
||||
# the key may have been deleted elsewhere
|
||||
pass
|
||||
del self.expirations[expiration_time]
|
||||
time.sleep(self.expire_freq)
|
||||
|
||||
def get(self):
|
||||
"""Return the current variant if in the cache, else None."""
|
||||
request = cherrypy.serving.request
|
||||
self.tot_gets += 1
|
||||
|
||||
uri = cherrypy.url(qs=request.query_string)
|
||||
uricache = self.store.get(uri)
|
||||
if uricache is None:
|
||||
return None
|
||||
|
||||
header_values = [request.headers.get(h, '')
|
||||
for h in uricache.selecting_headers]
|
||||
variant = uricache.wait(key=tuple(sorted(header_values)),
|
||||
timeout=self.antistampede_timeout,
|
||||
debug=self.debug)
|
||||
if variant is not None:
|
||||
self.tot_hist += 1
|
||||
return variant
|
||||
|
||||
def put(self, variant, size):
|
||||
"""Store the current variant in the cache."""
|
||||
request = cherrypy.serving.request
|
||||
response = cherrypy.serving.response
|
||||
|
||||
uri = cherrypy.url(qs=request.query_string)
|
||||
uricache = self.store.get(uri)
|
||||
if uricache is None:
|
||||
uricache = AntiStampedeCache()
|
||||
uricache.selecting_headers = [
|
||||
e.value for e in response.headers.elements('Vary')]
|
||||
self.store[uri] = uricache
|
||||
|
||||
if len(self.store) < self.maxobjects:
|
||||
total_size = self.cursize + size
|
||||
|
||||
# checks if there's space for the object
|
||||
if (size < self.maxobj_size and total_size < self.maxsize):
|
||||
# add to the expirations list
|
||||
expiration_time = response.time + self.delay
|
||||
bucket = self.expirations.setdefault(expiration_time, [])
|
||||
bucket.append((size, uri, uricache.selecting_headers))
|
||||
|
||||
# add to the cache
|
||||
header_values = [request.headers.get(h, '')
|
||||
for h in uricache.selecting_headers]
|
||||
uricache[tuple(sorted(header_values))] = variant
|
||||
self.tot_puts += 1
|
||||
self.cursize = total_size
|
||||
|
||||
def delete(self):
|
||||
"""Remove ALL cached variants of the current resource."""
|
||||
uri = cherrypy.url(qs=cherrypy.serving.request.query_string)
|
||||
self.store.pop(uri, None)
|
||||
|
||||
|
||||
def get(invalid_methods=("POST", "PUT", "DELETE"), debug=False, **kwargs):
|
||||
"""Try to obtain cached output. If fresh enough, raise HTTPError(304).
|
||||
|
||||
If POST, PUT, or DELETE:
|
||||
* invalidates (deletes) any cached response for this resource
|
||||
* sets request.cached = False
|
||||
* sets request.cacheable = False
|
||||
|
||||
else if a cached copy exists:
|
||||
* sets request.cached = True
|
||||
* sets request.cacheable = False
|
||||
* sets response.headers to the cached values
|
||||
* checks the cached Last-Modified response header against the
|
||||
current If-(Un)Modified-Since request headers; raises 304
|
||||
if necessary.
|
||||
* sets response.status and response.body to the cached values
|
||||
* returns True
|
||||
|
||||
otherwise:
|
||||
* sets request.cached = False
|
||||
* sets request.cacheable = True
|
||||
* returns False
|
||||
"""
|
||||
request = cherrypy.serving.request
|
||||
response = cherrypy.serving.response
|
||||
|
||||
if not hasattr(cherrypy, "_cache"):
|
||||
# Make a process-wide Cache object.
|
||||
cherrypy._cache = kwargs.pop("cache_class", MemoryCache)()
|
||||
|
||||
# Take all remaining kwargs and set them on the Cache object.
|
||||
for k, v in kwargs.items():
|
||||
setattr(cherrypy._cache, k, v)
|
||||
cherrypy._cache.debug = debug
|
||||
|
||||
# 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 invalid_methods:
|
||||
if debug:
|
||||
cherrypy.log('request.method %r in invalid_methods %r' %
|
||||
(request.method, invalid_methods), 'TOOLS.CACHING')
|
||||
cherrypy._cache.delete()
|
||||
request.cached = False
|
||||
request.cacheable = False
|
||||
return False
|
||||
|
||||
if 'no-cache' in [e.value for e in request.headers.elements('Pragma')]:
|
||||
request.cached = False
|
||||
request.cacheable = True
|
||||
return False
|
||||
|
||||
cache_data = cherrypy._cache.get()
|
||||
request.cached = bool(cache_data)
|
||||
request.cacheable = not request.cached
|
||||
if request.cached:
|
||||
# Serve the cached copy.
|
||||
max_age = cherrypy._cache.delay
|
||||
for v in [e.value for e in request.headers.elements('Cache-Control')]:
|
||||
atoms = v.split('=', 1)
|
||||
directive = atoms.pop(0)
|
||||
if directive == 'max-age':
|
||||
if len(atoms) != 1 or not atoms[0].isdigit():
|
||||
raise cherrypy.HTTPError(400, "Invalid Cache-Control header")
|
||||
max_age = int(atoms[0])
|
||||
break
|
||||
elif directive == 'no-cache':
|
||||
if debug:
|
||||
cherrypy.log('Ignoring cache due to Cache-Control: no-cache',
|
||||
'TOOLS.CACHING')
|
||||
request.cached = False
|
||||
request.cacheable = True
|
||||
return False
|
||||
|
||||
if debug:
|
||||
cherrypy.log('Reading response from cache', 'TOOLS.CACHING')
|
||||
s, h, b, create_time = cache_data
|
||||
age = int(response.time - create_time)
|
||||
if (age > max_age):
|
||||
if debug:
|
||||
cherrypy.log('Ignoring cache due to age > %d' % max_age,
|
||||
'TOOLS.CACHING')
|
||||
request.cached = False
|
||||
request.cacheable = True
|
||||
return False
|
||||
|
||||
# Copy the response headers. See http://www.cherrypy.org/ticket/721.
|
||||
response.headers = rh = httputil.HeaderMap()
|
||||
for k in h:
|
||||
dict.__setitem__(rh, k, dict.__getitem__(h, k))
|
||||
|
||||
# Add the required Age header
|
||||
response.headers["Age"] = str(age)
|
||||
|
||||
try:
|
||||
# Note that validate_since depends on a Last-Modified header;
|
||||
# this was put into the cached copy, and should have been
|
||||
# resurrected just above (response.headers = cache_data[1]).
|
||||
cptools.validate_since()
|
||||
except cherrypy.HTTPRedirect:
|
||||
x = sys.exc_info()[1]
|
||||
if x.status == 304:
|
||||
cherrypy._cache.tot_non_modified += 1
|
||||
raise
|
||||
|
||||
# serve it & get out from the request
|
||||
response.status = s
|
||||
response.body = b
|
||||
else:
|
||||
if debug:
|
||||
cherrypy.log('request is not cached', 'TOOLS.CACHING')
|
||||
return request.cached
|
||||
|
||||
|
||||
def tee_output():
|
||||
"""Tee response output to cache storage. Internal."""
|
||||
# Used by CachingTool by attaching to request.hooks
|
||||
|
||||
request = cherrypy.serving.request
|
||||
if 'no-store' in request.headers.values('Cache-Control'):
|
||||
return
|
||||
|
||||
def tee(body):
|
||||
"""Tee response.body into a list."""
|
||||
if ('no-cache' in response.headers.values('Pragma') or
|
||||
'no-store' in response.headers.values('Cache-Control')):
|
||||
for chunk in body:
|
||||
yield chunk
|
||||
return
|
||||
|
||||
output = []
|
||||
for chunk in body:
|
||||
output.append(chunk)
|
||||
yield chunk
|
||||
|
||||
# save the cache data
|
||||
body = ntob('').join(output)
|
||||
cherrypy._cache.put((response.status, response.headers or {},
|
||||
body, response.time), len(body))
|
||||
|
||||
response = cherrypy.serving.response
|
||||
response.body = tee(response.body)
|
||||
|
||||
|
||||
def expires(secs=0, force=False, debug=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 'Expires' header is set one year in the past, and
|
||||
the following "cache prevention" headers are also set:
|
||||
|
||||
* Pragma: no-cache
|
||||
* Cache-Control': no-cache, must-revalidate
|
||||
|
||||
force
|
||||
If False, 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.serving.response
|
||||
headers = response.headers
|
||||
|
||||
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 headers:
|
||||
cacheable = True
|
||||
break
|
||||
|
||||
if not cacheable and not force:
|
||||
if debug:
|
||||
cherrypy.log('request is not cacheable', 'TOOLS.EXPIRES')
|
||||
else:
|
||||
if debug:
|
||||
cherrypy.log('request is cacheable', 'TOOLS.EXPIRES')
|
||||
if isinstance(secs, datetime.timedelta):
|
||||
secs = (86400 * secs.days) + secs.seconds
|
||||
|
||||
if secs == 0:
|
||||
if force or ("Pragma" not in headers):
|
||||
headers["Pragma"] = "no-cache"
|
||||
if cherrypy.serving.request.protocol >= (1, 1):
|
||||
if force or "Cache-Control" not in headers:
|
||||
headers["Cache-Control"] = "no-cache, must-revalidate"
|
||||
# Set an explicit Expires date in the past.
|
||||
expiry = httputil.HTTPDate(1169942400.0)
|
||||
else:
|
||||
expiry = httputil.HTTPDate(response.time + secs)
|
||||
if force or "Expires" not in headers:
|
||||
headers["Expires"] = expiry
|
||||
365
python/packages/cherrypy/lib/covercp.py
Normal file
365
python/packages/cherrypy/lib/covercp.py
Normal file
@@ -0,0 +1,365 @@
|
||||
"""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>`_
|
||||
|
||||
To turn on coverage tracing, use the following code::
|
||||
|
||||
cherrypy.engine.subscribe('start', covercp.start)
|
||||
|
||||
DO NOT subscribe anything on the 'start_thread' channel, as previously
|
||||
recommended. Calling start once in the main thread should be sufficient
|
||||
to start coverage on all threads. Calling start again in each thread
|
||||
effectively clears any coverage data gathered up to that point.
|
||||
|
||||
Run your code, then use the ``covercp.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
|
||||
from cherrypy._cpcompat import quote_plus
|
||||
import os, os.path
|
||||
localFile = os.path.join(os.path.dirname(__file__), "coverage.cache")
|
||||
|
||||
the_coverage = None
|
||||
try:
|
||||
from coverage import coverage
|
||||
the_coverage = coverage(data_file=localFile)
|
||||
def start():
|
||||
the_coverage.start()
|
||||
except ImportError:
|
||||
# Setting the_coverage to None will raise errors
|
||||
# that need to be trapped downstream.
|
||||
the_coverage = None
|
||||
|
||||
import warnings
|
||||
warnings.warn("No code coverage will be performed; coverage.py could not be imported.")
|
||||
|
||||
def start():
|
||||
pass
|
||||
start.priority = 20
|
||||
|
||||
TEMPLATE_MENU = """<html>
|
||||
<head>
|
||||
<title>CherryPy Coverage Menu</title>
|
||||
<style>
|
||||
body {font: 9pt Arial, serif;}
|
||||
#tree {
|
||||
font-size: 8pt;
|
||||
font-family: Andale Mono, monospace;
|
||||
white-space: pre;
|
||||
}
|
||||
#tree a:active, a:focus {
|
||||
background-color: black;
|
||||
padding: 1px;
|
||||
color: white;
|
||||
border: 0px solid #9999FF;
|
||||
-moz-outline-style: none;
|
||||
}
|
||||
.fail { color: red;}
|
||||
.pass { color: #888;}
|
||||
#pct { text-align: right;}
|
||||
h3 {
|
||||
font-size: small;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
margin-top: 5px;
|
||||
}
|
||||
input { border: 1px solid #ccc; padding: 2px; }
|
||||
.directory {
|
||||
color: #933;
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
font-size: 10pt;
|
||||
}
|
||||
.file {
|
||||
color: #400;
|
||||
}
|
||||
a { text-decoration: none; }
|
||||
#crumbs {
|
||||
color: white;
|
||||
font-size: 8pt;
|
||||
font-family: Andale Mono, monospace;
|
||||
width: 100%;
|
||||
background-color: black;
|
||||
}
|
||||
#crumbs a {
|
||||
color: #f88;
|
||||
}
|
||||
#options {
|
||||
line-height: 2.3em;
|
||||
border: 1px solid black;
|
||||
background-color: #eee;
|
||||
padding: 4px;
|
||||
}
|
||||
#exclude {
|
||||
width: 100%;
|
||||
margin-bottom: 3px;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
#submit {
|
||||
background-color: black;
|
||||
color: white;
|
||||
border: 0;
|
||||
margin-bottom: -9px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>CherryPy Coverage</h2>"""
|
||||
|
||||
TEMPLATE_FORM = """
|
||||
<div id="options">
|
||||
<form action='menu' method=GET>
|
||||
<input type='hidden' name='base' value='%(base)s' />
|
||||
Show percentages <input type='checkbox' %(showpct)s name='showpct' value='checked' /><br />
|
||||
Hide files over <input type='text' id='pct' name='pct' value='%(pct)s' size='3' />%%<br />
|
||||
Exclude files matching<br />
|
||||
<input type='text' id='exclude' name='exclude' value='%(exclude)s' size='20' />
|
||||
<br />
|
||||
|
||||
<input type='submit' value='Change view' id="submit"/>
|
||||
</form>
|
||||
</div>"""
|
||||
|
||||
TEMPLATE_FRAMESET = """<html>
|
||||
<head><title>CherryPy coverage data</title></head>
|
||||
<frameset cols='250, 1*'>
|
||||
<frame src='menu?base=%s' />
|
||||
<frame name='main' src='' />
|
||||
</frameset>
|
||||
</html>
|
||||
"""
|
||||
|
||||
TEMPLATE_COVERAGE = """<html>
|
||||
<head>
|
||||
<title>Coverage for %(name)s</title>
|
||||
<style>
|
||||
h2 { margin-bottom: .25em; }
|
||||
p { margin: .25em; }
|
||||
.covered { color: #000; background-color: #fff; }
|
||||
.notcovered { color: #fee; background-color: #500; }
|
||||
.excluded { color: #00f; background-color: #fff; }
|
||||
table .covered, table .notcovered, table .excluded
|
||||
{ font-family: Andale Mono, monospace;
|
||||
font-size: 10pt; white-space: pre; }
|
||||
|
||||
.lineno { background-color: #eee;}
|
||||
.notcovered .lineno { background-color: #000;}
|
||||
table { border-collapse: collapse;
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>%(name)s</h2>
|
||||
<p>%(fullpath)s</p>
|
||||
<p>Coverage: %(pc)s%%</p>"""
|
||||
|
||||
TEMPLATE_LOC_COVERED = """<tr class="covered">
|
||||
<td class="lineno">%s </td>
|
||||
<td>%s</td>
|
||||
</tr>\n"""
|
||||
TEMPLATE_LOC_NOT_COVERED = """<tr class="notcovered">
|
||||
<td class="lineno">%s </td>
|
||||
<td>%s</td>
|
||||
</tr>\n"""
|
||||
TEMPLATE_LOC_EXCLUDED = """<tr class="excluded">
|
||||
<td class="lineno">%s </td>
|
||||
<td>%s</td>
|
||||
</tr>\n"""
|
||||
|
||||
TEMPLATE_ITEM = "%s%s<a class='file' href='report?name=%s' target='main'>%s</a>\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="",
|
||||
coverage=the_coverage):
|
||||
|
||||
# Show the directory name and any of our children
|
||||
dirs = [k for k, v in root.items() if v]
|
||||
dirs.sort()
|
||||
for name in dirs:
|
||||
newpath = os.path.join(path, name)
|
||||
|
||||
if newpath.lower().startswith(base):
|
||||
relpath = newpath[len(base):]
|
||||
yield "| " * relpath.count(os.sep)
|
||||
yield "<a class='directory' href='menu?base=%s&exclude=%s'>%s</a>\n" % \
|
||||
(newpath, quote_plus(exclude), name)
|
||||
|
||||
for chunk in _show_branch(root[name], base, newpath, pct, showpct, exclude, coverage=coverage):
|
||||
yield chunk
|
||||
|
||||
# Now list the files
|
||||
if path.lower().startswith(base):
|
||||
relpath = path[len(base):]
|
||||
files = [k for k, v in root.items() 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 = "<span class='fail'>%s</span>" % pc_str
|
||||
else:
|
||||
pc_str = "<span class='pass'>%s</span>" % 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, coverage=the_coverage):
|
||||
"""Return covered module names as a nested dict."""
|
||||
tree = {}
|
||||
runs = coverage.data.executed_files()
|
||||
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 __init__(self, coverage, root=None):
|
||||
self.coverage = coverage
|
||||
if root is None:
|
||||
# Guess initial depth. Files outside this path will not be
|
||||
# reachable from the web interface.
|
||||
import cherrypy
|
||||
root = os.path.dirname(cherrypy.__file__)
|
||||
self.root = root
|
||||
|
||||
def index(self):
|
||||
return TEMPLATE_FRAMESET % self.root.lower()
|
||||
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 "<div id='crumbs'>"
|
||||
path = ""
|
||||
atoms = base.split(os.sep)
|
||||
atoms.pop()
|
||||
for atom in atoms:
|
||||
path += atom + os.sep
|
||||
yield ("<a href='menu?base=%s&exclude=%s'>%s</a> %s"
|
||||
% (path, quote_plus(exclude), atom, os.sep))
|
||||
yield "</div>"
|
||||
|
||||
yield "<div id='tree'>"
|
||||
|
||||
# Then display the tree
|
||||
tree = get_tree(base, exclude, self.coverage)
|
||||
if not tree:
|
||||
yield "<p>No modules covered.</p>"
|
||||
else:
|
||||
for chunk in _show_branch(tree, base, "/", pct,
|
||||
showpct=='checked', exclude, coverage=self.coverage):
|
||||
yield chunk
|
||||
|
||||
yield "</div>"
|
||||
yield "</body></html>"
|
||||
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):
|
||||
filename, statements, excluded, missing, _ = self.coverage.analysis2(name)
|
||||
pc = _percent(statements, missing)
|
||||
yield TEMPLATE_COVERAGE % dict(name=os.path.basename(name),
|
||||
fullpath=name,
|
||||
pc=pc)
|
||||
yield '<table>\n'
|
||||
for line in self.annotated_file(filename, statements, excluded,
|
||||
missing):
|
||||
yield line
|
||||
yield '</table>'
|
||||
yield '</body>'
|
||||
yield '</html>'
|
||||
report.exposed = True
|
||||
|
||||
|
||||
def serve(path=localFile, port=8080, root=None):
|
||||
if coverage is None:
|
||||
raise ImportError("The coverage module could not be imported.")
|
||||
from coverage import coverage
|
||||
cov = coverage(data_file = path)
|
||||
cov.load()
|
||||
|
||||
import cherrypy
|
||||
cherrypy.config.update({'server.socket_port': int(port),
|
||||
'server.thread_pool': 10,
|
||||
'environment': "production",
|
||||
})
|
||||
cherrypy.quickstart(CoverStats(cov, root))
|
||||
|
||||
if __name__ == "__main__":
|
||||
serve(*tuple(sys.argv[1:]))
|
||||
|
||||
662
python/packages/cherrypy/lib/cpstats.py
Normal file
662
python/packages/cherrypy/lib/cpstats.py
Normal file
File diff suppressed because it is too large
Load Diff
617
python/packages/cherrypy/lib/cptools.py
Normal file
617
python/packages/cherrypy/lib/cptools.py
Normal file
File diff suppressed because it is too large
Load Diff
388
python/packages/cherrypy/lib/encoding.py
Normal file
388
python/packages/cherrypy/lib/encoding.py
Normal file
@@ -0,0 +1,388 @@
|
||||
import struct
|
||||
import time
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import basestring, BytesIO, ntob, set, unicodestr
|
||||
from cherrypy.lib import file_generator
|
||||
from cherrypy.lib import set_vary_header
|
||||
|
||||
|
||||
def decode(encoding=None, default_encoding='utf-8'):
|
||||
"""Replace or extend the list of charsets used to decode a request entity.
|
||||
|
||||
Either argument may be a single string or a list of strings.
|
||||
|
||||
encoding
|
||||
If not None, restricts the set of charsets attempted while decoding
|
||||
a request entity to the given set (even if a different charset is given in
|
||||
the Content-Type request header).
|
||||
|
||||
default_encoding
|
||||
Only in effect if the 'encoding' argument is not given.
|
||||
If given, the set of charsets attempted while decoding a request entity is
|
||||
*extended* with the given value(s).
|
||||
|
||||
"""
|
||||
body = cherrypy.request.body
|
||||
if encoding is not None:
|
||||
if not isinstance(encoding, list):
|
||||
encoding = [encoding]
|
||||
body.attempt_charsets = encoding
|
||||
elif default_encoding:
|
||||
if not isinstance(default_encoding, list):
|
||||
default_encoding = [default_encoding]
|
||||
body.attempt_charsets = body.attempt_charsets + default_encoding
|
||||
|
||||
|
||||
class ResponseEncoder:
|
||||
|
||||
default_encoding = 'utf-8'
|
||||
failmsg = "Response body could not be encoded with %r."
|
||||
encoding = None
|
||||
errors = 'strict'
|
||||
text_only = True
|
||||
add_charset = True
|
||||
debug = False
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
for k, v in kwargs.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
self.attempted_charsets = set()
|
||||
request = cherrypy.serving.request
|
||||
if request.handler is not None:
|
||||
# Replace request.handler with self
|
||||
if self.debug:
|
||||
cherrypy.log('Replacing request.handler', 'TOOLS.ENCODE')
|
||||
self.oldhandler = request.handler
|
||||
request.handler = self
|
||||
|
||||
def encode_stream(self, encoding):
|
||||
"""Encode a streaming response body.
|
||||
|
||||
Use a generator wrapper, and just pray it works as the stream is
|
||||
being written out.
|
||||
"""
|
||||
if encoding in self.attempted_charsets:
|
||||
return False
|
||||
self.attempted_charsets.add(encoding)
|
||||
|
||||
def encoder(body):
|
||||
for chunk in body:
|
||||
if isinstance(chunk, unicodestr):
|
||||
chunk = chunk.encode(encoding, self.errors)
|
||||
yield chunk
|
||||
self.body = encoder(self.body)
|
||||
return True
|
||||
|
||||
def encode_string(self, encoding):
|
||||
"""Encode a buffered response body."""
|
||||
if encoding in self.attempted_charsets:
|
||||
return False
|
||||
self.attempted_charsets.add(encoding)
|
||||
|
||||
try:
|
||||
body = []
|
||||
for chunk in self.body:
|
||||
if isinstance(chunk, unicodestr):
|
||||
chunk = chunk.encode(encoding, self.errors)
|
||||
body.append(chunk)
|
||||
self.body = body
|
||||
except (LookupError, UnicodeError):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def find_acceptable_charset(self):
|
||||
request = cherrypy.serving.request
|
||||
response = cherrypy.serving.response
|
||||
|
||||
if self.debug:
|
||||
cherrypy.log('response.stream %r' % response.stream, 'TOOLS.ENCODE')
|
||||
if response.stream:
|
||||
encoder = self.encode_stream
|
||||
else:
|
||||
encoder = self.encode_string
|
||||
if "Content-Length" in response.headers:
|
||||
# Delete Content-Length header so finalize() recalcs it.
|
||||
# Encoded strings may be of different lengths from their
|
||||
# unicode equivalents, and even from each other. For example:
|
||||
# >>> t = u"\u7007\u3040"
|
||||
# >>> len(t)
|
||||
# 2
|
||||
# >>> len(t.encode("UTF-8"))
|
||||
# 6
|
||||
# >>> len(t.encode("utf7"))
|
||||
# 8
|
||||
del response.headers["Content-Length"]
|
||||
|
||||
# Parse the Accept-Charset request header, and try to provide one
|
||||
# of the requested charsets (in order of user preference).
|
||||
encs = request.headers.elements('Accept-Charset')
|
||||
charsets = [enc.value.lower() for enc in encs]
|
||||
if self.debug:
|
||||
cherrypy.log('charsets %s' % repr(charsets), 'TOOLS.ENCODE')
|
||||
|
||||
if self.encoding is not None:
|
||||
# If specified, force this encoding to be used, or fail.
|
||||
encoding = self.encoding.lower()
|
||||
if self.debug:
|
||||
cherrypy.log('Specified encoding %r' % encoding, 'TOOLS.ENCODE')
|
||||
if (not charsets) or "*" in charsets or encoding in charsets:
|
||||
if self.debug:
|
||||
cherrypy.log('Attempting encoding %r' % encoding, 'TOOLS.ENCODE')
|
||||
if encoder(encoding):
|
||||
return encoding
|
||||
else:
|
||||
if not encs:
|
||||
if self.debug:
|
||||
cherrypy.log('Attempting default encoding %r' %
|
||||
self.default_encoding, 'TOOLS.ENCODE')
|
||||
# Any character-set is acceptable.
|
||||
if encoder(self.default_encoding):
|
||||
return self.default_encoding
|
||||
else:
|
||||
raise cherrypy.HTTPError(500, self.failmsg % self.default_encoding)
|
||||
else:
|
||||
for element in encs:
|
||||
if element.qvalue > 0:
|
||||
if element.value == "*":
|
||||
# Matches any charset. Try our default.
|
||||
if self.debug:
|
||||
cherrypy.log('Attempting default encoding due '
|
||||
'to %r' % element, 'TOOLS.ENCODE')
|
||||
if encoder(self.default_encoding):
|
||||
return self.default_encoding
|
||||
else:
|
||||
encoding = element.value
|
||||
if self.debug:
|
||||
cherrypy.log('Attempting encoding %s (qvalue >'
|
||||
'0)' % element, 'TOOLS.ENCODE')
|
||||
if encoder(encoding):
|
||||
return encoding
|
||||
|
||||
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:
|
||||
if self.debug:
|
||||
cherrypy.log('Attempting ISO-8859-1 encoding',
|
||||
'TOOLS.ENCODE')
|
||||
if encoder(iso):
|
||||
return iso
|
||||
|
||||
# No suitable encoding found.
|
||||
ac = 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(self.attempted_charsets)
|
||||
raise cherrypy.HTTPError(406, msg)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
response = cherrypy.serving.response
|
||||
self.body = self.oldhandler(*args, **kwargs)
|
||||
|
||||
if isinstance(self.body, basestring):
|
||||
# strings get wrapped in a list because iterating over a single
|
||||
# item list is much faster than iterating over every character
|
||||
# in a long string.
|
||||
if self.body:
|
||||
self.body = [self.body]
|
||||
else:
|
||||
# [''] doesn't evaluate to False, so replace it with [].
|
||||
self.body = []
|
||||
elif hasattr(self.body, 'read'):
|
||||
self.body = file_generator(self.body)
|
||||
elif self.body is None:
|
||||
self.body = []
|
||||
|
||||
ct = response.headers.elements("Content-Type")
|
||||
if self.debug:
|
||||
cherrypy.log('Content-Type: %r' % [str(h) for h in ct], 'TOOLS.ENCODE')
|
||||
if ct:
|
||||
ct = ct[0]
|
||||
if self.text_only:
|
||||
if ct.value.lower().startswith("text/"):
|
||||
if self.debug:
|
||||
cherrypy.log('Content-Type %s starts with "text/"' % ct,
|
||||
'TOOLS.ENCODE')
|
||||
do_find = True
|
||||
else:
|
||||
if self.debug:
|
||||
cherrypy.log('Not finding because Content-Type %s does '
|
||||
'not start with "text/"' % ct,
|
||||
'TOOLS.ENCODE')
|
||||
do_find = False
|
||||
else:
|
||||
if self.debug:
|
||||
cherrypy.log('Finding because not text_only', 'TOOLS.ENCODE')
|
||||
do_find = True
|
||||
|
||||
if do_find:
|
||||
# Set "charset=..." param on response Content-Type header
|
||||
ct.params['charset'] = self.find_acceptable_charset()
|
||||
if self.add_charset:
|
||||
if self.debug:
|
||||
cherrypy.log('Setting Content-Type %s' % ct,
|
||||
'TOOLS.ENCODE')
|
||||
response.headers["Content-Type"] = str(ct)
|
||||
|
||||
return self.body
|
||||
|
||||
# GZIP
|
||||
|
||||
def compress(body, compress_level):
|
||||
"""Compress 'body' at the given compress_level."""
|
||||
import zlib
|
||||
|
||||
# See http://www.gzip.org/zlib/rfc-gzip.html
|
||||
yield ntob('\x1f\x8b') # ID1 and ID2: gzip marker
|
||||
yield ntob('\x08') # CM: compression method
|
||||
yield ntob('\x00') # FLG: none set
|
||||
# MTIME: 4 bytes
|
||||
yield struct.pack("<L", int(time.time()) & int('FFFFFFFF', 16))
|
||||
yield ntob('\x02') # XFL: max compression, slowest algo
|
||||
yield ntob('\xff') # OS: unknown
|
||||
|
||||
crc = zlib.crc32(ntob(""))
|
||||
size = 0
|
||||
zobj = zlib.compressobj(compress_level,
|
||||
zlib.DEFLATED, -zlib.MAX_WBITS,
|
||||
zlib.DEF_MEM_LEVEL, 0)
|
||||
for line in body:
|
||||
size += len(line)
|
||||
crc = zlib.crc32(line, crc)
|
||||
yield zobj.compress(line)
|
||||
yield zobj.flush()
|
||||
|
||||
# CRC32: 4 bytes
|
||||
yield struct.pack("<L", crc & int('FFFFFFFF', 16))
|
||||
# ISIZE: 4 bytes
|
||||
yield struct.pack("<L", size & int('FFFFFFFF', 16))
|
||||
|
||||
def decompress(body):
|
||||
import gzip
|
||||
|
||||
zbuf = BytesIO()
|
||||
zbuf.write(body)
|
||||
zbuf.seek(0)
|
||||
zfile = gzip.GzipFile(mode='rb', fileobj=zbuf)
|
||||
data = zfile.read()
|
||||
zfile.close()
|
||||
return data
|
||||
|
||||
|
||||
def gzip(compress_level=5, mime_types=['text/html', 'text/plain'], debug=False):
|
||||
"""Try to gzip the response body if Content-Type in mime_types.
|
||||
|
||||
cherrypy.response.headers['Content-Type'] must be set to one of the
|
||||
values in the mime_types arg before calling this function.
|
||||
|
||||
The provided list of mime-types must be of one of the following form:
|
||||
* type/subtype
|
||||
* type/*
|
||||
* type/*+subtype
|
||||
|
||||
No compression is performed if any of the following hold:
|
||||
* The client sends no Accept-Encoding request header
|
||||
* No 'gzip' or 'x-gzip' is present in the Accept-Encoding header
|
||||
* No 'gzip' or 'x-gzip' with a qvalue > 0 is present
|
||||
* The 'identity' value is given with a qvalue > 0.
|
||||
|
||||
"""
|
||||
request = cherrypy.serving.request
|
||||
response = cherrypy.serving.response
|
||||
|
||||
set_vary_header(response, "Accept-Encoding")
|
||||
|
||||
if not response.body:
|
||||
# Response body is empty (might be a 304 for instance)
|
||||
if debug:
|
||||
cherrypy.log('No response body', context='TOOLS.GZIP')
|
||||
return
|
||||
|
||||
# If returning cached content (which should already have been gzipped),
|
||||
# don't re-zip.
|
||||
if getattr(request, "cached", False):
|
||||
if debug:
|
||||
cherrypy.log('Not gzipping cached response', context='TOOLS.GZIP')
|
||||
return
|
||||
|
||||
acceptable = 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.
|
||||
if debug:
|
||||
cherrypy.log('No Accept-Encoding', context='TOOLS.GZIP')
|
||||
return
|
||||
|
||||
ct = response.headers.get('Content-Type', '').split(';')[0]
|
||||
for coding in acceptable:
|
||||
if coding.value == 'identity' and coding.qvalue != 0:
|
||||
if debug:
|
||||
cherrypy.log('Non-zero identity qvalue: %s' % coding,
|
||||
context='TOOLS.GZIP')
|
||||
return
|
||||
if coding.value in ('gzip', 'x-gzip'):
|
||||
if coding.qvalue == 0:
|
||||
if debug:
|
||||
cherrypy.log('Zero gzip qvalue: %s' % coding,
|
||||
context='TOOLS.GZIP')
|
||||
return
|
||||
|
||||
if ct not in mime_types:
|
||||
# If the list of provided mime-types contains tokens
|
||||
# such as 'text/*' or 'application/*+xml',
|
||||
# we go through them and find the most appropriate one
|
||||
# based on the given content-type.
|
||||
# The pattern matching is only caring about the most
|
||||
# common cases, as stated above, and doesn't support
|
||||
# for extra parameters.
|
||||
found = False
|
||||
if '/' in ct:
|
||||
ct_media_type, ct_sub_type = ct.split('/')
|
||||
for mime_type in mime_types:
|
||||
if '/' in mime_type:
|
||||
media_type, sub_type = mime_type.split('/')
|
||||
if ct_media_type == media_type:
|
||||
if sub_type == '*':
|
||||
found = True
|
||||
break
|
||||
elif '+' in sub_type and '+' in ct_sub_type:
|
||||
ct_left, ct_right = ct_sub_type.split('+')
|
||||
left, right = sub_type.split('+')
|
||||
if left == '*' and ct_right == right:
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
if debug:
|
||||
cherrypy.log('Content-Type %s not in mime_types %r' %
|
||||
(ct, mime_types), context='TOOLS.GZIP')
|
||||
return
|
||||
|
||||
if debug:
|
||||
cherrypy.log('Gzipping', context='TOOLS.GZIP')
|
||||
# Return a generator that compresses the page
|
||||
response.headers['Content-Encoding'] = 'gzip'
|
||||
response.body = compress(response.body, compress_level)
|
||||
if "Content-Length" in response.headers:
|
||||
# Delete Content-Length header so finalize() recalcs it.
|
||||
del response.headers["Content-Length"]
|
||||
|
||||
return
|
||||
|
||||
if debug:
|
||||
cherrypy.log('No acceptable encoding found.', context='GZIP')
|
||||
cherrypy.HTTPError(406, "identity, gzip").set_response()
|
||||
|
||||
214
python/packages/cherrypy/lib/gctools.py
Normal file
214
python/packages/cherrypy/lib/gctools.py
Normal file
@@ -0,0 +1,214 @@
|
||||
import gc
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
try:
|
||||
import objgraph
|
||||
except ImportError:
|
||||
objgraph = None
|
||||
|
||||
import cherrypy
|
||||
from cherrypy import _cprequest, _cpwsgi
|
||||
from cherrypy.process.plugins import SimplePlugin
|
||||
|
||||
|
||||
class ReferrerTree(object):
|
||||
"""An object which gathers all referrers of an object to a given depth."""
|
||||
|
||||
peek_length = 40
|
||||
|
||||
def __init__(self, ignore=None, maxdepth=2, maxparents=10):
|
||||
self.ignore = ignore or []
|
||||
self.ignore.append(inspect.currentframe().f_back)
|
||||
self.maxdepth = maxdepth
|
||||
self.maxparents = maxparents
|
||||
|
||||
def ascend(self, obj, depth=1):
|
||||
"""Return a nested list containing referrers of the given object."""
|
||||
depth += 1
|
||||
parents = []
|
||||
|
||||
# Gather all referrers in one step to minimize
|
||||
# cascading references due to repr() logic.
|
||||
refs = gc.get_referrers(obj)
|
||||
self.ignore.append(refs)
|
||||
if len(refs) > self.maxparents:
|
||||
return [("[%s referrers]" % len(refs), [])]
|
||||
|
||||
try:
|
||||
ascendcode = self.ascend.__code__
|
||||
except AttributeError:
|
||||
ascendcode = self.ascend.im_func.func_code
|
||||
for parent in refs:
|
||||
if inspect.isframe(parent) and parent.f_code is ascendcode:
|
||||
continue
|
||||
if parent in self.ignore:
|
||||
continue
|
||||
if depth <= self.maxdepth:
|
||||
parents.append((parent, self.ascend(parent, depth)))
|
||||
else:
|
||||
parents.append((parent, []))
|
||||
|
||||
return parents
|
||||
|
||||
def peek(self, s):
|
||||
"""Return s, restricted to a sane length."""
|
||||
if len(s) > (self.peek_length + 3):
|
||||
half = self.peek_length // 2
|
||||
return s[:half] + '...' + s[-half:]
|
||||
else:
|
||||
return s
|
||||
|
||||
def _format(self, obj, descend=True):
|
||||
"""Return a string representation of a single object."""
|
||||
if inspect.isframe(obj):
|
||||
filename, lineno, func, context, index = inspect.getframeinfo(obj)
|
||||
return "<frame of function '%s'>" % func
|
||||
|
||||
if not descend:
|
||||
return self.peek(repr(obj))
|
||||
|
||||
if isinstance(obj, dict):
|
||||
return "{" + ", ".join(["%s: %s" % (self._format(k, descend=False),
|
||||
self._format(v, descend=False))
|
||||
for k, v in obj.items()]) + "}"
|
||||
elif isinstance(obj, list):
|
||||
return "[" + ", ".join([self._format(item, descend=False)
|
||||
for item in obj]) + "]"
|
||||
elif isinstance(obj, tuple):
|
||||
return "(" + ", ".join([self._format(item, descend=False)
|
||||
for item in obj]) + ")"
|
||||
|
||||
r = self.peek(repr(obj))
|
||||
if isinstance(obj, (str, int, float)):
|
||||
return r
|
||||
return "%s: %s" % (type(obj), r)
|
||||
|
||||
def format(self, tree):
|
||||
"""Return a list of string reprs from a nested list of referrers."""
|
||||
output = []
|
||||
def ascend(branch, depth=1):
|
||||
for parent, grandparents in branch:
|
||||
output.append((" " * depth) + self._format(parent))
|
||||
if grandparents:
|
||||
ascend(grandparents, depth + 1)
|
||||
ascend(tree)
|
||||
return output
|
||||
|
||||
|
||||
def get_instances(cls):
|
||||
return [x for x in gc.get_objects() if isinstance(x, cls)]
|
||||
|
||||
|
||||
class RequestCounter(SimplePlugin):
|
||||
|
||||
def start(self):
|
||||
self.count = 0
|
||||
|
||||
def before_request(self):
|
||||
self.count += 1
|
||||
|
||||
def after_request(self):
|
||||
self.count -=1
|
||||
request_counter = RequestCounter(cherrypy.engine)
|
||||
request_counter.subscribe()
|
||||
|
||||
|
||||
def get_context(obj):
|
||||
if isinstance(obj, _cprequest.Request):
|
||||
return "path=%s;stage=%s" % (obj.path_info, obj.stage)
|
||||
elif isinstance(obj, _cprequest.Response):
|
||||
return "status=%s" % obj.status
|
||||
elif isinstance(obj, _cpwsgi.AppResponse):
|
||||
return "PATH_INFO=%s" % obj.environ.get('PATH_INFO', '')
|
||||
elif hasattr(obj, "tb_lineno"):
|
||||
return "tb_lineno=%s" % obj.tb_lineno
|
||||
return ""
|
||||
|
||||
|
||||
class GCRoot(object):
|
||||
"""A CherryPy page handler for testing reference leaks."""
|
||||
|
||||
classes = [(_cprequest.Request, 2, 2,
|
||||
"Should be 1 in this request thread and 1 in the main thread."),
|
||||
(_cprequest.Response, 2, 2,
|
||||
"Should be 1 in this request thread and 1 in the main thread."),
|
||||
(_cpwsgi.AppResponse, 1, 1,
|
||||
"Should be 1 in this request thread only."),
|
||||
]
|
||||
|
||||
def index(self):
|
||||
return "Hello, world!"
|
||||
index.exposed = True
|
||||
|
||||
def stats(self):
|
||||
output = ["Statistics:"]
|
||||
|
||||
for trial in range(10):
|
||||
if request_counter.count > 0:
|
||||
break
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
output.append("\nNot all requests closed properly.")
|
||||
|
||||
# gc_collect isn't perfectly synchronous, because it may
|
||||
# break reference cycles that then take time to fully
|
||||
# finalize. Call it thrice and hope for the best.
|
||||
gc.collect()
|
||||
gc.collect()
|
||||
unreachable = gc.collect()
|
||||
if unreachable:
|
||||
if objgraph is not None:
|
||||
final = objgraph.by_type('Nondestructible')
|
||||
if final:
|
||||
objgraph.show_backrefs(final, filename='finalizers.png')
|
||||
|
||||
trash = {}
|
||||
for x in gc.garbage:
|
||||
trash[type(x)] = trash.get(type(x), 0) + 1
|
||||
if trash:
|
||||
output.insert(0, "\n%s unreachable objects:" % unreachable)
|
||||
trash = [(v, k) for k, v in trash.items()]
|
||||
trash.sort()
|
||||
for pair in trash:
|
||||
output.append(" " + repr(pair))
|
||||
|
||||
# Check declared classes to verify uncollected instances.
|
||||
# These don't have to be part of a cycle; they can be
|
||||
# any objects that have unanticipated referrers that keep
|
||||
# them from being collected.
|
||||
allobjs = {}
|
||||
for cls, minobj, maxobj, msg in self.classes:
|
||||
allobjs[cls] = get_instances(cls)
|
||||
|
||||
for cls, minobj, maxobj, msg in self.classes:
|
||||
objs = allobjs[cls]
|
||||
lenobj = len(objs)
|
||||
if lenobj < minobj or lenobj > maxobj:
|
||||
if minobj == maxobj:
|
||||
output.append(
|
||||
"\nExpected %s %r references, got %s." %
|
||||
(minobj, cls, lenobj))
|
||||
else:
|
||||
output.append(
|
||||
"\nExpected %s to %s %r references, got %s." %
|
||||
(minobj, maxobj, cls, lenobj))
|
||||
|
||||
for obj in objs:
|
||||
if objgraph is not None:
|
||||
ig = [id(objs), id(inspect.currentframe())]
|
||||
fname = "graph_%s_%s.png" % (cls.__name__, id(obj))
|
||||
objgraph.show_backrefs(
|
||||
obj, extra_ignore=ig, max_depth=4, too_many=20,
|
||||
filename=fname, extra_info=get_context)
|
||||
output.append("\nReferrers for %s (refcount=%s):" %
|
||||
(repr(obj), sys.getrefcount(obj)))
|
||||
t = ReferrerTree(ignore=[objs], maxdepth=3)
|
||||
tree = t.ascend(obj)
|
||||
output.extend(t.format(tree))
|
||||
|
||||
return "\n".join(output)
|
||||
stats.exposed = True
|
||||
|
||||
7
python/packages/cherrypy/lib/http.py
Normal file
7
python/packages/cherrypy/lib/http.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import warnings
|
||||
warnings.warn('cherrypy.lib.http has been deprecated and will be removed '
|
||||
'in CherryPy 3.3 use cherrypy.lib.httputil instead.',
|
||||
DeprecationWarning)
|
||||
|
||||
from cherrypy.lib.httputil import *
|
||||
|
||||
354
python/packages/cherrypy/lib/httpauth.py
Normal file
354
python/packages/cherrypy/lib/httpauth.py
Normal file
@@ -0,0 +1,354 @@
|
||||
"""
|
||||
This module defines functions to implement HTTP Digest Authentication (:rfc:`2617`).
|
||||
This has full compliance with 'Digest' and 'Basic' authentication methods. In
|
||||
'Digest' it supports both MD5 and MD5-sess algorithms.
|
||||
|
||||
Usage:
|
||||
First use 'doAuth' to request the client authentication for a
|
||||
certain resource. You should send an httplib.UNAUTHORIZED response to the
|
||||
client so he knows he has to authenticate itself.
|
||||
|
||||
Then use 'parseAuthorization' to retrieve the 'auth_map' used in
|
||||
'checkResponse'.
|
||||
|
||||
To use 'checkResponse' you must have already verified the password associated
|
||||
with the 'username' key in 'auth_map' dict. Then you use the 'checkResponse'
|
||||
function to verify if the password matches the one sent by the client.
|
||||
|
||||
SUPPORTED_ALGORITHM - list of supported 'Digest' algorithms
|
||||
SUPPORTED_QOP - list of supported 'Digest' 'qop'.
|
||||
"""
|
||||
__version__ = 1, 0, 1
|
||||
__author__ = "Tiago Cogumbreiro <cogumbreiro@users.sf.net>"
|
||||
__credits__ = """
|
||||
Peter van Kampen for its recipe which implement most of Digest authentication:
|
||||
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/302378
|
||||
"""
|
||||
|
||||
__license__ = """
|
||||
Copyright (c) 2005, Tiago Cogumbreiro <cogumbreiro@users.sf.net>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of Sylvain Hellegouarch nor the names of his contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
"""
|
||||
|
||||
__all__ = ("digestAuth", "basicAuth", "doAuth", "checkResponse",
|
||||
"parseAuthorization", "SUPPORTED_ALGORITHM", "md5SessionKey",
|
||||
"calculateNonce", "SUPPORTED_QOP")
|
||||
|
||||
################################################################################
|
||||
import time
|
||||
from cherrypy._cpcompat import base64_decode, ntob, md5
|
||||
from cherrypy._cpcompat import parse_http_list, parse_keqv_list
|
||||
|
||||
MD5 = "MD5"
|
||||
MD5_SESS = "MD5-sess"
|
||||
AUTH = "auth"
|
||||
AUTH_INT = "auth-int"
|
||||
|
||||
SUPPORTED_ALGORITHM = (MD5, MD5_SESS)
|
||||
SUPPORTED_QOP = (AUTH, AUTH_INT)
|
||||
|
||||
################################################################################
|
||||
# doAuth
|
||||
#
|
||||
DIGEST_AUTH_ENCODERS = {
|
||||
MD5: lambda val: md5(ntob(val)).hexdigest(),
|
||||
MD5_SESS: lambda val: md5(ntob(val)).hexdigest(),
|
||||
# SHA: lambda val: sha.new(ntob(val)).hexdigest (),
|
||||
}
|
||||
|
||||
def calculateNonce (realm, algorithm = MD5):
|
||||
"""This is an auxaliary function that calculates 'nonce' value. It is used
|
||||
to handle sessions."""
|
||||
|
||||
global SUPPORTED_ALGORITHM, DIGEST_AUTH_ENCODERS
|
||||
assert algorithm in SUPPORTED_ALGORITHM
|
||||
|
||||
try:
|
||||
encoder = DIGEST_AUTH_ENCODERS[algorithm]
|
||||
except KeyError:
|
||||
raise NotImplementedError ("The chosen algorithm (%s) does not have "\
|
||||
"an implementation yet" % algorithm)
|
||||
|
||||
return encoder ("%d:%s" % (time.time(), realm))
|
||||
|
||||
def digestAuth (realm, algorithm = MD5, nonce = None, qop = AUTH):
|
||||
"""Challenges the client for a Digest authentication."""
|
||||
global SUPPORTED_ALGORITHM, DIGEST_AUTH_ENCODERS, SUPPORTED_QOP
|
||||
assert algorithm in SUPPORTED_ALGORITHM
|
||||
assert qop in SUPPORTED_QOP
|
||||
|
||||
if nonce is None:
|
||||
nonce = calculateNonce (realm, algorithm)
|
||||
|
||||
return 'Digest realm="%s", nonce="%s", algorithm="%s", qop="%s"' % (
|
||||
realm, nonce, algorithm, qop
|
||||
)
|
||||
|
||||
def basicAuth (realm):
|
||||
"""Challengenes the client for a Basic authentication."""
|
||||
assert '"' not in realm, "Realms cannot contain the \" (quote) character."
|
||||
|
||||
return 'Basic realm="%s"' % realm
|
||||
|
||||
def doAuth (realm):
|
||||
"""'doAuth' function returns the challenge string b giving priority over
|
||||
Digest and fallback to Basic authentication when the browser doesn't
|
||||
support the first one.
|
||||
|
||||
This should be set in the HTTP header under the key 'WWW-Authenticate'."""
|
||||
|
||||
return digestAuth (realm) + " " + basicAuth (realm)
|
||||
|
||||
|
||||
################################################################################
|
||||
# Parse authorization parameters
|
||||
#
|
||||
def _parseDigestAuthorization (auth_params):
|
||||
# Convert the auth params to a dict
|
||||
items = parse_http_list(auth_params)
|
||||
params = parse_keqv_list(items)
|
||||
|
||||
# Now validate the params
|
||||
|
||||
# Check for required parameters
|
||||
required = ["username", "realm", "nonce", "uri", "response"]
|
||||
for k in required:
|
||||
if k not in params:
|
||||
return None
|
||||
|
||||
# If qop is sent then cnonce and nc MUST be present
|
||||
if "qop" in params and not ("cnonce" in params \
|
||||
and "nc" in params):
|
||||
return None
|
||||
|
||||
# If qop is not sent, neither cnonce nor nc can be present
|
||||
if ("cnonce" in params or "nc" in params) and \
|
||||
"qop" not in params:
|
||||
return None
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def _parseBasicAuthorization (auth_params):
|
||||
username, password = base64_decode(auth_params).split(":", 1)
|
||||
return {"username": username, "password": password}
|
||||
|
||||
AUTH_SCHEMES = {
|
||||
"basic": _parseBasicAuthorization,
|
||||
"digest": _parseDigestAuthorization,
|
||||
}
|
||||
|
||||
def parseAuthorization (credentials):
|
||||
"""parseAuthorization will convert the value of the 'Authorization' key in
|
||||
the HTTP header to a map itself. If the parsing fails 'None' is returned.
|
||||
"""
|
||||
|
||||
global AUTH_SCHEMES
|
||||
|
||||
auth_scheme, auth_params = credentials.split(" ", 1)
|
||||
auth_scheme = auth_scheme.lower ()
|
||||
|
||||
parser = AUTH_SCHEMES[auth_scheme]
|
||||
params = parser (auth_params)
|
||||
|
||||
if params is None:
|
||||
return
|
||||
|
||||
assert "auth_scheme" not in params
|
||||
params["auth_scheme"] = auth_scheme
|
||||
return params
|
||||
|
||||
|
||||
################################################################################
|
||||
# Check provided response for a valid password
|
||||
#
|
||||
def md5SessionKey (params, password):
|
||||
"""
|
||||
If the "algorithm" directive's value is "MD5-sess", then A1
|
||||
[the session key] is calculated only once - on the first request by the
|
||||
client following receipt of a WWW-Authenticate challenge from the server.
|
||||
|
||||
This creates a 'session key' for the authentication of subsequent
|
||||
requests and responses which is different for each "authentication
|
||||
session", thus limiting the amount of material hashed with any one
|
||||
key.
|
||||
|
||||
Because the server need only use the hash of the user
|
||||
credentials in order to create the A1 value, this construction could
|
||||
be used in conjunction with a third party authentication service so
|
||||
that the web server would not need the actual password value. The
|
||||
specification of such a protocol is beyond the scope of this
|
||||
specification.
|
||||
"""
|
||||
|
||||
keys = ("username", "realm", "nonce", "cnonce")
|
||||
params_copy = {}
|
||||
for key in keys:
|
||||
params_copy[key] = params[key]
|
||||
|
||||
params_copy["algorithm"] = MD5_SESS
|
||||
return _A1 (params_copy, password)
|
||||
|
||||
def _A1(params, password):
|
||||
algorithm = params.get ("algorithm", MD5)
|
||||
H = DIGEST_AUTH_ENCODERS[algorithm]
|
||||
|
||||
if algorithm == MD5:
|
||||
# If the "algorithm" directive's value is "MD5" or is
|
||||
# unspecified, then A1 is:
|
||||
# A1 = unq(username-value) ":" unq(realm-value) ":" passwd
|
||||
return "%s:%s:%s" % (params["username"], params["realm"], password)
|
||||
|
||||
elif algorithm == MD5_SESS:
|
||||
|
||||
# This is A1 if qop is set
|
||||
# A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd )
|
||||
# ":" unq(nonce-value) ":" unq(cnonce-value)
|
||||
h_a1 = H ("%s:%s:%s" % (params["username"], params["realm"], password))
|
||||
return "%s:%s:%s" % (h_a1, params["nonce"], params["cnonce"])
|
||||
|
||||
|
||||
def _A2(params, method, kwargs):
|
||||
# If the "qop" directive's value is "auth" or is unspecified, then A2 is:
|
||||
# A2 = Method ":" digest-uri-value
|
||||
|
||||
qop = params.get ("qop", "auth")
|
||||
if qop == "auth":
|
||||
return method + ":" + params["uri"]
|
||||
elif qop == "auth-int":
|
||||
# If the "qop" value is "auth-int", then A2 is:
|
||||
# A2 = Method ":" digest-uri-value ":" H(entity-body)
|
||||
entity_body = kwargs.get ("entity_body", "")
|
||||
H = kwargs["H"]
|
||||
|
||||
return "%s:%s:%s" % (
|
||||
method,
|
||||
params["uri"],
|
||||
H(entity_body)
|
||||
)
|
||||
|
||||
else:
|
||||
raise NotImplementedError ("The 'qop' method is unknown: %s" % qop)
|
||||
|
||||
def _computeDigestResponse(auth_map, password, method = "GET", A1 = None,**kwargs):
|
||||
"""
|
||||
Generates a response respecting the algorithm defined in RFC 2617
|
||||
"""
|
||||
params = auth_map
|
||||
|
||||
algorithm = params.get ("algorithm", MD5)
|
||||
|
||||
H = DIGEST_AUTH_ENCODERS[algorithm]
|
||||
KD = lambda secret, data: H(secret + ":" + data)
|
||||
|
||||
qop = params.get ("qop", None)
|
||||
|
||||
H_A2 = H(_A2(params, method, kwargs))
|
||||
|
||||
if algorithm == MD5_SESS and A1 is not None:
|
||||
H_A1 = H(A1)
|
||||
else:
|
||||
H_A1 = H(_A1(params, password))
|
||||
|
||||
if qop in ("auth", "auth-int"):
|
||||
# If the "qop" value is "auth" or "auth-int":
|
||||
# request-digest = <"> < KD ( H(A1), unq(nonce-value)
|
||||
# ":" nc-value
|
||||
# ":" unq(cnonce-value)
|
||||
# ":" unq(qop-value)
|
||||
# ":" H(A2)
|
||||
# ) <">
|
||||
request = "%s:%s:%s:%s:%s" % (
|
||||
params["nonce"],
|
||||
params["nc"],
|
||||
params["cnonce"],
|
||||
params["qop"],
|
||||
H_A2,
|
||||
)
|
||||
elif qop is None:
|
||||
# If the "qop" directive is not present (this construction is
|
||||
# for compatibility with RFC 2069):
|
||||
# request-digest =
|
||||
# <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">
|
||||
request = "%s:%s" % (params["nonce"], H_A2)
|
||||
|
||||
return KD(H_A1, request)
|
||||
|
||||
def _checkDigestResponse(auth_map, password, method = "GET", A1 = None, **kwargs):
|
||||
"""This function is used to verify the response given by the client when
|
||||
he tries to authenticate.
|
||||
Optional arguments:
|
||||
entity_body - when 'qop' is set to 'auth-int' you MUST provide the
|
||||
raw data you are going to send to the client (usually the
|
||||
HTML page.
|
||||
request_uri - the uri from the request line compared with the 'uri'
|
||||
directive of the authorization map. They must represent
|
||||
the same resource (unused at this time).
|
||||
"""
|
||||
|
||||
if auth_map['realm'] != kwargs.get('realm', None):
|
||||
return False
|
||||
|
||||
response = _computeDigestResponse(auth_map, password, method, A1,**kwargs)
|
||||
|
||||
return response == auth_map["response"]
|
||||
|
||||
def _checkBasicResponse (auth_map, password, method='GET', encrypt=None, **kwargs):
|
||||
# Note that the Basic response doesn't provide the realm value so we cannot
|
||||
# test it
|
||||
try:
|
||||
return encrypt(auth_map["password"], auth_map["username"]) == password
|
||||
except TypeError:
|
||||
return encrypt(auth_map["password"]) == password
|
||||
|
||||
AUTH_RESPONSES = {
|
||||
"basic": _checkBasicResponse,
|
||||
"digest": _checkDigestResponse,
|
||||
}
|
||||
|
||||
def checkResponse (auth_map, password, method = "GET", encrypt=None, **kwargs):
|
||||
"""'checkResponse' compares the auth_map with the password and optionally
|
||||
other arguments that each implementation might need.
|
||||
|
||||
If the response is of type 'Basic' then the function has the following
|
||||
signature::
|
||||
|
||||
checkBasicResponse (auth_map, password) -> bool
|
||||
|
||||
If the response is of type 'Digest' then the function has the following
|
||||
signature::
|
||||
|
||||
checkDigestResponse (auth_map, password, method = 'GET', A1 = None) -> bool
|
||||
|
||||
The 'A1' argument is only used in MD5_SESS algorithm based responses.
|
||||
Check md5SessionKey() for more info.
|
||||
"""
|
||||
checker = AUTH_RESPONSES[auth_map["auth_scheme"]]
|
||||
return checker (auth_map, password, method=method, encrypt=encrypt, **kwargs)
|
||||
|
||||
|
||||
|
||||
|
||||
506
python/packages/cherrypy/lib/httputil.py
Normal file
506
python/packages/cherrypy/lib/httputil.py
Normal file
File diff suppressed because it is too large
Load Diff
87
python/packages/cherrypy/lib/jsontools.py
Normal file
87
python/packages/cherrypy/lib/jsontools.py
Normal file
@@ -0,0 +1,87 @@
|
||||
import sys
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import basestring, ntou, json, json_encode, json_decode
|
||||
|
||||
def json_processor(entity):
|
||||
"""Read application/json data into request.json."""
|
||||
if not entity.headers.get(ntou("Content-Length"), ntou("")):
|
||||
raise cherrypy.HTTPError(411)
|
||||
|
||||
body = entity.fp.read()
|
||||
try:
|
||||
cherrypy.serving.request.json = json_decode(body.decode('utf-8'))
|
||||
except ValueError:
|
||||
raise cherrypy.HTTPError(400, 'Invalid JSON document')
|
||||
|
||||
def json_in(content_type=[ntou('application/json'), ntou('text/javascript')],
|
||||
force=True, debug=False, processor = json_processor):
|
||||
"""Add a processor to parse JSON request entities:
|
||||
The default processor places the parsed data into request.json.
|
||||
|
||||
Incoming request entities which match the given content_type(s) will
|
||||
be deserialized from JSON to the Python equivalent, and the result
|
||||
stored at cherrypy.request.json. The 'content_type' argument may
|
||||
be a Content-Type string or a list of allowable Content-Type strings.
|
||||
|
||||
If the 'force' argument is True (the default), then entities of other
|
||||
content types will not be allowed; "415 Unsupported Media Type" is
|
||||
raised instead.
|
||||
|
||||
Supply your own processor to use a custom decoder, or to handle the parsed
|
||||
data differently. The processor can be configured via
|
||||
tools.json_in.processor or via the decorator method.
|
||||
|
||||
Note that the deserializer requires the client send a Content-Length
|
||||
request header, or it will raise "411 Length Required". If for any
|
||||
other reason the request entity cannot be deserialized from JSON,
|
||||
it will raise "400 Bad Request: Invalid JSON document".
|
||||
|
||||
You must be using Python 2.6 or greater, or have the 'simplejson'
|
||||
package importable; otherwise, ValueError is raised during processing.
|
||||
"""
|
||||
request = cherrypy.serving.request
|
||||
if isinstance(content_type, basestring):
|
||||
content_type = [content_type]
|
||||
|
||||
if force:
|
||||
if debug:
|
||||
cherrypy.log('Removing body processors %s' %
|
||||
repr(request.body.processors.keys()), 'TOOLS.JSON_IN')
|
||||
request.body.processors.clear()
|
||||
request.body.default_proc = cherrypy.HTTPError(
|
||||
415, 'Expected an entity of content type %s' %
|
||||
', '.join(content_type))
|
||||
|
||||
for ct in content_type:
|
||||
if debug:
|
||||
cherrypy.log('Adding body processor for %s' % ct, 'TOOLS.JSON_IN')
|
||||
request.body.processors[ct] = processor
|
||||
|
||||
def json_handler(*args, **kwargs):
|
||||
value = cherrypy.serving.request._json_inner_handler(*args, **kwargs)
|
||||
return json_encode(value)
|
||||
|
||||
def json_out(content_type='application/json', debug=False, handler=json_handler):
|
||||
"""Wrap request.handler to serialize its output to JSON. Sets Content-Type.
|
||||
|
||||
If the given content_type is None, the Content-Type response header
|
||||
is not set.
|
||||
|
||||
Provide your own handler to use a custom encoder. For example
|
||||
cherrypy.config['tools.json_out.handler'] = <function>, or
|
||||
@json_out(handler=function).
|
||||
|
||||
You must be using Python 2.6 or greater, or have the 'simplejson'
|
||||
package importable; otherwise, ValueError is raised during processing.
|
||||
"""
|
||||
request = cherrypy.serving.request
|
||||
if debug:
|
||||
cherrypy.log('Replacing %s with JSON handler' % request.handler,
|
||||
'TOOLS.JSON_OUT')
|
||||
request._json_inner_handler = request.handler
|
||||
request.handler = handler
|
||||
if content_type is not None:
|
||||
if debug:
|
||||
cherrypy.log('Setting Content-Type to %s' % content_type, 'TOOLS.JSON_OUT')
|
||||
cherrypy.serving.response.headers['Content-Type'] = content_type
|
||||
|
||||
208
python/packages/cherrypy/lib/profiler.py
Normal file
208
python/packages/cherrypy/lib/profiler.py
Normal file
@@ -0,0 +1,208 @@
|
||||
"""Profiler tools for CherryPy.
|
||||
|
||||
CherryPy users
|
||||
==============
|
||||
|
||||
You can profile any of your pages as follows::
|
||||
|
||||
from cherrypy.lib import profiler
|
||||
|
||||
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.tree.mount(Root())
|
||||
|
||||
You can also turn on profiling for all requests
|
||||
using the ``make_app`` function as WSGI middleware.
|
||||
|
||||
CherryPy developers
|
||||
===================
|
||||
|
||||
This module can be used whenever you make changes to CherryPy,
|
||||
to get a quick sanity-check on overall CP performance. Use the
|
||||
``--profile`` flag when running the test suite. 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.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def new_func_strip_path(func_name):
|
||||
"""Make profiler output more readable by adding ``__init__`` modules' parents"""
|
||||
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 os, os.path
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from cherrypy._cpcompat import BytesIO
|
||||
|
||||
_count = 0
|
||||
|
||||
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)
|
||||
|
||||
def run(self, func, *args, **params):
|
||||
"""Dump profile data into self.path."""
|
||||
global _count
|
||||
c = _count = _count + 1
|
||||
path = os.path.join(self.path, "cp_%04d.prof" % c)
|
||||
prof = profile.Profile()
|
||||
result = prof.runcall(func, *args, **params)
|
||||
prof.dump_stats(path)
|
||||
return result
|
||||
|
||||
def statfiles(self):
|
||||
""":rtype: 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'):
|
||||
""":rtype stats(index): output of print_stats() for the given profile.
|
||||
"""
|
||||
sio = BytesIO()
|
||||
if sys.version_info >= (2, 5):
|
||||
s = pstats.Stats(os.path.join(self.path, filename), stream=sio)
|
||||
s.strip_dirs()
|
||||
s.sort_stats(sortby)
|
||||
s.print_stats()
|
||||
else:
|
||||
# pstats.Stats before Python 2.5 didn't take a 'stream' arg,
|
||||
# but just printed to stdout. So re-route stdout.
|
||||
s = pstats.Stats(os.path.join(self.path, filename))
|
||||
s.strip_dirs()
|
||||
s.sort_stats(sortby)
|
||||
oldout = sys.stdout
|
||||
try:
|
||||
sys.stdout = sio
|
||||
s.print_stats()
|
||||
finally:
|
||||
sys.stdout = oldout
|
||||
response = sio.getvalue()
|
||||
sio.close()
|
||||
return response
|
||||
|
||||
def index(self):
|
||||
return """<html>
|
||||
<head><title>CherryPy profile data</title></head>
|
||||
<frameset cols='200, 1*'>
|
||||
<frame src='menu' />
|
||||
<frame name='main' src='' />
|
||||
</frameset>
|
||||
</html>
|
||||
"""
|
||||
index.exposed = True
|
||||
|
||||
def menu(self):
|
||||
yield "<h2>Profiling runs</h2>"
|
||||
yield "<p>Click on one of the runs below to see profiling data.</p>"
|
||||
runs = self.statfiles()
|
||||
runs.sort()
|
||||
for i in runs:
|
||||
yield "<a href='report?filename=%s' target='main'>%s</a><br />" % (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
|
||||
|
||||
|
||||
class ProfileAggregator(Profiler):
|
||||
|
||||
def __init__(self, path=None):
|
||||
Profiler.__init__(self, path)
|
||||
global _count
|
||||
self.count = _count = _count + 1
|
||||
self.profiler = profile.Profile()
|
||||
|
||||
def run(self, func, *args):
|
||||
path = os.path.join(self.path, "cp_%04d.prof" % self.count)
|
||||
result = self.profiler.runcall(func, *args)
|
||||
self.profiler.dump_stats(path)
|
||||
return result
|
||||
|
||||
|
||||
class make_app:
|
||||
def __init__(self, nextapp, path=None, aggregate=False):
|
||||
"""Make a WSGI middleware app which wraps 'nextapp' with profiling.
|
||||
|
||||
nextapp
|
||||
the WSGI application to wrap, usually an instance of
|
||||
cherrypy.Application.
|
||||
|
||||
path
|
||||
where to dump the profiling output.
|
||||
|
||||
aggregate
|
||||
if True, profile data for all HTTP requests will go in
|
||||
a single file. If False (the default), each HTTP request will
|
||||
dump its profile data into a separate file.
|
||||
|
||||
"""
|
||||
if profile is None or pstats is None:
|
||||
msg = ("Your installation of Python does not have a profile module. "
|
||||
"If you're on Debian, try `sudo apt-get install python-profiler`. "
|
||||
"See http://www.cherrypy.org/wiki/ProfilingOnDebian for details.")
|
||||
warnings.warn(msg)
|
||||
|
||||
self.nextapp = nextapp
|
||||
self.aggregate = aggregate
|
||||
if aggregate:
|
||||
self.profiler = ProfileAggregator(path)
|
||||
else:
|
||||
self.profiler = Profiler(path)
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
def gather():
|
||||
result = []
|
||||
for line in self.nextapp(environ, start_response):
|
||||
result.append(line)
|
||||
return result
|
||||
return self.profiler.run(gather)
|
||||
|
||||
|
||||
def serve(path=None, port=8080):
|
||||
if profile is None or pstats is None:
|
||||
msg = ("Your installation of Python does not have a profile module. "
|
||||
"If you're on Debian, try `sudo apt-get install python-profiler`. "
|
||||
"See http://www.cherrypy.org/wiki/ProfilingOnDebian for details.")
|
||||
warnings.warn(msg)
|
||||
|
||||
import cherrypy
|
||||
cherrypy.config.update({'server.socket_port': int(port),
|
||||
'server.thread_pool': 10,
|
||||
'environment': "production",
|
||||
})
|
||||
cherrypy.quickstart(Profiler(path))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
serve(*tuple(sys.argv[1:]))
|
||||
|
||||
485
python/packages/cherrypy/lib/reprconf.py
Normal file
485
python/packages/cherrypy/lib/reprconf.py
Normal file
@@ -0,0 +1,485 @@
|
||||
"""Generic configuration system using unrepr.
|
||||
|
||||
Configuration data may be supplied as a Python dictionary, as a filename,
|
||||
or as an open file object. When you supply a filename or file, Python's
|
||||
builtin ConfigParser is used (with some extensions).
|
||||
|
||||
Namespaces
|
||||
----------
|
||||
|
||||
Configuration keys are separated into namespaces by the first "." in the key.
|
||||
|
||||
The only key that cannot exist in a namespace is the "environment" entry.
|
||||
This special entry 'imports' other config entries from a template stored in
|
||||
the Config.environments dict.
|
||||
|
||||
You can define your own namespaces to be called when new config is merged
|
||||
by adding a named handler to Config.namespaces. The name can be any string,
|
||||
and the handler must be either a callable or a context manager.
|
||||
"""
|
||||
|
||||
try:
|
||||
# Python 3.0+
|
||||
from configparser import ConfigParser
|
||||
except ImportError:
|
||||
from ConfigParser import ConfigParser
|
||||
|
||||
try:
|
||||
set
|
||||
except NameError:
|
||||
from sets import Set as set
|
||||
|
||||
try:
|
||||
basestring
|
||||
except NameError:
|
||||
basestring = str
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
import builtins
|
||||
except ImportError:
|
||||
# Python 2
|
||||
import __builtin__ as builtins
|
||||
|
||||
import operator as _operator
|
||||
import sys
|
||||
|
||||
def as_dict(config):
|
||||
"""Return a dict from 'config' whether it is a dict, file, or filename."""
|
||||
if isinstance(config, basestring):
|
||||
config = Parser().dict_from_file(config)
|
||||
elif hasattr(config, 'read'):
|
||||
config = Parser().dict_from_file(config)
|
||||
return config
|
||||
|
||||
|
||||
class NamespaceSet(dict):
|
||||
"""A dict of config namespace names and handlers.
|
||||
|
||||
Each config entry should begin with a namespace name; the corresponding
|
||||
namespace handler will be called once for each config entry in that
|
||||
namespace, and will be passed two arguments: the config key (with the
|
||||
namespace removed) and the config value.
|
||||
|
||||
Namespace handlers may be any Python callable; they may also be
|
||||
Python 2.5-style 'context managers', in which case their __enter__
|
||||
method should return a callable to be used as the handler.
|
||||
See cherrypy.tools (the Toolbox class) for an example.
|
||||
"""
|
||||
|
||||
def __call__(self, config):
|
||||
"""Iterate through config and pass it to each namespace handler.
|
||||
|
||||
config
|
||||
A flat dict, where keys use dots to separate
|
||||
namespaces, and values are arbitrary.
|
||||
|
||||
The first name in each config key is used to look up the corresponding
|
||||
namespace handler. For example, a config entry of {'tools.gzip.on': v}
|
||||
will call the 'tools' namespace handler with the args: ('gzip.on', v)
|
||||
"""
|
||||
# Separate the given config into namespaces
|
||||
ns_confs = {}
|
||||
for k in config:
|
||||
if "." in k:
|
||||
ns, name = k.split(".", 1)
|
||||
bucket = ns_confs.setdefault(ns, {})
|
||||
bucket[name] = config[k]
|
||||
|
||||
# I chose __enter__ and __exit__ so someday this could be
|
||||
# rewritten using Python 2.5's 'with' statement:
|
||||
# for ns, handler in self.iteritems():
|
||||
# with handler as callable:
|
||||
# for k, v in ns_confs.get(ns, {}).iteritems():
|
||||
# callable(k, v)
|
||||
for ns, handler in self.items():
|
||||
exit = getattr(handler, "__exit__", None)
|
||||
if exit:
|
||||
callable = handler.__enter__()
|
||||
no_exc = True
|
||||
try:
|
||||
try:
|
||||
for k, v in ns_confs.get(ns, {}).items():
|
||||
callable(k, v)
|
||||
except:
|
||||
# The exceptional case is handled here
|
||||
no_exc = False
|
||||
if exit is None:
|
||||
raise
|
||||
if not exit(*sys.exc_info()):
|
||||
raise
|
||||
# The exception is swallowed if exit() returns true
|
||||
finally:
|
||||
# The normal and non-local-goto cases are handled here
|
||||
if no_exc and exit:
|
||||
exit(None, None, None)
|
||||
else:
|
||||
for k, v in ns_confs.get(ns, {}).items():
|
||||
handler(k, v)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s.%s(%s)" % (self.__module__, self.__class__.__name__,
|
||||
dict.__repr__(self))
|
||||
|
||||
def __copy__(self):
|
||||
newobj = self.__class__()
|
||||
newobj.update(self)
|
||||
return newobj
|
||||
copy = __copy__
|
||||
|
||||
|
||||
class Config(dict):
|
||||
"""A dict-like set of configuration data, with defaults and namespaces.
|
||||
|
||||
May take a file, filename, or dict.
|
||||
"""
|
||||
|
||||
defaults = {}
|
||||
environments = {}
|
||||
namespaces = NamespaceSet()
|
||||
|
||||
def __init__(self, file=None, **kwargs):
|
||||
self.reset()
|
||||
if file is not None:
|
||||
self.update(file)
|
||||
if kwargs:
|
||||
self.update(kwargs)
|
||||
|
||||
def reset(self):
|
||||
"""Reset self to default values."""
|
||||
self.clear()
|
||||
dict.update(self, self.defaults)
|
||||
|
||||
def update(self, config):
|
||||
"""Update self from a dict, file or filename."""
|
||||
if isinstance(config, basestring):
|
||||
# Filename
|
||||
config = Parser().dict_from_file(config)
|
||||
elif hasattr(config, 'read'):
|
||||
# Open file object
|
||||
config = Parser().dict_from_file(config)
|
||||
else:
|
||||
config = config.copy()
|
||||
self._apply(config)
|
||||
|
||||
def _apply(self, config):
|
||||
"""Update self from a dict."""
|
||||
which_env = config.get('environment')
|
||||
if which_env:
|
||||
env = self.environments[which_env]
|
||||
for k in env:
|
||||
if k not in config:
|
||||
config[k] = env[k]
|
||||
|
||||
dict.update(self, config)
|
||||
self.namespaces(config)
|
||||
|
||||
def __setitem__(self, k, v):
|
||||
dict.__setitem__(self, k, v)
|
||||
self.namespaces({k: v})
|
||||
|
||||
|
||||
class Parser(ConfigParser):
|
||||
"""Sub-class of ConfigParser that keeps the case of options and that
|
||||
raises an exception if the file cannot be read.
|
||||
"""
|
||||
|
||||
def optionxform(self, optionstr):
|
||||
return optionstr
|
||||
|
||||
def read(self, filenames):
|
||||
if isinstance(filenames, basestring):
|
||||
filenames = [filenames]
|
||||
for filename in filenames:
|
||||
# try:
|
||||
# fp = open(filename)
|
||||
# except IOError:
|
||||
# continue
|
||||
fp = open(filename)
|
||||
try:
|
||||
self._read(fp, filename)
|
||||
finally:
|
||||
fp.close()
|
||||
|
||||
def as_dict(self, raw=False, vars=None):
|
||||
"""Convert an INI file to a dictionary"""
|
||||
# Load INI file into a dict
|
||||
result = {}
|
||||
for section in self.sections():
|
||||
if section not in result:
|
||||
result[section] = {}
|
||||
for option in self.options(section):
|
||||
value = self.get(section, option, raw=raw, vars=vars)
|
||||
try:
|
||||
value = unrepr(value)
|
||||
except Exception:
|
||||
x = sys.exc_info()[1]
|
||||
msg = ("Config error in section: %r, option: %r, "
|
||||
"value: %r. Config values must be valid Python." %
|
||||
(section, option, value))
|
||||
raise ValueError(msg, x.__class__.__name__, x.args)
|
||||
result[section][option] = value
|
||||
return result
|
||||
|
||||
def dict_from_file(self, file):
|
||||
if hasattr(file, 'read'):
|
||||
self.readfp(file)
|
||||
else:
|
||||
self.read(file)
|
||||
return self.as_dict()
|
||||
|
||||
|
||||
# public domain "unrepr" implementation, found on the web and then improved.
|
||||
|
||||
|
||||
class _Builder2:
|
||||
|
||||
def build(self, o):
|
||||
m = getattr(self, 'build_' + o.__class__.__name__, None)
|
||||
if m is None:
|
||||
raise TypeError("unrepr does not recognize %s" %
|
||||
repr(o.__class__.__name__))
|
||||
return m(o)
|
||||
|
||||
def astnode(self, s):
|
||||
"""Return a Python2 ast Node compiled from a string."""
|
||||
try:
|
||||
import compiler
|
||||
except ImportError:
|
||||
# Fallback to eval when compiler package is not available,
|
||||
# e.g. IronPython 1.0.
|
||||
return eval(s)
|
||||
|
||||
p = compiler.parse("__tempvalue__ = " + s)
|
||||
return p.getChildren()[1].getChildren()[0].getChildren()[1]
|
||||
|
||||
def build_Subscript(self, o):
|
||||
expr, flags, subs = o.getChildren()
|
||||
expr = self.build(expr)
|
||||
subs = self.build(subs)
|
||||
return expr[subs]
|
||||
|
||||
def build_CallFunc(self, o):
|
||||
children = map(self.build, o.getChildren())
|
||||
callee = children.pop(0)
|
||||
kwargs = children.pop() or {}
|
||||
starargs = children.pop() or ()
|
||||
args = tuple(children) + tuple(starargs)
|
||||
return callee(*args, **kwargs)
|
||||
|
||||
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):
|
||||
name = o.name
|
||||
if name == 'None':
|
||||
return None
|
||||
if name == 'True':
|
||||
return True
|
||||
if name == 'False':
|
||||
return False
|
||||
|
||||
# See if the Name is a package or module. If it is, import it.
|
||||
try:
|
||||
return modules(name)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# See if the Name is in builtins.
|
||||
try:
|
||||
return getattr(builtins, name)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
raise TypeError("unrepr could not resolve the name %s" % repr(name))
|
||||
|
||||
def build_Add(self, o):
|
||||
left, right = map(self.build, o.getChildren())
|
||||
return left + right
|
||||
|
||||
def build_Mul(self, o):
|
||||
left, right = map(self.build, o.getChildren())
|
||||
return left * right
|
||||
|
||||
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(o.getChildren()[0])
|
||||
|
||||
def build_UnaryAdd(self, o):
|
||||
return self.build(o.getChildren()[0])
|
||||
|
||||
|
||||
class _Builder3:
|
||||
|
||||
def build(self, o):
|
||||
m = getattr(self, 'build_' + o.__class__.__name__, None)
|
||||
if m is None:
|
||||
raise TypeError("unrepr does not recognize %s" %
|
||||
repr(o.__class__.__name__))
|
||||
return m(o)
|
||||
|
||||
def astnode(self, s):
|
||||
"""Return a Python3 ast Node compiled from a string."""
|
||||
try:
|
||||
import ast
|
||||
except ImportError:
|
||||
# Fallback to eval when ast package is not available,
|
||||
# e.g. IronPython 1.0.
|
||||
return eval(s)
|
||||
|
||||
p = ast.parse("__tempvalue__ = " + s)
|
||||
return p.body[0].value
|
||||
|
||||
def build_Subscript(self, o):
|
||||
return self.build(o.value)[self.build(o.slice)]
|
||||
|
||||
def build_Index(self, o):
|
||||
return self.build(o.value)
|
||||
|
||||
def build_Call(self, o):
|
||||
callee = self.build(o.func)
|
||||
|
||||
if o.args is None:
|
||||
args = ()
|
||||
else:
|
||||
args = tuple([self.build(a) for a in o.args])
|
||||
|
||||
if o.starargs is None:
|
||||
starargs = ()
|
||||
else:
|
||||
starargs = self.build(o.starargs)
|
||||
|
||||
if o.kwargs is None:
|
||||
kwargs = {}
|
||||
else:
|
||||
kwargs = self.build(o.kwargs)
|
||||
|
||||
return callee(*(args + starargs), **kwargs)
|
||||
|
||||
def build_List(self, o):
|
||||
return list(map(self.build, o.elts))
|
||||
|
||||
def build_Str(self, o):
|
||||
return o.s
|
||||
|
||||
def build_Num(self, o):
|
||||
return o.n
|
||||
|
||||
def build_Dict(self, o):
|
||||
return dict([(self.build(k), self.build(v))
|
||||
for k, v in zip(o.keys, o.values)])
|
||||
|
||||
def build_Tuple(self, o):
|
||||
return tuple(self.build_List(o))
|
||||
|
||||
def build_Name(self, o):
|
||||
name = o.id
|
||||
if name == 'None':
|
||||
return None
|
||||
if name == 'True':
|
||||
return True
|
||||
if name == 'False':
|
||||
return False
|
||||
|
||||
# See if the Name is a package or module. If it is, import it.
|
||||
try:
|
||||
return modules(name)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# See if the Name is in builtins.
|
||||
try:
|
||||
import builtins
|
||||
return getattr(builtins, name)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
raise TypeError("unrepr could not resolve the name %s" % repr(name))
|
||||
|
||||
def build_UnaryOp(self, o):
|
||||
op, operand = map(self.build, [o.op, o.operand])
|
||||
return op(operand)
|
||||
|
||||
def build_BinOp(self, o):
|
||||
left, op, right = map(self.build, [o.left, o.op, o.right])
|
||||
return op(left, right)
|
||||
|
||||
def build_Add(self, o):
|
||||
return _operator.add
|
||||
|
||||
def build_Mult(self, o):
|
||||
return _operator.mul
|
||||
|
||||
def build_USub(self, o):
|
||||
return _operator.neg
|
||||
|
||||
def build_Attribute(self, o):
|
||||
parent = self.build(o.value)
|
||||
return getattr(parent, o.attr)
|
||||
|
||||
def build_NoneType(self, o):
|
||||
return None
|
||||
|
||||
|
||||
def unrepr(s):
|
||||
"""Return a Python object compiled from a string."""
|
||||
if not s:
|
||||
return s
|
||||
if sys.version_info < (3, 0):
|
||||
b = _Builder2()
|
||||
else:
|
||||
b = _Builder3()
|
||||
obj = b.astnode(s)
|
||||
return b.build(obj)
|
||||
|
||||
|
||||
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(full_attribute_name):
|
||||
"""Load a module and retrieve an attribute of that module."""
|
||||
|
||||
# Parse out the path, module, and attribute
|
||||
last_dot = full_attribute_name.rfind(".")
|
||||
attr_name = full_attribute_name[last_dot + 1:]
|
||||
mod_path = full_attribute_name[:last_dot]
|
||||
|
||||
mod = modules(mod_path)
|
||||
# Let an AttributeError propagate outward.
|
||||
try:
|
||||
attr = getattr(mod, attr_name)
|
||||
except AttributeError:
|
||||
raise AttributeError("'%s' object has no attribute '%s'"
|
||||
% (mod_path, attr_name))
|
||||
|
||||
# Return a reference to the attribute.
|
||||
return attr
|
||||
|
||||
|
||||
871
python/packages/cherrypy/lib/sessions.py
Normal file
871
python/packages/cherrypy/lib/sessions.py
Normal file
File diff suppressed because it is too large
Load Diff
363
python/packages/cherrypy/lib/static.py
Normal file
363
python/packages/cherrypy/lib/static.py
Normal file
@@ -0,0 +1,363 @@
|
||||
try:
|
||||
from io import UnsupportedOperation
|
||||
except ImportError:
|
||||
UnsupportedOperation = object()
|
||||
import logging
|
||||
import mimetypes
|
||||
mimetypes.init()
|
||||
mimetypes.types_map['.dwg']='image/x-dwg'
|
||||
mimetypes.types_map['.ico']='image/x-icon'
|
||||
mimetypes.types_map['.bz2']='application/x-bzip2'
|
||||
mimetypes.types_map['.gz']='application/x-gzip'
|
||||
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
import time
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import ntob, unquote
|
||||
from cherrypy.lib import cptools, httputil, file_generator_limited
|
||||
|
||||
|
||||
def serve_file(path, content_type=None, disposition=None, name=None, debug=False):
|
||||
"""Set status, headers, and body in order to serve the given path.
|
||||
|
||||
The Content-Type header will be set to the content_type arg, if provided.
|
||||
If not provided, the Content-Type will be guessed by the file extension
|
||||
of the 'path' argument.
|
||||
|
||||
If disposition is not None, the Content-Disposition header will be set
|
||||
to "<disposition>; filename=<name>". 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.serving.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 tools.staticdir, you can make your relative
|
||||
# paths become absolute by supplying a value for "tools.staticdir.root".
|
||||
if not os.path.isabs(path):
|
||||
msg = "'%s' is not an absolute path." % path
|
||||
if debug:
|
||||
cherrypy.log(msg, 'TOOLS.STATICFILE')
|
||||
raise ValueError(msg)
|
||||
|
||||
try:
|
||||
st = os.stat(path)
|
||||
except OSError:
|
||||
if debug:
|
||||
cherrypy.log('os.stat(%r) failed' % path, 'TOOLS.STATIC')
|
||||
raise cherrypy.NotFound()
|
||||
|
||||
# Check if path is a directory.
|
||||
if stat.S_ISDIR(st.st_mode):
|
||||
# Let the caller deal with it as they like.
|
||||
if debug:
|
||||
cherrypy.log('%r is a directory' % path, 'TOOLS.STATIC')
|
||||
raise cherrypy.NotFound()
|
||||
|
||||
# Set the Last-Modified response header, so that
|
||||
# modified-since validation code can work.
|
||||
response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime)
|
||||
cptools.validate_since()
|
||||
|
||||
if content_type is None:
|
||||
# Set content-type based on filename extension
|
||||
ext = ""
|
||||
i = path.rfind('.')
|
||||
if i != -1:
|
||||
ext = path[i:].lower()
|
||||
content_type = mimetypes.types_map.get(ext, None)
|
||||
if content_type is not None:
|
||||
response.headers['Content-Type'] = content_type
|
||||
if debug:
|
||||
cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC')
|
||||
|
||||
cd = None
|
||||
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
|
||||
if debug:
|
||||
cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC')
|
||||
|
||||
# Set Content-Length and use an iterable (file object)
|
||||
# this way CP won't load the whole file in memory
|
||||
content_length = st.st_size
|
||||
fileobj = open(path, 'rb')
|
||||
return _serve_fileobj(fileobj, content_type, content_length, debug=debug)
|
||||
|
||||
def serve_fileobj(fileobj, content_type=None, disposition=None, name=None,
|
||||
debug=False):
|
||||
"""Set status, headers, and body in order to serve the given file object.
|
||||
|
||||
The Content-Type header will be set to the content_type arg, if provided.
|
||||
|
||||
If disposition is not None, the Content-Disposition header will be set
|
||||
to "<disposition>; filename=<name>". If name is None, 'filename' will
|
||||
not be set. If disposition is None, no Content-Disposition header will
|
||||
be written.
|
||||
|
||||
CAUTION: If the request contains a 'Range' header, one or more seek()s will
|
||||
be performed on the file object. This may cause undesired behavior if
|
||||
the file object is not seekable. It could also produce undesired results
|
||||
if the caller set the read position of the file object prior to calling
|
||||
serve_fileobj(), expecting that the data would be served starting from that
|
||||
position.
|
||||
"""
|
||||
|
||||
response = cherrypy.serving.response
|
||||
|
||||
try:
|
||||
st = os.fstat(fileobj.fileno())
|
||||
except AttributeError:
|
||||
if debug:
|
||||
cherrypy.log('os has no fstat attribute', 'TOOLS.STATIC')
|
||||
content_length = None
|
||||
except UnsupportedOperation:
|
||||
content_length = None
|
||||
else:
|
||||
# Set the Last-Modified response header, so that
|
||||
# modified-since validation code can work.
|
||||
response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime)
|
||||
cptools.validate_since()
|
||||
content_length = st.st_size
|
||||
|
||||
if content_type is not None:
|
||||
response.headers['Content-Type'] = content_type
|
||||
if debug:
|
||||
cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC')
|
||||
|
||||
cd = None
|
||||
if disposition is not None:
|
||||
if name is None:
|
||||
cd = disposition
|
||||
else:
|
||||
cd = '%s; filename="%s"' % (disposition, name)
|
||||
response.headers["Content-Disposition"] = cd
|
||||
if debug:
|
||||
cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC')
|
||||
|
||||
return _serve_fileobj(fileobj, content_type, content_length, debug=debug)
|
||||
|
||||
def _serve_fileobj(fileobj, content_type, content_length, debug=False):
|
||||
"""Internal. Set response.body to the given file object, perhaps ranged."""
|
||||
response = cherrypy.serving.response
|
||||
|
||||
# HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code
|
||||
request = cherrypy.serving.request
|
||||
if request.protocol >= (1, 1):
|
||||
response.headers["Accept-Ranges"] = "bytes"
|
||||
r = httputil.get_ranges(request.headers.get('Range'), content_length)
|
||||
if r == []:
|
||||
response.headers['Content-Range'] = "bytes */%s" % content_length
|
||||
message = "Invalid Range (first-byte-pos greater than Content-Length)"
|
||||
if debug:
|
||||
cherrypy.log(message, 'TOOLS.STATIC')
|
||||
raise cherrypy.HTTPError(416, message)
|
||||
|
||||
if r:
|
||||
if len(r) == 1:
|
||||
# Return a single-part response.
|
||||
start, stop = r[0]
|
||||
if stop > content_length:
|
||||
stop = content_length
|
||||
r_len = stop - start
|
||||
if debug:
|
||||
cherrypy.log('Single part; start: %r, stop: %r' % (start, stop),
|
||||
'TOOLS.STATIC')
|
||||
response.status = "206 Partial Content"
|
||||
response.headers['Content-Range'] = (
|
||||
"bytes %s-%s/%s" % (start, stop - 1, content_length))
|
||||
response.headers['Content-Length'] = r_len
|
||||
fileobj.seek(start)
|
||||
response.body = file_generator_limited(fileobj, r_len)
|
||||
else:
|
||||
# Return a multipart/byteranges response.
|
||||
response.status = "206 Partial Content"
|
||||
try:
|
||||
# Python 3
|
||||
from email.generator import _make_boundary as choose_boundary
|
||||
except ImportError:
|
||||
# Python 2
|
||||
from mimetools import choose_boundary
|
||||
boundary = choose_boundary()
|
||||
ct = "multipart/byteranges; boundary=%s" % boundary
|
||||
response.headers['Content-Type'] = ct
|
||||
if "Content-Length" in response.headers:
|
||||
# Delete Content-Length header so finalize() recalcs it.
|
||||
del response.headers["Content-Length"]
|
||||
|
||||
def file_ranges():
|
||||
# Apache compatibility:
|
||||
yield ntob("\r\n")
|
||||
|
||||
for start, stop in r:
|
||||
if debug:
|
||||
cherrypy.log('Multipart; start: %r, stop: %r' % (start, stop),
|
||||
'TOOLS.STATIC')
|
||||
yield ntob("--" + boundary, 'ascii')
|
||||
yield ntob("\r\nContent-type: %s" % content_type, 'ascii')
|
||||
yield ntob("\r\nContent-range: bytes %s-%s/%s\r\n\r\n"
|
||||
% (start, stop - 1, content_length), 'ascii')
|
||||
fileobj.seek(start)
|
||||
for chunk in file_generator_limited(fileobj, stop-start):
|
||||
yield chunk
|
||||
yield ntob("\r\n")
|
||||
# Final boundary
|
||||
yield ntob("--" + boundary + "--", 'ascii')
|
||||
|
||||
# Apache compatibility:
|
||||
yield ntob("\r\n")
|
||||
response.body = file_ranges()
|
||||
return response.body
|
||||
else:
|
||||
if debug:
|
||||
cherrypy.log('No byteranges requested', 'TOOLS.STATIC')
|
||||
|
||||
# Set Content-Length and use an iterable (file object)
|
||||
# this way CP won't load the whole file in memory
|
||||
response.headers['Content-Length'] = content_length
|
||||
response.body = fileobj
|
||||
return response.body
|
||||
|
||||
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 _attempt(filename, content_types, debug=False):
|
||||
if debug:
|
||||
cherrypy.log('Attempting %r (content_types %r)' %
|
||||
(filename, content_types), 'TOOLS.STATICDIR')
|
||||
try:
|
||||
# you can set the content types for a
|
||||
# complete directory per extension
|
||||
content_type = None
|
||||
if content_types:
|
||||
r, ext = os.path.splitext(filename)
|
||||
content_type = content_types.get(ext[1:], None)
|
||||
serve_file(filename, content_type=content_type, debug=debug)
|
||||
return True
|
||||
except cherrypy.NotFound:
|
||||
# If we didn't find the static file, continue handling the
|
||||
# request. We might find a dynamic handler instead.
|
||||
if debug:
|
||||
cherrypy.log('NotFound', 'TOOLS.STATICFILE')
|
||||
return False
|
||||
|
||||
def staticdir(section, dir, root="", match="", content_types=None, index="",
|
||||
debug=False):
|
||||
"""Serve a static resource from the given (root +) dir.
|
||||
|
||||
match
|
||||
If given, request.path_info will be searched for the given
|
||||
regular expression before attempting to serve static content.
|
||||
|
||||
content_types
|
||||
If given, it should be a Python dictionary of
|
||||
{file-extension: content-type} pairs, where 'file-extension' is
|
||||
a string (e.g. "gif") and 'content-type' is the value to write
|
||||
out in the Content-Type response header (e.g. "image/gif").
|
||||
|
||||
index
|
||||
If provided, it should be the (relative) name of a file to
|
||||
serve for directory requests. For example, if the dir argument is
|
||||
'/home/me', the Request-URI is 'myapp', and the index arg is
|
||||
'index.html', the file '/home/me/myapp/index.html' will be sought.
|
||||
"""
|
||||
request = cherrypy.serving.request
|
||||
if request.method not in ('GET', 'HEAD'):
|
||||
if debug:
|
||||
cherrypy.log('request.method not GET or HEAD', 'TOOLS.STATICDIR')
|
||||
return False
|
||||
|
||||
if match and not re.search(match, request.path_info):
|
||||
if debug:
|
||||
cherrypy.log('request.path_info %r does not match pattern %r' %
|
||||
(request.path_info, match), 'TOOLS.STATICDIR')
|
||||
return False
|
||||
|
||||
# Allow the use of '~' to refer to a user's home directory.
|
||||
dir = os.path.expanduser(dir)
|
||||
|
||||
# If dir is relative, make absolute using "root".
|
||||
if not os.path.isabs(dir):
|
||||
if not root:
|
||||
msg = "Static dir requires an absolute dir (or root)."
|
||||
if debug:
|
||||
cherrypy.log(msg, 'TOOLS.STATICDIR')
|
||||
raise ValueError(msg)
|
||||
dir = os.path.join(root, dir)
|
||||
|
||||
# Determine where we are in the object tree relative to 'section'
|
||||
# (where the static tool was defined).
|
||||
if section == 'global':
|
||||
section = "/"
|
||||
section = section.rstrip(r"\/")
|
||||
branch = request.path_info[len(section) + 1:]
|
||||
branch = unquote(branch.lstrip(r"\/"))
|
||||
|
||||
# If branch is "", filename will end in a slash
|
||||
filename = os.path.join(dir, branch)
|
||||
if debug:
|
||||
cherrypy.log('Checking file %r to fulfill %r' %
|
||||
(filename, request.path_info), 'TOOLS.STATICDIR')
|
||||
|
||||
# There's a chance that the branch pulled from the URL might
|
||||
# have ".." or similar uplevel attacks in it. Check that the final
|
||||
# filename is a child of dir.
|
||||
if not os.path.normpath(filename).startswith(os.path.normpath(dir)):
|
||||
raise cherrypy.HTTPError(403) # Forbidden
|
||||
|
||||
handled = _attempt(filename, content_types)
|
||||
if not handled:
|
||||
# Check for an index file if a folder was requested.
|
||||
if index:
|
||||
handled = _attempt(os.path.join(filename, index), content_types)
|
||||
if handled:
|
||||
request.is_index = filename[-1] in (r"\/")
|
||||
return handled
|
||||
|
||||
def staticfile(filename, root=None, match="", content_types=None, debug=False):
|
||||
"""Serve a static resource from the given (root +) filename.
|
||||
|
||||
match
|
||||
If given, request.path_info will be searched for the given
|
||||
regular expression before attempting to serve static content.
|
||||
|
||||
content_types
|
||||
If given, it should be a Python dictionary of
|
||||
{file-extension: content-type} pairs, where 'file-extension' is
|
||||
a string (e.g. "gif") and 'content-type' is the value to write
|
||||
out in the Content-Type response header (e.g. "image/gif").
|
||||
|
||||
"""
|
||||
request = cherrypy.serving.request
|
||||
if request.method not in ('GET', 'HEAD'):
|
||||
if debug:
|
||||
cherrypy.log('request.method not GET or HEAD', 'TOOLS.STATICFILE')
|
||||
return False
|
||||
|
||||
if match and not re.search(match, request.path_info):
|
||||
if debug:
|
||||
cherrypy.log('request.path_info %r does not match pattern %r' %
|
||||
(request.path_info, match), 'TOOLS.STATICFILE')
|
||||
return False
|
||||
|
||||
# If filename is relative, make absolute using "root".
|
||||
if not os.path.isabs(filename):
|
||||
if not root:
|
||||
msg = "Static tool requires an absolute filename (got '%s')." % filename
|
||||
if debug:
|
||||
cherrypy.log(msg, 'TOOLS.STATICFILE')
|
||||
raise ValueError(msg)
|
||||
filename = os.path.join(root, filename)
|
||||
|
||||
return _attempt(filename, content_types, debug=debug)
|
||||
55
python/packages/cherrypy/lib/xmlrpcutil.py
Normal file
55
python/packages/cherrypy/lib/xmlrpcutil.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import sys
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import ntob
|
||||
|
||||
def get_xmlrpclib():
|
||||
try:
|
||||
import xmlrpc.client as x
|
||||
except ImportError:
|
||||
import xmlrpclib as x
|
||||
return x
|
||||
|
||||
def process_body():
|
||||
"""Return (params, method) from request body."""
|
||||
try:
|
||||
return get_xmlrpclib().loads(cherrypy.request.body.read())
|
||||
except Exception:
|
||||
return ('ERROR PARAMS', ), 'ERRORMETHOD'
|
||||
|
||||
|
||||
def patched_path(path):
|
||||
"""Return 'path', doctored for RPC."""
|
||||
if not path.endswith('/'):
|
||||
path += '/'
|
||||
if path.startswith('/RPC2/'):
|
||||
# strip the first /rpc2
|
||||
path = path[5:]
|
||||
return path
|
||||
|
||||
|
||||
def _set_response(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 = ntob(body, 'utf-8')
|
||||
response.headers['Content-Type'] = 'text/xml'
|
||||
response.headers['Content-Length'] = len(body)
|
||||
|
||||
|
||||
def respond(body, encoding='utf-8', allow_none=0):
|
||||
xmlrpclib = get_xmlrpclib()
|
||||
if not isinstance(body, xmlrpclib.Fault):
|
||||
body = (body,)
|
||||
_set_response(xmlrpclib.dumps(body, methodresponse=1,
|
||||
encoding=encoding,
|
||||
allow_none=allow_none))
|
||||
|
||||
def on_error(*args, **kwargs):
|
||||
body = str(sys.exc_info()[1])
|
||||
xmlrpclib = get_xmlrpclib()
|
||||
_set_response(xmlrpclib.dumps(xmlrpclib.Fault(1, body)))
|
||||
|
||||
14
python/packages/cherrypy/process/__init__.py
Normal file
14
python/packages/cherrypy/process/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Site container for an HTTP server.
|
||||
|
||||
A Web Site Process Bus object is used to connect applications, servers,
|
||||
and frameworks with site-wide services such as daemonization, process
|
||||
reload, signal handling, drop privileges, PID file management, logging
|
||||
for all of these, and many more.
|
||||
|
||||
The 'plugins' module defines a few abstract and concrete services for
|
||||
use with the bus. Some use tool-specific channels; see the documentation
|
||||
for each class.
|
||||
"""
|
||||
|
||||
from cherrypy.process.wspbus import bus
|
||||
from cherrypy.process import plugins, servers
|
||||
683
python/packages/cherrypy/process/plugins.py
Normal file
683
python/packages/cherrypy/process/plugins.py
Normal file
File diff suppressed because it is too large
Load Diff
427
python/packages/cherrypy/process/servers.py
Normal file
427
python/packages/cherrypy/process/servers.py
Normal file
@@ -0,0 +1,427 @@
|
||||
"""
|
||||
Starting in CherryPy 3.1, cherrypy.server is implemented as an
|
||||
:ref:`Engine Plugin<plugins>`. It's an instance of
|
||||
:class:`cherrypy._cpserver.Server`, which is a subclass of
|
||||
:class:`cherrypy.process.servers.ServerAdapter`. The ``ServerAdapter`` class
|
||||
is designed to control other servers, as well.
|
||||
|
||||
Multiple servers/ports
|
||||
======================
|
||||
|
||||
If you need to start more than one HTTP server (to serve on multiple ports, or
|
||||
protocols, etc.), you can manually register each one and then start them all
|
||||
with engine.start::
|
||||
|
||||
s1 = ServerAdapter(cherrypy.engine, MyWSGIServer(host='0.0.0.0', port=80))
|
||||
s2 = ServerAdapter(cherrypy.engine, another.HTTPServer(host='127.0.0.1', SSL=True))
|
||||
s1.subscribe()
|
||||
s2.subscribe()
|
||||
cherrypy.engine.start()
|
||||
|
||||
.. index:: SCGI
|
||||
|
||||
FastCGI/SCGI
|
||||
============
|
||||
|
||||
There are also Flup\ **F**\ CGIServer and Flup\ **S**\ CGIServer classes in
|
||||
:mod:`cherrypy.process.servers`. To start an fcgi server, for example,
|
||||
wrap an instance of it in a ServerAdapter::
|
||||
|
||||
addr = ('0.0.0.0', 4000)
|
||||
f = servers.FlupFCGIServer(application=cherrypy.tree, bindAddress=addr)
|
||||
s = servers.ServerAdapter(cherrypy.engine, httpserver=f, bind_addr=addr)
|
||||
s.subscribe()
|
||||
|
||||
The :doc:`cherryd</deployguide/cherryd>` startup script will do the above for
|
||||
you via its `-f` flag.
|
||||
Note that you need to download and install `flup <http://trac.saddi.com/flup>`_
|
||||
yourself, whether you use ``cherryd`` or not.
|
||||
|
||||
.. _fastcgi:
|
||||
.. index:: FastCGI
|
||||
|
||||
FastCGI
|
||||
-------
|
||||
|
||||
A very simple setup lets your cherry run with FastCGI.
|
||||
You just need the flup library,
|
||||
plus a running Apache server (with ``mod_fastcgi``) or lighttpd server.
|
||||
|
||||
CherryPy code
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
hello.py::
|
||||
|
||||
#!/usr/bin/python
|
||||
import cherrypy
|
||||
|
||||
class HelloWorld:
|
||||
\"""Sample request handler class.\"""
|
||||
def index(self):
|
||||
return "Hello world!"
|
||||
index.exposed = True
|
||||
|
||||
cherrypy.tree.mount(HelloWorld())
|
||||
# CherryPy autoreload must be disabled for the flup server to work
|
||||
cherrypy.config.update({'engine.autoreload_on':False})
|
||||
|
||||
Then run :doc:`/deployguide/cherryd` with the '-f' arg::
|
||||
|
||||
cherryd -c <myconfig> -d -f -i hello.py
|
||||
|
||||
Apache
|
||||
^^^^^^
|
||||
|
||||
At the top level in httpd.conf::
|
||||
|
||||
FastCgiIpcDir /tmp
|
||||
FastCgiServer /path/to/cherry.fcgi -idle-timeout 120 -processes 4
|
||||
|
||||
And inside the relevant VirtualHost section::
|
||||
|
||||
# FastCGI config
|
||||
AddHandler fastcgi-script .fcgi
|
||||
ScriptAliasMatch (.*$) /path/to/cherry.fcgi$1
|
||||
|
||||
Lighttpd
|
||||
^^^^^^^^
|
||||
|
||||
For `Lighttpd <http://www.lighttpd.net/>`_ you can follow these
|
||||
instructions. Within ``lighttpd.conf`` make sure ``mod_fastcgi`` is
|
||||
active within ``server.modules``. Then, within your ``$HTTP["host"]``
|
||||
directive, configure your fastcgi script like the following::
|
||||
|
||||
$HTTP["url"] =~ "" {
|
||||
fastcgi.server = (
|
||||
"/" => (
|
||||
"script.fcgi" => (
|
||||
"bin-path" => "/path/to/your/script.fcgi",
|
||||
"socket" => "/tmp/script.sock",
|
||||
"check-local" => "disable",
|
||||
"disable-time" => 1,
|
||||
"min-procs" => 1,
|
||||
"max-procs" => 1, # adjust as needed
|
||||
),
|
||||
),
|
||||
)
|
||||
} # end of $HTTP["url"] =~ "^/"
|
||||
|
||||
Please see `Lighttpd FastCGI Docs
|
||||
<http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModFastCGI>`_ for an explanation
|
||||
of the possible configuration options.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
class ServerAdapter(object):
|
||||
"""Adapter for an HTTP server.
|
||||
|
||||
If you need to start more than one HTTP server (to serve on multiple
|
||||
ports, or protocols, etc.), you can manually register each one and then
|
||||
start them all with bus.start:
|
||||
|
||||
s1 = ServerAdapter(bus, MyWSGIServer(host='0.0.0.0', port=80))
|
||||
s2 = ServerAdapter(bus, another.HTTPServer(host='127.0.0.1', SSL=True))
|
||||
s1.subscribe()
|
||||
s2.subscribe()
|
||||
bus.start()
|
||||
"""
|
||||
|
||||
def __init__(self, bus, httpserver=None, bind_addr=None):
|
||||
self.bus = bus
|
||||
self.httpserver = httpserver
|
||||
self.bind_addr = bind_addr
|
||||
self.interrupt = None
|
||||
self.running = False
|
||||
|
||||
def subscribe(self):
|
||||
self.bus.subscribe('start', self.start)
|
||||
self.bus.subscribe('stop', self.stop)
|
||||
|
||||
def unsubscribe(self):
|
||||
self.bus.unsubscribe('start', self.start)
|
||||
self.bus.unsubscribe('stop', self.stop)
|
||||
|
||||
def start(self):
|
||||
"""Start the HTTP server."""
|
||||
if self.bind_addr is None:
|
||||
on_what = "unknown interface (dynamic?)"
|
||||
elif isinstance(self.bind_addr, tuple):
|
||||
host, port = self.bind_addr
|
||||
on_what = "%s:%s" % (host, port)
|
||||
else:
|
||||
on_what = "socket file: %s" % self.bind_addr
|
||||
|
||||
if self.running:
|
||||
self.bus.log("Already serving on %s" % on_what)
|
||||
return
|
||||
|
||||
self.interrupt = None
|
||||
if not self.httpserver:
|
||||
raise ValueError("No HTTP server has been created.")
|
||||
|
||||
# Start the httpserver in a new thread.
|
||||
if isinstance(self.bind_addr, tuple):
|
||||
wait_for_free_port(*self.bind_addr)
|
||||
|
||||
import threading
|
||||
t = threading.Thread(target=self._start_http_thread)
|
||||
t.setName("HTTPServer " + t.getName())
|
||||
t.start()
|
||||
|
||||
self.wait()
|
||||
self.running = True
|
||||
self.bus.log("Serving on %s" % on_what)
|
||||
start.priority = 75
|
||||
|
||||
def _start_http_thread(self):
|
||||
"""HTTP servers MUST be running in new threads, so that the
|
||||
main thread persists to receive KeyboardInterrupt's. If an
|
||||
exception is raised in the httpserver's thread then it's
|
||||
trapped here, and the bus (and therefore our httpserver)
|
||||
are shut down.
|
||||
"""
|
||||
try:
|
||||
self.httpserver.start()
|
||||
except KeyboardInterrupt:
|
||||
self.bus.log("<Ctrl-C> hit: shutting down HTTP server")
|
||||
self.interrupt = sys.exc_info()[1]
|
||||
self.bus.exit()
|
||||
except SystemExit:
|
||||
self.bus.log("SystemExit raised: shutting down HTTP server")
|
||||
self.interrupt = sys.exc_info()[1]
|
||||
self.bus.exit()
|
||||
raise
|
||||
except:
|
||||
self.interrupt = sys.exc_info()[1]
|
||||
self.bus.log("Error in HTTP server: shutting down",
|
||||
traceback=True, level=40)
|
||||
self.bus.exit()
|
||||
raise
|
||||
|
||||
def wait(self):
|
||||
"""Wait until the HTTP server is ready to receive requests."""
|
||||
while not getattr(self.httpserver, "ready", False):
|
||||
if self.interrupt:
|
||||
raise self.interrupt
|
||||
time.sleep(.1)
|
||||
|
||||
# Wait for port to be occupied
|
||||
if isinstance(self.bind_addr, tuple):
|
||||
host, port = self.bind_addr
|
||||
wait_for_occupied_port(host, port)
|
||||
|
||||
def stop(self):
|
||||
"""Stop the HTTP server."""
|
||||
if self.running:
|
||||
# stop() MUST block until the server is *truly* stopped.
|
||||
self.httpserver.stop()
|
||||
# Wait for the socket to be truly freed.
|
||||
if isinstance(self.bind_addr, tuple):
|
||||
wait_for_free_port(*self.bind_addr)
|
||||
self.running = False
|
||||
self.bus.log("HTTP Server %s shut down" % self.httpserver)
|
||||
else:
|
||||
self.bus.log("HTTP Server %s already shut down" % self.httpserver)
|
||||
stop.priority = 25
|
||||
|
||||
def restart(self):
|
||||
"""Restart the HTTP server."""
|
||||
self.stop()
|
||||
self.start()
|
||||
|
||||
|
||||
class FlupCGIServer(object):
|
||||
"""Adapter for a flup.server.cgi.WSGIServer."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.ready = False
|
||||
|
||||
def start(self):
|
||||
"""Start the CGI server."""
|
||||
# We have to instantiate the server class here because its __init__
|
||||
# starts a threadpool. If we do it too early, daemonize won't work.
|
||||
from flup.server.cgi import WSGIServer
|
||||
|
||||
self.cgiserver = WSGIServer(*self.args, **self.kwargs)
|
||||
self.ready = True
|
||||
self.cgiserver.run()
|
||||
|
||||
def stop(self):
|
||||
"""Stop the HTTP server."""
|
||||
self.ready = False
|
||||
|
||||
|
||||
class FlupFCGIServer(object):
|
||||
"""Adapter for a flup.server.fcgi.WSGIServer."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if kwargs.get('bindAddress', None) is None:
|
||||
import socket
|
||||
if not hasattr(socket, 'fromfd'):
|
||||
raise ValueError(
|
||||
'Dynamic FCGI server not available on this platform. '
|
||||
'You must use a static or external one by providing a '
|
||||
'legal bindAddress.')
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.ready = False
|
||||
|
||||
def start(self):
|
||||
"""Start the FCGI server."""
|
||||
# We have to instantiate the server class here because its __init__
|
||||
# starts a threadpool. If we do it too early, daemonize won't work.
|
||||
from flup.server.fcgi import WSGIServer
|
||||
self.fcgiserver = WSGIServer(*self.args, **self.kwargs)
|
||||
# TODO: report this bug upstream to flup.
|
||||
# If we don't set _oldSIGs on Windows, we get:
|
||||
# File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py",
|
||||
# line 108, in run
|
||||
# self._restoreSignalHandlers()
|
||||
# File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py",
|
||||
# line 156, in _restoreSignalHandlers
|
||||
# for signum,handler in self._oldSIGs:
|
||||
# AttributeError: 'WSGIServer' object has no attribute '_oldSIGs'
|
||||
self.fcgiserver._installSignalHandlers = lambda: None
|
||||
self.fcgiserver._oldSIGs = []
|
||||
self.ready = True
|
||||
self.fcgiserver.run()
|
||||
|
||||
def stop(self):
|
||||
"""Stop the HTTP server."""
|
||||
# Forcibly stop the fcgi server main event loop.
|
||||
self.fcgiserver._keepGoing = False
|
||||
# Force all worker threads to die off.
|
||||
self.fcgiserver._threadPool.maxSpare = self.fcgiserver._threadPool._idleCount
|
||||
self.ready = False
|
||||
|
||||
|
||||
class FlupSCGIServer(object):
|
||||
"""Adapter for a flup.server.scgi.WSGIServer."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.ready = False
|
||||
|
||||
def start(self):
|
||||
"""Start the SCGI server."""
|
||||
# We have to instantiate the server class here because its __init__
|
||||
# starts a threadpool. If we do it too early, daemonize won't work.
|
||||
from flup.server.scgi import WSGIServer
|
||||
self.scgiserver = WSGIServer(*self.args, **self.kwargs)
|
||||
# TODO: report this bug upstream to flup.
|
||||
# If we don't set _oldSIGs on Windows, we get:
|
||||
# File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py",
|
||||
# line 108, in run
|
||||
# self._restoreSignalHandlers()
|
||||
# File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py",
|
||||
# line 156, in _restoreSignalHandlers
|
||||
# for signum,handler in self._oldSIGs:
|
||||
# AttributeError: 'WSGIServer' object has no attribute '_oldSIGs'
|
||||
self.scgiserver._installSignalHandlers = lambda: None
|
||||
self.scgiserver._oldSIGs = []
|
||||
self.ready = True
|
||||
self.scgiserver.run()
|
||||
|
||||
def stop(self):
|
||||
"""Stop the HTTP server."""
|
||||
self.ready = False
|
||||
# Forcibly stop the scgi server main event loop.
|
||||
self.scgiserver._keepGoing = False
|
||||
# Force all worker threads to die off.
|
||||
self.scgiserver._threadPool.maxSpare = 0
|
||||
|
||||
|
||||
def client_host(server_host):
|
||||
"""Return the host on which a client can connect to the given listener."""
|
||||
if server_host == '0.0.0.0':
|
||||
# 0.0.0.0 is INADDR_ANY, which should answer on localhost.
|
||||
return '127.0.0.1'
|
||||
if server_host in ('::', '::0', '::0.0.0.0'):
|
||||
# :: is IN6ADDR_ANY, which should answer on localhost.
|
||||
# ::0 and ::0.0.0.0 are non-canonical but common ways to write IN6ADDR_ANY.
|
||||
return '::1'
|
||||
return server_host
|
||||
|
||||
def check_port(host, port, timeout=1.0):
|
||||
"""Raise an error if the given port is not free on the given host."""
|
||||
if not host:
|
||||
raise ValueError("Host values of '' or None are not allowed.")
|
||||
host = client_host(host)
|
||||
port = int(port)
|
||||
|
||||
import socket
|
||||
|
||||
# AF_INET or AF_INET6 socket
|
||||
# Get the correct address family for our host (allows IPv6 addresses)
|
||||
try:
|
||||
info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
|
||||
socket.SOCK_STREAM)
|
||||
except socket.gaierror:
|
||||
if ':' in host:
|
||||
info = [(socket.AF_INET6, socket.SOCK_STREAM, 0, "", (host, port, 0, 0))]
|
||||
else:
|
||||
info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", (host, port))]
|
||||
|
||||
for res in info:
|
||||
af, socktype, proto, canonname, sa = res
|
||||
s = None
|
||||
try:
|
||||
s = socket.socket(af, socktype, proto)
|
||||
# See http://groups.google.com/group/cherrypy-users/
|
||||
# browse_frm/thread/bbfe5eb39c904fe0
|
||||
s.settimeout(timeout)
|
||||
s.connect((host, port))
|
||||
s.close()
|
||||
raise IOError("Port %s is in use on %s; perhaps the previous "
|
||||
"httpserver did not shut down properly." %
|
||||
(repr(port), repr(host)))
|
||||
except socket.error:
|
||||
if s:
|
||||
s.close()
|
||||
|
||||
|
||||
# Feel free to increase these defaults on slow systems:
|
||||
free_port_timeout = 0.1
|
||||
occupied_port_timeout = 1.0
|
||||
|
||||
def wait_for_free_port(host, port, timeout=None):
|
||||
"""Wait for the specified port to become free (drop requests)."""
|
||||
if not host:
|
||||
raise ValueError("Host values of '' or None are not allowed.")
|
||||
if timeout is None:
|
||||
timeout = free_port_timeout
|
||||
|
||||
for trial in range(50):
|
||||
try:
|
||||
# we are expecting a free port, so reduce the timeout
|
||||
check_port(host, port, timeout=timeout)
|
||||
except IOError:
|
||||
# Give the old server thread time to free the port.
|
||||
time.sleep(timeout)
|
||||
else:
|
||||
return
|
||||
|
||||
raise IOError("Port %r not free on %r" % (port, host))
|
||||
|
||||
def wait_for_occupied_port(host, port, timeout=None):
|
||||
"""Wait for the specified port to become active (receive requests)."""
|
||||
if not host:
|
||||
raise ValueError("Host values of '' or None are not allowed.")
|
||||
if timeout is None:
|
||||
timeout = occupied_port_timeout
|
||||
|
||||
for trial in range(50):
|
||||
try:
|
||||
check_port(host, port, timeout=timeout)
|
||||
except IOError:
|
||||
return
|
||||
else:
|
||||
time.sleep(timeout)
|
||||
|
||||
raise IOError("Port %r not bound on %r" % (port, host))
|
||||
174
python/packages/cherrypy/process/win32.py
Normal file
174
python/packages/cherrypy/process/win32.py
Normal file
@@ -0,0 +1,174 @@
|
||||
"""Windows service. Requires pywin32."""
|
||||
|
||||
import os
|
||||
import win32api
|
||||
import win32con
|
||||
import win32event
|
||||
import win32service
|
||||
import win32serviceutil
|
||||
|
||||
from cherrypy.process import wspbus, plugins
|
||||
|
||||
|
||||
class ConsoleCtrlHandler(plugins.SimplePlugin):
|
||||
"""A WSPBus plugin for handling Win32 console events (like Ctrl-C)."""
|
||||
|
||||
def __init__(self, bus):
|
||||
self.is_set = False
|
||||
plugins.SimplePlugin.__init__(self, bus)
|
||||
|
||||
def start(self):
|
||||
if self.is_set:
|
||||
self.bus.log('Handler for console events already set.', level=40)
|
||||
return
|
||||
|
||||
result = win32api.SetConsoleCtrlHandler(self.handle, 1)
|
||||
if result == 0:
|
||||
self.bus.log('Could not SetConsoleCtrlHandler (error %r)' %
|
||||
win32api.GetLastError(), level=40)
|
||||
else:
|
||||
self.bus.log('Set handler for console events.', level=40)
|
||||
self.is_set = True
|
||||
|
||||
def stop(self):
|
||||
if not self.is_set:
|
||||
self.bus.log('Handler for console events already off.', level=40)
|
||||
return
|
||||
|
||||
try:
|
||||
result = win32api.SetConsoleCtrlHandler(self.handle, 0)
|
||||
except ValueError:
|
||||
# "ValueError: The object has not been registered"
|
||||
result = 1
|
||||
|
||||
if result == 0:
|
||||
self.bus.log('Could not remove SetConsoleCtrlHandler (error %r)' %
|
||||
win32api.GetLastError(), level=40)
|
||||
else:
|
||||
self.bus.log('Removed handler for console events.', level=40)
|
||||
self.is_set = False
|
||||
|
||||
def handle(self, event):
|
||||
"""Handle console control events (like Ctrl-C)."""
|
||||
if event in (win32con.CTRL_C_EVENT, win32con.CTRL_LOGOFF_EVENT,
|
||||
win32con.CTRL_BREAK_EVENT, win32con.CTRL_SHUTDOWN_EVENT,
|
||||
win32con.CTRL_CLOSE_EVENT):
|
||||
self.bus.log('Console event %s: shutting down bus' % event)
|
||||
|
||||
# Remove self immediately so repeated Ctrl-C doesn't re-call it.
|
||||
try:
|
||||
self.stop()
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
self.bus.exit()
|
||||
# 'First to return True stops the calls'
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
class Win32Bus(wspbus.Bus):
|
||||
"""A Web Site Process Bus implementation for Win32.
|
||||
|
||||
Instead of time.sleep, this bus blocks using native win32event objects.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.events = {}
|
||||
wspbus.Bus.__init__(self)
|
||||
|
||||
def _get_state_event(self, state):
|
||||
"""Return a win32event for the given state (creating it if needed)."""
|
||||
try:
|
||||
return self.events[state]
|
||||
except KeyError:
|
||||
event = win32event.CreateEvent(None, 0, 0,
|
||||
"WSPBus %s Event (pid=%r)" %
|
||||
(state.name, os.getpid()))
|
||||
self.events[state] = event
|
||||
return event
|
||||
|
||||
def _get_state(self):
|
||||
return self._state
|
||||
def _set_state(self, value):
|
||||
self._state = value
|
||||
event = self._get_state_event(value)
|
||||
win32event.PulseEvent(event)
|
||||
state = property(_get_state, _set_state)
|
||||
|
||||
def wait(self, state, interval=0.1, channel=None):
|
||||
"""Wait for the given state(s), KeyboardInterrupt or SystemExit.
|
||||
|
||||
Since this class uses native win32event objects, the interval
|
||||
argument is ignored.
|
||||
"""
|
||||
if isinstance(state, (tuple, list)):
|
||||
# Don't wait for an event that beat us to the punch ;)
|
||||
if self.state not in state:
|
||||
events = tuple([self._get_state_event(s) for s in state])
|
||||
win32event.WaitForMultipleObjects(events, 0, win32event.INFINITE)
|
||||
else:
|
||||
# Don't wait for an event that beat us to the punch ;)
|
||||
if self.state != state:
|
||||
event = self._get_state_event(state)
|
||||
win32event.WaitForSingleObject(event, win32event.INFINITE)
|
||||
|
||||
|
||||
class _ControlCodes(dict):
|
||||
"""Control codes used to "signal" a service via ControlService.
|
||||
|
||||
User-defined control codes are in the range 128-255. We generally use
|
||||
the standard Python value for the Linux signal and add 128. Example:
|
||||
|
||||
>>> signal.SIGUSR1
|
||||
10
|
||||
control_codes['graceful'] = 128 + 10
|
||||
"""
|
||||
|
||||
def key_for(self, obj):
|
||||
"""For the given value, return its corresponding key."""
|
||||
for key, val in self.items():
|
||||
if val is obj:
|
||||
return key
|
||||
raise ValueError("The given object could not be found: %r" % obj)
|
||||
|
||||
control_codes = _ControlCodes({'graceful': 138})
|
||||
|
||||
|
||||
def signal_child(service, command):
|
||||
if command == 'stop':
|
||||
win32serviceutil.StopService(service)
|
||||
elif command == 'restart':
|
||||
win32serviceutil.RestartService(service)
|
||||
else:
|
||||
win32serviceutil.ControlService(service, control_codes[command])
|
||||
|
||||
|
||||
class PyWebService(win32serviceutil.ServiceFramework):
|
||||
"""Python Web Service."""
|
||||
|
||||
_svc_name_ = "Python Web Service"
|
||||
_svc_display_name_ = "Python Web Service"
|
||||
_svc_deps_ = None # sequence of service names on which this depends
|
||||
_exe_name_ = "pywebsvc"
|
||||
_exe_args_ = None # Default to no arguments
|
||||
|
||||
# Only exists on Windows 2000 or later, ignored on windows NT
|
||||
_svc_description_ = "Python Web Service"
|
||||
|
||||
def SvcDoRun(self):
|
||||
from cherrypy import process
|
||||
process.bus.start()
|
||||
process.bus.block()
|
||||
|
||||
def SvcStop(self):
|
||||
from cherrypy import process
|
||||
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
||||
process.bus.exit()
|
||||
|
||||
def SvcOther(self, control):
|
||||
process.bus.publish(control_codes.key_for(control))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
win32serviceutil.HandleCommandLine(PyWebService)
|
||||
432
python/packages/cherrypy/process/wspbus.py
Normal file
432
python/packages/cherrypy/process/wspbus.py
Normal file
@@ -0,0 +1,432 @@
|
||||
"""An implementation of the Web Site Process Bus.
|
||||
|
||||
This module is completely standalone, depending only on the stdlib.
|
||||
|
||||
Web Site Process Bus
|
||||
--------------------
|
||||
|
||||
A Bus object is used to contain and manage site-wide behavior:
|
||||
daemonization, HTTP server start/stop, process reload, signal handling,
|
||||
drop privileges, PID file management, logging for all of these,
|
||||
and many more.
|
||||
|
||||
In addition, a Bus object provides a place for each web framework
|
||||
to register code that runs in response to site-wide events (like
|
||||
process start and stop), or which controls or otherwise interacts with
|
||||
the site-wide components mentioned above. For example, a framework which
|
||||
uses file-based templates would add known template filenames to an
|
||||
autoreload component.
|
||||
|
||||
Ideally, a Bus object will be flexible enough to be useful in a variety
|
||||
of invocation scenarios:
|
||||
|
||||
1. The deployer starts a site from the command line via a
|
||||
framework-neutral deployment script; applications from multiple frameworks
|
||||
are mixed in a single site. Command-line arguments and configuration
|
||||
files are used to define site-wide components such as the HTTP server,
|
||||
WSGI component graph, autoreload behavior, signal handling, etc.
|
||||
2. The deployer starts a site via some other process, such as Apache;
|
||||
applications from multiple frameworks are mixed in a single site.
|
||||
Autoreload and signal handling (from Python at least) are disabled.
|
||||
3. The deployer starts a site via a framework-specific mechanism;
|
||||
for example, when running tests, exploring tutorials, or deploying
|
||||
single applications from a single framework. The framework controls
|
||||
which site-wide components are enabled as it sees fit.
|
||||
|
||||
The Bus object in this package uses topic-based publish-subscribe
|
||||
messaging to accomplish all this. A few topic channels are built in
|
||||
('start', 'stop', 'exit', 'graceful', 'log', and 'main'). Frameworks and
|
||||
site containers are free to define their own. If a message is sent to a
|
||||
channel that has not been defined or has no listeners, there is no effect.
|
||||
|
||||
In general, there should only ever be a single Bus object per process.
|
||||
Frameworks and site containers share a single Bus object by publishing
|
||||
messages and subscribing listeners.
|
||||
|
||||
The Bus object works as a finite state machine which models the current
|
||||
state of the process. Bus methods move it from one state to another;
|
||||
those methods then publish to subscribed listeners on the channel for
|
||||
the new state.::
|
||||
|
||||
O
|
||||
|
|
||||
V
|
||||
STOPPING --> STOPPED --> EXITING -> X
|
||||
A A |
|
||||
| \___ |
|
||||
| \ |
|
||||
| V V
|
||||
STARTED <-- STARTING
|
||||
|
||||
"""
|
||||
|
||||
import atexit
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import traceback as _traceback
|
||||
import warnings
|
||||
|
||||
from cherrypy._cpcompat import set
|
||||
|
||||
# Here I save the value of os.getcwd(), which, if I am imported early enough,
|
||||
# will be the directory from which the startup script was run. This is needed
|
||||
# by _do_execv(), to change back to the original directory before execv()ing a
|
||||
# new process. This is a defense against the application having changed the
|
||||
# current working directory (which could make sys.executable "not found" if
|
||||
# sys.executable is a relative-path, and/or cause other problems).
|
||||
_startup_cwd = os.getcwd()
|
||||
|
||||
class ChannelFailures(Exception):
|
||||
"""Exception raised when errors occur in a listener during Bus.publish()."""
|
||||
delimiter = '\n'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Don't use 'super' here; Exceptions are old-style in Py2.4
|
||||
# See http://www.cherrypy.org/ticket/959
|
||||
Exception.__init__(self, *args, **kwargs)
|
||||
self._exceptions = list()
|
||||
|
||||
def handle_exception(self):
|
||||
"""Append the current exception to self."""
|
||||
self._exceptions.append(sys.exc_info()[1])
|
||||
|
||||
def get_instances(self):
|
||||
"""Return a list of seen exception instances."""
|
||||
return self._exceptions[:]
|
||||
|
||||
def __str__(self):
|
||||
exception_strings = map(repr, self.get_instances())
|
||||
return self.delimiter.join(exception_strings)
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self._exceptions)
|
||||
__nonzero__ = __bool__
|
||||
|
||||
# Use a flag to indicate the state of the bus.
|
||||
class _StateEnum(object):
|
||||
class State(object):
|
||||
name = None
|
||||
def __repr__(self):
|
||||
return "states.%s" % self.name
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if isinstance(value, self.State):
|
||||
value.name = key
|
||||
object.__setattr__(self, key, value)
|
||||
states = _StateEnum()
|
||||
states.STOPPED = states.State()
|
||||
states.STARTING = states.State()
|
||||
states.STARTED = states.State()
|
||||
states.STOPPING = states.State()
|
||||
states.EXITING = states.State()
|
||||
|
||||
|
||||
try:
|
||||
import fcntl
|
||||
except ImportError:
|
||||
max_files = 0
|
||||
else:
|
||||
try:
|
||||
max_files = os.sysconf('SC_OPEN_MAX')
|
||||
except AttributeError:
|
||||
max_files = 1024
|
||||
|
||||
|
||||
class Bus(object):
|
||||
"""Process state-machine and messenger for HTTP site deployment.
|
||||
|
||||
All listeners for a given channel are guaranteed to be called even
|
||||
if others at the same channel fail. Each failure is logged, but
|
||||
execution proceeds on to the next listener. The only way to stop all
|
||||
processing from inside a listener is to raise SystemExit and stop the
|
||||
whole server.
|
||||
"""
|
||||
|
||||
states = states
|
||||
state = states.STOPPED
|
||||
execv = False
|
||||
max_cloexec_files = max_files
|
||||
|
||||
def __init__(self):
|
||||
self.execv = False
|
||||
self.state = states.STOPPED
|
||||
self.listeners = dict(
|
||||
[(channel, set()) for channel
|
||||
in ('start', 'stop', 'exit', 'graceful', 'log', 'main')])
|
||||
self._priorities = {}
|
||||
|
||||
def subscribe(self, channel, callback, priority=None):
|
||||
"""Add the given callback at the given channel (if not present)."""
|
||||
if channel not in self.listeners:
|
||||
self.listeners[channel] = set()
|
||||
self.listeners[channel].add(callback)
|
||||
|
||||
if priority is None:
|
||||
priority = getattr(callback, 'priority', 50)
|
||||
self._priorities[(channel, callback)] = priority
|
||||
|
||||
def unsubscribe(self, channel, callback):
|
||||
"""Discard the given callback (if present)."""
|
||||
listeners = self.listeners.get(channel)
|
||||
if listeners and callback in listeners:
|
||||
listeners.discard(callback)
|
||||
del self._priorities[(channel, callback)]
|
||||
|
||||
def publish(self, channel, *args, **kwargs):
|
||||
"""Return output of all subscribers for the given channel."""
|
||||
if channel not in self.listeners:
|
||||
return []
|
||||
|
||||
exc = ChannelFailures()
|
||||
output = []
|
||||
|
||||
items = [(self._priorities[(channel, listener)], listener)
|
||||
for listener in self.listeners[channel]]
|
||||
try:
|
||||
items.sort(key=lambda item: item[0])
|
||||
except TypeError:
|
||||
# Python 2.3 had no 'key' arg, but that doesn't matter
|
||||
# since it could sort dissimilar types just fine.
|
||||
items.sort()
|
||||
for priority, listener in items:
|
||||
try:
|
||||
output.append(listener(*args, **kwargs))
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except SystemExit:
|
||||
e = sys.exc_info()[1]
|
||||
# If we have previous errors ensure the exit code is non-zero
|
||||
if exc and e.code == 0:
|
||||
e.code = 1
|
||||
raise
|
||||
except:
|
||||
exc.handle_exception()
|
||||
if channel == 'log':
|
||||
# Assume any further messages to 'log' will fail.
|
||||
pass
|
||||
else:
|
||||
self.log("Error in %r listener %r" % (channel, listener),
|
||||
level=40, traceback=True)
|
||||
if exc:
|
||||
raise exc
|
||||
return output
|
||||
|
||||
def _clean_exit(self):
|
||||
"""An atexit handler which asserts the Bus is not running."""
|
||||
if self.state != states.EXITING:
|
||||
warnings.warn(
|
||||
"The main thread is exiting, but the Bus is in the %r state; "
|
||||
"shutting it down automatically now. You must either call "
|
||||
"bus.block() after start(), or call bus.exit() before the "
|
||||
"main thread exits." % self.state, RuntimeWarning)
|
||||
self.exit()
|
||||
|
||||
def start(self):
|
||||
"""Start all services."""
|
||||
atexit.register(self._clean_exit)
|
||||
|
||||
self.state = states.STARTING
|
||||
self.log('Bus STARTING')
|
||||
try:
|
||||
self.publish('start')
|
||||
self.state = states.STARTED
|
||||
self.log('Bus STARTED')
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
self.log("Shutting down due to error in start listener:",
|
||||
level=40, traceback=True)
|
||||
e_info = sys.exc_info()[1]
|
||||
try:
|
||||
self.exit()
|
||||
except:
|
||||
# Any stop/exit errors will be logged inside publish().
|
||||
pass
|
||||
# Re-raise the original error
|
||||
raise e_info
|
||||
|
||||
def exit(self):
|
||||
"""Stop all services and prepare to exit the process."""
|
||||
exitstate = self.state
|
||||
try:
|
||||
self.stop()
|
||||
|
||||
self.state = states.EXITING
|
||||
self.log('Bus EXITING')
|
||||
self.publish('exit')
|
||||
# This isn't strictly necessary, but it's better than seeing
|
||||
# "Waiting for child threads to terminate..." and then nothing.
|
||||
self.log('Bus EXITED')
|
||||
except:
|
||||
# This method is often called asynchronously (whether thread,
|
||||
# signal handler, console handler, or atexit handler), so we
|
||||
# can't just let exceptions propagate out unhandled.
|
||||
# Assume it's been logged and just die.
|
||||
os._exit(70) # EX_SOFTWARE
|
||||
|
||||
if exitstate == states.STARTING:
|
||||
# exit() was called before start() finished, possibly due to
|
||||
# Ctrl-C because a start listener got stuck. In this case,
|
||||
# we could get stuck in a loop where Ctrl-C never exits the
|
||||
# process, so we just call os.exit here.
|
||||
os._exit(70) # EX_SOFTWARE
|
||||
|
||||
def restart(self):
|
||||
"""Restart the process (may close connections).
|
||||
|
||||
This method does not restart the process from the calling thread;
|
||||
instead, it stops the bus and asks the main thread to call execv.
|
||||
"""
|
||||
self.execv = True
|
||||
self.exit()
|
||||
|
||||
def graceful(self):
|
||||
"""Advise all services to reload."""
|
||||
self.log('Bus graceful')
|
||||
self.publish('graceful')
|
||||
|
||||
def block(self, interval=0.1):
|
||||
"""Wait for the EXITING state, KeyboardInterrupt or SystemExit.
|
||||
|
||||
This function is intended to be called only by the main thread.
|
||||
After waiting for the EXITING state, it also waits for all threads
|
||||
to terminate, and then calls os.execv if self.execv is True. This
|
||||
design allows another thread to call bus.restart, yet have the main
|
||||
thread perform the actual execv call (required on some platforms).
|
||||
"""
|
||||
try:
|
||||
self.wait(states.EXITING, interval=interval, channel='main')
|
||||
except (KeyboardInterrupt, IOError):
|
||||
# The time.sleep call might raise
|
||||
# "IOError: [Errno 4] Interrupted function call" on KBInt.
|
||||
self.log('Keyboard Interrupt: shutting down bus')
|
||||
self.exit()
|
||||
except SystemExit:
|
||||
self.log('SystemExit raised: shutting down bus')
|
||||
self.exit()
|
||||
raise
|
||||
|
||||
# Waiting for ALL child threads to finish is necessary on OS X.
|
||||
# See http://www.cherrypy.org/ticket/581.
|
||||
# It's also good to let them all shut down before allowing
|
||||
# the main thread to call atexit handlers.
|
||||
# See http://www.cherrypy.org/ticket/751.
|
||||
self.log("Waiting for child threads to terminate...")
|
||||
for t in threading.enumerate():
|
||||
if t != threading.currentThread() and t.isAlive():
|
||||
# Note that any dummy (external) threads are always daemonic.
|
||||
if hasattr(threading.Thread, "daemon"):
|
||||
# Python 2.6+
|
||||
d = t.daemon
|
||||
else:
|
||||
d = t.isDaemon()
|
||||
if not d:
|
||||
self.log("Waiting for thread %s." % t.getName())
|
||||
t.join()
|
||||
|
||||
if self.execv:
|
||||
self._do_execv()
|
||||
|
||||
def wait(self, state, interval=0.1, channel=None):
|
||||
"""Poll for the given state(s) at intervals; publish to channel."""
|
||||
if isinstance(state, (tuple, list)):
|
||||
states = state
|
||||
else:
|
||||
states = [state]
|
||||
|
||||
def _wait():
|
||||
while self.state not in states:
|
||||
time.sleep(interval)
|
||||
self.publish(channel)
|
||||
|
||||
# From http://psyco.sourceforge.net/psycoguide/bugs.html:
|
||||
# "The compiled machine code does not include the regular polling
|
||||
# done by Python, meaning that a KeyboardInterrupt will not be
|
||||
# detected before execution comes back to the regular Python
|
||||
# interpreter. Your program cannot be interrupted if caught
|
||||
# into an infinite Psyco-compiled loop."
|
||||
try:
|
||||
sys.modules['psyco'].cannotcompile(_wait)
|
||||
except (KeyError, AttributeError):
|
||||
pass
|
||||
|
||||
_wait()
|
||||
|
||||
def _do_execv(self):
|
||||
"""Re-execute the current process.
|
||||
|
||||
This must be called from the main thread, because certain platforms
|
||||
(OS X) don't allow execv to be called in a child thread very well.
|
||||
"""
|
||||
args = sys.argv[:]
|
||||
self.log('Re-spawning %s' % ' '.join(args))
|
||||
|
||||
if sys.platform[:4] == 'java':
|
||||
from _systemrestart import SystemRestart
|
||||
raise SystemRestart
|
||||
else:
|
||||
args.insert(0, sys.executable)
|
||||
if sys.platform == 'win32':
|
||||
args = ['"%s"' % arg for arg in args]
|
||||
|
||||
os.chdir(_startup_cwd)
|
||||
if self.max_cloexec_files:
|
||||
self._set_cloexec()
|
||||
os.execv(sys.executable, args)
|
||||
|
||||
def _set_cloexec(self):
|
||||
"""Set the CLOEXEC flag on all open files (except stdin/out/err).
|
||||
|
||||
If self.max_cloexec_files is an integer (the default), then on
|
||||
platforms which support it, it represents the max open files setting
|
||||
for the operating system. This function will be called just before
|
||||
the process is restarted via os.execv() to prevent open files
|
||||
from persisting into the new process.
|
||||
|
||||
Set self.max_cloexec_files to 0 to disable this behavior.
|
||||
"""
|
||||
for fd in range(3, self.max_cloexec_files): # skip stdin/out/err
|
||||
try:
|
||||
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
|
||||
except IOError:
|
||||
continue
|
||||
fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
|
||||
|
||||
def stop(self):
|
||||
"""Stop all services."""
|
||||
self.state = states.STOPPING
|
||||
self.log('Bus STOPPING')
|
||||
self.publish('stop')
|
||||
self.state = states.STOPPED
|
||||
self.log('Bus STOPPED')
|
||||
|
||||
def start_with_callback(self, func, args=None, kwargs=None):
|
||||
"""Start 'func' in a new thread T, then start self (and return T)."""
|
||||
if args is None:
|
||||
args = ()
|
||||
if kwargs is None:
|
||||
kwargs = {}
|
||||
args = (func,) + args
|
||||
|
||||
def _callback(func, *a, **kw):
|
||||
self.wait(states.STARTED)
|
||||
func(*a, **kw)
|
||||
t = threading.Thread(target=_callback, args=args, kwargs=kwargs)
|
||||
t.setName('Bus Callback ' + t.getName())
|
||||
t.start()
|
||||
|
||||
self.start()
|
||||
|
||||
return t
|
||||
|
||||
def log(self, msg="", level=20, traceback=False):
|
||||
"""Log the given message. Append the last traceback if requested."""
|
||||
if traceback:
|
||||
msg += "\n" + "".join(_traceback.format_exception(*sys.exc_info()))
|
||||
self.publish('log', msg, level)
|
||||
|
||||
bus = Bus()
|
||||
61
python/packages/cherrypy/scaffold/__init__.py
Normal file
61
python/packages/cherrypy/scaffold/__init__.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""<MyProject>, a CherryPy application.
|
||||
|
||||
Use this as a base for creating new CherryPy applications. When you want
|
||||
to make a new app, copy and paste this folder to some other location
|
||||
(maybe site-packages) and rename it to the name of your project,
|
||||
then tweak as desired.
|
||||
|
||||
Even before any tweaking, this should serve a few demonstration pages.
|
||||
Change to this directory and run:
|
||||
|
||||
../cherryd -c site.conf
|
||||
|
||||
"""
|
||||
|
||||
import cherrypy
|
||||
from cherrypy import tools, url
|
||||
|
||||
import os
|
||||
local_dir = os.path.join(os.getcwd(), os.path.dirname(__file__))
|
||||
|
||||
|
||||
class Root:
|
||||
|
||||
_cp_config = {'tools.log_tracebacks.on': True,
|
||||
}
|
||||
|
||||
def index(self):
|
||||
return """<html>
|
||||
<body>Try some <a href='%s?a=7'>other</a> path,
|
||||
or a <a href='%s?n=14'>default</a> path.<br />
|
||||
Or, just look at the pretty picture:<br />
|
||||
<img src='%s' />
|
||||
</body></html>""" % (url("other"), url("else"),
|
||||
url("files/made_with_cherrypy_small.png"))
|
||||
index.exposed = True
|
||||
|
||||
def default(self, *args, **kwargs):
|
||||
return "args: %s kwargs: %s" % (args, kwargs)
|
||||
default.exposed = True
|
||||
|
||||
def other(self, a=2, b='bananas', c=None):
|
||||
cherrypy.response.headers['Content-Type'] = 'text/plain'
|
||||
if c is None:
|
||||
return "Have %d %s." % (int(a), b)
|
||||
else:
|
||||
return "Have %d %s, %s." % (int(a), b, c)
|
||||
other.exposed = True
|
||||
|
||||
files = cherrypy.tools.staticdir.handler(
|
||||
section="/files",
|
||||
dir=os.path.join(local_dir, "static"),
|
||||
# Ignore .php files, etc.
|
||||
match=r'\.(css|gif|html?|ico|jpe?g|js|png|swf|xml)$',
|
||||
)
|
||||
|
||||
|
||||
root = Root()
|
||||
|
||||
# Uncomment the following to use your own favicon instead of CP's default.
|
||||
#favicon_path = os.path.join(local_dir, "favicon.ico")
|
||||
#root.favicon_ico = tools.staticfile.handler(filename=favicon_path)
|
||||
22
python/packages/cherrypy/scaffold/apache-fcgi.conf
Normal file
22
python/packages/cherrypy/scaffold/apache-fcgi.conf
Normal file
@@ -0,0 +1,22 @@
|
||||
# Apache2 server conf file for using CherryPy with mod_fcgid.
|
||||
|
||||
# This doesn't have to be "C:/", but it has to be a directory somewhere, and
|
||||
# MUST match the directory used in the FastCgiExternalServer directive, below.
|
||||
DocumentRoot "C:/"
|
||||
|
||||
ServerName 127.0.0.1
|
||||
Listen 80
|
||||
LoadModule fastcgi_module modules/mod_fastcgi.dll
|
||||
LoadModule rewrite_module modules/mod_rewrite.so
|
||||
|
||||
Options ExecCGI
|
||||
SetHandler fastcgi-script
|
||||
RewriteEngine On
|
||||
# Send requests for any URI to our fastcgi handler.
|
||||
RewriteRule ^(.*)$ /fastcgi.pyc [L]
|
||||
|
||||
# The FastCgiExternalServer directive defines filename as an external FastCGI application.
|
||||
# If filename does not begin with a slash (/) then it is assumed to be relative to the ServerRoot.
|
||||
# The filename does not have to exist in the local filesystem. URIs that Apache resolves to this
|
||||
# filename will be handled by this external FastCGI application.
|
||||
FastCgiExternalServer "C:/fastcgi.pyc" -host 127.0.0.1:8088
|
||||
3
python/packages/cherrypy/scaffold/example.conf
Normal file
3
python/packages/cherrypy/scaffold/example.conf
Normal file
@@ -0,0 +1,3 @@
|
||||
[/]
|
||||
log.error_file: "error.log"
|
||||
log.access_file: "access.log"
|
||||
14
python/packages/cherrypy/scaffold/site.conf
Normal file
14
python/packages/cherrypy/scaffold/site.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
[global]
|
||||
# Uncomment this when you're done developing
|
||||
#environment: "production"
|
||||
|
||||
server.socket_host: "0.0.0.0"
|
||||
server.socket_port: 8088
|
||||
|
||||
# Uncomment the following lines to run on HTTPS at the same time
|
||||
#server.2.socket_host: "0.0.0.0"
|
||||
#server.2.socket_port: 8433
|
||||
#server.2.ssl_certificate: '../test/test.pem'
|
||||
#server.2.ssl_private_key: '../test/test.pem'
|
||||
|
||||
tree.myapp: cherrypy.Application(scaffold.root, "/", "example.conf")
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
14
python/packages/cherrypy/wsgiserver/__init__.py
Normal file
14
python/packages/cherrypy/wsgiserver/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
__all__ = ['HTTPRequest', 'HTTPConnection', 'HTTPServer',
|
||||
'SizeCheckWrapper', 'KnownLengthRFile', 'ChunkedRFile',
|
||||
'MaxSizeExceeded', 'NoSSLError', 'FatalSSLAlert',
|
||||
'WorkerThread', 'ThreadPool', 'SSLAdapter',
|
||||
'CherryPyWSGIServer',
|
||||
'Gateway', 'WSGIGateway', 'WSGIGateway_10', 'WSGIGateway_u0',
|
||||
'WSGIPathInfoDispatcher', 'get_ssl_adapter_class']
|
||||
|
||||
import sys
|
||||
if sys.version_info < (3, 0):
|
||||
from wsgiserver2 import *
|
||||
else:
|
||||
# Le sigh. Boo for backward-incompatible syntax.
|
||||
exec('from .wsgiserver3 import *')
|
||||
91
python/packages/cherrypy/wsgiserver/ssl_builtin.py
Normal file
91
python/packages/cherrypy/wsgiserver/ssl_builtin.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""A library for integrating Python's builtin ``ssl`` library with CherryPy.
|
||||
|
||||
The ssl module must be importable for SSL functionality.
|
||||
|
||||
To use this module, set ``CherryPyWSGIServer.ssl_adapter`` to an instance of
|
||||
``BuiltinSSLAdapter``.
|
||||
"""
|
||||
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
ssl = None
|
||||
|
||||
try:
|
||||
from _pyio import DEFAULT_BUFFER_SIZE
|
||||
except ImportError:
|
||||
try:
|
||||
from io import DEFAULT_BUFFER_SIZE
|
||||
except ImportError:
|
||||
DEFAULT_BUFFER_SIZE = -1
|
||||
|
||||
import sys
|
||||
|
||||
from cherrypy import wsgiserver
|
||||
|
||||
|
||||
class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
|
||||
"""A wrapper for integrating Python's builtin ssl module with CherryPy."""
|
||||
|
||||
certificate = None
|
||||
"""The filename of the server SSL certificate."""
|
||||
|
||||
private_key = None
|
||||
"""The filename of the server's private key file."""
|
||||
|
||||
def __init__(self, certificate, private_key, certificate_chain=None):
|
||||
if ssl is None:
|
||||
raise ImportError("You must install the ssl module to use HTTPS.")
|
||||
self.certificate = certificate
|
||||
self.private_key = private_key
|
||||
self.certificate_chain = certificate_chain
|
||||
|
||||
def bind(self, sock):
|
||||
"""Wrap and return the given socket."""
|
||||
return sock
|
||||
|
||||
def wrap(self, sock):
|
||||
"""Wrap and return the given socket, plus WSGI environ entries."""
|
||||
try:
|
||||
s = ssl.wrap_socket(sock, do_handshake_on_connect=True,
|
||||
server_side=True, certfile=self.certificate,
|
||||
keyfile=self.private_key, ssl_version=ssl.PROTOCOL_SSLv23)
|
||||
except ssl.SSLError:
|
||||
e = sys.exc_info()[1]
|
||||
if e.errno == ssl.SSL_ERROR_EOF:
|
||||
# This is almost certainly due to the cherrypy engine
|
||||
# 'pinging' the socket to assert it's connectable;
|
||||
# the 'ping' isn't SSL.
|
||||
return None, {}
|
||||
elif e.errno == ssl.SSL_ERROR_SSL:
|
||||
if e.args[1].endswith('http request'):
|
||||
# The client is speaking HTTP to an HTTPS server.
|
||||
raise wsgiserver.NoSSLError
|
||||
elif e.args[1].endswith('unknown protocol'):
|
||||
# The client is speaking some non-HTTP protocol.
|
||||
# Drop the conn.
|
||||
return None, {}
|
||||
raise
|
||||
return s, self.get_environ(s)
|
||||
|
||||
# TODO: fill this out more with mod ssl env
|
||||
def get_environ(self, sock):
|
||||
"""Create WSGI environ entries to be merged into each request."""
|
||||
cipher = sock.cipher()
|
||||
ssl_environ = {
|
||||
"wsgi.url_scheme": "https",
|
||||
"HTTPS": "on",
|
||||
'SSL_PROTOCOL': cipher[1],
|
||||
'SSL_CIPHER': cipher[0]
|
||||
## SSL_VERSION_INTERFACE string The mod_ssl program version
|
||||
## SSL_VERSION_LIBRARY string The OpenSSL program version
|
||||
}
|
||||
return ssl_environ
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
|
||||
return wsgiserver.CP_makefile(sock, mode, bufsize)
|
||||
else:
|
||||
def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
|
||||
return wsgiserver.CP_fileobject(sock, mode, bufsize)
|
||||
|
||||
256
python/packages/cherrypy/wsgiserver/ssl_pyopenssl.py
Normal file
256
python/packages/cherrypy/wsgiserver/ssl_pyopenssl.py
Normal file
@@ -0,0 +1,256 @@
|
||||
"""A library for integrating pyOpenSSL with CherryPy.
|
||||
|
||||
The OpenSSL module must be importable for SSL functionality.
|
||||
You can obtain it from http://pyopenssl.sourceforge.net/
|
||||
|
||||
To use this module, set CherryPyWSGIServer.ssl_adapter to an instance of
|
||||
SSLAdapter. There are two ways to use SSL:
|
||||
|
||||
Method One
|
||||
----------
|
||||
|
||||
* ``ssl_adapter.context``: an instance of SSL.Context.
|
||||
|
||||
If this is not None, it is assumed to be an SSL.Context instance,
|
||||
and will be passed to SSL.Connection on bind(). The developer is
|
||||
responsible for forming a valid Context object. This approach is
|
||||
to be preferred for more flexibility, e.g. if the cert and key are
|
||||
streams instead of files, or need decryption, or SSL.SSLv3_METHOD
|
||||
is desired instead of the default SSL.SSLv23_METHOD, etc. Consult
|
||||
the pyOpenSSL documentation for complete options.
|
||||
|
||||
Method Two (shortcut)
|
||||
---------------------
|
||||
|
||||
* ``ssl_adapter.certificate``: the filename of the server SSL certificate.
|
||||
* ``ssl_adapter.private_key``: the filename of the server's private key file.
|
||||
|
||||
Both are None by default. If ssl_adapter.context is None, but .private_key
|
||||
and .certificate are both given and valid, they will be read, and the
|
||||
context will be automatically created from them.
|
||||
"""
|
||||
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
from cherrypy import wsgiserver
|
||||
|
||||
try:
|
||||
from OpenSSL import SSL
|
||||
from OpenSSL import crypto
|
||||
except ImportError:
|
||||
SSL = None
|
||||
|
||||
|
||||
class SSL_fileobject(wsgiserver.CP_fileobject):
|
||||
"""SSL file object attached to a socket object."""
|
||||
|
||||
ssl_timeout = 3
|
||||
ssl_retry = .01
|
||||
|
||||
def _safe_call(self, is_reader, call, *args, **kwargs):
|
||||
"""Wrap the given call with SSL error-trapping.
|
||||
|
||||
is_reader: if False EOF errors will be raised. If True, EOF errors
|
||||
will return "" (to emulate normal sockets).
|
||||
"""
|
||||
start = time.time()
|
||||
while True:
|
||||
try:
|
||||
return call(*args, **kwargs)
|
||||
except SSL.WantReadError:
|
||||
# Sleep and try again. This is dangerous, because it means
|
||||
# the rest of the stack has no way of differentiating
|
||||
# between a "new handshake" error and "client dropped".
|
||||
# Note this isn't an endless loop: there's a timeout below.
|
||||
time.sleep(self.ssl_retry)
|
||||
except SSL.WantWriteError:
|
||||
time.sleep(self.ssl_retry)
|
||||
except SSL.SysCallError, e:
|
||||
if is_reader and e.args == (-1, 'Unexpected EOF'):
|
||||
return ""
|
||||
|
||||
errnum = e.args[0]
|
||||
if is_reader and errnum in wsgiserver.socket_errors_to_ignore:
|
||||
return ""
|
||||
raise socket.error(errnum)
|
||||
except SSL.Error, e:
|
||||
if is_reader and e.args == (-1, 'Unexpected EOF'):
|
||||
return ""
|
||||
|
||||
thirdarg = None
|
||||
try:
|
||||
thirdarg = e.args[0][0][2]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
if thirdarg == 'http request':
|
||||
# The client is talking HTTP to an HTTPS server.
|
||||
raise wsgiserver.NoSSLError()
|
||||
|
||||
raise wsgiserver.FatalSSLAlert(*e.args)
|
||||
except:
|
||||
raise
|
||||
|
||||
if time.time() - start > self.ssl_timeout:
|
||||
raise socket.timeout("timed out")
|
||||
|
||||
def recv(self, *args, **kwargs):
|
||||
buf = []
|
||||
r = super(SSL_fileobject, self).recv
|
||||
while True:
|
||||
data = self._safe_call(True, r, *args, **kwargs)
|
||||
buf.append(data)
|
||||
p = self._sock.pending()
|
||||
if not p:
|
||||
return "".join(buf)
|
||||
|
||||
def sendall(self, *args, **kwargs):
|
||||
return self._safe_call(False, super(SSL_fileobject, self).sendall,
|
||||
*args, **kwargs)
|
||||
|
||||
def send(self, *args, **kwargs):
|
||||
return self._safe_call(False, super(SSL_fileobject, self).send,
|
||||
*args, **kwargs)
|
||||
|
||||
|
||||
class SSLConnection:
|
||||
"""A thread-safe wrapper for an SSL.Connection.
|
||||
|
||||
``*args``: the arguments to create the wrapped ``SSL.Connection(*args)``.
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
self._ssl_conn = SSL.Connection(*args)
|
||||
self._lock = threading.RLock()
|
||||
|
||||
for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read',
|
||||
'renegotiate', 'bind', 'listen', 'connect', 'accept',
|
||||
'setblocking', 'fileno', 'close', 'get_cipher_list',
|
||||
'getpeername', 'getsockname', 'getsockopt', 'setsockopt',
|
||||
'makefile', 'get_app_data', 'set_app_data', 'state_string',
|
||||
'sock_shutdown', 'get_peer_certificate', 'want_read',
|
||||
'want_write', 'set_connect_state', 'set_accept_state',
|
||||
'connect_ex', 'sendall', 'settimeout', 'gettimeout'):
|
||||
exec("""def %s(self, *args):
|
||||
self._lock.acquire()
|
||||
try:
|
||||
return self._ssl_conn.%s(*args)
|
||||
finally:
|
||||
self._lock.release()
|
||||
""" % (f, f))
|
||||
|
||||
def shutdown(self, *args):
|
||||
self._lock.acquire()
|
||||
try:
|
||||
# pyOpenSSL.socket.shutdown takes no args
|
||||
return self._ssl_conn.shutdown()
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
|
||||
class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
|
||||
"""A wrapper for integrating pyOpenSSL with CherryPy."""
|
||||
|
||||
context = None
|
||||
"""An instance of SSL.Context."""
|
||||
|
||||
certificate = None
|
||||
"""The filename of the server SSL certificate."""
|
||||
|
||||
private_key = None
|
||||
"""The filename of the server's private key file."""
|
||||
|
||||
certificate_chain = None
|
||||
"""Optional. The filename of CA's intermediate certificate bundle.
|
||||
|
||||
This is needed for cheaper "chained root" SSL certificates, and should be
|
||||
left as None if not required."""
|
||||
|
||||
def __init__(self, certificate, private_key, certificate_chain=None):
|
||||
if SSL is None:
|
||||
raise ImportError("You must install pyOpenSSL to use HTTPS.")
|
||||
|
||||
self.context = None
|
||||
self.certificate = certificate
|
||||
self.private_key = private_key
|
||||
self.certificate_chain = certificate_chain
|
||||
self._environ = None
|
||||
|
||||
def bind(self, sock):
|
||||
"""Wrap and return the given socket."""
|
||||
if self.context is None:
|
||||
self.context = self.get_context()
|
||||
conn = SSLConnection(self.context, sock)
|
||||
self._environ = self.get_environ()
|
||||
return conn
|
||||
|
||||
def wrap(self, sock):
|
||||
"""Wrap and return the given socket, plus WSGI environ entries."""
|
||||
return sock, self._environ.copy()
|
||||
|
||||
def get_context(self):
|
||||
"""Return an SSL.Context from self attributes."""
|
||||
# See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473
|
||||
c = SSL.Context(SSL.SSLv23_METHOD)
|
||||
c.use_privatekey_file(self.private_key)
|
||||
if self.certificate_chain:
|
||||
c.load_verify_locations(self.certificate_chain)
|
||||
c.use_certificate_file(self.certificate)
|
||||
return c
|
||||
|
||||
def get_environ(self):
|
||||
"""Return WSGI environ entries to be merged into each request."""
|
||||
ssl_environ = {
|
||||
"HTTPS": "on",
|
||||
# pyOpenSSL doesn't provide access to any of these AFAICT
|
||||
## 'SSL_PROTOCOL': 'SSLv2',
|
||||
## SSL_CIPHER string The cipher specification name
|
||||
## SSL_VERSION_INTERFACE string The mod_ssl program version
|
||||
## SSL_VERSION_LIBRARY string The OpenSSL program version
|
||||
}
|
||||
|
||||
if self.certificate:
|
||||
# Server certificate attributes
|
||||
cert = open(self.certificate, 'rb').read()
|
||||
cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
|
||||
ssl_environ.update({
|
||||
'SSL_SERVER_M_VERSION': cert.get_version(),
|
||||
'SSL_SERVER_M_SERIAL': cert.get_serial_number(),
|
||||
## 'SSL_SERVER_V_START': Validity of server's certificate (start time),
|
||||
## 'SSL_SERVER_V_END': Validity of server's certificate (end time),
|
||||
})
|
||||
|
||||
for prefix, dn in [("I", cert.get_issuer()),
|
||||
("S", cert.get_subject())]:
|
||||
# X509Name objects don't seem to have a way to get the
|
||||
# complete DN string. Use str() and slice it instead,
|
||||
# because str(dn) == "<X509Name object '/C=US/ST=...'>"
|
||||
dnstr = str(dn)[18:-2]
|
||||
|
||||
wsgikey = 'SSL_SERVER_%s_DN' % prefix
|
||||
ssl_environ[wsgikey] = dnstr
|
||||
|
||||
# The DN should be of the form: /k1=v1/k2=v2, but we must allow
|
||||
# for any value to contain slashes itself (in a URL).
|
||||
while dnstr:
|
||||
pos = dnstr.rfind("=")
|
||||
dnstr, value = dnstr[:pos], dnstr[pos + 1:]
|
||||
pos = dnstr.rfind("/")
|
||||
dnstr, key = dnstr[:pos], dnstr[pos + 1:]
|
||||
if key and value:
|
||||
wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
|
||||
ssl_environ[wsgikey] = value
|
||||
|
||||
return ssl_environ
|
||||
|
||||
def makefile(self, sock, mode='r', bufsize=-1):
|
||||
if SSL and isinstance(sock, SSL.ConnectionType):
|
||||
timeout = sock.gettimeout()
|
||||
f = SSL_fileobject(sock, mode, bufsize)
|
||||
f.ssl_timeout = timeout
|
||||
return f
|
||||
else:
|
||||
return wsgiserver.CP_fileobject(sock, mode, bufsize)
|
||||
|
||||
2322
python/packages/cherrypy/wsgiserver/wsgiserver2.py
Normal file
2322
python/packages/cherrypy/wsgiserver/wsgiserver2.py
Normal file
File diff suppressed because it is too large
Load Diff
2040
python/packages/cherrypy/wsgiserver/wsgiserver3.py
Normal file
2040
python/packages/cherrypy/wsgiserver/wsgiserver3.py
Normal file
File diff suppressed because it is too large
Load Diff
7
python/packages/formencode/__init__.py
Normal file
7
python/packages/formencode/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from api import *
|
||||
# @@ ianb 2005-05: should these be lazily loaded? Especially validators?
|
||||
from schema import *
|
||||
from compound import *
|
||||
from foreach import *
|
||||
import validators
|
||||
from variabledecode import NestedVariables
|
||||
494
python/packages/formencode/api.py
Normal file
494
python/packages/formencode/api.py
Normal file
@@ -0,0 +1,494 @@
|
||||
"""
|
||||
Core classes for validation.
|
||||
"""
|
||||
|
||||
import declarative
|
||||
import gettext
|
||||
import os
|
||||
import re
|
||||
import textwrap
|
||||
|
||||
try:
|
||||
from pkg_resources import resource_filename
|
||||
except ImportError:
|
||||
resource_filename = None
|
||||
|
||||
__all__ = ['NoDefault', 'Invalid', 'Validator', 'Identity',
|
||||
'FancyValidator', 'is_validator']
|
||||
|
||||
|
||||
def get_localedir():
|
||||
"""
|
||||
Retrieve the location of locales.
|
||||
|
||||
If we're built as an egg, we need to find the resource within the egg.
|
||||
Otherwise, we need to look for the locales on the filesystem or in the
|
||||
system message catalog.
|
||||
"""
|
||||
locale_dir = ''
|
||||
# Check the egg first
|
||||
if resource_filename is not None:
|
||||
try:
|
||||
locale_dir = resource_filename(__name__, "/i18n")
|
||||
except NotImplementedError:
|
||||
# resource_filename doesn't work with non-egg zip files
|
||||
pass
|
||||
if not hasattr(os, 'access'):
|
||||
# This happens on Google App Engine
|
||||
return os.path.join(os.path.dirname(__file__), 'i18n')
|
||||
if os.access(locale_dir, os.R_OK | os.X_OK):
|
||||
# If the resource is present in the egg, use it
|
||||
return locale_dir
|
||||
|
||||
# Otherwise, search the filesystem
|
||||
locale_dir = os.path.join(os.path.dirname(__file__), 'i18n')
|
||||
if not os.access(locale_dir, os.R_OK | os.X_OK):
|
||||
# Fallback on the system catalog
|
||||
locale_dir = os.path.normpath('/usr/share/locale')
|
||||
|
||||
return locale_dir
|
||||
|
||||
|
||||
def set_stdtranslation(domain="FormEncode", languages=None,
|
||||
localedir = get_localedir()):
|
||||
|
||||
t = gettext.translation(domain=domain,
|
||||
languages=languages,
|
||||
localedir=localedir, fallback=True)
|
||||
global _stdtrans
|
||||
_stdtrans = t.ugettext
|
||||
|
||||
set_stdtranslation()
|
||||
|
||||
# Dummy i18n translation function, nothing is translated here.
|
||||
# Instead this is actually done in api.Validator.message.
|
||||
# The surrounding _('string') of the strings is only for extracting
|
||||
# the strings automatically.
|
||||
# If you run pygettext with this source comment this function out temporarily.
|
||||
_ = lambda s: s
|
||||
|
||||
|
||||
class NoDefault(object):
|
||||
"""A dummy value used for parameters with no default."""
|
||||
|
||||
|
||||
def is_validator(obj):
|
||||
return (isinstance(obj, Validator) or
|
||||
(isinstance(obj, type) and issubclass(obj, Validator)))
|
||||
|
||||
|
||||
class Invalid(Exception):
|
||||
|
||||
"""
|
||||
This is raised in response to invalid input. It has several
|
||||
public attributes:
|
||||
|
||||
msg:
|
||||
The message, *without* values substituted. For instance, if
|
||||
you want HTML quoting of values, you can apply that.
|
||||
substituteArgs:
|
||||
The arguments (a dictionary) to go with `msg`.
|
||||
str(self):
|
||||
The message describing the error, with values substituted.
|
||||
value:
|
||||
The offending (invalid) value.
|
||||
state:
|
||||
The state that went with this validator. This is an
|
||||
application-specific object.
|
||||
error_list:
|
||||
If this was a compound validator that takes a repeating value,
|
||||
and sub-validator(s) had errors, then this is a list of those
|
||||
exceptions. The list will be the same length as the number of
|
||||
values -- valid values will have None instead of an exception.
|
||||
error_dict:
|
||||
Like `error_list`, but for dictionary compound validators.
|
||||
"""
|
||||
|
||||
def __init__(self, msg,
|
||||
value, state, error_list=None, error_dict=None):
|
||||
Exception.__init__(self, msg, value, state, error_list, error_dict)
|
||||
self.msg = msg
|
||||
self.value = value
|
||||
self.state = state
|
||||
self.error_list = error_list
|
||||
self.error_dict = error_dict
|
||||
assert (not self.error_list or not self.error_dict), (
|
||||
"Errors shouldn't have both error dicts and lists "
|
||||
"(error %s has %s and %s)"
|
||||
% (self, self.error_list, self.error_dict))
|
||||
|
||||
def __str__(self):
|
||||
val = self.msg
|
||||
#if self.value:
|
||||
# val += " (value: %s)" % repr(self.value)
|
||||
return val
|
||||
|
||||
def __unicode__(self):
|
||||
if isinstance(self.msg, unicode):
|
||||
return self.msg
|
||||
elif isinstance(self.msg, str):
|
||||
return self.msg.decode('utf8')
|
||||
else:
|
||||
return unicode(self.msg)
|
||||
|
||||
def unpack_errors(self, encode_variables=False, dict_char='.',
|
||||
list_char='-'):
|
||||
"""
|
||||
Returns the error as a simple data structure -- lists,
|
||||
dictionaries, and strings.
|
||||
|
||||
If ``encode_variables`` is true, then this will return a flat
|
||||
dictionary, encoded with variable_encode
|
||||
"""
|
||||
if self.error_list:
|
||||
assert not encode_variables, (
|
||||
"You can only encode dictionary errors")
|
||||
assert not self.error_dict
|
||||
result = []
|
||||
for item in self.error_list:
|
||||
if not item:
|
||||
result.append(item)
|
||||
else:
|
||||
result.append(item.unpack_errors())
|
||||
return result
|
||||
elif self.error_dict:
|
||||
result = {}
|
||||
for name, item in self.error_dict.items():
|
||||
if isinstance(item, (str, unicode)):
|
||||
result[name] = item
|
||||
else:
|
||||
result[name] = item.unpack_errors()
|
||||
if encode_variables:
|
||||
import variabledecode
|
||||
result = variabledecode.variable_encode(result, add_repetitions=False,
|
||||
dict_char=dict_char,
|
||||
list_char=list_char)
|
||||
for key in result.keys():
|
||||
if not result[key]:
|
||||
del result[key]
|
||||
return result
|
||||
else:
|
||||
assert not encode_variables, (
|
||||
"You can only encode dictionary errors")
|
||||
return self.msg
|
||||
|
||||
|
||||
############################################################
|
||||
## Base Classes
|
||||
############################################################
|
||||
|
||||
class Validator(declarative.Declarative):
|
||||
|
||||
"""
|
||||
The base class of most validators. See `IValidator` for more, and
|
||||
`FancyValidator` for the more common (and more featureful) class.
|
||||
"""
|
||||
|
||||
_messages = {}
|
||||
if_missing = NoDefault
|
||||
repeating = False
|
||||
compound = False
|
||||
gettextargs = {}
|
||||
use_builtins_gettext = True # In case you don't want to use __builtins__._
|
||||
# although it may be defined, set this to False
|
||||
|
||||
__singletonmethods__ = ('to_python', 'from_python', 'message', 'all_messages',
|
||||
'subvalidators')
|
||||
|
||||
def __classinit__(cls, new_attrs):
|
||||
if 'messages' in new_attrs:
|
||||
cls._messages = cls._messages.copy()
|
||||
cls._messages.update(cls.messages)
|
||||
del cls.messages
|
||||
cls._initialize_docstring()
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
if 'messages' in kw:
|
||||
self._messages = self._messages.copy()
|
||||
self._messages.update(kw.pop('messages'))
|
||||
declarative.Declarative.__init__(self, *args, **kw)
|
||||
|
||||
def to_python(self, value, state=None):
|
||||
return value
|
||||
|
||||
def from_python(self, value, state=None):
|
||||
return value
|
||||
|
||||
def message(self, msgName, state, **kw):
|
||||
# determine translation function
|
||||
try:
|
||||
trans = state._
|
||||
except AttributeError:
|
||||
try:
|
||||
if self.use_builtins_gettext:
|
||||
import __builtin__
|
||||
trans = __builtin__._
|
||||
|
||||
else:
|
||||
trans = _stdtrans
|
||||
|
||||
except AttributeError:
|
||||
trans = _stdtrans
|
||||
|
||||
if not callable(trans):
|
||||
trans = _stdtrans
|
||||
|
||||
msg = self._messages[msgName]
|
||||
msg = trans(msg, **self.gettextargs)
|
||||
try:
|
||||
return msg % kw
|
||||
except KeyError, e:
|
||||
raise KeyError(
|
||||
"Key not found (%s) for %r=%r %% %r (from: %s)"
|
||||
% (e, msgName, self._messages.get(msgName), kw,
|
||||
', '.join(self._messages.keys())))
|
||||
|
||||
def all_messages(self):
|
||||
"""
|
||||
Return a dictionary of all the messages of this validator, and
|
||||
any subvalidators if present. Keys are message names, values
|
||||
may be a message or list of messages. This is really just
|
||||
intended for documentation purposes, to show someone all the
|
||||
messages that a validator or compound validator (like Schemas)
|
||||
can produce.
|
||||
|
||||
@@: Should this produce a more structured set of messages, so
|
||||
that messages could be unpacked into a rendered form to see
|
||||
the placement of all the messages? Well, probably so.
|
||||
"""
|
||||
msgs = self._messages.copy()
|
||||
for v in self.subvalidators():
|
||||
inner = v.all_messages()
|
||||
for key, msg in inner:
|
||||
if key in msgs:
|
||||
if msgs[key] == msg:
|
||||
continue
|
||||
if isinstance(msgs[key], list):
|
||||
msgs[key].append(msg)
|
||||
else:
|
||||
msgs[key] = [msgs[key], msg]
|
||||
else:
|
||||
msgs[key] = msg
|
||||
return msgs
|
||||
|
||||
def subvalidators(self):
|
||||
"""
|
||||
Return any validators that this validator contains. This is
|
||||
not useful for functional, except to inspect what values are
|
||||
available. Specifically the ``.all_messages()`` method uses
|
||||
this to accumulate all possible messages.
|
||||
"""
|
||||
return []
|
||||
|
||||
def _initialize_docstring(cls):
|
||||
"""
|
||||
This changes the class's docstring to include information
|
||||
about all the messages this validator uses.
|
||||
"""
|
||||
doc = cls.__doc__ or ''
|
||||
doc = [textwrap.dedent(doc).rstrip()]
|
||||
messages = cls._messages.items()
|
||||
messages.sort()
|
||||
doc.append('\n\n**Messages**\n\n')
|
||||
for name, default in messages:
|
||||
default = re.sub(r'(%\(.*?\)[rsifcx])', r'``\1``', default)
|
||||
doc.append('``'+name+'``:\n')
|
||||
doc.append(' '+default+'\n\n')
|
||||
cls.__doc__ = ''.join(doc)
|
||||
_initialize_docstring = classmethod(_initialize_docstring)
|
||||
|
||||
|
||||
class _Identity(Validator):
|
||||
|
||||
def __repr__(self):
|
||||
return 'validators.Identity'
|
||||
|
||||
Identity = _Identity()
|
||||
|
||||
|
||||
class FancyValidator(Validator):
|
||||
|
||||
"""
|
||||
FancyValidator is the (abstract) superclass for various validators
|
||||
and converters. A subclass can validate, convert, or do both.
|
||||
There is no formal distinction made here.
|
||||
|
||||
Validators have two important external methods:
|
||||
|
||||
* .to_python(value, state):
|
||||
Attempts to convert the value. If there is a problem, or the
|
||||
value is not valid, an Invalid exception is raised. The
|
||||
argument for this exception is the (potentially HTML-formatted)
|
||||
error message to give the user.
|
||||
|
||||
* .from_python(value, state):
|
||||
Reverses to_python.
|
||||
|
||||
There are five important methods for subclasses to override,
|
||||
however none of these *have* to be overridden, only the ones that
|
||||
are appropriate for the validator:
|
||||
|
||||
* __init__():
|
||||
if the `declarative.Declarative` model doesn't work for this.
|
||||
|
||||
* .validate_python(value, state):
|
||||
This should raise an error if necessary. The value is a Python
|
||||
object, either the result of to_python, or the input to
|
||||
from_python.
|
||||
|
||||
* .validate_other(value, state):
|
||||
Validates the source, before to_python, or after from_python.
|
||||
It's more common to use `.validate_python()` however.
|
||||
|
||||
* ._to_python(value, state):
|
||||
This returns the converted value, or raises an Invalid
|
||||
exception if there is an error. The argument to this exception
|
||||
should be the error message.
|
||||
|
||||
* ._from_python(value, state):
|
||||
Should undo .to_python() in some reasonable way, returning
|
||||
a string.
|
||||
|
||||
Validators should have no internal state besides the
|
||||
values given at instantiation. They should be reusable and
|
||||
reentrant.
|
||||
|
||||
All subclasses can take the arguments/instance variables:
|
||||
|
||||
* if_empty:
|
||||
If set, then this value will be returned if the input evaluates
|
||||
to false (empty list, empty string, None, etc), but not the 0 or
|
||||
False objects. This only applies to ``.to_python()``.
|
||||
|
||||
* not_empty:
|
||||
If true, then if an empty value is given raise an error.
|
||||
(Both with ``.to_python()`` and also ``.from_python()``
|
||||
if ``.validate_python`` is true).
|
||||
|
||||
* strip:
|
||||
If true and the input is a string, strip it (occurs before empty
|
||||
tests).
|
||||
|
||||
* if_invalid:
|
||||
If set, then when this validator would raise Invalid during
|
||||
``.to_python()``, instead return this value.
|
||||
|
||||
* if_invalid_python:
|
||||
If set, when the Python value (converted with
|
||||
``.from_python()``) is invalid, this value will be returned.
|
||||
|
||||
* accept_python:
|
||||
If True (the default), then ``.validate_python()`` and
|
||||
``.validate_other()`` will not be called when
|
||||
``.from_python()`` is used.
|
||||
"""
|
||||
|
||||
if_invalid = NoDefault
|
||||
if_invalid_python = NoDefault
|
||||
if_empty = NoDefault
|
||||
not_empty = False
|
||||
accept_python = True
|
||||
strip = False
|
||||
|
||||
messages = {
|
||||
'empty': _("Please enter a value"),
|
||||
'badType': _("The input must be a string (not a %(type)s: %(value)r)"),
|
||||
'noneType': _("The input must be a string (not None)"),
|
||||
}
|
||||
|
||||
def to_python(self, value, state=None):
|
||||
try:
|
||||
if self.strip and isinstance(value, (str, unicode)):
|
||||
value = value.strip()
|
||||
elif hasattr(value, 'mixed'):
|
||||
# Support Paste's MultiDict
|
||||
value = value.mixed()
|
||||
if self.is_empty(value):
|
||||
if self.not_empty:
|
||||
raise Invalid(self.message('empty', state), value, state)
|
||||
else:
|
||||
if self.if_empty is not NoDefault:
|
||||
return self.if_empty
|
||||
else:
|
||||
return self.empty_value(value)
|
||||
vo = self.validate_other
|
||||
if vo and vo is not self._validate_noop:
|
||||
vo(value, state)
|
||||
tp = self._to_python
|
||||
if tp:
|
||||
value = tp(value, state)
|
||||
vp = self.validate_python
|
||||
if vp and vp is not self._validate_noop:
|
||||
vp(value, state)
|
||||
return value
|
||||
except Invalid:
|
||||
if self.if_invalid is NoDefault:
|
||||
raise
|
||||
else:
|
||||
return self.if_invalid
|
||||
|
||||
def from_python(self, value, state=None):
|
||||
try:
|
||||
if self.strip and isinstance(value, (str, unicode)):
|
||||
value = value.strip()
|
||||
if not self.accept_python:
|
||||
if self.is_empty(value):
|
||||
if self.not_empty:
|
||||
raise Invalid(self.message('empty', state),
|
||||
value, state)
|
||||
else:
|
||||
return self.empty_value(value)
|
||||
vp = self.validate_python
|
||||
if vp and vp is not self._validate_noop:
|
||||
vp(value, state)
|
||||
fp = self._from_python
|
||||
if fp:
|
||||
value = fp(value, state)
|
||||
vo = self.validate_other
|
||||
if vo and vo is not self._validate_noop:
|
||||
vo(value, state)
|
||||
return value
|
||||
else:
|
||||
if self.is_empty(value):
|
||||
return self.empty_value(value)
|
||||
fp = self._from_python
|
||||
if fp:
|
||||
value = self._from_python(value, state)
|
||||
return value
|
||||
except Invalid:
|
||||
if self.if_invalid_python is NoDefault:
|
||||
raise
|
||||
else:
|
||||
return self.if_invalid_python
|
||||
|
||||
def is_empty(self, value):
|
||||
# None and '' are "empty"
|
||||
return value is None or value == '' or (
|
||||
isinstance(value, (list, tuple, dict)) and not value)
|
||||
|
||||
def empty_value(self, value):
|
||||
return None
|
||||
|
||||
def assert_string(self, value, state):
|
||||
if not isinstance(value, (str, unicode)):
|
||||
raise Invalid(self.message('badType', state,
|
||||
type=type(value), value=value),
|
||||
value, state)
|
||||
|
||||
def base64encode(self, value):
|
||||
"""
|
||||
Encode a string in base64, stripping whitespace and removing
|
||||
newlines.
|
||||
"""
|
||||
return value.encode('base64').strip().replace('\n', '')
|
||||
|
||||
def _validate_noop(self, value, state):
|
||||
"""
|
||||
A validation method that doesn't do anything.
|
||||
"""
|
||||
pass
|
||||
|
||||
validate_python = validate_other = _validate_noop
|
||||
_to_python = None
|
||||
_from_python = None
|
||||
|
||||
221
python/packages/formencode/compound.py
Normal file
221
python/packages/formencode/compound.py
Normal file
@@ -0,0 +1,221 @@
|
||||
"""
|
||||
Validators for applying validations in sequence.
|
||||
"""
|
||||
|
||||
from api import *
|
||||
|
||||
# @@ ianb 2005-05: should CompoundValidator be included?
|
||||
__all__ = ['Any', 'All', 'Pipe']
|
||||
|
||||
############################################################
|
||||
## Compound Validators
|
||||
############################################################
|
||||
|
||||
def to_python(validator, value, state):
|
||||
return validator.to_python(value, state)
|
||||
|
||||
|
||||
def from_python(validator, value, state):
|
||||
return validator.from_python(value, state)
|
||||
|
||||
|
||||
class CompoundValidator(FancyValidator):
|
||||
|
||||
if_invalid = NoDefault
|
||||
|
||||
validators = []
|
||||
|
||||
__unpackargs__ = ('*', 'validatorArgs')
|
||||
|
||||
__mutableattributes__ = ('validators',)
|
||||
|
||||
def __classinit__(cls, new_attrs):
|
||||
toAdd = []
|
||||
for name, value in new_attrs.items():
|
||||
if name in ('view',):
|
||||
continue
|
||||
if is_validator(value) and value is not Identity:
|
||||
toAdd.append((name, value))
|
||||
# @@: Should we really delete too?
|
||||
delattr(cls, name)
|
||||
toAdd.sort()
|
||||
cls.validators.extend([v for n, v in toAdd])
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
Validator.__init__(self, *args, **kw)
|
||||
self.validators = self.validators[:]
|
||||
self.validators.extend(self.validatorArgs)
|
||||
|
||||
def _reprVars(names):
|
||||
return [n for n in Validator._reprVars(names)
|
||||
if n != 'validatorArgs']
|
||||
_reprVars = staticmethod(_reprVars)
|
||||
|
||||
def attempt_convert(self, value, state, convertFunc):
|
||||
raise NotImplementedError, "Subclasses must implement attempt_convert"
|
||||
|
||||
def _to_python(self, value, state=None):
|
||||
return self.attempt_convert(value, state,
|
||||
to_python)
|
||||
|
||||
def _from_python(self, value, state=None):
|
||||
return self.attempt_convert(value, state,
|
||||
from_python)
|
||||
|
||||
def subvalidators(self):
|
||||
return self.validators
|
||||
|
||||
|
||||
class Any(CompoundValidator):
|
||||
"""
|
||||
This class is like an 'or' operator for validators. The first
|
||||
validator/converter that validates the value will be used. (You
|
||||
can pass in lists of validators, which will be ANDed)
|
||||
"""
|
||||
|
||||
def attempt_convert(self, value, state, validate):
|
||||
lastException = None
|
||||
if validate is to_python:
|
||||
validators = self.validators[::-1]
|
||||
else:
|
||||
validators = self.validators
|
||||
for validator in validators:
|
||||
try:
|
||||
return validate(validator, value, state)
|
||||
except Invalid, e:
|
||||
lastException = e
|
||||
if self.if_invalid is NoDefault:
|
||||
raise lastException
|
||||
else:
|
||||
return self.if_invalid
|
||||
|
||||
def not_empty__get(self):
|
||||
not_empty = True
|
||||
for validator in self.validators:
|
||||
not_empty = not_empty and getattr(validator, 'not_empty', False)
|
||||
return not_empty
|
||||
not_empty = property(not_empty__get)
|
||||
|
||||
def is_empty(self, value):
|
||||
# sub-validators should handle emptiness.
|
||||
return False
|
||||
|
||||
|
||||
class All(CompoundValidator):
|
||||
"""
|
||||
This class is like an 'and' operator for validators. All
|
||||
validators must work, and the results are passed in turn through
|
||||
all validators for conversion.
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return '<All %s>' % self.validators
|
||||
|
||||
def attempt_convert(self, value, state, validate):
|
||||
# To preserve the order of the transformations, we do them
|
||||
# differently when we are converting to and from python.
|
||||
if validate is to_python:
|
||||
validators = list(self.validators)
|
||||
validators.reverse()
|
||||
else:
|
||||
validators = self.validators
|
||||
try:
|
||||
for validator in validators:
|
||||
value = validate(validator, value, state)
|
||||
return value
|
||||
except Invalid:
|
||||
if self.if_invalid is NoDefault:
|
||||
raise
|
||||
return self.if_invalid
|
||||
|
||||
def with_validator(self, validator):
|
||||
"""
|
||||
Adds the validator (or list of validators) to a copy of
|
||||
this validator.
|
||||
"""
|
||||
new = self.validators[:]
|
||||
if isinstance(validator, list) or isinstance(validator, tuple):
|
||||
new.extend(validator)
|
||||
else:
|
||||
new.append(validator)
|
||||
return self.__class__(*new, **dict(if_invalid=self.if_invalid))
|
||||
|
||||
def join(cls, *validators):
|
||||
"""
|
||||
Joins several validators together as a single validator,
|
||||
filtering out None and trying to keep `All` validators from
|
||||
being nested (which isn't needed).
|
||||
"""
|
||||
validators = filter(lambda v: v and v is not Identity, validators)
|
||||
if not validators:
|
||||
return Identity
|
||||
if len(validators) == 1:
|
||||
return validators[0]
|
||||
elif isinstance(validators[0], All):
|
||||
return validators[0].with_validator(validators[1:])
|
||||
else:
|
||||
return cls(*validators)
|
||||
join = classmethod(join)
|
||||
|
||||
def if_missing__get(self):
|
||||
for validator in self.validators:
|
||||
v = validator.if_missing
|
||||
if v is not NoDefault:
|
||||
return v
|
||||
return NoDefault
|
||||
if_missing = property(if_missing__get)
|
||||
|
||||
def not_empty__get(self):
|
||||
not_empty = False
|
||||
for validator in self.validators:
|
||||
not_empty = not_empty or getattr(validator, 'not_empty', False)
|
||||
return not_empty
|
||||
not_empty = property(not_empty__get)
|
||||
|
||||
def is_empty(self, value):
|
||||
# sub-validators should handle emptiness.
|
||||
return False
|
||||
|
||||
|
||||
class Pipe(All):
|
||||
"""
|
||||
This class works like 'All', all validators muss pass, but the result
|
||||
of one validation pass is handled over to the next validator. A behaviour
|
||||
known to Unix and GNU users as 'pipe'.
|
||||
|
||||
::
|
||||
|
||||
>>> from validators import DictConverter
|
||||
>>> pv = Pipe(validators=[DictConverter({1: 2}), DictConverter({2: 3}), DictConverter({3: 4})])
|
||||
>>> pv.to_python(1)
|
||||
4
|
||||
>>> pv.to_python(1)
|
||||
4
|
||||
>>> pv.from_python(4)
|
||||
1
|
||||
>>> pv.from_python(4)
|
||||
1
|
||||
>>> pv.to_python(1)
|
||||
4
|
||||
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return '<Pipe %s>' % self.validators
|
||||
|
||||
def attempt_convert(self, value, state, validate):
|
||||
# To preserve the order of the transformations, we do them
|
||||
# differently when we are converting to and from Python.
|
||||
if validate is from_python:
|
||||
validators = list(self.validators)
|
||||
validators.reverse()
|
||||
else:
|
||||
validators = self.validators
|
||||
try:
|
||||
for validator in validators:
|
||||
value = validate(validator, value, state)
|
||||
return value
|
||||
except Invalid:
|
||||
if self.if_invalid is NoDefault:
|
||||
raise
|
||||
return self.if_invalid
|
||||
168
python/packages/formencode/context.py
Normal file
168
python/packages/formencode/context.py
Normal file
@@ -0,0 +1,168 @@
|
||||
"""
|
||||
A dynamic-scope-like system, aka fluid variables.
|
||||
|
||||
The idea behind dynamic scoped variables is for when, at one level,
|
||||
you want to change the behavior of something you call. Except you
|
||||
can't pass in any new arguments (e.g., there's some function or object
|
||||
inbetween you and the thing you want to change), or you can't predict
|
||||
exactly what you will want to change.
|
||||
|
||||
You should use it like::
|
||||
|
||||
context = Context()
|
||||
|
||||
def do_stuff():
|
||||
state = context.set(inside='do_stuff')
|
||||
try:
|
||||
do stuff...
|
||||
finally:
|
||||
state.restore()
|
||||
|
||||
Then ``context.inside`` will be set to ``'do_stuff'`` inside that try
|
||||
block. If a value isn't set, you'll get an attribute error.
|
||||
|
||||
Note that all values are thread local; this means you cannot use a
|
||||
context object to pass information to another thread. In a
|
||||
single-thread environment it doesn't really matter.
|
||||
|
||||
Typically you will create ``Context`` instances for your application,
|
||||
environment, etc. These should be global module-level variables, that
|
||||
may be imported by any interested module; each instance is a namespace
|
||||
of its own.
|
||||
|
||||
Sometimes it's nice to have default values, instead of getting
|
||||
attribute errors. This makes it easier to put in new variables that
|
||||
are intended to be used elsewhere, without having to use
|
||||
``getattr(context, 'var', default)`` to avoid AttributeErrors.
|
||||
There are two ways (that can be used together) to do this.
|
||||
|
||||
First, when instantiating a ``Context`` object, you can give it a
|
||||
``default`` value. If given, then all variables will default to that
|
||||
value. ``None`` is a typical value for that.
|
||||
|
||||
Another is ``context.set_default(**vars)``, which will set only those
|
||||
variables to default values. This will not effect the stack of
|
||||
scopes, but will only add defaults.
|
||||
|
||||
When Python 2.5 comes out, this syntax would certainly be useful::
|
||||
|
||||
with context(page='view'):
|
||||
do stuff...
|
||||
|
||||
And ``page`` will be set to ``'view'`` only inside that ``with`` block.
|
||||
"""
|
||||
|
||||
from itertools import count
|
||||
|
||||
from formencode.util import threadinglocal
|
||||
|
||||
__all__ = ['Context', 'ContextRestoreError']
|
||||
|
||||
_restore_ids = count()
|
||||
|
||||
|
||||
class NoDefault(object):
|
||||
"""A dummy value used for parameters with no default."""
|
||||
|
||||
|
||||
class ContextRestoreError(Exception):
|
||||
"""Raised when something is restored out-of-order."""
|
||||
|
||||
|
||||
class Context(object):
|
||||
|
||||
def __init__(self, default=NoDefault):
|
||||
self.__dict__['_local'] = threadinglocal.local()
|
||||
self.__dict__['_default'] = default
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr.startswith('_'):
|
||||
raise AttributeError
|
||||
try:
|
||||
stack = self._local.stack
|
||||
except AttributeError:
|
||||
stack = []
|
||||
for i in range(len(stack)-1, -1, -1):
|
||||
if attr in stack[i][0]:
|
||||
return stack[i][0][attr]
|
||||
if self._default is NoDefault:
|
||||
raise AttributeError(
|
||||
"The attribute %s has not been set on %r"
|
||||
% (attr, self))
|
||||
return self._default
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
raise AttributeError(
|
||||
"You can only write attribute on context object with the .set() method")
|
||||
|
||||
def set(self, **kw):
|
||||
state_id = _restore_ids.next()
|
||||
try:
|
||||
stack = self._local.stack
|
||||
except AttributeError:
|
||||
stack = self._local.stack = [({}, -1)]
|
||||
restorer = RestoreState(self, state_id)
|
||||
stack.append((kw, state_id))
|
||||
return restorer
|
||||
|
||||
def _restore(self, state_id):
|
||||
try:
|
||||
stack = self._local.stack
|
||||
except AttributeError:
|
||||
raise ContextRestoreError(
|
||||
"Tried to restore context %r (to state ID %s) but no variables have been set in context"
|
||||
% (self, state_id))
|
||||
if stack[-1][1] == -1:
|
||||
raise ContextRestoreError(
|
||||
"Out of order restoration of context %r (to state ID %s); the stack state is empty"
|
||||
% (self, state_id))
|
||||
if stack[-1][1] != state_id:
|
||||
raise ContextRestoreError(
|
||||
"Out of order restoration of context %r (to state ID %s) when last state is %s"
|
||||
% (self, state_id, stack[-1][1]))
|
||||
stack.pop()
|
||||
|
||||
def set_default(self, **kw):
|
||||
try:
|
||||
stack = self._local.stack
|
||||
except AttributeError:
|
||||
stack = self._local.stack = [({}, -1)]
|
||||
stack[0][0].update(kw)
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
stack = self._local.stack
|
||||
except AttributeError:
|
||||
stack = []
|
||||
myid = hex(abs(id(self)))[2:]
|
||||
if not stack:
|
||||
return '<%s %s (empty)>' % (self.__class__.__name__, myid)
|
||||
cur = {}
|
||||
for vars, state_id in stack:
|
||||
cur.update(vars)
|
||||
keys = cur.keys()
|
||||
keys.sort()
|
||||
varlist = []
|
||||
for key in keys:
|
||||
rep = repr(cur[key])
|
||||
if len(rep) > 10:
|
||||
rep = rep[:9]+'...'+rep[-1]
|
||||
varlist.append('%s=%s' % (key, rep))
|
||||
return '<%s %s %s>' % (
|
||||
self.__class__.__name__, myid, ' '.join(varlist))
|
||||
|
||||
|
||||
class RestoreState(object):
|
||||
|
||||
def __init__(self, context, state_id):
|
||||
self.state_id = state_id
|
||||
self.context = context
|
||||
self.restored = False
|
||||
|
||||
def restore(self):
|
||||
if self.restored:
|
||||
# @@: Should this really be allowed?
|
||||
return
|
||||
self.context._restore(self.state_id)
|
||||
self.restored = True
|
||||
|
||||
232
python/packages/formencode/declarative.py
Normal file
232
python/packages/formencode/declarative.py
Normal file
@@ -0,0 +1,232 @@
|
||||
"""
|
||||
Declarative objects for FormEncode.
|
||||
|
||||
Declarative objects have a simple protocol: you can use classes in
|
||||
lieu of instances and they are equivalent, and any keyword arguments
|
||||
you give to the constructor will override those instance variables.
|
||||
(So if a class is received, we'll simply instantiate an instance with
|
||||
no arguments).
|
||||
|
||||
You can provide a variable __unpackargs__ (a list of strings), and if
|
||||
the constructor is called with non-keyword arguments they will be
|
||||
interpreted as the given keyword arguments.
|
||||
|
||||
If __unpackargs__ is ('*', name), then all the arguments will be put
|
||||
in a variable by that name.
|
||||
|
||||
Also, you can define a __classinit__(cls, new_attrs) method, which
|
||||
will be called when the class is created (including subclasses).
|
||||
"""
|
||||
|
||||
import copy
|
||||
import new
|
||||
|
||||
from itertools import count
|
||||
|
||||
|
||||
class classinstancemethod(object):
|
||||
"""
|
||||
Acts like a class method when called from a class, like an
|
||||
instance method when called by an instance. The method should
|
||||
take two arguments, 'self' and 'cls'; one of these will be None
|
||||
depending on how the method was called.
|
||||
"""
|
||||
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
|
||||
def __get__(self, obj, type=None):
|
||||
return _methodwrapper(self.func, obj=obj, type=type)
|
||||
|
||||
|
||||
class _methodwrapper(object):
|
||||
|
||||
def __init__(self, func, obj, type):
|
||||
self.func = func
|
||||
self.obj = obj
|
||||
self.type = type
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
assert 'self' not in kw and 'cls' not in kw, (
|
||||
"You cannot use 'self' or 'cls' arguments to a "
|
||||
"classinstancemethod")
|
||||
return self.func(*((self.obj, self.type) + args), **kw)
|
||||
|
||||
def __repr__(self):
|
||||
if self.obj is None:
|
||||
return ('<bound class method %s.%s>'
|
||||
% (self.type.__name__, self.func.func_name))
|
||||
else:
|
||||
return ('<bound method %s.%s of %r>'
|
||||
% (self.type.__name__, self.func.func_name, self.obj))
|
||||
|
||||
|
||||
class DeclarativeMeta(type):
|
||||
|
||||
def __new__(meta, class_name, bases, new_attrs):
|
||||
cls = type.__new__(meta, class_name, bases, new_attrs)
|
||||
for name in cls.__mutableattributes__:
|
||||
setattr(cls, name, copy.copy(getattr(cls, name)))
|
||||
cls.declarative_count = cls.counter.next()
|
||||
if ('__classinit__' in new_attrs
|
||||
and not isinstance(cls.__classinit__, staticmethod)):
|
||||
setattr(cls, '__classinit__',
|
||||
staticmethod(cls.__classinit__.im_func))
|
||||
cls.__classinit__(cls, new_attrs)
|
||||
names = getattr(cls, '__singletonmethods__', None)
|
||||
if names:
|
||||
for name in names:
|
||||
meth = cls.__dict__.get(name)
|
||||
if meth and not isinstance(meth, singletonmethod):
|
||||
setattr(cls, name, singletonmethod(meth))
|
||||
return cls
|
||||
|
||||
|
||||
class singletonmethod(object):
|
||||
"""
|
||||
For Declarative subclasses, this decorator will call the method
|
||||
on the cls.singleton() object if called as a class method (or
|
||||
as normal if called as an instance method).
|
||||
"""
|
||||
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
|
||||
def __get__(self, obj, type=None):
|
||||
if obj is None:
|
||||
obj = type.singleton()
|
||||
if type is None:
|
||||
type = obj.__class__
|
||||
return new.instancemethod(self.func, obj, type)
|
||||
|
||||
|
||||
class Declarative(object):
|
||||
|
||||
__unpackargs__ = ()
|
||||
|
||||
__mutableattributes__ = ()
|
||||
|
||||
__metaclass__ = DeclarativeMeta
|
||||
|
||||
__singletonmethods__ = ()
|
||||
|
||||
counter = count()
|
||||
|
||||
def __classinit__(cls, new_attrs):
|
||||
pass
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
if self.__unpackargs__ and self.__unpackargs__[0] == '*':
|
||||
assert len(self.__unpackargs__) == 2, (
|
||||
"When using __unpackargs__ = ('*', varname),"
|
||||
" you must only provide a single variable name"
|
||||
" (you gave %r)" % self.__unpackargs__)
|
||||
name = self.__unpackargs__[1]
|
||||
if name in kw:
|
||||
raise TypeError(
|
||||
"keyword parameter '%s' was given by position and name"
|
||||
% name)
|
||||
kw[name] = args
|
||||
else:
|
||||
if len(args) > len(self.__unpackargs__):
|
||||
raise TypeError(
|
||||
'%s() takes at most %i arguments (%i given)'
|
||||
% (self.__class__.__name__,
|
||||
len(self.__unpackargs__),
|
||||
len(args)))
|
||||
for name, arg in zip(self.__unpackargs__, args):
|
||||
if name in kw:
|
||||
raise TypeError(
|
||||
"keyword parameter '%s' was given by position and name"
|
||||
% name)
|
||||
kw[name] = arg
|
||||
for name in self.__mutableattributes__:
|
||||
if name not in kw:
|
||||
setattr(self, name, copy.copy(getattr(self, name)))
|
||||
for name, value in kw.items():
|
||||
setattr(self, name, value)
|
||||
if 'declarative_count' not in kw:
|
||||
self.declarative_count = self.counter.next()
|
||||
self.__initargs__(kw)
|
||||
|
||||
def __initargs__(self, new_attrs):
|
||||
pass
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
current = self.__dict__.copy()
|
||||
current.update(kw)
|
||||
return self.__class__(*args, **current)
|
||||
|
||||
def singleton(cls):
|
||||
name = '_%s__singleton' % cls.__name__
|
||||
if not hasattr(cls, name):
|
||||
setattr(cls, name, cls(declarative_count=cls.declarative_count))
|
||||
return getattr(cls, name)
|
||||
singleton = classmethod(singleton)
|
||||
|
||||
def __sourcerepr__(self, source, binding=None):
|
||||
if binding and len(self.__dict__) > 3:
|
||||
return self._source_repr_class(source, binding=binding)
|
||||
else:
|
||||
vals = self.__dict__.copy()
|
||||
if 'declarative_count' in vals:
|
||||
del vals['declarative_count']
|
||||
args = []
|
||||
if (self.__unpackargs__ and self.__unpackargs__[0] == '*'
|
||||
and self.__unpackargs__[1] in vals):
|
||||
v = vals[self.__unpackargs__[1]]
|
||||
if isinstance(v, (list, int)):
|
||||
args.extend(map(source.makeRepr, v))
|
||||
del v[self.__unpackargs__[1]]
|
||||
for name in self.__unpackargs__:
|
||||
if name in vals:
|
||||
args.append(source.makeRepr(vals[name]))
|
||||
del vals[name]
|
||||
else:
|
||||
break
|
||||
args.extend(['%s=%s' % (name, source.makeRepr(value))
|
||||
for (name, value) in vals.items()])
|
||||
return '%s(%s)' % (self.__class__.__name__,
|
||||
', '.join(args))
|
||||
|
||||
def _source_repr_class(self, source, binding=None):
|
||||
d = self.__dict__.copy()
|
||||
if 'declarative_count' in d:
|
||||
del d['declarative_count']
|
||||
return source.makeClass(self, binding, d,
|
||||
(self.__class__,))
|
||||
|
||||
def __classsourcerepr__(cls, source, binding=None):
|
||||
d = cls.__dict__.copy()
|
||||
del d['declarative_count']
|
||||
return source.makeClass(cls, binding or cls.__name__, d,
|
||||
cls.__bases__)
|
||||
__classsourcerepr__ = classmethod(__classsourcerepr__)
|
||||
|
||||
def __repr__(self, cls):
|
||||
if self:
|
||||
name = '%s object' % self.__class__.__name__
|
||||
v = self.__dict__.copy()
|
||||
else:
|
||||
name = '%s class' % cls.__name__
|
||||
v = cls.__dict__.copy()
|
||||
if 'declarative_count' in v:
|
||||
name = '%s %i' % (name, v.pop('declarative_count'))
|
||||
names = v.keys()
|
||||
args = []
|
||||
for n in self._repr_vars(names):
|
||||
args.append('%s=%r' % (n, v[n]))
|
||||
if not args:
|
||||
return '<%s>' % name
|
||||
else:
|
||||
return '<%s %s>' % (name, ' '.join(args))
|
||||
|
||||
def _repr_vars(dictNames):
|
||||
names = [n for n in dictNames
|
||||
if not n.startswith('_') and n != 'declarative_count']
|
||||
names.sort()
|
||||
return names
|
||||
_repr_vars = staticmethod(_repr_vars)
|
||||
|
||||
__repr__ = classinstancemethod(__repr__)
|
||||
|
||||
137
python/packages/formencode/doctest_xml_compare.py
Normal file
137
python/packages/formencode/doctest_xml_compare.py
Normal file
@@ -0,0 +1,137 @@
|
||||
try:
|
||||
import doctest
|
||||
doctest.OutputChecker
|
||||
except AttributeError: # Python < 2.4
|
||||
import util.doctest24 as doctest
|
||||
try:
|
||||
import xml.etree.ElementTree as ET
|
||||
except ImportError:
|
||||
import elementtree.ElementTree as ET
|
||||
from xml.parsers.expat import ExpatError as XMLParseError
|
||||
|
||||
RealOutputChecker = doctest.OutputChecker
|
||||
|
||||
|
||||
def debug(*msg):
|
||||
import sys
|
||||
print >> sys.stderr, ' '.join(map(str, msg))
|
||||
|
||||
|
||||
class HTMLOutputChecker(RealOutputChecker):
|
||||
|
||||
def check_output(self, want, got, optionflags):
|
||||
normal = RealOutputChecker.check_output(self, want, got, optionflags)
|
||||
if normal or not got:
|
||||
return normal
|
||||
try:
|
||||
want_xml = make_xml(want)
|
||||
except XMLParseError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
got_xml = make_xml(got)
|
||||
except XMLParseError:
|
||||
pass
|
||||
else:
|
||||
if xml_compare(want_xml, got_xml):
|
||||
return True
|
||||
return False
|
||||
|
||||
def output_difference(self, example, got, optionflags):
|
||||
actual = RealOutputChecker.output_difference(
|
||||
self, example, got, optionflags)
|
||||
want_xml = got_xml = None
|
||||
try:
|
||||
want_xml = make_xml(example.want)
|
||||
want_norm = make_string(want_xml)
|
||||
except XMLParseError, e:
|
||||
if example.want.startswith('<'):
|
||||
want_norm = '(bad XML: %s)' % e
|
||||
# '<xml>%s</xml>' % example.want
|
||||
else:
|
||||
return actual
|
||||
try:
|
||||
got_xml = make_xml(got)
|
||||
got_norm = make_string(got_xml)
|
||||
except XMLParseError, e:
|
||||
if example.want.startswith('<'):
|
||||
got_norm = '(bad XML: %s)' % e
|
||||
else:
|
||||
return actual
|
||||
s = '%s\nXML Wanted: %s\nXML Got : %s\n' % (
|
||||
actual, want_norm, got_norm)
|
||||
if got_xml and want_xml:
|
||||
result = []
|
||||
xml_compare(want_xml, got_xml, result.append)
|
||||
s += 'Difference report:\n%s\n' % '\n'.join(result)
|
||||
return s
|
||||
|
||||
|
||||
def xml_compare(x1, x2, reporter=None):
|
||||
if x1.tag != x2.tag:
|
||||
if reporter:
|
||||
reporter('Tags do not match: %s and %s' % (x1.tag, x2.tag))
|
||||
return False
|
||||
for name, value in x1.attrib.items():
|
||||
if x2.attrib.get(name) != value:
|
||||
if reporter:
|
||||
reporter('Attributes do not match: %s=%r, %s=%r'
|
||||
% (name, value, name, x2.attrib.get(name)))
|
||||
return False
|
||||
for name in x2.attrib.keys():
|
||||
if name not in x1.attrib:
|
||||
if reporter:
|
||||
reporter('x2 has an attribute x1 is missing: %s'
|
||||
% name)
|
||||
return False
|
||||
if not text_compare(x1.text, x2.text):
|
||||
if reporter:
|
||||
reporter('text: %r != %r' % (x1.text, x2.text))
|
||||
return False
|
||||
if not text_compare(x1.tail, x2.tail):
|
||||
if reporter:
|
||||
reporter('tail: %r != %r' % (x1.tail, x2.tail))
|
||||
return False
|
||||
cl1 = x1.getchildren()
|
||||
cl2 = x2.getchildren()
|
||||
if len(cl1) != len(cl2):
|
||||
if reporter:
|
||||
reporter('children length differs, %i != %i'
|
||||
% (len(cl1), len(cl2)))
|
||||
return False
|
||||
i = 0
|
||||
for c1, c2 in zip(cl1, cl2):
|
||||
i += 1
|
||||
if not xml_compare(c1, c2, reporter=reporter):
|
||||
if reporter:
|
||||
reporter('children %i do not match: %s'
|
||||
% (i, c1.tag))
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def text_compare(t1, t2):
|
||||
if not t1 and not t2:
|
||||
return True
|
||||
if t1 == '*' or t2 == '*':
|
||||
return True
|
||||
return (t1 or '').strip() == (t2 or '').strip()
|
||||
|
||||
|
||||
def make_xml(s):
|
||||
return ET.XML('<xml>%s</xml>' % s)
|
||||
|
||||
|
||||
def make_string(xml):
|
||||
if isinstance(xml, (str, unicode)):
|
||||
xml = make_xml(xml)
|
||||
s = ET.tostring(xml)
|
||||
if s == '<xml />':
|
||||
return ''
|
||||
assert s.startswith('<xml>') and s.endswith('</xml>'), repr(s)
|
||||
return s[5:-6]
|
||||
|
||||
|
||||
def install():
|
||||
doctest.OutputChecker = HTMLOutputChecker
|
||||
|
||||
13
python/packages/formencode/fieldstorage.py
Normal file
13
python/packages/formencode/fieldstorage.py
Normal file
@@ -0,0 +1,13 @@
|
||||
## FormEncode, a Form processor
|
||||
## Copyright (C) 2003, Ian Bicking <ianb@colorstudy.com>
|
||||
"""
|
||||
Wrapper class for use with cgi.FieldStorage types for file uploads
|
||||
"""
|
||||
|
||||
import cgi
|
||||
|
||||
def convert_fieldstorage(fs):
|
||||
if fs.filename:
|
||||
return fs
|
||||
else:
|
||||
return None
|
||||
151
python/packages/formencode/foreach.py
Normal file
151
python/packages/formencode/foreach.py
Normal file
@@ -0,0 +1,151 @@
|
||||
"""
|
||||
Validator for repeating items.
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
try:
|
||||
set
|
||||
except NameError: # Python < 2.4
|
||||
from sets import Set as set
|
||||
|
||||
filters = warnings.filters[:]
|
||||
warnings.simplefilter('ignore', DeprecationWarning)
|
||||
warnings.filters = filters
|
||||
|
||||
|
||||
from api import NoDefault, Invalid
|
||||
from compound import CompoundValidator, from_python
|
||||
|
||||
__all__ = ['ForEach']
|
||||
|
||||
|
||||
class ForEach(CompoundValidator):
|
||||
"""
|
||||
Use this to apply a validator/converter to each item in a list.
|
||||
|
||||
For instance::
|
||||
|
||||
ForEach(AsInt(), InList([1, 2, 3]))
|
||||
|
||||
Will take a list of values and try to convert each of them to
|
||||
an integer, and then check if each integer is 1, 2, or 3. Using
|
||||
multiple arguments is equivalent to::
|
||||
|
||||
ForEach(All(AsInt(), InList([1, 2, 3])))
|
||||
|
||||
Use convert_to_list=True if you want to force the input to be a
|
||||
list. This will turn non-lists into one-element lists, and None
|
||||
into the empty list. This tries to detect sequences by iterating
|
||||
over them (except strings, which aren't considered sequences).
|
||||
|
||||
ForEach will try to convert the entire list, even if errors are
|
||||
encountered. If errors are encountered, they will be collected
|
||||
and a single Invalid exception will be raised at the end (with
|
||||
error_list set).
|
||||
|
||||
If the incoming value is a set, then we return a set.
|
||||
"""
|
||||
|
||||
convert_to_list = True
|
||||
if_empty = NoDefault
|
||||
repeating = True
|
||||
_if_missing = ()
|
||||
|
||||
def attempt_convert(self, value, state, validate):
|
||||
if self.convert_to_list:
|
||||
value = self._convert_to_list(value)
|
||||
if self.if_empty is not NoDefault and not value:
|
||||
return self.if_empty
|
||||
if self.not_empty and not value:
|
||||
if validate is from_python and self.accept_python:
|
||||
return []
|
||||
raise Invalid(
|
||||
self.message('empty', state),
|
||||
value, state)
|
||||
new_list = []
|
||||
errors = []
|
||||
all_good = True
|
||||
is_set = isinstance(value, set)
|
||||
if state is not None:
|
||||
previous_index = getattr(state, 'index', NoDefault)
|
||||
previous_full_list = getattr(state, 'full_list', NoDefault)
|
||||
index = 0
|
||||
state.full_list = value
|
||||
try:
|
||||
for sub_value in value:
|
||||
if state:
|
||||
state.index = index
|
||||
index += 1
|
||||
good_pass = True
|
||||
for validator in self.validators:
|
||||
try:
|
||||
sub_value = validate(validator, sub_value, state)
|
||||
except Invalid, e:
|
||||
errors.append(e)
|
||||
all_good = False
|
||||
good_pass = False
|
||||
break
|
||||
if good_pass:
|
||||
errors.append(None)
|
||||
new_list.append(sub_value)
|
||||
if all_good:
|
||||
if is_set:
|
||||
new_list = set(new_list)
|
||||
return new_list
|
||||
else:
|
||||
raise Invalid(
|
||||
'Errors:\n%s' % '\n'.join([unicode(e) for e in errors if e]),
|
||||
value,
|
||||
state,
|
||||
error_list=errors)
|
||||
finally:
|
||||
if state is not None:
|
||||
if previous_index is NoDefault:
|
||||
try:
|
||||
del state.index
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
state.index = previous_index
|
||||
if previous_full_list is NoDefault:
|
||||
try:
|
||||
del state.full_list
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
state.full_list = previous_full_list
|
||||
|
||||
def empty_value(self, value):
|
||||
return []
|
||||
|
||||
class _IfMissing(object):
|
||||
def __get__(self, obj, type=None):
|
||||
if obj is None:
|
||||
return []
|
||||
elif obj._if_missing is ForEach._if_missing:
|
||||
return []
|
||||
else:
|
||||
return obj._if_missing
|
||||
def __set__(self, obj, value):
|
||||
obj._if_missing = value
|
||||
def __delete__(self, obj):
|
||||
obj._if_missing = NoDefault
|
||||
|
||||
if_missing = _IfMissing()
|
||||
del _IfMissing
|
||||
|
||||
def _convert_to_list(self, value):
|
||||
if isinstance(value, (str, unicode)):
|
||||
return [value]
|
||||
elif value is None:
|
||||
return []
|
||||
elif isinstance(value, (list, tuple)):
|
||||
return value
|
||||
try:
|
||||
for n in value:
|
||||
break
|
||||
return value
|
||||
## @@: Should this catch any other errors?:
|
||||
except TypeError:
|
||||
return [value]
|
||||
538
python/packages/formencode/htmlfill.py
Normal file
538
python/packages/formencode/htmlfill.py
Normal file
File diff suppressed because it is too large
Load Diff
118
python/packages/formencode/htmlfill_schemabuilder.py
Normal file
118
python/packages/formencode/htmlfill_schemabuilder.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""
|
||||
Extension to ``htmlfill`` that can parse out schema-defining
|
||||
statements.
|
||||
|
||||
You can either pass ``SchemaBuilder`` to ``htmlfill.render`` (the
|
||||
``listen`` argument), or call ``parse_schema`` to just parse out a
|
||||
``Schema`` object.
|
||||
"""
|
||||
|
||||
import validators, schema, compound, htmlfill
|
||||
|
||||
__all__ = ['parse_schema', 'SchemaBuilder']
|
||||
|
||||
|
||||
def parse_schema(form):
|
||||
"""
|
||||
Given an HTML form, parse out the schema defined in it and return
|
||||
that schema.
|
||||
"""
|
||||
listener = SchemaBuilder()
|
||||
p = htmlfill.FillingParser(
|
||||
defaults={}, listener=listener)
|
||||
p.feed(form)
|
||||
p.close()
|
||||
return listener.schema()
|
||||
|
||||
|
||||
default_validators = dict(
|
||||
[(name.lower(), getattr(validators, name))
|
||||
for name in dir(validators)])
|
||||
|
||||
|
||||
def get_messages(cls, message):
|
||||
if not message:
|
||||
return {}
|
||||
else:
|
||||
return dict([(k, message) for k in cls._messages.keys()])
|
||||
|
||||
|
||||
def to_bool(value):
|
||||
value = value.strip().lower()
|
||||
if value in ('true', 't', 'yes', 'y', 'on', '1'):
|
||||
return True
|
||||
elif value in ('false', 'f', 'no', 'n', 'off', '0'):
|
||||
return False
|
||||
else:
|
||||
raise ValueError("Not a boolean value: %r (use 'true'/'false')")
|
||||
|
||||
|
||||
def force_list(v):
|
||||
"""
|
||||
Force single items into a list. This is useful for checkboxes.
|
||||
"""
|
||||
if isinstance(v, list):
|
||||
return v
|
||||
elif isinstance(v, tuple):
|
||||
return list(v)
|
||||
else:
|
||||
return [v]
|
||||
|
||||
|
||||
class SchemaBuilder(object):
|
||||
|
||||
def __init__(self, validators=default_validators):
|
||||
self.validators = validators
|
||||
self._schema = None
|
||||
|
||||
def reset(self):
|
||||
self._schema = schema.Schema()
|
||||
|
||||
def schema(self):
|
||||
return self._schema
|
||||
|
||||
def listen_input(self, parser, tag, attrs):
|
||||
get_attr = parser.get_attr
|
||||
name = get_attr(attrs, 'name')
|
||||
if not name:
|
||||
# @@: should warn if you try to validate unnamed fields
|
||||
return
|
||||
v = compound.All(validators.Identity())
|
||||
add_to_end = None
|
||||
# for checkboxes, we must set if_missing = False
|
||||
if tag.lower() == "input":
|
||||
type_attr = get_attr(attrs, "type").lower().strip()
|
||||
if type_attr == "submit":
|
||||
v.validators.append(validators.Bool())
|
||||
elif type_attr == "checkbox":
|
||||
v.validators.append(validators.Wrapper(to_python = force_list))
|
||||
elif type_attr == "file":
|
||||
add_to_end = validators.FieldStorageUploadConverter()
|
||||
message = get_attr(attrs, 'form:message')
|
||||
required = to_bool(get_attr(attrs, 'form:required', 'false'))
|
||||
if required:
|
||||
v.validators.append(
|
||||
validators.NotEmpty(
|
||||
messages=get_messages(validators.NotEmpty, message)))
|
||||
else:
|
||||
v.validators[0].if_missing = False
|
||||
if add_to_end:
|
||||
v.validators.append(add_to_end)
|
||||
v_type = get_attr(attrs, 'form:validate', None)
|
||||
if v_type:
|
||||
pos = v_type.find(':')
|
||||
if pos != -1:
|
||||
# @@: should parse args
|
||||
args = (v_type[pos+1:],)
|
||||
v_type = v_type[:pos]
|
||||
else:
|
||||
args = ()
|
||||
v_type = v_type.lower()
|
||||
v_class = self.validators.get(v_type)
|
||||
if not v_class:
|
||||
raise ValueError("Invalid validation type: %r" % v_type)
|
||||
kw_args={'messages': get_messages(v_class, message)}
|
||||
v_inst = v_class(
|
||||
*args, **kw_args)
|
||||
v.validators.append(v_inst)
|
||||
self._schema.add_field(name, v)
|
||||
190
python/packages/formencode/htmlgen.py
Normal file
190
python/packages/formencode/htmlgen.py
Normal file
@@ -0,0 +1,190 @@
|
||||
"""
|
||||
Kind of like htmlgen, only much simpler. The only important symbol
|
||||
that is exported is ``html``.
|
||||
|
||||
This builds ElementTree nodes, but with some extra useful methods.
|
||||
(Open issue: should it use ``ElementTree`` more, and the raw
|
||||
``Element`` stuff less?)
|
||||
|
||||
You create tags with attribute access. I.e., the ``A`` anchor tag is
|
||||
``html.a``. The attributes of the HTML tag are done with keyword
|
||||
arguments. The contents of the tag are the non-keyword arguments
|
||||
(concatenated). You can also use the special ``c`` keyword, passing a
|
||||
list, tuple, or single tag, and it will make up the contents (this is
|
||||
useful because keywords have to come after all non-keyword arguments,
|
||||
which is non-intuitive). Or you can chain them, adding the keywords
|
||||
with one call, then the body with a second call, like::
|
||||
|
||||
>>> print html.a(href='http://yahoo.com')('<Yahoo>')
|
||||
<a href=\"http://yahoo.com\"><Yahoo></a>
|
||||
|
||||
Note that strings will be quoted; only tags given explicitly will
|
||||
remain unquoted.
|
||||
|
||||
If the value of an attribute is None, then no attribute
|
||||
will be inserted. So::
|
||||
|
||||
>>> print html.a(href='http://www.yahoo.com', name=None,
|
||||
... c='Click Here')
|
||||
<a href=\"http://www.yahoo.com\">Click Here</a>
|
||||
|
||||
If the value is None, then the empty string is used. Otherwise str()
|
||||
is called on the value.
|
||||
|
||||
``html`` can also be called, and it will produce a special list from
|
||||
its arguments, which adds a ``__str__`` method that does ``html.str``
|
||||
(which handles quoting, flattening these lists recursively, and using
|
||||
'' for ``None``).
|
||||
|
||||
``html.comment`` will generate an HTML comment, like
|
||||
``html.comment('comment text')`` -- note that it cannot take keyword
|
||||
arguments (because they wouldn't mean anything).
|
||||
|
||||
Examples::
|
||||
|
||||
>>> print html.html(
|
||||
... html.head(html.title(\"Page Title\")),
|
||||
... html.body(
|
||||
... bgcolor='#000066',
|
||||
... text='#ffffff',
|
||||
... c=[html.h1('Page Title'),
|
||||
... html.p('Hello world!')],
|
||||
... ))
|
||||
<html><head><title>Page Title</title></head><body bgcolor=\"#000066\" text=\"#ffffff\"><h1>Page Title</h1><p>Hello world!</p></body></html>
|
||||
>>> print html.a(href='#top')('return to top')
|
||||
<a href=\"#top\">return to top</a>
|
||||
|
||||
"""
|
||||
|
||||
from cgi import escape
|
||||
try:
|
||||
import xml.etree.ElementTree as ET
|
||||
except ImportError: # Python < 2.5
|
||||
import elementtree.ElementTree as ET
|
||||
|
||||
__all__ = ['html']
|
||||
|
||||
default_encoding = 'utf-8'
|
||||
|
||||
|
||||
class _HTML:
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr.startswith('_'):
|
||||
raise AttributeError
|
||||
attr = attr.lower()
|
||||
if attr.endswith('_'):
|
||||
attr = attr[:-1]
|
||||
if attr.find('__') != -1:
|
||||
attr = attr.replace('__', ':')
|
||||
if attr == 'comment':
|
||||
return Element(ET.Comment, {})
|
||||
else:
|
||||
return Element(attr, {})
|
||||
|
||||
def __call__(self, *args):
|
||||
return ElementList(args)
|
||||
|
||||
def quote(self, arg):
|
||||
if arg is None:
|
||||
return ''
|
||||
return escape(unicode(arg).encode(default_encoding), 1)
|
||||
|
||||
def str(self, arg, encoding=None):
|
||||
if isinstance(arg, str):
|
||||
return arg
|
||||
elif arg is None:
|
||||
return ''
|
||||
elif isinstance(arg, unicode):
|
||||
return arg.encode(default_encoding)
|
||||
elif isinstance(arg, (list, tuple)):
|
||||
return ''.join(map(self.str, arg))
|
||||
elif isinstance(arg, Element):
|
||||
return str(arg)
|
||||
else:
|
||||
return unicode(arg).encode(default_encoding)
|
||||
|
||||
html = _HTML()
|
||||
|
||||
|
||||
class Element(ET._ElementInterface):
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
el = self.__class__(self.tag, self.attrib)
|
||||
if 'c' in kw:
|
||||
if args:
|
||||
raise ValueError(
|
||||
"You may either provide positional arguments or a "
|
||||
"'c' keyword argument, but not both")
|
||||
args = kw.pop('c')
|
||||
if not isinstance(args, (list, tuple)):
|
||||
args = (args,)
|
||||
for name, value in kw.items():
|
||||
if value is None:
|
||||
del kw[name]
|
||||
continue
|
||||
kw[name] = unicode(value)
|
||||
if name.endswith('_'):
|
||||
kw[name[:-1]] = value
|
||||
del kw[name]
|
||||
if name.find('__') != -1:
|
||||
new_name = name.replace('__', ':')
|
||||
kw[new_name] = value
|
||||
del kw[name]
|
||||
el.attrib.update(kw)
|
||||
el.text = self.text
|
||||
last = None
|
||||
for item in self.getchildren():
|
||||
last = item
|
||||
el.append(item)
|
||||
for arg in flatten(args):
|
||||
if arg is None:
|
||||
continue
|
||||
if not ET.iselement(arg):
|
||||
if last is None:
|
||||
if el.text is None:
|
||||
el.text = unicode(arg)
|
||||
else:
|
||||
el.text += unicode(arg)
|
||||
else:
|
||||
if last.tail is None:
|
||||
last.tail = unicode(arg)
|
||||
else:
|
||||
last.tail += unicode(arg)
|
||||
else:
|
||||
last = arg
|
||||
el.append(last)
|
||||
return el
|
||||
|
||||
def __str__(self):
|
||||
return ET.tostring(self, default_encoding)
|
||||
|
||||
def __unicode__(self):
|
||||
# This is lame!
|
||||
return str(self).decode(default_encoding)
|
||||
|
||||
def __repr__(self):
|
||||
content = str(self)
|
||||
if len(content) > 25:
|
||||
content = repr(content[:25]) + '...'
|
||||
else:
|
||||
content = repr(content)
|
||||
return '<Element %r>' % content
|
||||
|
||||
|
||||
class ElementList(list):
|
||||
|
||||
def __str__(self):
|
||||
return html.str(self)
|
||||
|
||||
def __repr__(self):
|
||||
return 'ElementList(%s)' % list.__repr__(self)
|
||||
|
||||
|
||||
def flatten(items):
|
||||
for item in items:
|
||||
if isinstance(item, (list, tuple)):
|
||||
for sub in flatten(item):
|
||||
yield sub
|
||||
else:
|
||||
yield item
|
||||
78
python/packages/formencode/htmlrename.py
Normal file
78
python/packages/formencode/htmlrename.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
Module to rename form fields
|
||||
"""
|
||||
|
||||
from formencode.rewritingparser import RewritingParser
|
||||
|
||||
__all__ = ['rename', 'add_prefix']
|
||||
|
||||
|
||||
def rename(form, rename_func):
|
||||
"""
|
||||
Rename all the form fields in the form (a string), using rename_func
|
||||
|
||||
rename_func will be called with one argument, the name of the
|
||||
field, and should return a new name.
|
||||
"""
|
||||
p = RenamingParser(rename_func)
|
||||
p.feed(form)
|
||||
p.close()
|
||||
return p.text()
|
||||
|
||||
|
||||
def add_prefix(form, prefix, dotted=False):
|
||||
"""
|
||||
Add the given prefix to all the fields in the form.
|
||||
|
||||
If dotted is true, then add a dot between prefix and the previous
|
||||
name. Empty fields will use the prefix as the name (with no dot).
|
||||
"""
|
||||
def rename_func(field_name):
|
||||
if dotted:
|
||||
if field_name:
|
||||
return prefix + '.' + field_name
|
||||
else:
|
||||
return prefix
|
||||
else:
|
||||
return prefix + field_name
|
||||
return rename(form, rename_func)
|
||||
|
||||
|
||||
class RenamingParser(RewritingParser):
|
||||
|
||||
def __init__(self, rename_func):
|
||||
RewritingParser.__init__(self)
|
||||
self.rename_func = rename_func
|
||||
|
||||
def close(self):
|
||||
self.handle_misc(None)
|
||||
RewritingParser.close(self)
|
||||
self._text = self._get_text()
|
||||
|
||||
def text(self):
|
||||
try:
|
||||
return self._text
|
||||
except AttributeError:
|
||||
raise Exception(
|
||||
"You must .close() a parser instance before getting "
|
||||
"the text from it")
|
||||
|
||||
def handle_starttag(self, tag, attrs, startend=False):
|
||||
self.write_pos()
|
||||
if tag in ('input', 'textarea', 'select'):
|
||||
self.handle_field(tag, attrs, startend)
|
||||
else:
|
||||
return
|
||||
|
||||
def handle_startendtag(self, tag, attrs):
|
||||
return self.handle_starttag(tag, attrs, True)
|
||||
|
||||
def handle_field(self, tag, attrs, startend):
|
||||
name = self.get_attr(attrs, 'name', '')
|
||||
new_name = self.rename_func(name)
|
||||
if name is None:
|
||||
self.del_attr(attrs, 'name')
|
||||
else:
|
||||
self.set_attr(attrs, 'name', new_name)
|
||||
self.write_tag(tag, attrs)
|
||||
self.skip_next = True
|
||||
543
python/packages/formencode/i18n/FormEncode.pot
Normal file
543
python/packages/formencode/i18n/FormEncode.pot
Normal file
File diff suppressed because it is too large
Load Diff
BIN
python/packages/formencode/i18n/cs/LC_MESSAGES/FormEncode.mo
Normal file
BIN
python/packages/formencode/i18n/cs/LC_MESSAGES/FormEncode.mo
Normal file
Binary file not shown.
547
python/packages/formencode/i18n/cs/LC_MESSAGES/FormEncode.po
Normal file
547
python/packages/formencode/i18n/cs/LC_MESSAGES/FormEncode.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
python/packages/formencode/i18n/de/LC_MESSAGES/FormEncode.mo
Normal file
BIN
python/packages/formencode/i18n/de/LC_MESSAGES/FormEncode.mo
Normal file
Binary file not shown.
563
python/packages/formencode/i18n/de/LC_MESSAGES/FormEncode.po
Normal file
563
python/packages/formencode/i18n/de/LC_MESSAGES/FormEncode.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
python/packages/formencode/i18n/el/LC_MESSAGES/FormEncode.mo
Normal file
BIN
python/packages/formencode/i18n/el/LC_MESSAGES/FormEncode.mo
Normal file
Binary file not shown.
564
python/packages/formencode/i18n/el/LC_MESSAGES/FormEncode.po
Normal file
564
python/packages/formencode/i18n/el/LC_MESSAGES/FormEncode.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
python/packages/formencode/i18n/es/LC_MESSAGES/FormEncode.mo
Normal file
BIN
python/packages/formencode/i18n/es/LC_MESSAGES/FormEncode.mo
Normal file
Binary file not shown.
559
python/packages/formencode/i18n/es/LC_MESSAGES/FormEncode.po
Normal file
559
python/packages/formencode/i18n/es/LC_MESSAGES/FormEncode.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
python/packages/formencode/i18n/et/LC_MESSAGES/FormEncode.mo
Normal file
BIN
python/packages/formencode/i18n/et/LC_MESSAGES/FormEncode.mo
Normal file
Binary file not shown.
552
python/packages/formencode/i18n/et/LC_MESSAGES/FormEncode.po
Normal file
552
python/packages/formencode/i18n/et/LC_MESSAGES/FormEncode.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
python/packages/formencode/i18n/fi/LC_MESSAGES/FormEncode.mo
Normal file
BIN
python/packages/formencode/i18n/fi/LC_MESSAGES/FormEncode.mo
Normal file
Binary file not shown.
559
python/packages/formencode/i18n/fi/LC_MESSAGES/FormEncode.po
Normal file
559
python/packages/formencode/i18n/fi/LC_MESSAGES/FormEncode.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
python/packages/formencode/i18n/fr/LC_MESSAGES/FormEncode.mo
Normal file
BIN
python/packages/formencode/i18n/fr/LC_MESSAGES/FormEncode.mo
Normal file
Binary file not shown.
570
python/packages/formencode/i18n/fr/LC_MESSAGES/FormEncode.po
Normal file
570
python/packages/formencode/i18n/fr/LC_MESSAGES/FormEncode.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
python/packages/formencode/i18n/it/LC_MESSAGES/FormEncode.mo
Normal file
BIN
python/packages/formencode/i18n/it/LC_MESSAGES/FormEncode.mo
Normal file
Binary file not shown.
559
python/packages/formencode/i18n/it/LC_MESSAGES/FormEncode.po
Normal file
559
python/packages/formencode/i18n/it/LC_MESSAGES/FormEncode.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
python/packages/formencode/i18n/ja/LC_MESSAGES/FormEncode.mo
Normal file
BIN
python/packages/formencode/i18n/ja/LC_MESSAGES/FormEncode.mo
Normal file
Binary file not shown.
544
python/packages/formencode/i18n/ja/LC_MESSAGES/FormEncode.po
Normal file
544
python/packages/formencode/i18n/ja/LC_MESSAGES/FormEncode.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
python/packages/formencode/i18n/lt/LC_MESSAGES/FormEncode.mo
Normal file
BIN
python/packages/formencode/i18n/lt/LC_MESSAGES/FormEncode.mo
Normal file
Binary file not shown.
554
python/packages/formencode/i18n/lt/LC_MESSAGES/FormEncode.po
Normal file
554
python/packages/formencode/i18n/lt/LC_MESSAGES/FormEncode.po
Normal file
File diff suppressed because it is too large
Load Diff
203
python/packages/formencode/i18n/msgfmt.py
Executable file
203
python/packages/formencode/i18n/msgfmt.py
Executable file
@@ -0,0 +1,203 @@
|
||||
#! /usr/bin/python
|
||||
# -*- coding: iso-8859-1 -*-
|
||||
# Written by Martin v. L<>wis <loewis@informatik.hu-berlin.de>
|
||||
|
||||
"""Generate binary message catalog from textual translation description.
|
||||
|
||||
This program converts a textual Uniforum-style message catalog (.po file) into
|
||||
a binary GNU catalog (.mo file). This is essentially the same function as the
|
||||
GNU msgfmt program, however, it is a simpler implementation.
|
||||
|
||||
Usage: msgfmt.py [OPTIONS] filename.po
|
||||
|
||||
Options:
|
||||
-o file
|
||||
--output-file=file
|
||||
Specify the output file to write to. If omitted, output will go to a
|
||||
file named filename.mo (based off the input file name).
|
||||
|
||||
-h
|
||||
--help
|
||||
Print this message and exit.
|
||||
|
||||
-V
|
||||
--version
|
||||
Display version information and exit.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import getopt
|
||||
import struct
|
||||
import array
|
||||
|
||||
__version__ = "1.1"
|
||||
|
||||
MESSAGES = {}
|
||||
|
||||
|
||||
|
||||
def usage(code, msg=''):
|
||||
print >> sys.stderr, __doc__
|
||||
if msg:
|
||||
print >> sys.stderr, msg
|
||||
sys.exit(code)
|
||||
|
||||
|
||||
|
||||
def add(id, str, fuzzy):
|
||||
"Add a non-fuzzy translation to the dictionary."
|
||||
global MESSAGES
|
||||
if not fuzzy and str:
|
||||
MESSAGES[id] = str
|
||||
|
||||
|
||||
|
||||
def generate():
|
||||
"Return the generated output."
|
||||
global MESSAGES
|
||||
keys = MESSAGES.keys()
|
||||
# the keys are sorted in the .mo file
|
||||
keys.sort()
|
||||
offsets = []
|
||||
ids = strs = ''
|
||||
for id in keys:
|
||||
# For each string, we need size and file offset. Each string is NUL
|
||||
# terminated; the NUL does not count into the size.
|
||||
offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id])))
|
||||
ids += id + '\0'
|
||||
strs += MESSAGES[id] + '\0'
|
||||
output = ''
|
||||
# The header is 7 32-bit unsigned integers. We don't use hash tables, so
|
||||
# the keys start right after the index tables.
|
||||
# translated string.
|
||||
keystart = 7*4+16*len(keys)
|
||||
# and the values start after the keys
|
||||
valuestart = keystart + len(ids)
|
||||
koffsets = []
|
||||
voffsets = []
|
||||
# The string table first has the list of keys, then the list of values.
|
||||
# Each entry has first the size of the string, then the file offset.
|
||||
for o1, l1, o2, l2 in offsets:
|
||||
koffsets += [l1, o1+keystart]
|
||||
voffsets += [l2, o2+valuestart]
|
||||
offsets = koffsets + voffsets
|
||||
output = struct.pack("Iiiiiii",
|
||||
0x950412deL, # Magic
|
||||
0, # Version
|
||||
len(keys), # # of entries
|
||||
7*4, # start of key index
|
||||
7*4+len(keys)*8, # start of value index
|
||||
0, 0) # size and offset of hash table
|
||||
output += array.array("i", offsets).tostring()
|
||||
output += ids
|
||||
output += strs
|
||||
return output
|
||||
|
||||
|
||||
|
||||
def make(filename, outfile):
|
||||
ID = 1
|
||||
STR = 2
|
||||
|
||||
# Compute .mo name from .po name and arguments
|
||||
if filename.endswith('.po'):
|
||||
infile = filename
|
||||
else:
|
||||
infile = filename + '.po'
|
||||
if outfile is None:
|
||||
outfile = os.path.splitext(infile)[0] + '.mo'
|
||||
|
||||
try:
|
||||
lines = open(infile).readlines()
|
||||
except IOError, msg:
|
||||
print >> sys.stderr, msg
|
||||
sys.exit(1)
|
||||
|
||||
section = None
|
||||
fuzzy = 0
|
||||
|
||||
# Parse the catalog
|
||||
lno = 0
|
||||
for l in lines:
|
||||
lno += 1
|
||||
# If we get a comment line after a msgstr, this is a new entry
|
||||
if l[0] == '#' and section == STR:
|
||||
add(msgid, msgstr, fuzzy)
|
||||
section = None
|
||||
fuzzy = 0
|
||||
# Record a fuzzy mark
|
||||
if l[:2] == '#,' and 'fuzzy' in l:
|
||||
fuzzy = 1
|
||||
# Skip comments
|
||||
if l[0] == '#':
|
||||
continue
|
||||
# Now we are in a msgid section, output previous section
|
||||
if l.startswith('msgid'):
|
||||
if section == STR:
|
||||
add(msgid, msgstr, fuzzy)
|
||||
section = ID
|
||||
l = l[5:]
|
||||
msgid = msgstr = ''
|
||||
# Now we are in a msgstr section
|
||||
elif l.startswith('msgstr'):
|
||||
section = STR
|
||||
l = l[6:]
|
||||
# Skip empty lines
|
||||
l = l.strip()
|
||||
if not l:
|
||||
continue
|
||||
# XXX: Does this always follow Python escape semantics?
|
||||
l = eval(l)
|
||||
if section == ID:
|
||||
msgid += l
|
||||
elif section == STR:
|
||||
msgstr += l
|
||||
else:
|
||||
print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \
|
||||
'before:'
|
||||
print >> sys.stderr, l
|
||||
sys.exit(1)
|
||||
# Add last entry
|
||||
if section == STR:
|
||||
add(msgid, msgstr, fuzzy)
|
||||
|
||||
# Compute output
|
||||
output = generate()
|
||||
|
||||
try:
|
||||
open(outfile,"wb").write(output)
|
||||
except IOError,msg:
|
||||
print >> sys.stderr, msg
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'hVo:',
|
||||
['help', 'version', 'output-file='])
|
||||
except getopt.error, msg:
|
||||
usage(1, msg)
|
||||
|
||||
outfile = None
|
||||
# parse options
|
||||
for opt, arg in opts:
|
||||
if opt in ('-h', '--help'):
|
||||
usage(0)
|
||||
elif opt in ('-V', '--version'):
|
||||
print >> sys.stderr, "msgfmt.py", __version__
|
||||
sys.exit(0)
|
||||
elif opt in ('-o', '--output-file'):
|
||||
outfile = arg
|
||||
# do it
|
||||
if not args:
|
||||
print >> sys.stderr, 'No input file given'
|
||||
print >> sys.stderr, "Try `msgfmt --help' for more information."
|
||||
return
|
||||
|
||||
for filename in args:
|
||||
make(filename, outfile)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
BIN
python/packages/formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.mo
Normal file
BIN
python/packages/formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.mo
Normal file
Binary file not shown.
551
python/packages/formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.po
Normal file
551
python/packages/formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.po
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user