Hello community, here is the log from the commit of package python-web.py for openSUSE:Factory checked in at 2019-03-19 10:00:51 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-web.py (Old) and /work/SRC/openSUSE:Factory/.python-web.py.new.28833 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-web.py" Tue Mar 19 10:00:51 2019 rev:8 rq:686082 version:0.39 Changes: -------- --- /work/SRC/openSUSE:Factory/python-web.py/python-web.py.changes 2017-01-25 23:32:39.450462416 +0100 +++ /work/SRC/openSUSE:Factory/.python-web.py.new.28833/python-web.py.changes 2019-03-19 10:01:00.155915375 +0100 @@ -1,0 +2,7 @@ +Mon Mar 18 11:05:06 UTC 2019 - Michael Ströder <[email protected]> + +- Updated to 0.39 + * Fixed a security issue with the form module + * Fixed a security issue with the db module + +------------------------------------------------------------------- Old: ---- web.py-0.38.tar.gz New: ---- web.py-0.39.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-web.py.spec ++++++ --- /var/tmp/diff_new_pack.wS1IU2/_old 2019-03-19 10:01:01.223914262 +0100 +++ /var/tmp/diff_new_pack.wS1IU2/_new 2019-03-19 10:01:01.243914241 +0100 @@ -17,7 +17,7 @@ Name: python-web.py -Version: 0.38 +Version: 0.39 Release: 0 Url: http://webpy.org/ Summary: web.py: makes web apps @@ -26,6 +26,7 @@ Source: https://pypi.io/packages/source/w/web.py/web.py-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRequires: python-devel +BuildRequires: python-setuptools %if 0%{?suse_version} && 0%{?suse_version} <= 1110 %{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} %else ++++++ web.py-0.38.tar.gz -> web.py-0.39.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.38/PKG-INFO new/web.py-0.39/PKG-INFO --- old/web.py-0.38/PKG-INFO 2016-07-08 09:07:25.000000000 +0200 +++ new/web.py-0.39/PKG-INFO 2018-02-28 07:36:07.000000000 +0100 @@ -1,8 +1,8 @@ Metadata-Version: 1.0 Name: web.py -Version: 0.38 +Version: 0.39 Summary: web.py: makes web apps -Home-page: http://webpy.org/ +Home-page: http://webpy.org/ Author: Anand Chitipothu Author-email: [email protected] License: Public domain diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.38/setup.cfg new/web.py-0.39/setup.cfg --- old/web.py-0.38/setup.cfg 1970-01-01 01:00:00.000000000 +0100 +++ new/web.py-0.39/setup.cfg 2018-02-28 07:36:07.000000000 +0100 @@ -0,0 +1,4 @@ +[egg_info] +tag_build = +tag_date = 0 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.38/setup.py new/web.py-0.39/setup.py --- old/web.py-0.38/setup.py 2016-07-08 09:03:09.000000000 +0200 +++ new/web.py-0.39/setup.py 2018-02-28 07:35:03.000000000 +0100 @@ -2,7 +2,7 @@ # ... -from distutils.core import setup +from setuptools import setup from web import __version__ setup(name='web.py', @@ -12,7 +12,7 @@ author_email='[email protected]', maintainer='Anand Chitipothu', maintainer_email='[email protected]', - url=' http://webpy.org/', + url='http://webpy.org/', packages=['web', 'web.wsgiserver', 'web.contrib'], long_description="Think about the ideal way to write a web app. Write the code to make it happen.", license="Public domain", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.38/web/__init__.py new/web.py-0.39/web/__init__.py --- old/web.py-0.38/web/__init__.py 2016-07-08 09:03:09.000000000 +0200 +++ new/web.py-0.39/web/__init__.py 2018-02-28 07:32:28.000000000 +0100 @@ -3,7 +3,7 @@ from __future__ import generators -__version__ = "0.38" +__version__ = "0.39" __author__ = [ "Aaron Swartz <[email protected]>", "Anand Chitipothu <[email protected]>" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.38/web/db.py new/web.py-0.39/web/db.py --- old/web.py-0.38/web/db.py 2016-07-08 09:03:09.000000000 +0200 +++ new/web.py-0.39/web/db.py 2018-02-28 07:32:28.000000000 +0100 @@ -17,6 +17,16 @@ except ImportError: datetime = None +try: + import ast +except ImportError: + ast = None + +try: + from tokenize import tokenprog +except ImportError: + tokenprog = None + try: set except NameError: from sets import Set as set @@ -91,6 +101,9 @@ def __str__(self): return str(self.value) + + def __eq__(self, other): + return isinstance(other, SQLParam) and other.value == self.value def __repr__(self): return '<param: %s>' % repr(self.value) @@ -153,9 +166,10 @@ def __radd__(self, other): if isinstance(other, basestring): items = [other] + elif isinstance(other, SQLQuery): + items = other.items else: return NotImplemented - return SQLQuery(items + self.items) def __iadd__(self, other): @@ -169,6 +183,9 @@ def __len__(self): return len(self.query()) + + def __eq__(self, other): + return isinstance(other, SQLQuery) and other.items == self.items def query(self, paramstyle=None): """ @@ -226,10 +243,12 @@ target_items.append(prefix) for i, item in enumerate(items): - if i != 0: + if i != 0 and sep != "": target_items.append(sep) if isinstance(item, SQLQuery): target_items.extend(item.items) + elif item == "": # joins with empty strings + continue else: target_items.append(item) @@ -267,7 +286,7 @@ self.v = v def __repr__(self): - return self.v + return "<literal: %r>" % self.v sqlliteral = SQLLiteral @@ -295,10 +314,11 @@ >>> reparam("s IN $s", dict(s=[1, 2])) <sql: 's IN (1, 2)'> """ + return SafeEval().safeeval(string_, dictionary) + dictionary = dictionary.copy() # eval mucks with it # disable builtins to avoid risk for remote code exection. dictionary['__builtins__'] = object() - vals = [] result = [] for live, chunk in _interpolate(string_): if live: @@ -723,8 +743,11 @@ ('WHERE', where), ('GROUP BY', group), ('ORDER BY', order), - ('LIMIT', limit), - ('OFFSET', offset)) + # The limit and offset could be the values provided by + # the end-user and are potentially unsafe. + # Using them as parameters to avoid any risk. + ('LIMIT', limit and SQLParam(limit).sqlquery()), + ('OFFSET', offset and SQLParam(offset).sqlquery())) def gen_clause(self, sql, val, vars): if isinstance(val, (int, long)): @@ -1273,6 +1296,175 @@ chunks.append((0, format[pos:])) return chunks +class _Node(object): + def __init__(self, type, first, second=None): + self.type = type + self.first = first + self.second = second + + def __eq__(self, other): + return (isinstance(other, _Node) + and self.type == other.type + and self.first == other.first + and self.second == other.second) + + def __repr__(self): + return "Node(%r, %r, %r)" % (self.type, self.first, self.second) + +class Parser: + """Parser to parse string templates like "Hello $name". + + Loosely based on <http://lfw.org/python/Itpl.py> (public domain, Ka-Ping Yee) + """ + namechars = "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" + + def __init__(self): + self.reset() + + def reset(self): + self.pos = 0 + self.level = 0 + self.text = "" + + def parse(self, text): + """Parses the given text and returns a parse tree. + """ + self.reset() + self.text = text + return self.parse_all() + + def parse_all(self): + while True: + dollar = self.text.find("$", self.pos) + if dollar < 0: + break + nextchar = self.text[dollar + 1] + if nextchar in self.namechars: + yield _Node("text", self.text[self.pos:dollar]) + self.pos = dollar+1 + yield self.parse_expr() + + # for supporting ${x.id}, for backward compataility + elif nextchar == '{': + saved_pos = self.pos + self.pos = dollar+2 # skip "${" + expr = self.parse_expr() + if self.text[self.pos] == '}': + self.pos += 1 + yield _Node("text", self.text[self.pos:dollar]) + yield expr + else: + self.pos = saved_pos + break + else: + yield _Node("text", self.text[self.pos:dollar+1]) + self.pos = dollar + 1 + # $$ is used to escape $ + if nextchar == "$": + self.pos += 1 + + if self.pos < len(self.text): + yield _Node("text", self.text[self.pos:]) + + def match(self): + match = tokenprog.match(self.text, self.pos) + if match is None: + raise _ItplError(self.text, self.pos) + return match, match.end() + + def is_literal(self, text): + return text and text[0] in "0123456789\"'" + + def parse_expr(self): + match, pos = self.match() + if self.is_literal(match.group()): + expr = _Node("literal", match.group()) + else: + expr = _Node("param", self.text[self.pos:pos]) + self.pos = pos + while self.pos < len(self.text): + if self.text[self.pos] == "." and \ + self.pos + 1 < len(self.text) and self.text[self.pos + 1] in self.namechars: + self.pos += 1 + match, pos = self.match() + attr = match.group() + expr = _Node("getattr", expr, attr) + self.pos = pos + elif self.text[self.pos] == "[": + saved_pos = self.pos + self.pos += 1 + key = self.parse_expr() + if self.text[self.pos] == ']': + self.pos += 1 + expr = _Node("getitem", expr, key) + else: + self.pos = saved_pos + break + else: + break + return expr + +class SafeEval(object): + """Safe evaluator for binding params to db queries. + """ + def safeeval(self, text, mapping): + nodes = Parser().parse(text) + return SQLQuery.join([self.eval_node(node, mapping) for node in nodes], "") + + def eval_node(self, node, mapping): + if node.type == "text": + return node.first + else: + return sqlquote(self.eval_expr(node, mapping)) + + def eval_expr(self, node, mapping): + if node.type == "literal": + return ast.literal_eval(node.first) + elif node.type == "getattr": + return getattr(self.eval_expr(node.first, mapping), node.second) + elif node.type == "getitem": + return self.eval_expr(node.first, mapping)[self.eval_expr(node.second, mapping)] + elif node.type == "param": + return mapping[node.first] + +def test_parser(): + def f(text, expected): + p = Parser() + nodes = list(p.parse(text)) + print repr(text), nodes + assert nodes == expected, "Expected %r" % expected + + f("Hello", [_Node("text", "Hello")]) + f("Hello $name", [_Node("text", "Hello "), _Node("param", "name")]) + f("Hello $name.foo", [ + _Node("text", "Hello "), + _Node("getattr", + _Node("param", "name"), + "foo")]) + f("WHERE id=$self.id LIMIT 1", [ + _Node("text", "WHERE id="), + _Node('getattr', + _Node('param', 'self', None), + 'id'), + _Node("text", " LIMIT 1")]) + + f("WHERE id=$self['id'] LIMIT 1", [ + _Node("text", "WHERE id="), + _Node('getitem', + _Node('param', 'self', None), + _Node('literal', "'id'")), + _Node("text", " LIMIT 1")]) + +def test_safeeval(): + def f(q, vars): + return SafeEval().safeeval(q, vars) + + print f("WHERE id=$id", {"id": 1}).items + assert f("WHERE id=$id", {"id": 1}).items == ["WHERE id=", sqlparam(1)] + if __name__ == "__main__": import doctest doctest.testmod() + test_parser() + test_safeeval() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.38/web/form.py new/web.py-0.39/web/form.py --- old/web.py-0.38/web/form.py 2016-07-08 09:03:09.000000000 +0200 +++ new/web.py-0.39/web/form.py 2018-02-28 07:32:28.000000000 +0100 @@ -48,7 +48,7 @@ if i.is_hidden(): out += ' <tr style="display: none;"><th></th><td>%s</td></tr>\n' % (html) else: - out += ' <tr><th><label for="%s">%s</label></th><td>%s</td></tr>\n' % (i.id, net.websafe(i.description), html) + out += ' <tr><th><label for="%s">%s</label></th><td>%s</td></tr>\n' % (net.websafe(i.id), net.websafe(i.description), html) out += "</table>" return out @@ -57,7 +57,7 @@ out.append(self.rendernote(self.note)) for i in self.inputs: if not i.is_hidden(): - out.append('<label for="%s">%s</label>' % (i.id, net.websafe(i.description))) + out.append('<label for="%s">%s</label>' % (net.websafe(i.id), net.websafe(i.description))) out.append(i.pre) out.append(i.render()) out.append(self.rendernote(i.note)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.38/web.py.egg-info/PKG-INFO new/web.py-0.39/web.py.egg-info/PKG-INFO --- old/web.py-0.38/web.py.egg-info/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 +++ new/web.py-0.39/web.py.egg-info/PKG-INFO 2018-02-28 07:36:07.000000000 +0100 @@ -0,0 +1,10 @@ +Metadata-Version: 1.0 +Name: web.py +Version: 0.39 +Summary: web.py: makes web apps +Home-page: http://webpy.org/ +Author: Anand Chitipothu +Author-email: [email protected] +License: Public domain +Description: Think about the ideal way to write a web app. Write the code to make it happen. +Platform: any diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.38/web.py.egg-info/SOURCES.txt new/web.py-0.39/web.py.egg-info/SOURCES.txt --- old/web.py-0.38/web.py.egg-info/SOURCES.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/web.py-0.39/web.py.egg-info/SOURCES.txt 2018-02-28 07:36:07.000000000 +0100 @@ -0,0 +1,27 @@ +setup.py +web/__init__.py +web/application.py +web/browser.py +web/db.py +web/debugerror.py +web/form.py +web/http.py +web/httpserver.py +web/net.py +web/python23.py +web/session.py +web/template.py +web/test.py +web/utils.py +web/webapi.py +web/webopenid.py +web/wsgi.py +web.py.egg-info/PKG-INFO +web.py.egg-info/SOURCES.txt +web.py.egg-info/dependency_links.txt +web.py.egg-info/top_level.txt +web/contrib/__init__.py +web/contrib/template.py +web/wsgiserver/__init__.py +web/wsgiserver/ssl_builtin.py +web/wsgiserver/ssl_pyopenssl.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.38/web.py.egg-info/dependency_links.txt new/web.py-0.39/web.py.egg-info/dependency_links.txt --- old/web.py-0.38/web.py.egg-info/dependency_links.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/web.py-0.39/web.py.egg-info/dependency_links.txt 2018-02-28 07:36:07.000000000 +0100 @@ -0,0 +1 @@ + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/web.py-0.38/web.py.egg-info/top_level.txt new/web.py-0.39/web.py.egg-info/top_level.txt --- old/web.py-0.38/web.py.egg-info/top_level.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/web.py-0.39/web.py.egg-info/top_level.txt 2018-02-28 07:36:07.000000000 +0100 @@ -0,0 +1 @@ +web
