Hello community, here is the log from the commit of package python-web.py for openSUSE:Factory checked in at 2012-07-02 11:07:46 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-web.py (Old) and /work/SRC/openSUSE:Factory/.python-web.py.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-web.py", Maintainer is "" Changes: -------- --- /work/SRC/openSUSE:Factory/python-web.py/python-web.py.changes 2012-03-22 12:39:51.000000000 +0100 +++ /work/SRC/openSUSE:Factory/.python-web.py.new/python-web.py.changes 2012-07-02 11:07:48.000000000 +0200 @@ -1,0 +2,19 @@ +Fri Jun 29 08:23:50 UTC 2012 - cfarr...@suse.com + +- Updated to 0.37 +* Fixed datestr issue on Windows -- #155 +* Fixed Python 2.4 compatability issues (tx fredludlow) +* Fixed error in utils.safewrite (tx shuge) -- #95 +* Allow use of web.data() with app.request() -- #105 +* Fixed an issue with session initializaton (tx beardedprojamz) -- #109 +* Allow custom message on 400 Bad Request (tx patryk) -- #121 +* Made djangoerror work on GAE. -- #80 +* Handle malformatted data in the urls. -- #117 +* Made it easier to stop the dev server -- #100, #122 +* Added support fot customizing cookie_path in session (tx larsga) -- #89 +* Added exception for "415 Unsupported Media" (tx JirkaChadima) -- #145 +* Added GroupedDropdown to support `<optgroup>` tag (tx jzellman) -- #152 +* Fixed failure in embedded interpreter - #87 +* Optimized web.cookies (tx benhoyt) - #148 + +------------------------------------------------------------------- Old: ---- web.py-0.36.tar.gz New: ---- web.py-0.37.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-web.py.spec ++++++ --- /var/tmp/diff_new_pack.paMyRr/_old 2012-07-02 11:07:50.000000000 +0200 +++ /var/tmp/diff_new_pack.paMyRr/_new 2012-07-02 11:07:50.000000000 +0200 @@ -11,12 +11,13 @@ # case the license is the MIT License). An "Open Source License" is a # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. -# + # Please submit bugfixes or comments via http://bugs.opensuse.org/ # + Name: python-web.py -Version: 0.36 +Version: 0.37 Release: 0 Url: http://webpy.org/ Summary: web.py: makes web apps ++++++ web.py-0.36.tar.gz -> web.py-0.37.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.36/PKG-INFO new/web.py-0.37/PKG-INFO --- old/web.py-0.36/PKG-INFO 2011-07-04 12:11:27.000000000 +0200 +++ new/web.py-0.37/PKG-INFO 2012-06-26 07:21:31.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: web.py -Version: 0.36 +Version: 0.37 Summary: web.py: makes web apps Home-page: http://webpy.org/ Author: Anand Chitipothu diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.36/web/__init__.py new/web.py-0.37/web/__init__.py --- old/web.py-0.36/web/__init__.py 2011-07-04 12:10:39.000000000 +0200 +++ new/web.py-0.37/web/__init__.py 2012-06-26 07:19:55.000000000 +0200 @@ -3,7 +3,7 @@ from __future__ import generators -__version__ = "0.36" +__version__ = "0.37" __author__ = [ "Aaron Swartz <m...@aaronsw.com>", "Anand Chitipothu <anandol...@gmail.com>" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.36/web/application.py new/web.py-0.37/web/application.py --- old/web.py-0.36/web/application.py 2011-07-04 12:10:39.000000000 +0200 +++ new/web.py-0.37/web/application.py 2012-06-26 07:19:55.000000000 +0200 @@ -5,6 +5,8 @@ import webapi as web import webapi, wsgi, utils import debugerror +import httpserver + from utils import lstrips, safeunicode import sys @@ -201,7 +203,7 @@ if 'HTTP_CONTENT_TYPE' in env: env['CONTENT_TYPE'] = env.pop('HTTP_CONTENT_TYPE') - if method in ["POST", "PUT"]: + if method not in ["HEAD", "GET"]: data = data or '' import StringIO if isinstance(data, dict): @@ -309,6 +311,13 @@ function. """ return wsgi.runwsgi(self.wsgifunc(*middleware)) + + def stop(self): + """Stops the http server started by run. + """ + if httpserver.server: + httpserver.server.stop() + httpserver.server = None def cgirun(self, *middleware): """ @@ -366,8 +375,10 @@ ctx.fullpath = ctx.path + ctx.query for k, v in ctx.iteritems(): + # convert all string values to unicode values and replace + # malformed data with a suitable replacement marker. if isinstance(v, str): - ctx[k] = safeunicode(v) + ctx[k] = v.decode('utf-8', 'replace') # status must always be str ctx.status = '200 OK' @@ -636,9 +647,12 @@ if so, reloads them. """ - SUFFIX = '$py.class' if sys.platform.startswith('java') else '.pyc' """File suffix of compiled modules.""" - + if sys.platform.startswith('java'): + SUFFIX = '$py.class' + else: + SUFFIX = '.pyc' + def __init__(self): self.mtimes = {} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.36/web/db.py new/web.py-0.37/web/db.py --- old/web.py-0.36/web/db.py 2011-07-04 12:10:39.000000000 +0200 +++ new/web.py-0.37/web/db.py 2012-06-26 07:19:55.000000000 +0200 @@ -1019,6 +1019,7 @@ self.paramstyle = db.paramstyle keywords['database'] = keywords.pop('db') + keywords['pooling'] = False # sqlite don't allows connections to be shared by threads self.dbname = "sqlite" DB.__init__(self, db, keywords) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.36/web/debugerror.py new/web.py-0.37/web/debugerror.py --- old/web.py-0.36/web/debugerror.py 2011-07-04 12:10:39.000000000 +0200 +++ new/web.py-0.37/web/debugerror.py 2012-06-26 07:19:55.000000000 +0200 @@ -217,7 +217,7 @@ <div id="explanation"> <p> You're seeing this error because you have <code>web.config.debug</code> - set to <code>True</code>. Set that to <code>False</code> if you don't to see this. + set to <code>True</code>. Set that to <code>False</code> if you don't want to see this. </p> </div> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.36/web/form.py new/web.py-0.37/web/form.py --- old/web.py-0.36/web/form.py 2011-07-04 12:10:39.000000000 +0200 +++ new/web.py-0.37/web/form.py 2012-06-26 07:19:55.000000000 +0200 @@ -8,8 +8,15 @@ import utils, net def attrget(obj, attr, value=None): - if hasattr(obj, 'has_key') and obj.has_key(attr): return obj[attr] - if hasattr(obj, attr): return getattr(obj, attr) + try: + if hasattr(obj, 'has_key') and obj.has_key(attr): + return obj[attr] + except TypeError: + # Handle the case where has_key takes different number of arguments. + # This is the case with Model objects on appengine. See #134 + pass + if hasattr(obj, attr): + return getattr(obj, attr) return value class Form(object): @@ -18,7 +25,7 @@ >>> f = Form(Textbox("x")) >>> f.render() - '<table>\n <tr><th><label for="x">x</label></th><td><input type="text" id="x" name="x"/></td></tr>\n</table>' + u'<table>\n <tr><th><label for="x">x</label></th><td><input type="text" id="x" name="x"/></td></tr>\n</table>' """ def __init__(self, *inputs, **kw): self.inputs = inputs @@ -187,9 +194,9 @@ """Textbox input. >>> Textbox(name='foo', value='bar').render() - '<input type="text" id="foo" value="bar" name="foo"/>' + u'<input type="text" id="foo" value="bar" name="foo"/>' >>> Textbox(name='foo', value=0).render() - '<input type="text" id="foo" value="0" name="foo"/>' + u'<input type="text" id="foo" value="0" name="foo"/>' """ def get_type(self): return 'text' @@ -198,7 +205,7 @@ """Password input. >>> Password(name='password', value='secret').render() - '<input type="password" id="password" value="secret" name="password"/>' + u'<input type="password" id="password" value="secret" name="password"/>' """ def get_type(self): @@ -208,7 +215,7 @@ """Textarea input. >>> Textarea(name='foo', value='bar').render() - '<textarea id="foo" name="foo">bar</textarea>' + u'<textarea id="foo" name="foo">bar</textarea>' """ def render(self): attrs = self.attrs.copy() @@ -220,9 +227,9 @@ r"""Dropdown/select input. >>> Dropdown(name='foo', args=['a', 'b', 'c'], value='b').render() - '<select id="foo" name="foo">\n <option value="a">a</option>\n <option selected="selected" value="b">b</option>\n <option value="c">c</option>\n</select>\n' + u'<select id="foo" name="foo">\n <option value="a">a</option>\n <option selected="selected" value="b">b</option>\n <option value="c">c</option>\n</select>\n' >>> Dropdown(name='foo', args=[('a', 'aa'), ('b', 'bb'), ('c', 'cc')], value='b').render() - '<select id="foo" name="foo">\n <option value="a">aa</option>\n <option selected="selected" value="b">bb</option>\n <option value="c">cc</option>\n</select>\n' + u'<select id="foo" name="foo">\n <option value="a">aa</option>\n <option selected="selected" value="b">bb</option>\n <option value="c">cc</option>\n</select>\n' """ def __init__(self, name, args, *validators, **attrs): self.args = args @@ -235,15 +242,48 @@ x = '<select %s>\n' % attrs for arg in self.args: - if isinstance(arg, (tuple, list)): - value, desc= arg - else: - value, desc = arg, arg + x += self._render_option(arg) + + x += '</select>\n' + return x + + def _render_option(self, arg, indent=' '): + if isinstance(arg, (tuple, list)): + value, desc= arg + else: + value, desc = arg, arg + + if self.value == value or (isinstance(self.value, list) and value in self.value): + select_p = ' selected="selected"' + else: + select_p = '' + return indent + '<option%s value="%s">%s</option>\n' % (select_p, net.websafe(value), net.websafe(desc)) + + +class GroupedDropdown(Dropdown): + r"""Grouped Dropdown/select input. + + >>> GroupedDropdown(name='car_type', args=(('Swedish Cars', ('Volvo', 'Saab')), ('German Cars', ('Mercedes', 'Audi'))), value='Audi').render() + u'<select id="car_type" name="car_type">\n <optgroup label="Swedish Cars">\n <option value="Volvo">Volvo</option>\n <option value="Saab">Saab</option>\n </optgroup>\n <optgroup label="German Cars">\n <option value="Mercedes">Mercedes</option>\n <option selected="selected" value="Audi">Audi</option>\n </optgroup>\n</select>\n' + >>> GroupedDropdown(name='car_type', args=(('Swedish Cars', (('v', 'Volvo'), ('s', 'Saab'))), ('German Cars', (('m', 'Mercedes'), ('a', 'Audi')))), value='a').render() + u'<select id="car_type" name="car_type">\n <optgroup label="Swedish Cars">\n <option value="v">Volvo</option>\n <option value="s">Saab</option>\n </optgroup>\n <optgroup label="German Cars">\n <option value="m">Mercedes</option>\n <option selected="selected" value="a">Audi</option>\n </optgroup>\n</select>\n' - if self.value == value or (isinstance(self.value, list) and value in self.value): - select_p = ' selected="selected"' - else: select_p = '' - x += ' <option%s value="%s">%s</option>\n' % (select_p, net.websafe(value), net.websafe(desc)) + """ + def __init__(self, name, args, *validators, **attrs): + self.args = args + super(Dropdown, self).__init__(name, *validators, **attrs) + + def render(self): + attrs = self.attrs.copy() + attrs['name'] = self.name + + x = '<select %s>\n' % attrs + + for label, options in self.args: + x += ' <optgroup label="%s">\n' % net.websafe(label) + for arg in options: + x += self._render_option(arg, indent = ' ') + x += ' </optgroup>\n' x += '</select>\n' return x @@ -274,14 +314,14 @@ """Checkbox input. >>> Checkbox('foo', value='bar', checked=True).render() - '<input checked="checked" type="checkbox" id="foo_bar" value="bar" name="foo"/>' + u'<input checked="checked" type="checkbox" id="foo_bar" value="bar" name="foo"/>' >>> Checkbox('foo', value='bar').render() - '<input type="checkbox" id="foo_bar" value="bar" name="foo"/>' + u'<input type="checkbox" id="foo_bar" value="bar" name="foo"/>' >>> c = Checkbox('foo', value='bar') >>> c.validate('on') True >>> c.render() - '<input checked="checked" type="checkbox" id="foo_bar" value="bar" name="foo"/>' + u'<input checked="checked" type="checkbox" id="foo_bar" value="bar" name="foo"/>' """ def __init__(self, name, *validators, **attrs): self.checked = attrs.pop('checked', False) @@ -311,9 +351,9 @@ """HTML Button. >>> Button("save").render() - '<button id="save" name="save">save</button>' + u'<button id="save" name="save">save</button>' >>> Button("action", value="save", html="<b>Save Changes</b>").render() - '<button id="action" value="save" name="action"><b>Save Changes</b></button>' + u'<button id="action" value="save" name="action"><b>Save Changes</b></button>' """ def __init__(self, name, *validators, **attrs): super(Button, self).__init__(name, *validators, **attrs) @@ -331,7 +371,7 @@ """Hidden Input. >>> Hidden(name='foo', value='bar').render() - '<input type="hidden" id="foo" value="bar" name="foo"/>' + u'<input type="hidden" id="foo" value="bar" name="foo"/>' """ def is_hidden(self): return True @@ -343,7 +383,7 @@ """File input. >>> File(name='f').render() - '<input type="file" id="f" name="f"/>' + u'<input type="file" id="f" name="f"/>' """ def get_type(self): return 'file' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.36/web/http.py new/web.py-0.37/web/http.py --- old/web.py-0.36/web/http.py 2011-07-04 12:10:39.000000000 +0200 +++ new/web.py-0.37/web/http.py 2012-06-26 07:19:55.000000000 +0200 @@ -122,7 +122,7 @@ def url(path=None, doseq=False, **kw): """ - Makes url by concatinating web.ctx.homepath and path and the + Makes url by concatenating web.ctx.homepath and path and the query string created using the arguments. """ if path is None: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.36/web/httpserver.py new/web.py-0.37/web/httpserver.py --- old/web.py-0.36/web/httpserver.py 2011-07-04 12:10:40.000000000 +0200 +++ new/web.py-0.37/web/httpserver.py 2012-06-26 07:19:55.000000000 +0200 @@ -131,6 +131,10 @@ print "http://%s:%d/" % server_address WSGIServer(func, server_address).serve_forever() +# The WSGIServer instance. +# Made global so that it can be stopped in embedded mode. +server = None + def runsimple(func, server_address=("0.0.0.0", 8080)): """ Runs [CherryPy][cp] WSGI server hosting WSGI app `func`. @@ -138,16 +142,22 @@ [cp]: http://www.cherrypy.org """ + global server func = StaticMiddleware(func) func = LogMiddleware(func) server = WSGIServer(server_address, func) - print "http://%s:%d/" % server_address + if server.ssl_adapter: + print "https://%s:%d/" % server_address + else: + print "http://%s:%d/" % server_address + try: server.start() - except KeyboardInterrupt: + except (KeyboardInterrupt, SystemExit): server.stop() + server = None def WSGIServer(server_address, wsgi_app): """Creates CherryPy WSGI server listening at `server_address` to serve `wsgi_app`. @@ -155,7 +165,7 @@ """ import wsgiserver - # Default values of wsgiserver.ssl_adapters uses cheerypy.wsgiserver + # Default values of wsgiserver.ssl_adapters uses cherrypy.wsgiserver # prefix. Overwriting it make it work with web.wsgiserver. wsgiserver.ssl_adapters = { 'builtin': 'web.wsgiserver.ssl_builtin.BuiltinSSLAdapter', @@ -163,6 +173,32 @@ } server = wsgiserver.CherryPyWSGIServer(server_address, wsgi_app, server_name="localhost") + + def create_ssl_adapter(cert, key): + # wsgiserver tries to import submodules as cherrypy.wsgiserver.foo. + # That doesn't work as not it is web.wsgiserver. + # Patching sys.modules temporarily to make it work. + import types + cherrypy = types.ModuleType('cherrypy') + cherrypy.wsgiserver = wsgiserver + sys.modules['cherrypy'] = cherrypy + sys.modules['cherrypy.wsgiserver'] = wsgiserver + + from wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter + adapter = pyOpenSSLAdapter(cert, key) + + # We are done with our work. Cleanup the patches. + del sys.modules['cherrypy'] + del sys.modules['cherrypy.wsgiserver'] + + return adapter + + # SSL backward compatibility + if (server.ssl_adapter is None and + getattr(server, 'ssl_certificate', None) and + getattr(server, 'ssl_private_key', None)): + server.ssl_adapter = create_ssl_adapter(server.ssl_certificate, server.ssl_private_key) + server.nodelay = not sys.platform.startswith('java') # TCP_NODELAY isn't supported on the JVM return server diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.36/web/session.py new/web.py-0.37/web/session.py --- old/web.py-0.36/web/session.py 2011-07-04 12:10:40.000000000 +0200 +++ new/web.py-0.37/web/session.py 2012-06-26 07:19:55.000000000 +0200 @@ -5,6 +5,7 @@ import os, time, datetime, random, base64 import os.path +from copy import deepcopy try: import cPickle as pickle except ImportError: @@ -27,6 +28,7 @@ web.config.session_parameters = utils.storage({ 'cookie_name': 'webpy_session_id', 'cookie_domain': None, + 'cookie_path' : None, 'timeout': 86400, #24 * 60 * 60, # 24 hours in seconds 'ignore_expiry': True, 'ignore_change_ip': True, @@ -91,6 +93,7 @@ """Load the session from the store, by the id from cookie""" cookie_name = self._config.cookie_name cookie_domain = self._config.cookie_domain + cookie_path = self._config.cookie_path httponly = self._config.httponly self.session_id = web.cookies().get(cookie_name) @@ -109,7 +112,7 @@ if self._initializer: if isinstance(self._initializer, dict): - self.update(self._initializer) + self.update(deepcopy(self._initializer)) elif hasattr(self._initializer, '__call__'): self._initializer() @@ -139,9 +142,10 @@ def _setcookie(self, session_id, expires='', **kw): cookie_name = self._config.cookie_name cookie_domain = self._config.cookie_domain + cookie_path = self._config.cookie_path httponly = self._config.httponly secure = self._config.secure - web.setcookie(cookie_name, session_id, expires=expires, domain=cookie_domain, httponly=httponly, secure=secure) + web.setcookie(cookie_name, session_id, expires=expires, domain=cookie_domain, httponly=httponly, secure=secure, path=cookie_path) def _generate_session_id(self): """Generate a random id for session""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.36/web/template.py new/web.py-0.37/web/template.py --- old/web.py-0.36/web/template.py 2011-07-04 12:10:40.000000000 +0200 +++ new/web.py-0.37/web/template.py 2012-06-26 07:19:55.000000000 +0200 @@ -41,6 +41,7 @@ import glob import re from UserDict import DictMixin +import warnings from utils import storage, safeunicode, safestr, re_compile from webapi import config @@ -920,11 +921,13 @@ # make sure code is safe - but not with jython, it doesn't have a working compiler module if not sys.platform.startswith('java'): - import compiler - ast = compiler.parse(code) - SafeVisitor().walk(ast, filename) + try: + import compiler + ast = compiler.parse(code) + SafeVisitor().walk(ast, filename) + except ImportError: + warnings.warn("Unabled to import compiler module. Unable to check templates for safety.") else: - import warnings warnings.warn("SECURITY ISSUE: You are using Jython, which does not support checking templates for safety. Your templates can execute arbitrary code.") return compiled_code diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.36/web/utils.py new/web.py-0.37/web/utils.py --- old/web.py-0.36/web/utils.py 2011-07-04 12:10:40.000000000 +0200 +++ new/web.py-0.37/web/utils.py 2012-06-26 07:19:55.000000000 +0200 @@ -131,8 +131,14 @@ <Storage {'x': u'a'}> """ _unicode = defaults.pop('_unicode', False) + + # if _unicode is callable object, use it convert a string to unicode. + to_unicode = safeunicode + if _unicode is not False and hasattr(_unicode, "__call__"): + to_unicode = _unicode + def unicodify(s): - if _unicode and isinstance(s, str): return safeunicode(s) + if _unicode and isinstance(s, str): return to_unicode(s) else: return s def getvalue(x): @@ -715,7 +721,7 @@ f = file(filename + '.tmp', 'w') f.write(content) f.close() - os.rename(f.name, path) + os.rename(f.name, filename) def dictreverse(mapping): """ @@ -905,7 +911,12 @@ if abs(deltadays) < 4: return agohence(deltadays, 'day') - out = then.strftime('%B %e') # e.g. 'June 13' + try: + out = then.strftime('%B %e') # e.g. 'June 3' + except ValueError: + # %e doesn't work on Windows. + out = then.strftime('%B %d') # e.g. 'June 03' + if then.year != now.year or deltadays < 0: out += ', %s' % then.year return out @@ -1199,7 +1210,7 @@ def clear_all(): """Clears all ThreadedDict instances. """ - for t in ThreadedDict._instances: + for t in list(ThreadedDict._instances): t.clear() clear_all = staticmethod(clear_all) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.36/web/webapi.py new/web.py-0.37/web/webapi.py --- old/web.py-0.36/web/webapi.py 2011-07-04 12:10:40.000000000 +0200 +++ new/web.py-0.37/web/webapi.py 2012-06-26 07:19:55.000000000 +0200 @@ -19,9 +19,9 @@ "Redirect", "Found", "SeeOther", "NotModified", "TempRedirect", "redirect", "found", "seeother", "notmodified", "tempredirect", - # 400, 401, 403, 404, 405, 406, 409, 410, 412 - "BadRequest", "Unauthorized", "Forbidden", "NotFound", "NoMethod", "NotAcceptable", "Conflict", "Gone", "PreconditionFailed", - "badrequest", "unauthorized", "forbidden", "notfound", "nomethod", "notacceptable", "conflict", "gone", "preconditionfailed", + # 400, 401, 403, 404, 405, 406, 409, 410, 412, 415 + "BadRequest", "Unauthorized", "Forbidden", "NotFound", "NoMethod", "NotAcceptable", "Conflict", "Gone", "PreconditionFailed", "UnsupportedMediaType", + "badrequest", "unauthorized", "forbidden", "notfound", "nomethod", "notacceptable", "conflict", "gone", "preconditionfailed", "unsupportedmediatype", # 500 "InternalError", @@ -122,13 +122,33 @@ class BadRequest(HTTPError): """`400 Bad Request` error.""" message = "bad request" - def __init__(self): + def __init__(self, message=None): status = "400 Bad Request" headers = {'Content-Type': 'text/html'} - HTTPError.__init__(self, status, headers, self.message) + HTTPError.__init__(self, status, headers, message or self.message) badrequest = BadRequest +class Unauthorized(HTTPError): + """`401 Unauthorized` error.""" + message = "unauthorized" + def __init__(self): + status = "401 Unauthorized" + headers = {'Content-Type': 'text/html'} + HTTPError.__init__(self, status, headers, self.message) + +unauthorized = Unauthorized + +class Forbidden(HTTPError): + """`403 Forbidden` error.""" + message = "forbidden" + def __init__(self): + status = "403 Forbidden" + headers = {'Content-Type': 'text/html'} + HTTPError.__init__(self, status, headers, self.message) + +forbidden = Forbidden + class _NotFound(HTTPError): """`404 Not Found` error.""" message = "not found" @@ -149,12 +169,6 @@ notfound = NotFound -unauthorized = Unauthorized = _status_code("401 Unauthorized") -forbidden = Forbidden = _status_code("403 Forbidden") -notacceptable = NotAcceptable = _status_code("406 Not Acceptable") -conflict = Conflict = _status_code("409 Conflict") -preconditionfailed = PreconditionFailed = _status_code("412 Precondition Failed") - class NoMethod(HTTPError): """A `405 Method Not Allowed` error.""" def __init__(self, cls=None): @@ -172,6 +186,26 @@ nomethod = NoMethod +class NotAcceptable(HTTPError): + """`406 Not Acceptable` error.""" + message = "not acceptable" + def __init__(self): + status = "406 Not Acceptable" + headers = {'Content-Type': 'text/html'} + HTTPError.__init__(self, status, headers, self.message) + +notacceptable = NotAcceptable + +class Conflict(HTTPError): + """`409 Conflict` error.""" + message = "conflict" + def __init__(self): + status = "409 Conflict" + headers = {'Content-Type': 'text/html'} + HTTPError.__init__(self, status, headers, self.message) + +conflict = Conflict + class Gone(HTTPError): """`410 Gone` error.""" message = "gone" @@ -182,6 +216,26 @@ gone = Gone +class PreconditionFailed(HTTPError): + """`412 Precondition Failed` error.""" + message = "precondition failed" + def __init__(self): + status = "412 Precondition Failed" + headers = {'Content-Type': 'text/html'} + HTTPError.__init__(self, status, headers, self.message) + +preconditionfailed = PreconditionFailed + +class UnsupportedMediaType(HTTPError): + """`415 Unsupported Media Type` error.""" + message = "unsupported media type" + def __init__(self): + status = "415 Unsupported Media Type" + headers = {'Content-Type': 'text/html'} + HTTPError.__init__(self, status, headers, self.message) + +unsupportedmediatype = UnsupportedMediaType + class _InternalError(HTTPError): """500 Internal Server Error`.""" message = "internal server error" @@ -305,19 +359,101 @@ if httponly: value += '; httponly' header('Set-Cookie', value) + +def decode_cookie(value): + r"""Safely decodes a cookie value to unicode. + + Tries us-ascii, utf-8 and io8859 encodings, in that order. -def cookies(*requireds, **defaults): + >>> decode_cookie('') + u'' + >>> decode_cookie('asdf') + u'asdf' + >>> decode_cookie('foo \xC3\xA9 bar') + u'foo \xe9 bar' + >>> decode_cookie('foo \xE9 bar') + u'foo \xe9 bar' """ - Returns a `storage` object with all the cookies in it. + try: + # First try plain ASCII encoding + return unicode(value, 'us-ascii') + except UnicodeError: + # Then try UTF-8, and if that fails, ISO8859 + try: + return unicode(value, 'utf-8') + except UnicodeError: + return unicode(value, 'iso8859', 'ignore') + +def parse_cookies(http_cookie): + r"""Parse a HTTP_COOKIE header and return dict of cookie names and decoded values. + + >>> sorted(parse_cookies('').items()) + [] + >>> sorted(parse_cookies('a=1').items()) + [('a', '1')] + >>> sorted(parse_cookies('a=1%202').items()) + [('a', '1 2')] + >>> sorted(parse_cookies('a=Z%C3%A9Z').items()) + [('a', 'Z\xc3\xa9Z')] + >>> sorted(parse_cookies('a=1; b=2; c=3').items()) + [('a', '1'), ('b', '2'), ('c', '3')] + >>> sorted(parse_cookies('a=1; b=w("x")|y=z; c=3').items()) + [('a', '1'), ('b', 'w('), ('c', '3')] + >>> sorted(parse_cookies('a=1; b=w(%22x%22)|y=z; c=3').items()) + [('a', '1'), ('b', 'w("x")|y=z'), ('c', '3')] + + >>> sorted(parse_cookies('keebler=E=mc2').items()) + [('keebler', 'E=mc2')] + >>> sorted(parse_cookies(r'keebler="E=mc2; L=\"Loves\"; fudge=\012;"').items()) + [('keebler', 'E=mc2; L="Loves"; fudge=\n;')] + """ + #print "parse_cookies" + if '"' in http_cookie: + # HTTP_COOKIE has quotes in it, use slow but correct cookie parsing + cookie = Cookie.SimpleCookie() + try: + cookie.load(http_cookie) + except Cookie.CookieError: + # If HTTP_COOKIE header is malformed, try at least to load the cookies we can by + # first splitting on ';' and loading each attr=value pair separately + cookie = Cookie.SimpleCookie() + for attr_value in http_cookie.split(';'): + try: + cookie.load(attr_value) + except Cookie.CookieError: + pass + cookies = dict((k, urllib.unquote(v.value)) for k, v in cookie.iteritems()) + else: + # HTTP_COOKIE doesn't have quotes, use fast cookie parsing + cookies = {} + for key_value in http_cookie.split(';'): + key_value = key_value.split('=', 1) + if len(key_value) == 2: + key, value = key_value + cookies[key.strip()] = urllib.unquote(value.strip()) + return cookies + +def cookies(*requireds, **defaults): + r"""Returns a `storage` object with all the request cookies in it. + See `storify` for how `requireds` and `defaults` work. + + This is forgiving on bad HTTP_COOKIE input, it tries to parse at least + the cookies it can. + + The values are converted to unicode if _unicode=True is passed. """ - cookie = Cookie.SimpleCookie() - cookie.load(ctx.env.get('HTTP_COOKIE', '')) + # If _unicode=True is specified, use decode_cookie to convert cookie value to unicode + if defaults.get("_unicode") is True: + defaults['_unicode'] = decode_cookie + + # parse cookie string and cache the result for next time. + if '_parsed_cookies' not in ctx: + http_cookie = ctx.env.get("HTTP_COOKIE", "") + ctx._parsed_cookies = parse_cookies(http_cookie) + try: - d = storify(cookie, *requireds, **defaults) - for k, v in d.items(): - d[k] = v and urllib.unquote(v) - return d + return storify(ctx._parsed_cookies, *requireds, **defaults) except KeyError: badrequest() raise StopIteration @@ -383,3 +519,7 @@ `output` : A string to be used as the response. """ + +if __name__ == "__main__": + import doctest + doctest.testmod() \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.36/web/wsgi.py new/web.py-0.37/web/wsgi.py --- old/web.py-0.36/web/wsgi.py 2011-07-04 12:10:40.000000000 +0200 +++ new/web.py-0.37/web/wsgi.py 2012-06-26 07:19:55.000000000 +0200 @@ -54,11 +54,15 @@ return httpserver.runsimple(func, validip(listget(sys.argv, 1, ''))) def _is_dev_mode(): + # Some embedded python interpreters won't have sys.arv + # For details, see https://github.com/webpy/webpy/issues/87 + argv = getattr(sys, "argv", []) + # quick hack to check if the program is running in dev mode. if os.environ.has_key('SERVER_SOFTWARE') \ or os.environ.has_key('PHP_FCGI_CHILDREN') \ - or 'fcgi' in sys.argv or 'fastcgi' in sys.argv \ - or 'mod_wsgi' in sys.argv: + or 'fcgi' in argv or 'fastcgi' in argv \ + or 'mod_wsgi' in argv: return False return True -- To unsubscribe, e-mail: opensuse-commit+unsubscr...@opensuse.org For additional commands, e-mail: opensuse-commit+h...@opensuse.org