Commits

Lynn Rees  committed ad4c96f

[svn]

  • Participants
  • Parent commits 789e303
  • Branches wsgiform
  • Tags svn.40

Comments (0)

Files changed (18)

File branches/0.3-r/trunk/ez_setup.py

-#!python
-"""Bootstrap setuptools installation
-
-If you want to use setuptools in your package's setup.py, just include this
-file in the same directory with it, and add this to the top of your setup.py::
-
-    from ez_setup import use_setuptools
-    use_setuptools()
-
-If you want to require a specific version of setuptools, set a download
-mirror, or use an alternate download directory, you can do so by supplying
-the appropriate options to ``use_setuptools()``.
-
-This file can also be run as a script to install or upgrade setuptools.
-"""
-import sys
-DEFAULT_VERSION = "0.6c3"
-DEFAULT_URL     = "http://cheeseshop.python.org/packages/%s/s/setuptools/" % sys.version[:3]
-
-md5_data = {
-    'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
-    'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
-    'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
-    'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
-    'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
-    'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
-    'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
-    'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
-    'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
-    'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
-    'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
-    'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
-    'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
-    'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
-    'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
-}
-
-import sys, os
-
-def _validate_md5(egg_name, data):
-    if egg_name in md5_data:
-        from md5 import md5
-        digest = md5(data).hexdigest()
-        if digest != md5_data[egg_name]:
-            print >>sys.stderr, (
-                "md5 validation of %s failed!  (Possible download problem?)"
-                % egg_name
-            )
-            sys.exit(2)
-    return data
-
-
-def use_setuptools(
-    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
-    download_delay=15
-):
-    """Automatically find/download setuptools and make it available on sys.path
-
-    `version` should be a valid setuptools version number that is available
-    as an egg for download under the `download_base` URL (which should end with
-    a '/').  `to_dir` is the directory where setuptools will be downloaded, if
-    it is not already available.  If `download_delay` is specified, it should
-    be the number of seconds that will be paused before initiating a download,
-    should one be required.  If an older version of setuptools is installed,
-    this routine will print a message to ``sys.stderr`` and raise SystemExit in
-    an attempt to abort the calling script.
-    """
-    try:
-        import setuptools
-        if setuptools.__version__ == '0.0.1':
-            print >>sys.stderr, (
-            "You have an obsolete version of setuptools installed.  Please\n"
-            "remove it from your system entirely before rerunning this script."
-            )
-            sys.exit(2)
-    except ImportError:
-        egg = download_setuptools(version, download_base, to_dir, download_delay)
-        sys.path.insert(0, egg)
-        import setuptools; setuptools.bootstrap_install_from = egg
-
-    import pkg_resources
-    try:
-        pkg_resources.require("setuptools>="+version)
-
-    except pkg_resources.VersionConflict, e:
-        # XXX could we install in a subprocess here?
-        print >>sys.stderr, (
-            "The required version of setuptools (>=%s) is not available, and\n"
-            "can't be installed while this script is running. Please install\n"
-            " a more recent version first.\n\n(Currently using %r)"
-        ) % (version, e.args[0])
-        sys.exit(2)
-
-def download_setuptools(
-    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
-    delay = 15
-):
-    """Download setuptools from a specified location and return its filename
-
-    `version` should be a valid setuptools version number that is available
-    as an egg for download under the `download_base` URL (which should end
-    with a '/'). `to_dir` is the directory where the egg will be downloaded.
-    `delay` is the number of seconds to pause before an actual download attempt.
-    """
-    import urllib2, shutil
-    egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
-    url = download_base + egg_name
-    saveto = os.path.join(to_dir, egg_name)
-    src = dst = None
-    if not os.path.exists(saveto):  # Avoid repeated downloads
-        try:
-            from distutils import log
-            if delay:
-                log.warn("""
----------------------------------------------------------------------------
-This script requires setuptools version %s to run (even to display
-help).  I will attempt to download it for you (from
-%s), but
-you may need to enable firewall access for this script first.
-I will start the download in %d seconds.
-
-(Note: if this machine does not have network access, please obtain the file
-
-   %s
-
-and place it in this directory before rerunning this script.)
----------------------------------------------------------------------------""",
-                    version, download_base, delay, url
-                ); from time import sleep; sleep(delay)
-            log.warn("Downloading %s", url)
-            src = urllib2.urlopen(url)
-            # Read/write all in one block, so we don't create a corrupt file
-            # if the download is interrupted.
-            data = _validate_md5(egg_name, src.read())
-            dst = open(saveto,"wb"); dst.write(data)
-        finally:
-            if src: src.close()
-            if dst: dst.close()
-    return os.path.realpath(saveto)
-
-def main(argv, version=DEFAULT_VERSION):
-    """Install or upgrade setuptools and EasyInstall"""
-
-    try:
-        import setuptools
-    except ImportError:
-        egg = None
-        try:
-            egg = download_setuptools(version, delay=0)
-            sys.path.insert(0,egg)
-            from setuptools.command.easy_install import main
-            return main(list(argv)+[egg])   # we're done here
-        finally:
-            if egg and os.path.exists(egg):
-                os.unlink(egg)
-    else:
-        if setuptools.__version__ == '0.0.1':
-            # tell the user to uninstall obsolete version
-            use_setuptools(version)
-
-    req = "setuptools>="+version
-    import pkg_resources
-    try:
-        pkg_resources.require(req)
-    except pkg_resources.VersionConflict:
-        try:
-            from setuptools.command.easy_install import main
-        except ImportError:
-            from easy_install import main
-        main(list(argv)+[download_setuptools(delay=0)])
-        sys.exit(0) # try to force an exit
-    else:
-        if argv:
-            from setuptools.command.easy_install import main
-            main(argv)
-        else:
-            print "Setuptools version",version,"or greater has been installed."
-            print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
-
-
-
-def update_md5(filenames):
-    """Update our built-in md5 registry"""
-
-    import re
-    from md5 import md5
-
-    for name in filenames:
-        base = os.path.basename(name)
-        f = open(name,'rb')
-        md5_data[base] = md5(f.read()).hexdigest()
-        f.close()
-
-    data = ["    %r: %r,\n" % it for it in md5_data.items()]
-    data.sort()
-    repl = "".join(data)
-
-    import inspect
-    srcfile = inspect.getsourcefile(sys.modules[__name__])
-    f = open(srcfile, 'rb'); src = f.read(); f.close()
-
-    match = re.search("\nmd5_data = {\n([^}]+)}", src)
-    if not match:
-        print >>sys.stderr, "Internal error!"
-        sys.exit(2)
-
-    src = src[:match.start(1)] + repl + src[match.end(1):]
-    f = open(srcfile,'w')
-    f.write(src)
-    f.close()
-
-
-if __name__=='__main__':
-    if len(sys.argv)>2 and sys.argv[1]=='--md5update':
-        update_md5(sys.argv[2:])
-    else:
-        main(sys.argv[1:])
-
-
-
-
-

File branches/0.3-r/trunk/setup.py

-# Copyright (c) 2006 L. C. Rees.  All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1.  Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2.  Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-# 3.  Neither the name of the Portable Site Information Project 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.
-
-'''setup - setuptools based setup for wsgiform.'''
-
-import ez_setup
-ez_setup.use_setuptools()
-
-try:
-    from setuptools import setup
-except:
-    from distutils.core import setup
-
-setup(name='wsgiform',
-      version='0.3',
-      description='''WSGI form parsing and validation middleware''',
-      long_description='''WSGI middleware for validating form submissions
-and parsing them into dictionaries, individual WSGI 'environ' dictionary
-entries, cgi.FieldStorage instances, or keyword arguments passed to a
-WSGI application. Supports automatically escaping and sterilizing form
-submissions.
-
-# Simple wsgiform usage format example:
-
-from wsgiform import form
-
-@form()
-def app(environ, start_response):
-    return environ['wsgiform.dict']['name']''',
-      author='L. C. Rees',
-      author_email='lcrees@gmail.com',
-      license='BSD',
-      test_suite='tests.test_wsgiform',
-      packages = ['wsgiform', 'wsgiform.tests'],
-      zip_safe = True,
-      keywords='WSGI dispatch middleware web HTTP decorators',
-      classifiers=['Development Status :: 3 - Alpha',
-                    'Environment :: Web Environment',
-                    'License :: OSI Approved :: BSD License',
-                    'Natural Language :: English',
-                    'Operating System :: OS Independent',
-                    'Programming Language :: Python',
-                    'Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware'])

File branches/0.3-r/trunk/wsgiform/__init__.py

-# Copyright (c) 2006 L. C. Rees
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-#    1. Redistributions of source code must retain the above copyright notice,
-#       this list of conditions and the following disclaimer.
-#
-#    2. Redistributions in binary form must reproduce the above copyright
-#       notice, this list of conditions and the following disclaimer in the
-#       documentation and/or other materials provided with the distribution.
-#
-#    3. Neither the name of WsgiForm 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.
-
-
-__author__ = 'L.C. Rees (lcrees@gmail.com)'
-__revision__ = '0.2.7'

File branches/0.3-r/trunk/wsgiform/form.py

-# Copyright (c) 2006 L. C. Rees
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-#    1. Redistributions of source code must retain the above copyright notice, 
-#       this list of conditions and the following disclaimer.
-#    
-#    2. Redistributions in binary form must reproduce the above copyright 
-#       notice, this list of conditions and the following disclaimer in the
-#       documentation and/or other materials provided with the distribution.
-#
-#    3. Neither the name of WsgiForm 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.
-
-'''WSGI middleware for validating form submissions and parsing them into
-dictionaries, individual WSGI 'environ' dictionary entries, cgi.FieldStorage
-instances, or keyword arguments passed to a WSGI application. Supports
-automatically escaping and sterilizing form submissions.
-'''
-
-import cgi
-import urllib
-from StringIO import StringIO
-from wsgiform.util import escapeform, hyperform, sterileform, getinput
-
-__all__ = ['WsgiForm', 'form']
-
-def _errorapp(environ, start_response):
-    '''Default error handler for form validation errors.
-
-    Replace with custom handler.
-    '''
-    start_response('200 OK', ('Content-type', 'text/plain'))
-    return ['Data in field(s) %s was invalid.' %
-            ' '.join(environ['wsgiform.error'])]
-
-def validate(qdict, validators, environ, strict=False):
-    '''Validates form data.
-
-    qdict Dictionary of validators where the key is a form field
-    environ A WSGI environment dictionary
-    validators An iterable with validators
-    strict Keys w/out validators are form errors (default: False)
-    '''
-    errors = []
-    # Validate each form entry
-    for key, value in qdict.iteritems():
-        try:
-            # Accumulate any errors
-            if not validators[key](value): errors.append(key)
-        except KeyError:
-            if strict: errors.append(key)
-    # Put any errors in environ error entry and fail
-    if errors:
-        environ['wsgiform.error'] = errors
-        return False        
-    return True
-
-def form(**kw):
-    '''Decorator for form parsing.'''
-    def decorator(application):
-        return WsgiForm(application, **kw)
-    return decorator
-    
-
-class WsgiForm(object):
-
-    '''Class that parses form data into dictionaries, individual 'environ'
-    entries, FieldStorage instances, or keyword arguments that can be passed to
-    WSGI applications in the environ dictionary.
-    '''
-    
-    environ = None
-    # Environ key styles
-    keys = {'fieldstorage':'wsgiform.fieldstorage', 'dict':'wsgiform.dict',
-        'kwargs':'wsgize.kwargs', 'environ':'wsgiform.%s',
-        'routing_args':'wsgiorg.routing_args'}
-    # Data sterilizing functions
-    funclist = {'escape':escapeform, 'hyperescape':hyperform,
-        'sterilize':sterileform} 
-
-    def __init__(self, application, **kw):
-        self.application = application
-        # Style and key for form data passing format
-        self.style = kw.get('style', 'dict')
-        self.key = self.keys.get(self.style)
-        # Dictionary of validating functions where keywords == form field names 
-        self.validators = kw.get('validators', {})
-        # Function to validate form submissions
-        self.validate = kw.get('validfunc', validate)
-        # WSGI application to handle form validation errors
-        self.handler = kw.get('errapp', _errorapp)
-        # Stop on errors, empty fields, and validation errors
-        self.strict = kw.get('strict', False)
-        # Function to escape metachars
-        self.func = self.funclist[(kw.get('func', 'escape'))]
-        
-    def __call__(self, env, start_response):       
-        qdict = self.func(env, self.strict)            
-        # Validate form if validators present            
-        if self.validators:
-            if not self.validate(qdict, self.validators, env, self.strict):
-                return self.handler(env, start_response)
-        if self.style == 'fieldstorage':
-            # Reparse query back into a query string
-            cginput = StringIO(urllib.urlencode(qdict))
-            # Put into cgi.FieldStorage instance
-            qdict = cgi.FieldStorage(fp=cginput, environ=env)
-        # Store form submissions as individual environ entries
-        if self.style == 'environ':
-            for k, v in qdict.iteritems(): env[self.key % k] = v
-        # Store form submissions using wsgi.routing_vars style
-        elif self.style == 'routing_args':
-            args, kwargs = env.get(self.key, ((), {}))
-            env[self.key] = (args, kwargs.update(qdict))
-        # Store form submissions as kwargs, dict, or cgi.FieldStorage
-        else:
-            env[self.key] = qdict
-        return self.application(env, start_response)

File branches/0.3-r/trunk/wsgiform/tests/__init__.py

Empty file removed.

File branches/0.3-r/trunk/wsgiform/tests/test_validators.py

-# Copyright (c) 2006 L. C. Rees
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-#    1. Redistributions of source code must retain the above copyright notice,
-#       this list of conditions and the following disclaimer.
-#
-#    2. Redistributions in binary form must reproduce the above copyright
-#       notice, this list of conditions and the following disclaimer in the
-#       documentation and/or other materials provided with the distribution.
-#
-#    3. Neither the name of WsgiForm nor the names of its contributors may be
-#       used to endorse or promote products derived from this software without
-#       specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-import unittest
-import wsgiform.validators as validators
-
-class TestValidators(unittest.TestCase):
-
-    def sixcheck(self, func, array):
-        num = 0
-        if func(array[0]): num += 1
-        if func(array[1]): num += 1
-        if func(array[2]): num += 1
-        if not func(array[3]): num += 1
-        if not func(array[4]): num += 1
-        if not func(array[5]): num += 1   
-        return num == 6
-
-    def twocheck(self, func, array):
-        num = 0
-        if func(array[0]): num += 1
-        if not func(array[1]): num += 1
-        return num == 2
-
-    def test_isascii(self):
-        array = ('', u'')
-        self.assertEqual(self.twocheck(validators.isascii, array), True)
-
-    def test_isunicode(self):
-        array = (u'', '')
-        self.assertEqual(self.twocheck(validators.isunicode, array), True)
-
-    def test_isfloat(self):
-        array = ('4.133', '4')
-        self.assertEqual(self.twocheck(validators.isfloat, array), True)
-
-    def test_ismember(self):
-        array = ['1', '2', '3', '4']
-        self.assertEqual(validators.ismember('2', array), True)
-
-    def test_inrange(self):
-        self.assertEqual(validators.inrange('2', 1, 10), True)        
-
-    def test_isnumber(self):
-        array = ('4', 'a')
-        self.assertEqual(self.twocheck(validators.isnumber, array), True)
-
-    def test_isalpha(self):
-        array = ('afafaeb', 'afaf3ae6b')
-        self.assertEqual(self.twocheck(validators.isalpha, array), True)
-
-    def test_isalphanum(self):
-        array = ('afaf3ae6b', '.]}:"')
-        self.assertEqual(self.twocheck(validators.isalphanum, array), True)
-
-    def test_isspace(self):
-        array = ('   ', '.]}:"')
-        self.assertEqual(self.twocheck(validators.isspace, array), True)
-
-    def test_istitle(self):
-        array = ('Title', 'title')
-        self.assertEqual(self.twocheck(validators.istitle, array), True)
-
-    def test_isupper(self):
-        array = ('TITLE', 'title')
-        self.assertEqual(self.twocheck(validators.isupper, array), True)
-
-    def test_islower(self):
-        array = ('title', 'TITLE')
-        self.assertEqual(self.twocheck(validators.islower, array), True)
-
-    def test_islength(self):
-        st = 'werwafeafe'
-        self.assertEqual(validators.islength(st, 10), True)
-
-    def test_isempty(self):
-        array = (' ', 'eare')
-        self.assertEqual(self.twocheck(validators.isempty, array), True)
-
-    def test_malicious(self):
-        array = ('http://www.domain.com/page.asp?param=</script>',
-        'https://www.domain.com/page.asp?param=;SELECT',
-        'https://www.domain.com/page.asp?param=;INSERT',
-        'https://www.domain.com/page.asp?param=RealParam',
-        'https://www.domain.com/page.asp?param=RealParam1',         
-        'https://www.domain.com/page.asp?param=RealParam2'        )
-        self.assertEqual(self.sixcheck(validators.malicious, array), True)    
-
-    def test_notmalicious(self):
-        array = ('Normal input', 'This is a test', 'Test Test',
-        'http://www.domain.com/page.asp?param=</script>',
-        'https://www.domain.com/page.asp?param=;SELECT',
-        'https://www.domain.com/page.asp?param=;INSERT')        
-
-    def test_isxml(self):
-        array = ('<html><head></head><body></body></html>', '<html></head></body>')
-        self.assertEqual(self.twocheck(validators.isxml, array), True)
-
-    def test_isip4(self):
-        array = ('0.0.0.0', '255.255.255.02', '192.168.0.136',
-            '256.1.3.4', '023.44.33.22', '10.57.98.23.')
-        self.assertEqual(self.sixcheck(validators.isip4, array), True)
-
-    def test_isip6(self):
-        array = ('::0:0:0:FFFF:129.144.52.38', 'FEDC:BA98::3210:FEDC:BA98:7654:3210',
-            '::13.1.68.3', 'FEDC:BA98:7654:3210:FEDC:BA98:7654:3210:1234',
-            '3210:FEDC:BA98:7654:3210:1234', ':FEDC:BA98:7654:3210:')
-        self.assertEqual(self.sixcheck(validators.isip6, array), True)
-
-    def test_ismacaddr(self):
-        array = ('01:23:45:67:89:ab', '01:23:45:67:89:AB', 'fE:dC:bA:98:76:54',
-            '01:23:45:67:89:ab:cd', '01:23:45:67:89:Az', '01:23:45:56:')
-        self.assertEqual(self.sixcheck(validators.ismacaddr, array), True)
-
-    def test_isemail(self):
-        array = ('bob@smith.com', 'bob@j.smith.museum', 'bob.smith@a-1.smith.com',
-            'bob@.com', 'bob@-a.smith.com', 'bob@-a.com')
-        self.assertEqual(self.sixcheck(validators.isemail, array), True)
-
-    def test_isurl(self):
-        array = ('https://localhost', "https://64.81.85.161/site/file.php?cow=moo's",
-            'ftp://user:pass@site.com:21/file/dir', 'sysrage.net', 'site.com',
-            'http://site.com/dir//')
-        self.assertEqual(self.sixcheck(validators.isurl, array), True)
-
-    def test_isliveurl(self):
-        array = ('http://www.python.org/', 'http://www.pythonorgy.org/')
-        self.assertEqual(self.twocheck(validators.isliveurl, array), True)
-
-    def test_ispassword(self):
-        array = ('Passw0rd', 'assW@rd1', '1B2a345@#$%',
-            '123123123', 'Password', 'asdf&amp;')
-        self.assertEqual(self.sixcheck(validators.ispassword, array), True)
-
-    def test_iszip(self):
-        array = ('14467', '144679554', '14467-9554', '14467 955', '14467-', '1446-9554')
-        self.assertEqual(self.sixcheck(validators.iszip, array), True)
-
-    def test_isukpost(self):
-        array = ('CF1 2AA', 'cf564fg', 'CC1 2AF', 'a1234d', 'A12 77Y', 'A12 77')
-        self.assertEqual(self.sixcheck(validators.isukpost, array), True)
-    
-    def test_isnapost(self):
-        array = ('00501', '84118-3423', 'n3a 3B7', '501-342', '123324', 'Q4B 5C5')
-        self.assertEqual(self.sixcheck(validators.isnapost, array), True)
-
-    def test_isprovince(self):
-        array = ('ON', 'SK', 'QC', 'UT', 'ND', 'SD')
-        self.assertEqual(self.sixcheck(validators.isprovince, array), True)
-
-    def test_isstate(self):
-        array = ('AL', 'CA', 'AA', 'New York', 'California', 'ny')
-        self.assertEqual(self.sixcheck(validators.isstate, array), True)
-
-    def test_isdatetime(self):
-        array = ('12/25/2003', '08:03:31', '02/29/2004 12 AM',
-            '02/29/2003 1:34 PM', '13:23 PM', '24:00:00')
-        self.assertEqual(self.sixcheck(validators.isdatetime, array), True)
-
-    def test_isedatetime(self):
-        array = ('2003/12/25', '08:03:31', '2004/02/29 12 AM',
-            '2003/02/29 1:34 PM', '13:23 PM', '24:00:00')
-
-    def test_iscreditcard(self):
-        array = ('341-1111-1111-1111', '5431-1111-1111-1111', '30569309025904',
-            '30-5693-0902-5904', '5631-1111-1111-1111', '31169309025904')
-        self.assertEqual(self.sixcheck(validators.iscreditcard, array), True)
-
-    def test_isisbn(self):
-        array = ('ISBN 0 93028 923 4', 'ISBN 1-56389-668-0', 'ISBN 1-56389-016-X',
-            '123456789X', 'ISBN 9-87654321-2', 'ISBN 123 456-789X')
-        self.assertEqual(self.sixcheck(validators.isisbn, array), True)
-
-    def test_isssn(self):
-        array = ('123-45-6789', '123 45 6789', '123456789', '12345-67-890123',
-          '1234-56-7890', '123-45-78901')
-        self.assertEqual(self.sixcheck(validators.isssn, array), True)     
-
-    def test_isvat(self):
-        array = ('CZ-7907111883', 'ESA12345678', 'FRAB123456789',
-            'CZ55912', 'XY123456', 'FR-IB123456789')
-        self.assertEqual(self.sixcheck(validators.isvat, array), True) 
-
-    def test_isusd(self):
-        array = ('$0.84', '$123458', '$1,234,567.89', '$12,3456.01', '12345', '$1.234')
-        self.assertEqual(self.sixcheck(validators.isusd, array), True)   
-
-    def test_isintlphone(self):
-        array = ('+123(45)678-910', '+123-045-67 89 10', '01-234-56-78',
-            '123(45)678 91011', '(12)345-678', '+0(12)345-6789')
-        self.assertEqual(self.sixcheck(validators.isintlphone, array), True)  
-
-    def test_isukphone(self):
-        array = ('+447222555555', '+44 7222 555 555', '(0722) 5555555 #2222',
-            '(+447222)555555', '+44(7222)555555', '(0722) 5555555 #22')
-        self.assertEqual(self.sixcheck(validators.isukphone, array), True)
-
-    def test_isusphone(self):
-        array = ('987-654-3210 ', '(555) 487-1391 x652', 'phn (555) 987-6743 ext. 21012',
-            '123 456-7890', '(555) 000-1111', '923-907')
-        self.assertEqual(self.sixcheck(validators.isusphone, array), True)
-
-    def test_isphone(self):
-        array = ('1234567', '12345678', '(555) 987-6543 ext 210',
-            '123456', '555-123', '434232')
-        self.assertEqual(self.sixcheck(validators.isphone, array), True)
-
-    def test_chainvalidate(self):
-        chain = ('number', 'alphanum', ('range', 1, 15))
-        self.assertEqual(validators.getvalidator(chain)('13'), True)
-
-    def test_chainvalidatefalse(self):
-        chain = ('number', 'alphanum', ('range', 1, 15))
-        self.assertEqual(validators.getvalidator(chain)('13.3'), False)        
-
-if __name__ == '__main__':
-    unittest.main()

File branches/0.3-r/trunk/wsgiform/tests/test_wsgiform.py

-# Copyright (c) 2006 L. C. Rees
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-#    1. Redistributions of source code must retain the above copyright notice,
-#       this list of conditions and the following disclaimer.
-#
-#    2. Redistributions in binary form must reproduce the above copyright
-#       notice, this list of conditions and the following disclaimer in the
-#       documentation and/or other materials provided with the distribution.
-#
-#    3. Neither the name of WsgiForm 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.
-
-'''Unit tests for wsgiform.'''
-
-import wsgiform.form as wsgiform
-from wsgiform.validators import getvalidator
-import unittest
-import StringIO
-import copy
-
-class TestWsgiForm(unittest.TestCase):
-    
-    '''Test cases for wsgiform.'''
-        
-    test_env = {
-        'CONTENT_LENGTH': '118',
-        'wsgi.multiprocess': 0,
-        'wsgi.version': (1, 0),
-        'CONTENT_TYPE': 'application/x-www-form-urlencoded',
-        'SERVER_NAME': '127.0.0.1',
-        'wsgi.run_once': 0,
-        'wsgi.errors': StringIO.StringIO(),
-        'wsgi.multithread': 0,
-        'SCRIPT_NAME': '',
-        'wsgi.url_scheme': 'http',
-        'wsgi.input': StringIO.StringIO(
-'num=12121&str1=test&name=%3Ctag+id%3D%22Test%22%3EThis+is+a+%27test%27+%26+another.%3C%2Ftag%3E&state=NV&Submit=Submit'
-            ), 
-        'REQUEST_METHOD': 'POST',
-        'HTTP_HOST': '127.0.0.1',
-        'PATH_INFO': '/',
-        'SERVER_PORT': '80',
-        'SERVER_PROTOCOL': 'HTTP/1.0'}       
-
-    def dummy_app(self, environ, func):
-        return environ
-
-    def dummy_sr(self, status, headers, exc_info=None):
-        pass
-
-    def test_fieldstorage(self):
-        '''Parses form data into a FieldStorage instance.'''
-        form = wsgiform.WsgiForm(self.dummy_app, style='fieldstorage')
-        env = form(copy.deepcopy(self.test_env), self.dummy_sr)
-        self.assertEqual(env['wsgiform.fieldstorage'].getfirst('num'), '12121')
-
-    def test_dictionary(self):
-        '''Parses form data into a dictionary.'''
-        form = wsgiform.WsgiForm(self.dummy_app, style='dict')
-        env = form(copy.deepcopy(self.test_env), self.dummy_sr)
-        self.assertEqual(env['wsgiform.dict']['num'], '12121')
-
-    def test_kwargs(self):
-        '''Parses form data into keyword arguments.'''
-        form = wsgiform.WsgiForm(self.dummy_app, style='kwargs')
-        env = form(copy.deepcopy(self.test_env), self.dummy_sr)
-        self.assertEqual(env['wsgize.kwargs']['num'], '12121')
-
-    def test_environ(self):
-        '''Parses form data into individual environ entries.'''
-        form = wsgiform.WsgiForm(self.dummy_app, style='environ')
-        env = form(copy.deepcopy(self.test_env), self.dummy_sr)
-        self.assertEqual(env['wsgiform.num'], '12121')
-
-    def test_escape(self):
-        '''Parses form data into a dictionary with HTML escaping. '''
-        form = wsgiform.WsgiForm(self.dummy_app)
-        env = form(copy.deepcopy(self.test_env), self.dummy_sr)
-        self.assertEqual(env['wsgiform.dict']['name'], '&lt;tag id=&quot;Test&quot;&gt;This is a &#39;test&#39; &amp; another.&lt;/tag&gt;')
-
-    def test_hyperescape(self):
-        '''Parses form data into a dictionary with HTML hyperescaping. '''
-        form = wsgiform.WsgiForm(self.dummy_app, func='hyperescape')
-        env = form(copy.deepcopy(self.test_env), self.dummy_sr)
-        self.assertEqual(env['wsgiform.dict']['name'], '&#60;tag id&#61;&#34;Test&#34;&#62;This is a &#39;test&#39; &#38; another.&#60;&#47;tag&#62;')                 
-
-    def test_strictescape(self):
-        '''Parses form data into a dictionary with strict HTML escaping.'''
-        form = wsgiform.WsgiForm(self.dummy_app, strict=True)
-        env = form(copy.deepcopy(self.test_env), self.dummy_sr)
-        self.assertEqual(env['wsgiform.dict']['name'], '&lt;tag id=&quot;Test&quot;&gt;This is a &#39;test&#39; &amp; another.&lt;/tag&gt;')
-
-    def test_stricthyperescape(self):
-        '''Parses form data into a dictionary with strict HTML hyperescaping.'''
-        form = wsgiform.WsgiForm(self.dummy_app, func='hyperescape', strict=True)
-        env = form(copy.deepcopy(self.test_env), self.dummy_sr)
-        self.assertEqual(env['wsgiform.dict']['name'], '&#60;tag id&#61;&#34;Test&#34;&#62;This is a &#39;test&#39; &#38; another.&#60;&#47;tag&#62;')        
-
-    def test_sterilize(self):
-        '''Parses form data into a dictionary with data sterilization.'''
-        form = wsgiform.WsgiForm(self.dummy_app, func='sterilize')
-        env = form(copy.deepcopy(self.test_env), self.dummy_sr)
-        self.assertEqual(env['wsgiform.dict']['name'], 'tag idTestThis is a test  another.tag')
-
-    def test_strictsterilize(self):
-        '''Parses form data into a dictionary with strict data sterilization.'''
-        form = wsgiform.WsgiForm(self.dummy_app, func='sterilize', strict=True)
-        env = form(copy.deepcopy(self.test_env), self.dummy_sr)
-        self.assertEqual(env['wsgiform.dict']['name'], 'tag idTestThis is a test  another.tag')
-
-    def test_validation(self):
-        '''Tests data validation.'''
-        vdict = {'num':getvalidator(('number', ('range', 10000, 15000)))}
-        form = wsgiform.WsgiForm(self.dummy_app, func='sterilize', validators=vdict)
-        env = form(copy.deepcopy(self.test_env), self.dummy_sr)
-        self.assertEqual(env['wsgiform.dict']['name'], 'tag idTestThis is a test  another.tag')
-
-    def test_validation_false(self):
-        '''Tests bad data validation.'''
-        vdict = {'num':getvalidator(('float', ('range', 1000, 1500)))}
-        form = wsgiform.WsgiForm(self.dummy_app, func='sterilize', validators=vdict)
-        iteble = form(copy.deepcopy(self.test_env), self.dummy_sr)
-        self.assertEqual(iteble[0], 'Data in field(s) num was invalid.')        
-
-    def test_validation_strict(self):    
-        '''Tests strict data validation.'''
-        vdict = {'num':getvalidator(('number', ('range', 10000, 15000)))}
-        form = wsgiform.WsgiForm(self.dummy_app, func='sterilize', strict=True, validators=vdict)
-        iteble = form(copy.deepcopy(self.test_env), self.dummy_sr)
-        self.assertEqual(iteble[0], 'Data in field(s) str1 name Submit state was invalid.')        
-    
-
-if __name__ == '__main__': unittest.main()

File branches/0.3-r/trunk/wsgiform/util.py

-# Copyright (c) 2006 L. C. Rees
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-#    1. Redistributions of source code must retain the above copyright notice,
-#       this list of conditions and the following disclaimer.
-#
-#    2. Redistributions in binary form must reproduce the above copyright
-#       notice, this list of conditions and the following disclaimer in the
-#       documentation and/or other materials provided with the distribution.
-#
-#    3. Neither the name of WsgiForm 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.
-
-'''Utilities for server-side form processing.'''
-
-import cgi
-import re
-import string
-from StringIO import StringIO
-from xml.sax import saxutils
-
-__all__ = ['hyperescape', 'escape', 'sterilize', 'escapeform', 'hyperform',
-    'sterileform']
-
-_trans = string.maketrans('', '')
-
-def getinput(environ):
-    '''Non-destructively retrieves wsgi.input value.'''
-    wsginput = environ['wsgi.input']
-    # Non-destructively fetch string value of wsgi.input
-    if hasattr(wsginput, 'getvalue'):
-        qs = wsginput.getvalue()
-    # Otherwise, read and reconstruct wsgi.input
-    else:
-        # Never read more than content length
-        clength = int(environ['CONTENT_LENGTH'])
-        qs = wsginput.read(clength)
-        environ['wsgi.input'] = StringIO(qs)
-    return qs
-
-def formparse(environ, strict=False):
-    '''Stores data from form submissions in a dictionary.
-
-    @param environ Environment dictionary
-    @param strict Stops on errors (default: False)
-    '''
-    qdict = cgi.parse(StringIO(getinput(environ)), environ, strict, strict)
-    # Remove invididual entries from list and store as naked string
-    for key, value in qdict.iteritems():
-        if len(value) == 1: qdict[key] = value[0]
-    return qdict
-
-def _runfunc(qdict, func):
-    '''Runs a function on a dictionary.
-
-    @param qdict Dictionary
-    @param func Function
-    '''
-    # Handle single entries
-    for key, value in qdict.iteritems():
-        if isinstance(value, basestring):
-            qdict[key] = func(value)
-        # Handle lists
-        elif isinstance(value, list):
-            for num, item in enumerate(value):
-                if isinstance(item, basestring): value[num] = func(item)
-    return qdict
-
-def escape(data):
-    '''Escapes &, <, >, ", and ' with HTML entities.
-
-    @param data Text data
-    '''
-    return saxutils.escape(data, {'"':"&quot;", "'":'&#39;'})
-
-def escapeform(environ, strict=False):
-    '''Escapes common XML/HTML entities in form data.
-
-    @param environ Environment dictionary
-    @param strict Stops on errors (default: False)
-    '''    
-    return _runfunc(formparse(environ, strict), escape)
-
-def hyperescape(data):
-    '''Escapes punctuation with HTML entities except ., -, and _.
-
-    @param data Text data
-    '''
-    # Escape &
-    data = re.sub(r'&(?!#\d\d;)', '&#38;', data)
-    # Escape ;
-    data = re.sub(r'(?<!&#\d\d);', '&#59;', data)
-    # Escape #
-    data = re.sub(r'(?<!&)#(?!\d\d;)', '&#35;', data)
-    # Escape other chars except ., -, and _
-    for char in '<>"\'()!${}*+,%/:=?@[\\]^`|~':
-        data = data.replace(char, '&#%d;' % ord(char))
-    return data
-
-def hyperform(environ, strict=False):
-    '''Hyper-escapes all XML/HTML entitites in form data.
-
-    @param environ Environment dictionary
-    @param strict Stops on errors (default: False)
-    '''    
-    return _runfunc(formparse(environ, strict), hyperescape) 
-
-def sterileform(environ, strict=False):
-    '''Removes all form data characters except alphanumerics, ., -, and _.
-
-    @param environ Environment dictionary
-    @param strict Stops on errors (default: False)
-    '''
-    return _runfunc(formparse(environ, strict), sterilize)
-
-def sterilize(data):
-    '''Removes all ASCII characters except alphanumerics, ., -, and _.
-
-    @param data Text data
-    '''
-    return data.translate(_trans, '&#;<>"\'()!${}*+,%/:=?@[\\]^`|~')

File branches/0.3-r/trunk/wsgiform/validators.py

-# Copyright (c) 2006 L. C. Rees
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-#    1. Redistributions of source code must retain the above copyright notice,
-#       this list of conditions and the following disclaimer.
-#
-#    2. Redistributions in binary form must reproduce the above copyright
-#       notice, this list of conditions and the following disclaimer in the
-#       documentation and/or other materials provided with the distribution.
-#
-#    3. Neither the name of WsgiForm 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.
-
-'''Form validation utilities.'''
-
-import re, urllib
-from xml.dom.expatbuilder import parseString
-
-# Specialized regexes
-_patterns = {
-# US Social Security Number - Dennis Flynn - regexlib.com 
-'ssn':r'(^|\s)(00[1-9]|0[1-9]0|0[1-9][1-9]|[1-6]\d{2}|7[0-6]\d|77[0-2])(-?|[\. ])([1-9]0|0[1-9]|[1-9][1-9])\3(\d{3}[1-9]|[1-9]\d{3}|\d[1-9]\d{2}|\d{2}[1-9]\d)($|\s|[;:,!\.\?])',
-# US currency checker - Michael Ash - regexlib.com
-'usd':r'^\$(\d{1,3}(\,\d{3})*|(\d+))(\.\d{2})?$',
-# MAC Address - Ted Rudyk - regexlib.com 
-'macaddr':r'^([0-9a-fA-F][0-9a-fA-F]:){5}([0-9a-fA-F][0-9a-fA-F])$',
-# IP4 address - Andrew Polshaw - regexlib.com
-'ip4':r'^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$',
-# IP6 address - Glynn Beeken - regexlib.com
-'ip6':r'^(^(([0-9A-F]{1,4}(((:[0-9A-F]{1,4}){5}::[0-9A-F]{1,4})|((:[0-9A-F]{1,4}){4}::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,1})|((:[0-9A-F]{1,4}){3}::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,2})|((:[0-9A-F]{1,4}){2}::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,3})|(:[0-9A-F]{1,4}::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,4})|(::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,5})|(:[0-9A-F]{1,4}){7}))$|^(::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,6})$)|^::$)|^((([0-9A-F]{1,4}(((:[0-9A-F]{1,4}){3}::([0-9A-F]{1,4}){1})|((:[0-9A-F]{1,4}){2}::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,1})|((:[0-9A-F]{1,4}){1}::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,2})|(::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,3})|((:[0-9A-F]{1,4}){0,5})))|([:]{2}[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,4})):|::)((25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{0,2})\.){3}(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{0,2})$$',
-# Complex password enforcer 1 upper 1 lower 1 num 1 special min/max - Matthew Hazzard - regexlib.com
-'password':r'(?=^.{8,255}$)((?=.*\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[^A-Za-z0-9])(?=.*[a-z])|(?=.*[^A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9]))^.*',
-# US state abbreviations - Michael Ash - regexlib.com
-'usstates':r'^(A[LKSZRAEP]|C[AOT]|D[EC]|F[LM]|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEHINOPST]|N[CDEHJMVY]|O[HKR]|P[ARW]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$',
-# Canada provice abbeviations - Matthew Hartman - regexlib.com
-'canada':r'^(N[BLSTU]|[AMN]B|[BQ]C|ON|PE|SK)$',
-# Credit Card - David Conorozzo - regexlib.com
-'creditcard':r'^3(?:[47]\d([ -]?)\d{4}(?:\1\d{4}){2}|0[0-5]\d{11}|[68]\d{12})$|^4(?:\d\d\d)?([ -]?)\d{4}(?:\2\d{4}){2}$|^6011([ -]?)\d{4}(?:\3\d{4}){2}$|^5[1-5]\d\d([ -]?)\d{4}(?:\4\d{4}){2}$|^2014\d{11}$|^2149\d{11}$|^2131\d{11}$|^1800\d{11}$|^3\d{15}$',
-# Malice detection - Shahar Bracha - regexlib.com
-'malice':r'(script)|(&lt;)|(&gt;)|(%3c)|(%3e)|(SELECT)|(UPDATE)|(INSERT)|(DELETE)|(GRANT)|(REVOKE)|(UNION)|(&amp;lt;)|(&amp;gt;)',
-# Malice protection - Brenden Salta - regexlib.com
-'notmalice':r'^[^<>`~!/@\#}$%:;)(_^{&*=|\'+]+$',
-# Email - Shaune Stark - regexlib.com
-'email':r'^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@(([0-9a-zA-Z])+([-\w]*[0-9a-zA-Z])*\.)+[a-zA-Z]{2,9})$',
-# URL - Brian Bothwell - regexlib.com
-'url':r'^(file|ftp|gopher|hdl|http|https|imap|mailto|mms|news|nntp|prospero|rsync|rtsp|rtspu|shttp|sip|snews|svn|svn+ssh|telnet|wais)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&amp;%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(\:[0-9]+)*(/($|[a-zA-Z0-9\.\,\?\'\\\+&amp;%\$#\=~_\-]+))*$',
-# DateTime mm/dd/yyyy hh:MM:ss - Michael Ash - regexlib.com
-'datetime':r'(?=\d)^(?:(?!(?:10\D(?:0?[5-9]|1[0-4])\D(?:1582))|(?:0?9\D(?:0?[3-9]|1[0-3])\D(?:1752)))((?:0?[13578]|1[02])|(?:0?[469]|11)(?!\/31)(?!-31)(?!\.31)|(?:0?2(?=.?(?:(?:29.(?!000[04]|(?:(?:1[^0-6]|[2468][^048]|[3579][^26])00))(?:(?:(?:\d\d)(?:[02468][048]|[13579][26])(?!\x20BC))|(?:00(?:42|3[0369]|2[147]|1[258]|09)\x20BC))))))|(?:0?2(?=.(?:(?:\d\D)|(?:[01]\d)|(?:2[0-8])))))([-.\/])(0?[1-9]|[12]\d|3[01])\2(?!0000)((?=(?:00(?:4[0-5]|[0-3]?\d)\x20BC)|(?:\d{4}(?!\x20BC)))\d{4}(?:\x20BC)?)(?:$|(?=\x20\d)\x20))?((?:(?:0?[1-9]|1[012])(?::[0-5]\d){0,2}(?:\x20[aApP][mM]))|(?:[01]\d|2[0-3])(?::[0-5]\d){1,2})?$',
-# DateTime yyyy/mm/dd hh:MM:ss - Michael Ash - regexlib.com
-'eurodatetime':r'^(?=\d)(?:(?!(?:1582(?:\.|-|\/)10(?:\.|-|\/)(?:0?[5-9]|1[0-4]))|(?:1752(?:\.|-|\/)0?9(?:\.|-|\/)(?:0?[3-9]|1[0-3])))(?=(?:(?!000[04]|(?:(?:1[^0-6]|[2468][^048]|[3579][^26])00))(?:(?:\d\d)(?:[02468][048]|[13579][26]))\D0?2\D29)|(?:\d{4}\D(?!(?:0?[2469]|11)\D31)(?!0?2(?:\.|-|\/)(?:29|30))))(\d{4})([-\/.])(0?\d|1[012])\2((?!00)[012]?\d|3[01])(?:$|(?=\x20\d)\x20))?((?:(?:0?[1-9]|1[012])(?::[0-5]\d){0,2}(?:\x20[aApP][mM]))|(?:[01]\d|2[0-3])(?::[0-5]\d){1,2})?$',
-# US Phone - Tim N Tousley - regexlib.com 
-'usphone':r'^([a-zA-Z,#/ \.\(\)\-\+\*]*[2-9])([a-zA-Z,#/ \.\(\)\-\+\*]*[0-9]){2}([a-zA-Z,#/ \.\(\)\-\+\*]*[2-9])([a-zA-Z,#/ \.\(\)\-\+\*]*[0-9]){6}[0-9a-zA-Z,#/ \.\(\)\-\+\*]*$',
-# UK Phone - Amos Hurd - regexlib.com 
-'ukphone':r'(((\+44\s?\d{4}|\(?0\d{4}\)?)\s?\d{3}\s?\d{3})|((\+44\s?\d{3}|\(?0\d{3}\)?)\s?\d{3}\s?\d{4})|((\+44\s?\d{2}|\(?0\d{2}\)?)\s?\d{4}\s?\d{4}))(\s?\#(\d{4}|\d{3}))?$',
-# International phone - Dmitry Kandiner - regexlib.com
-'intlphone':r'^(\+[1-9][0-9]*(\([0-9]*\)|-[0-9]*-))?[0]?[1-9][0-9\- ]*$',
-# Phone checker - Tim N Tousley - regexlib.com 
-'phone':r'^([a-zA-Z,#/ \.\(\)\-\+\*]*[0-9]){7}[0-9a-zA-Z,#/ \.\(\)\-\+\*]*$',
-# US-Canada postal/zip codes - Matthew Aznoe - regexlib.com 
-'napost':r'^((\d{5}-\d{4})|(\d{5})|([AaBbCcEeGgHhJjKkLlMmNnPpRrSsTtVvXxYy]\d[A-Za-z]\s?\d[A-Za-z]\d))$',
-# US Zip codes 5+4 +/- hyphen
-'zip':r'(^\d{5}$)|(\d{5}-*\d{4}$)',
-# UK postal codes - Scott Pite - regexlib.com
-'ukpost':r'^[A-Za-z]{1,2}[\d]{1,2}([A-Za-z])?\s?[\d][A-Za-z]{2}$',
-# ISBN - Michael Ash - regexlib.com
-'isbn':r'ISBN\x20(?=.{13}$)\d{1,5}([- ])\d{1,7}\1\d{1,6}\1(\d|X)$',
-# Number checker - Paul Auger - regexlib.com
-'num':r'^((\d?)|(([-+]?\d+\.?\d*)|([-+]?\d*\.?\d+))|(([-+]?\d+\.?\d*\,\ ?)*([-+]?\d+\.?\d*))|(([-+]?\d*\.?\d+\,\ ?)*([-+]?\d*\.?\d+))|(([-+]?\d+\.?\d*\,\ ?)*([-+]?\d*\.?\d+))|(([-+]?\d*\.?\d+\,\ ?)*([-+]?\d+\.?\d*)))$',
-# Euro VAT - Michal Valasek - regexlib.com
-'vat':r'((DK|FI|HU|LU|MT|SI)(-)?\d{8})|((BE|EE|DE|EL|LT|PT)(-)?\d{9})|((PL|SK)(-)?\d{10})|((IT|LV)(-)?\d{11})|((LT|SE)(-)?\d{12})|(AT(-)?U\d{8})|(CY(-)?\d{8}[A-Z])|(CZ(-)?\d{8,10})|(FR(-)?[\dA-HJ-NP-Z]{2}\d{9})|(IE(-)?\d[A-Z\d]\d{5}[A-Z])|(NL(-)?\d{9}B\d{2})|(ES(-)?[A-Z\d]\d{7}[A-Z\d])',
-}
-
-patterns = dict((k, re.compile(v)) for k, v in _patterns.iteritems())
-
-def getvalidator(chain):
-    '''Returns a chained validator.
-
-    @param chain List of validator names.
-    '''
-    return ChainValidator(chain)
-
-def _validate(key, data):
-    '''Validates data by key.'''
-    if patterns[key].search(data): return True
-    return False
-
-# Member
-
-def ismember(data, members):
-    '''Validates data is a member.'''
-    return data in members
-
-def inrange(data, start, stop):
-    '''Validates data falls within a numeric range.'''
-    return int(data) in range(start, stop)
-
-# Character
-    
-def isascii(data):
-    '''Validates data is ASCII'''
-    return isinstance(data, str)
-
-def isunicode(data):
-    '''Validates data is unicode.'''
-    return isinstance(data, unicode)
-
-def isnumber(data):
-    '''Validates data is number.'''
-    return data.isdigit()
-
-def isfloat(data):
-    '''Validates data is a float.'''
-    # Make sure not int
-    try:
-        int(data)
-    except ValueError:
-        return isinstance(float(data), float)
-
-def isalpha(data):
-    '''Validates data is all alphabetical.'''   
-    return data.isalpha()
-
-def isalphanum(data):
-    '''Validates data is alphanumeric.'''
-    return data.isalnum()
-
-def isspace(data):
-    '''Validates data is all spaces.'''
-    return data.isspace()
-
-def istitle(data):
-    '''Validates data is in title form.'''
-    return data.istitle()
-
-def isupper(data):
-    '''Validates data is uppercase.'''
-    return data.isupper()
-
-def islower(data):
-    '''Validates data is lowercase.'''
-    return data.islower()
-
-def islength(data, length):
-    '''Validates data is under or equals a defined length.'''
-    return len(data) <= length
-
-def isempty(data):
-    '''Validates data is empty.'''
-    return data.strip() == ''
-
-# User submissions
-
-def malicious(data):
-    '''Validates that data has certain potentially malicious content.'''
-    return _validate('malice', data)
-
-def notmalicious(data):
-    '''Validates that data does not have certain potentially malicious content.'''
-    return _validate('notmalice', data)
-
-def isxml(data):
-    '''Validates data is well-formed XML.'''
-    try:
-        parseString(data)
-    except:
-        return False
-    return True
-
-# Network Addresses
-
-def isip4(data):
-    '''Validates data is a valid IPv4 address.'''
-    return _validate('ip4', data)
-
-def isip6(data):
-    '''Validates data is a valid IPv6 address.'''
-    return _validate('ip6', data)
-
-def ismacaddr(data):
-    '''Validates data is a valid MAC address.'''
-    return _validate('macaddr', data)
-
-def isemail(data):
-    '''Validates data is a valid email address.'''
-    return _validate('email', data)
-
-def isurl(data):
-    '''Validates data is a valid URL.'''
-    return _validate('url', data)
-
-def isliveurl(data):
-    '''Validates data is a live URL.'''
-    try:
-        urllib.urlopen(data)
-    except:
-        return False
-    return True
-
-# Username/password
-
-def ispassword(data):
-    '''Validates that data is a password with 1 upper 1 lower 1 num
-    1 and at least 8 characters.
-    '''
-    return _validate('password', data)
-
-# Postal codes
-
-def iszip(data):
-    '''Validates data is a valid US ZIP code.'''
-    return _validate('zip', data)
-
-def isukpost(data):
-    '''Validates data is a valid UK postal code.'''
-    return _validate('ukpost', data)
-
-def isnapost(data):
-    '''Validates data is a US ZIP or Canadian postal code.'''
-    return _validate('napost', data)
-
-# Territory names
-
-def isprovince(data):
-    '''Validates data is a valid Canadian provincial abbreviation.'''
-    return _validate('canada', data)
-
-def isstate(data):
-    '''Validates that data is a US state abbreviation.'''
-    return _validate('usstates', data)
-
-# Date time
-
-def isdatetime(data):
-    '''Validates data is a valid format for mm/dd/yyyy hh:MM:ss.'''
-    return _validate('datetime', data)
-
-def isedatetime(data):
-    '''Validates data is a valid format for yyyy/mm/dd hh:MM:ss.'''
-    return _validate('eurodatetime', data)
-
-# Numbers
-
-def iscreditcard(data):
-    '''Validates data is a valid format for a credit card number.'''
-    return _validate('creditcard', data)
-
-def isisbn(data):
-    '''Validates data is ISBN.'''
-    return _validate('isbn', data)
-
-def isssn(data):
-    '''Validates that data is a social security number.'''
-    return _validate('ssn', data)
-
-def isvat(data):
-    '''Validates that data is a EU VAT number.'''
-    return _validate('vat', data)
-
-def isusd(data):
-    '''Validates that data is a UK phone number.'''
-    return _validate('usd', data)
-
-# Phone numbers
-
-def isphone(data):
-    '''Validates that data is a phone number.'''
-    return _validate('phone', data)
-
-def isukphone(data):
-    '''Validates that data is a UK phone number.'''
-    return _validate('ukphone', data)
-
-def isintlphone(data):
-    '''Validates that data is an international phone number.'''
-    return _validate('intlphone', data)
-
-def isusphone(data):
-    '''Validates that data is a US phone number.'''
-    return _validate('usphone', data)
-
-# Registry of validators
-validators = dict(members=ismember, range=inrange, ascii=isascii,
-    unicode=isunicode, number=isnumber, float=isfloat, alpha=isalpha,
-    alphanum=isalphanum, space=isspace, cap=istitle, upper=isupper,
-    lc=islower, length=islength, empty=isempty, malice=malicious,
-    notmalice=notmalicious, xml=isxml, ip4=isip4, ip6=isip6,
-    macaddr=ismacaddr, email=isemail, url=isurl, liveurl=isliveurl,
-    password=ispassword, zip=iszip, ukpost=isukpost, napost=isnapost,
-    province=isprovince, state=isstate, datetime=isdatetime, 
-    edatetime=isedatetime, creditcard=iscreditcard, isbn=isisbn,
-    ssn=isssn, vat=isvat, usd=isusd, phone=isphone, ukphone=isukphone,
-    intlphone=isintlphone, usphone=isusphone)
-
-
-class ChainValidator(object):
-
-    '''Class to build a chain of validators'''    
-
-    def __init__(self, chain):
-        self.chain = chain
-
-    def __call__(self, data):
-        '''Validates against a chain of validating functions.'''
-        for v in self.chain:
-            # Load validator
-            if not isinstance(v, basestring):
-                validator = validators[v[0]]
-            else:
-                validator = validators[v]
-            # One validation failure invalidates the entire chain
-            try:
-                if len(v) == 3:
-                    if not validator(data, v[1], v[2]): return False
-                elif len(v) == 2:
-                    if not validator(data, v[1]): return False
-                else:
-                    if not validator(data): return False
-            # Any exceptions cause validation failure
-            except:
-                return False
-        return True

File branches/0.3/trunk/ez_setup.py

+#!python
+"""Bootstrap setuptools installation
+
+If you want to use setuptools in your package's setup.py, just include this
+file in the same directory with it, and add this to the top of your setup.py::
+
+    from ez_setup import use_setuptools
+    use_setuptools()
+
+If you want to require a specific version of setuptools, set a download
+mirror, or use an alternate download directory, you can do so by supplying
+the appropriate options to ``use_setuptools()``.
+
+This file can also be run as a script to install or upgrade setuptools.
+"""
+import sys
+DEFAULT_VERSION = "0.6c3"
+DEFAULT_URL     = "http://cheeseshop.python.org/packages/%s/s/setuptools/" % sys.version[:3]
+
+md5_data = {
+    'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
+    'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
+    'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
+    'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
+    'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
+    'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
+    'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
+    'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
+    'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
+    'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
+    'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
+    'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
+    'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
+    'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
+    'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
+}
+
+import sys, os
+
+def _validate_md5(egg_name, data):
+    if egg_name in md5_data:
+        from md5 import md5
+        digest = md5(data).hexdigest()
+        if digest != md5_data[egg_name]:
+            print >>sys.stderr, (
+                "md5 validation of %s failed!  (Possible download problem?)"
+                % egg_name
+            )
+            sys.exit(2)
+    return data
+
+
+def use_setuptools(
+    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+    download_delay=15
+):
+    """Automatically find/download setuptools and make it available on sys.path
+
+    `version` should be a valid setuptools version number that is available
+    as an egg for download under the `download_base` URL (which should end with
+    a '/').  `to_dir` is the directory where setuptools will be downloaded, if
+    it is not already available.  If `download_delay` is specified, it should
+    be the number of seconds that will be paused before initiating a download,
+    should one be required.  If an older version of setuptools is installed,
+    this routine will print a message to ``sys.stderr`` and raise SystemExit in
+    an attempt to abort the calling script.
+    """
+    try:
+        import setuptools
+        if setuptools.__version__ == '0.0.1':
+            print >>sys.stderr, (
+            "You have an obsolete version of setuptools installed.  Please\n"
+            "remove it from your system entirely before rerunning this script."
+            )
+            sys.exit(2)
+    except ImportError:
+        egg = download_setuptools(version, download_base, to_dir, download_delay)
+        sys.path.insert(0, egg)
+        import setuptools; setuptools.bootstrap_install_from = egg
+
+    import pkg_resources
+    try:
+        pkg_resources.require("setuptools>="+version)
+
+    except pkg_resources.VersionConflict, e:
+        # XXX could we install in a subprocess here?
+        print >>sys.stderr, (
+            "The required version of setuptools (>=%s) is not available, and\n"
+            "can't be installed while this script is running. Please install\n"
+            " a more recent version first.\n\n(Currently using %r)"
+        ) % (version, e.args[0])
+        sys.exit(2)
+
+def download_setuptools(
+    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+    delay = 15
+):
+    """Download setuptools from a specified location and return its filename
+
+    `version` should be a valid setuptools version number that is available
+    as an egg for download under the `download_base` URL (which should end
+    with a '/'). `to_dir` is the directory where the egg will be downloaded.
+    `delay` is the number of seconds to pause before an actual download attempt.
+    """
+    import urllib2, shutil
+    egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
+    url = download_base + egg_name
+    saveto = os.path.join(to_dir, egg_name)
+    src = dst = None
+    if not os.path.exists(saveto):  # Avoid repeated downloads
+        try:
+            from distutils import log
+            if delay:
+                log.warn("""
+---------------------------------------------------------------------------
+This script requires setuptools version %s to run (even to display
+help).  I will attempt to download it for you (from
+%s), but
+you may need to enable firewall access for this script first.
+I will start the download in %d seconds.
+
+(Note: if this machine does not have network access, please obtain the file
+
+   %s
+
+and place it in this directory before rerunning this script.)
+---------------------------------------------------------------------------""",
+                    version, download_base, delay, url
+                ); from time import sleep; sleep(delay)
+            log.warn("Downloading %s", url)
+            src = urllib2.urlopen(url)
+            # Read/write all in one block, so we don't create a corrupt file
+            # if the download is interrupted.
+            data = _validate_md5(egg_name, src.read())
+            dst = open(saveto,"wb"); dst.write(data)
+        finally:
+            if src: src.close()
+            if dst: dst.close()
+    return os.path.realpath(saveto)
+
+def main(argv, version=DEFAULT_VERSION):
+    """Install or upgrade setuptools and EasyInstall"""
+
+    try:
+        import setuptools
+    except ImportError:
+        egg = None
+        try:
+            egg = download_setuptools(version, delay=0)
+            sys.path.insert(0,egg)
+            from setuptools.command.easy_install import main
+            return main(list(argv)+[egg])   # we're done here
+        finally:
+            if egg and os.path.exists(egg):
+                os.unlink(egg)
+    else:
+        if setuptools.__version__ == '0.0.1':
+            # tell the user to uninstall obsolete version
+            use_setuptools(version)
+
+    req = "setuptools>="+version
+    import pkg_resources
+    try:
+        pkg_resources.require(req)
+    except pkg_resources.VersionConflict:
+        try:
+            from setuptools.command.easy_install import main
+        except ImportError:
+            from easy_install import main
+        main(list(argv)+[download_setuptools(delay=0)])
+        sys.exit(0) # try to force an exit
+    else:
+        if argv:
+            from setuptools.command.easy_install import main
+            main(argv)
+        else:
+            print "Setuptools version",version,"or greater has been installed."
+            print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
+
+
+
+def update_md5(filenames):
+    """Update our built-in md5 registry"""
+
+    import re
+    from md5 import md5
+
+    for name in filenames:
+        base = os.path.basename(name)
+        f = open(name,'rb')
+        md5_data[base] = md5(f.read()).hexdigest()
+        f.close()
+
+    data = ["    %r: %r,\n" % it for it in md5_data.items()]
+    data.sort()
+    repl = "".join(data)
+
+    import inspect
+    srcfile = inspect.getsourcefile(sys.modules[__name__])
+    f = open(srcfile, 'rb'); src = f.read(); f.close()
+
+    match = re.search("\nmd5_data = {\n([^}]+)}", src)
+    if not match:
+        print >>sys.stderr, "Internal error!"
+        sys.exit(2)
+
+    src = src[:match.start(1)] + repl + src[match.end(1):]
+    f = open(srcfile,'w')
+    f.write(src)
+    f.close()
+
+
+if __name__=='__main__':
+    if len(sys.argv)>2 and sys.argv[1]=='--md5update':
+        update_md5(sys.argv[2:])
+    else:
+        main(sys.argv[1:])
+
+
+
+
+

File branches/0.3/trunk/setup.py

+# Copyright (c) 2006 L. C. Rees.  All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1.  Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3.  Neither the name of the Portable Site Information Project 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.
+
+'''setup - setuptools based setup for wsgiform.'''
+
+import ez_setup
+ez_setup.use_setuptools()
+
+try:
+    from setuptools import setup
+except:
+    from distutils.core import setup
+
+setup(name='wsgiform',
+      version='0.3',
+      description='''WSGI form parsing and validation middleware''',
+      long_description='''WSGI middleware for validating form submissions
+and parsing them into dictionaries, individual WSGI 'environ' dictionary
+entries, cgi.FieldStorage instances, or keyword arguments passed to a
+WSGI application. Supports automatically escaping and sterilizing form
+submissions.
+
+# Simple wsgiform usage format example:
+
+from wsgiform import form
+
+@form()
+def app(environ, start_response):
+    return environ['wsgiform.dict']['name']''',
+      author='L. C. Rees',
+      author_email='lcrees@gmail.com',
+      license='BSD',
+      test_suite='tests.test_wsgiform',
+      packages = ['wsgiform', 'wsgiform.tests'],
+      zip_safe = True,
+      keywords='WSGI dispatch middleware web HTTP decorators',
+      classifiers=['Development Status :: 3 - Alpha',
+                    'Environment :: Web Environment',
+                    'License :: OSI Approved :: BSD License',
+                    'Natural Language :: English',
+                    'Operating System :: OS Independent',
+                    'Programming Language :: Python',
+                    'Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware'])

File branches/0.3/trunk/wsgiform/__init__.py

+# Copyright (c) 2006 L. C. Rees
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#    1. Redistributions of source code must retain the above copyright notice,
+#       this list of conditions and the following disclaimer.
+#
+#    2. Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#
+#    3. Neither the name of WsgiForm 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.
+
+
+__author__ = 'L.C. Rees (lcrees@gmail.com)'
+__revision__ = '0.2.7'

File branches/0.3/trunk/wsgiform/form.py

+# Copyright (c) 2006 L. C. Rees
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#    1. Redistributions of source code must retain the above copyright notice, 
+#       this list of conditions and the following disclaimer.
+#    
+#    2. Redistributions in binary form must reproduce the above copyright 
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#
+#    3. Neither the name of WsgiForm 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.
+
+'''WSGI middleware for validating form submissions and parsing them into
+dictionaries, individual WSGI 'environ' dictionary entries, cgi.FieldStorage
+instances, or keyword arguments passed to a WSGI application. Supports
+automatically escaping and sterilizing form submissions.
+'''
+
+import cgi
+import urllib
+from StringIO import StringIO
+from wsgiform.util import escapeform, hyperform, sterileform, getinput
+
+__all__ = ['WsgiForm', 'form']
+
+def _errorapp(environ, start_response):
+    '''Default error handler for form validation errors.
+
+    Replace with custom handler.
+    '''
+    start_response('200 OK', ('Content-type', 'text/plain'))
+    return ['Data in field(s) %s was invalid.' %
+            ' '.join(environ['wsgiform.error'])]
+
+def validate(qdict, validators, environ, strict=False):
+    '''Validates form data.
+
+    qdict Dictionary of validators where the key is a form field
+    environ A WSGI environment dictionary
+    validators An iterable with validators
+    strict Keys w/out validators are form errors (default: False)
+    '''
+    errors = []
+    # Validate each form entry
+    for key, value in qdict.iteritems():
+        try:
+            # Accumulate any errors
+            if not validators[key](value): errors.append(key)
+        except KeyError:
+            if strict: errors.append(key)
+    # Put any errors in environ error entry and fail
+    if errors:
+        environ['wsgiform.error'] = errors
+        return False        
+    return True
+
+def form(**kw):
+    '''Decorator for form parsing.'''
+    def decorator(application):
+        return WsgiForm(application, **kw)
+    return decorator
+    
+
+class WsgiForm(object):
+
+    '''Class that parses form data into dictionaries, individual 'environ'
+    entries, FieldStorage instances, or keyword arguments that can be passed to
+    WSGI applications in the environ dictionary.
+    '''
+    
+    environ = None
+    # Environ key styles
+    keys = {'fieldstorage':'wsgiform.fieldstorage', 'dict':'wsgiform.dict',
+        'kwargs':'wsgize.kwargs', 'environ':'wsgiform.%s',
+        'routing_args':'wsgiorg.routing_args'}
+    # Data sterilizing functions
+    funclist = {'escape':escapeform, 'hyperescape':hyperform,
+        'sterilize':sterileform} 
+
+    def __init__(self, application, **kw):
+        self.application = application
+        # Style and key for form data passing format
+        self.style = kw.get('style', 'dict')
+        self.key = self.keys.get(self.style)
+        # Dictionary of validating functions where keywords == form field names 
+        self.validators = kw.get('validators', {})
+        # Function to validate form submissions
+        self.validate = kw.get('validfunc', validate)
+        # WSGI application to handle form validation errors
+        self.handler = kw.get('errapp', _errorapp)
+        # Stop on errors, empty fields, and validation errors
+        self.strict = kw.get('strict', False)
+        # Function to escape metachars
+        self.func = self.funclist[(kw.get('func', 'escape'))]
+        
+    def __call__(self, env, start_response):       
+        qdict = self.func(env, self.strict)            
+        # Validate form if validators present            
+        if self.validators:
+            if not self.validate(qdict, self.validators, env, self.strict):
+                return self.handler(env, start_response)
+        if self.style == 'fieldstorage':
+            # Reparse query back into a query string
+            cginput = StringIO(urllib.urlencode(qdict))
+            # Put into cgi.FieldStorage instance
+            qdict = cgi.FieldStorage(fp=cginput, environ=env)
+        # Store form submissions as individual environ entries
+        if self.style == 'environ':
+            for k, v in qdict.iteritems(): env[self.key % k] = v
+        # Store form submissions using wsgi.routing_vars style
+        elif self.style == 'routing_args':
+            args, kwargs = env.get(self.key, ((), {}))
+            env[self.key] = (args, kwargs.update(qdict))
+        # Store form submissions as kwargs, dict, or cgi.FieldStorage
+        else:
+            env[self.key] = qdict
+        return self.application(env, start_response)

File branches/0.3/trunk/wsgiform/tests/__init__.py

Empty file added.

File branches/0.3/trunk/wsgiform/tests/test_validators.py

+# Copyright (c) 2006 L. C. Rees
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#    1. Redistributions of source code must retain the above copyright notice,
+#       this list of conditions and the following disclaimer.
+#
+#    2. Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#
+#    3. Neither the name of WsgiForm nor the names of its contributors may be
+#       used to endorse or promote products derived from this software without
+#       specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import unittest
+import wsgiform.validators as validators
+
+class TestValidators(unittest.TestCase):
+
+    def sixcheck(self, func, array):
+        num = 0
+        if func(array[0]): num += 1
+        if func(array[1]): num += 1
+        if func(array[2]): num += 1
+        if not func(array[3]): num += 1
+        if not func(array[4]): num += 1
+        if not func(array[5]): num += 1   
+        return num == 6
+
+    def twocheck(self, func, array):
+        num = 0
+        if func(array[0]): num += 1
+        if not func(array[1]): num += 1
+        return num == 2
+
+    def test_isascii(self):
+        array = ('', u'')
+        self.assertEqual(self.twocheck(validators.isascii, array), True)
+
+    def test_isunicode(self):
+        array = (u'', '')
+        self.assertEqual(self.twocheck(validators.isunicode, array), True)
+
+    def test_isfloat(self):
+        array = ('4.133', '4')
+        self.assertEqual(self.twocheck(validators.isfloat, array), True)
+
+    def test_ismember(self):
+        array = ['1', '2', '3', '4']
+        self.assertEqual(validators.ismember('2', array), True)
+
+    def test_inrange(self):
+        self.assertEqual(validators.inrange('2', 1, 10), True)        
+
+    def test_isnumber(self):
+        array = ('4', 'a')
+        self.assertEqual(self.twocheck(validators.isnumber, array), True)
+
+    def test_isalpha(self):
+        array = ('afafaeb', 'afaf3ae6b')
+        self.assertEqual(self.twocheck(validators.isalpha, array), True)
+
+    def test_isalphanum(self):
+        array = ('afaf3ae6b', '.]}:"')
+        self.assertEqual(self.twocheck(validators.isalphanum, array), True)
+
+    def test_isspace(self):
+        array = ('   ', '.]}:"')
+        self.assertEqual(self.twocheck(validators.isspace, array), True)
+
+    def test_istitle(self):
+        array = ('Title', 'title')
+        self.assertEqual(self.twocheck(validators.istitle, array), True)
+
+    def test_isupper(self):
+        array = ('TITLE', 'title')
+        self.assertEqual(self.twocheck(validators.isupper, array), True)
+
+    def test_islower(self):
+        array = ('title', 'TITLE')
+        self.assertEqual(self.twocheck(validators.islower, array), True)
+
+    def test_islength(self):
+        st = 'werwafeafe'
+        self.assertEqual(validators.islength(st, 10), True)
+
+    def test_isempty(self):
+        array = (' ', 'eare')
+        self.assertEqual(self.twocheck(validators.isempty, array), True)
+
+    def test_malicious(self):
+        array = ('http://www.domain.com/page.asp?param=&lt;/script&gt;',
+        'https://www.domain.com/page.asp?param=;SELECT',
+        'https://www.domain.com/page.asp?param=;INSERT',
+        'https://www.domain.com/page.asp?param=RealParam',
+        'https://www.domain.com/page.asp?param=RealParam1',         
+        'https://www.domain.com/page.asp?param=RealParam2'        )
+        self.assertEqual(self.sixcheck(validators.malicious, array), True)    
+
+    def test_notmalicious(self):
+        array = ('Normal input', 'This is a test', 'Test Test',
+        'http://www.domain.com/page.asp?param=&lt;/script&gt;',