[Zope-Checkins] CVS: Zope/lib/python/ZPublisher/tests - testTaintedString.py:1.1.2.1 testHTTPRequest.py:1.1.2.4
Martijn Pieters
mj@zope.com
Thu, 1 Aug 2002 12:01:00 -0400
Update of /cvs-repository/Zope/lib/python/ZPublisher/tests
In directory cvs.zope.org:/tmp/cvs-serv9310/lib/python/ZPublisher/tests
Modified Files:
Tag: Zope-2_5-branch
testHTTPRequest.py
Added Files:
Tag: Zope-2_5-branch
testTaintedString.py
Log Message:
Big change, merge from trunk.
- Make DTML automatically html quote data indirectly taken from REQUEST
which contain a '<'. Make sure (almost) all string operation preserve the
taint on this data.
- Fix exceptions that use REQUEST data; quote the data.
- Don't let form and cookie values mask the REQUEST computed values such as
URL0 and BASE1.
=== Added File Zope/lib/python/ZPublisher/tests/testTaintedString.py ===
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
import sys
import unittest
class TestTaintedString(unittest.TestCase):
def setUp(self):
self.unquoted = '<test attr="&">'
self.quoted = '<test attr="&">'
self.tainted = self._getClass()(self.unquoted)
def _getClass(self):
sys.path.insert(0, '.')
try:
from ZPublisher.TaintedString import TaintedString
except ImportError:
sys.path[0]='../..'
from ZPublisher.TaintedString import TaintedString
return TaintedString
def testStr(self):
self.assertEquals(str(self.tainted), self.unquoted)
def testRepr(self):
self.assertEquals(repr(self.tainted), repr(self.quoted))
def testCmp(self):
self.assertEquals(cmp(self.tainted, self.unquoted), 0)
self.assertEquals(cmp(self.tainted, 'a'), -1)
self.assertEquals(cmp(self.tainted, '.'), 1)
def testHash(self):
hash = {}
hash[self.tainted] = self.quoted
hash[self.unquoted] = self.unquoted
self.assertEquals(hash[self.tainted], self.unquoted)
def testLen(self):
self.assertEquals(len(self.tainted), len(self.unquoted))
def testGetItem(self):
self.assert_(isinstance(self.tainted[0], self._getClass()))
self.assertEquals(self.tainted[0], '<')
self.failIf(isinstance(self.tainted[-1], self._getClass()))
self.assertEquals(self.tainted[-1], '>')
def testGetSlice(self):
self.assert_(isinstance(self.tainted[0:1], self._getClass()))
self.assertEquals(self.tainted[0:1], '<')
self.failIf(isinstance(self.tainted[1:], self._getClass()))
self.assertEquals(self.tainted[1:], self.unquoted[1:])
def testConcat(self):
self.assert_(isinstance(self.tainted + 'test', self._getClass()))
self.assertEquals(self.tainted + 'test', self.unquoted + 'test')
self.assert_(isinstance('test' + self.tainted, self._getClass()))
self.assertEquals('test' + self.tainted, 'test' + self.unquoted)
def testMultiply(self):
self.assert_(isinstance(2 * self.tainted, self._getClass()))
self.assertEquals(2 * self.tainted, 2 * self.unquoted)
self.assert_(isinstance(self.tainted * 2, self._getClass()))
self.assertEquals(self.tainted * 2, self.unquoted * 2)
def testInterpolate(self):
tainted = self._getClass()('<%s>')
self.assert_(isinstance(tainted % 'foo', self._getClass()))
self.assertEquals(tainted % 'foo', '<foo>')
tainted = self._getClass()('<%s attr="%s">')
self.assert_(isinstance(tainted % ('foo', 'bar'), self._getClass()))
self.assertEquals(tainted % ('foo', 'bar'), '<foo attr="bar">')
def testStringMethods(self):
simple = "capitalize isalpha isdigit islower isspace istitle isupper" \
" lower lstrip rstrip strip swapcase upper".split()
returnsTainted = "capitalize lower lstrip rstrip strip swapcase upper"
returnsTainted = returnsTainted.split()
unquoted = '\tThis is a test '
tainted = self._getClass()(unquoted)
for f in simple:
v = getattr(tainted, f)()
self.assertEquals(v, getattr(unquoted, f)())
if f in returnsTainted:
self.assert_(isinstance(v, self._getClass()))
else:
self.failIf(isinstance(v, self._getClass()))
justify = "center ljust rjust".split()
for f in justify:
v = getattr(tainted, f)(30)
self.assertEquals(v, getattr(unquoted, f)(30))
self.assert_(isinstance(v, self._getClass()))
searches = "find index rfind rindex endswith startswith".split()
searchraises = "index rindex".split()
for f in searches:
v = getattr(tainted, f)('test')
self.assertEquals(v, getattr(unquoted, f)('test'))
if f in searchraises:
self.assertRaises(ValueError, getattr(tainted, f), 'nada')
self.assertEquals(tainted.count('test', 1, -1),
unquoted.count('test', 1, -1))
self.assertEquals(tainted.encode(), unquoted.encode())
self.assert_(isinstance(tainted.encode(), self._getClass()))
self.assertEquals(tainted.expandtabs(10),
unquoted.expandtabs(10))
self.assert_(isinstance(tainted.expandtabs(), self._getClass()))
self.assertEquals(tainted.replace('test', 'spam'),
unquoted.replace('test', 'spam'))
self.assert_(isinstance(tainted.replace('test', '<'), self._getClass()))
self.failIf(isinstance(tainted.replace('test', 'spam'),
self._getClass()))
self.assertEquals(tainted.split(), unquoted.split())
for part in self._getClass()('< < <').split():
self.assert_(isinstance(part, self._getClass()))
for part in tainted.split():
self.failIf(isinstance(part, self._getClass()))
multiline = 'test\n<tainted>'
lines = self._getClass()(multiline).split()
self.assertEquals(lines, multiline.split())
self.assert_(isinstance(lines[1], self._getClass()))
self.failIf(isinstance(lines[0], self._getClass()))
transtable = ''.join(map(chr, range(256)))
self.assertEquals(tainted.translate(transtable),
unquoted.translate(transtable))
self.assert_(isinstance(self._getClass()('<').translate(transtable),
self._getClass()))
self.failIf(isinstance(self._getClass()('<').translate(transtable, '<'),
self._getClass()))
def testQuoted(self):
self.assertEquals(self.tainted.quoted(), self.quoted)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestTaintedString, 'test'))
return suite
def main():
unittest.TextTestRunner().run(test_suite())
def debug():
test_suite().debug()
def pdebug():
import pdb
pdb.run('debug()')
if __name__=='__main__':
if len(sys.argv) > 1:
globals()[sys.argv[1]]()
else:
main()
=== Zope/lib/python/ZPublisher/tests/testHTTPRequest.py 1.1.2.3 => 1.1.2.4 ===
req.processInputs()
return req
+ def _noTaintedValues(self, req):
+ self.failIf(req.taintedform.keys())
+
+ def _valueIsOrHoldsTainted(self, val):
+ # Recursively searches a structure for a TaintedString and returns 1
+ # when one is found.
+ # Also raises an Assertion if a string which *should* have been
+ # tainted is found, or when a tainted string is not deemed dangerous.
+ from types import ListType, TupleType, StringType, UnicodeType
+ from ZPublisher.HTTPRequest import record
+ from ZPublisher.TaintedString import TaintedString
+
+ retval = 0
+
+ if isinstance(val, TaintedString):
+ self.failIf(not '<' in val,
+ "%r is not dangerous, no taint required." % val)
+ retval = 1
+
+ elif isinstance(val, record):
+ for attr, value in val.__dict__.items():
+ rval = self._valueIsOrHoldsTainted(attr)
+ if rval: retval = 1
+ rval = self._valueIsOrHoldsTainted(value)
+ if rval: retval = 1
+
+ elif type(val) in (ListType, TupleType):
+ for entry in val:
+ rval = self._valueIsOrHoldsTainted(entry)
+ if rval: retval = 1
+
+ elif type(val) in (StringType, UnicodeType):
+ self.failIf('<' in val,
+ "'%s' is dangerous and should have been tainted." % val)
+
+ return retval
+
+ def _noFormValuesInOther(self, req):
+ for key in req.taintedform.keys():
+ self.failIf(req.other.has_key(key),
+ 'REQUEST.other should not hold tainted values at first!')
+
+ for key in req.form.keys():
+ self.failIf(req.other.has_key(key),
+ 'REQUEST.other should not hold form values at first!')
+
+ def _onlyTaintedformHoldsTaintedStrings(self, req):
+ for key, val in req.taintedform.items():
+ self.assert_(self._valueIsOrHoldsTainted(key) or
+ self._valueIsOrHoldsTainted(val),
+ 'Tainted form holds item %s that is not tainted' % key)
+
+ for key, val in req.form.items():
+ if req.taintedform.has_key(key):
+ continue
+ self.failIf(self._valueIsOrHoldsTainted(key) or
+ self._valueIsOrHoldsTainted(val),
+ 'Normal form holds item %s that is tainted' % key)
+
+ def _taintedKeysAlsoInForm(self, req):
+ for key in req.taintedform.keys():
+ self.assert_(req.form.has_key(key),
+ "Found tainted %s not in form" % key)
+ self.assertEquals(req.form[key], req.taintedform[key],
+ "Key %s not correctly reproduced in tainted; expected %r, "
+ "got %r" % (key, req.form[key], req.taintedform[key]))
+
def testNoMarshalling(self):
inputs = (
('foo', 'bar'), ('spam', 'eggs'),
@@ -43,6 +110,7 @@
('spacey key', 'val'), ('key', 'spacey val'),
('multi', '1'), ('multi', '2'))
req = self._processInputs(inputs)
+ self._noFormValuesInOther(req)
formkeys = list(req.form.keys())
formkeys.sort()
@@ -53,6 +121,9 @@
self.assertEquals(req['spacey key'], 'val')
self.assertEquals(req['key'], 'spacey val')
+ self._noTaintedValues(req)
+ self._onlyTaintedformHoldsTaintedStrings(req)
+
def testSimpleMarshalling(self):
from DateTime import DateTime
@@ -64,6 +135,7 @@
('multiline:lines', 'one\ntwo'),
('morewords:text', 'one\ntwo\n'))
req = self._processInputs(inputs)
+ self._noFormValuesInOther(req)
formkeys = list(req.form.keys())
formkeys.sort()
@@ -80,6 +152,9 @@
self.assertEquals(req['num'], 42)
self.assertEquals(req['words'], 'Some words')
+ self._noTaintedValues(req)
+ self._onlyTaintedformHoldsTaintedStrings(req)
+
def testSimpleContainers(self):
inputs = (
('oneitem:list', 'one'),
@@ -90,6 +165,7 @@
('setrec.foo:records', 'foo'), ('setrec.bar:records', 'bar'),
('setrec.foo:records', 'spam'), ('setrec.bar:records', 'eggs'))
req = self._processInputs(inputs)
+ self._noFormValuesInOther(req)
formkeys = list(req.form.keys())
formkeys.sort()
@@ -108,6 +184,9 @@
self.assertEquals(req['setrec'][1].foo, 'spam')
self.assertEquals(req['setrec'][1].bar, 'eggs')
+ self._noTaintedValues(req)
+ self._onlyTaintedformHoldsTaintedStrings(req)
+
def testMarshallIntoSequences(self):
inputs = (
('ilist:int:list', '1'), ('ilist:int:list', '2'),
@@ -116,6 +195,7 @@
('ftuple:tuple:float', '1.2'),
('tlist:tokens:list', 'one two'), ('tlist:list:tokens', '3 4'))
req = self._processInputs(inputs)
+ self._noFormValuesInOther(req)
formkeys = list(req.form.keys())
formkeys.sort()
@@ -125,6 +205,9 @@
self.assertEquals(req['ftuple'], (1.0, 1.1, 1.2))
self.assertEquals(req['tlist'], [['one', 'two'], ['3', '4']])
+ self._noTaintedValues(req)
+ self._onlyTaintedformHoldsTaintedStrings(req)
+
def testRecordsWithSequences(self):
inputs = (
('onerec.name:record', 'foo'),
@@ -143,6 +226,7 @@
('setrec.ituple:tuple:int:records', '1'),
('setrec.ituple:tuple:int:records', '2'))
req = self._processInputs(inputs)
+ self._noFormValuesInOther(req)
formkeys = list(req.form.keys())
formkeys.sort()
@@ -161,6 +245,9 @@
self.assertEquals(req['setrec'][i].ilist, [1, 2])
self.assertEquals(req['setrec'][i].ituple, (1, 2))
+ self._noTaintedValues(req)
+ self._onlyTaintedformHoldsTaintedStrings(req)
+
def testDefaults(self):
inputs = (
('foo:default:int', '5'),
@@ -187,6 +274,7 @@
('setrec.foo:records', 'ham'),
)
req = self._processInputs(inputs)
+ self._noFormValuesInOther(req)
formkeys = list(req.form.keys())
formkeys.sort()
@@ -205,6 +293,217 @@
self.assertEquals(req['setrec'][0].foo, 'baz')
self.assertEquals(req['setrec'][1].spam, 'eggs')
self.assertEquals(req['setrec'][1].foo, 'ham')
+
+ self._noTaintedValues(req)
+ self._onlyTaintedformHoldsTaintedStrings(req)
+
+ def testNoMarshallingWithTaints(self):
+ inputs = (
+ ('foo', 'bar'), ('spam', 'eggs'),
+ ('number', '1'),
+ ('tainted', '<tainted value>'),
+ ('<tainted key>', 'value'),
+ ('spacey key', 'val'), ('key', 'spacey val'),
+ ('multi', '1'), ('multi', '2'))
+ req = self._processInputs(inputs)
+ self._noFormValuesInOther(req)
+
+ taintedformkeys = list(req.taintedform.keys())
+ taintedformkeys.sort()
+ self.assertEquals(taintedformkeys, ['<tainted key>', 'tainted'])
+
+ self._taintedKeysAlsoInForm(req)
+ self._onlyTaintedformHoldsTaintedStrings(req)
+
+ def testSimpleMarshallingWithTaints(self):
+ inputs = (
+ ('foo', 'bar'), ('spam', 'eggs'),
+ ('number', '1'),
+ ('tainted', '<tainted value>'), ('<tainted key>', 'value'),
+ ('spacey key', 'val'), ('key', 'spacey val'),
+ ('multi', '1'), ('multi', '2'))
+ req = self._processInputs(inputs)
+ self._noFormValuesInOther(req)
+
+ taintedformkeys = list(req.taintedform.keys())
+ taintedformkeys.sort()
+ self.assertEquals(taintedformkeys, ['<tainted key>', 'tainted'])
+
+ self._taintedKeysAlsoInForm(req)
+ self._onlyTaintedformHoldsTaintedStrings(req)
+
+ def testSimpleContainersWithTaints(self):
+ from types import ListType, TupleType
+ from ZPublisher.HTTPRequest import record
+
+ inputs = (
+ ('toneitem:list', '<one>'),
+ ('<tkeyoneitem>:list', 'one'),
+ ('tinitalist:list', '<one>'), ('tinitalist:list', 'two'),
+ ('tdeferalist:list', 'one'), ('tdeferalist:list', '<two>'),
+
+ ('toneitemtuple:tuple', '<one>'),
+ ('tinitatuple:tuple', '<one>'), ('tinitatuple:tuple', 'two'),
+ ('tdeferatuple:tuple', 'one'), ('tdeferatuple:tuple', '<two>'),
+
+ ('tinitonerec.foo:record', '<foo>'),
+ ('tinitonerec.bar:record', 'bar'),
+ ('tdeferonerec.foo:record', 'foo'),
+ ('tdeferonerec.bar:record', '<bar>'),
+
+ ('tinitinitsetrec.foo:records', '<foo>'),
+ ('tinitinitsetrec.bar:records', 'bar'),
+ ('tinitinitsetrec.foo:records', 'spam'),
+ ('tinitinitsetrec.bar:records', 'eggs'),
+
+ ('tinitdefersetrec.foo:records', 'foo'),
+ ('tinitdefersetrec.bar:records', '<bar>'),
+ ('tinitdefersetrec.foo:records', 'spam'),
+ ('tinitdefersetrec.bar:records', 'eggs'),
+
+ ('tdeferinitsetrec.foo:records', 'foo'),
+ ('tdeferinitsetrec.bar:records', 'bar'),
+ ('tdeferinitsetrec.foo:records', '<spam>'),
+ ('tdeferinitsetrec.bar:records', 'eggs'),
+
+ ('tdeferdefersetrec.foo:records', 'foo'),
+ ('tdeferdefersetrec.bar:records', 'bar'),
+ ('tdeferdefersetrec.foo:records', 'spam'),
+ ('tdeferdefersetrec.bar:records', '<eggs>'))
+ req = self._processInputs(inputs)
+ self._noFormValuesInOther(req)
+
+ taintedformkeys = list(req.taintedform.keys())
+ taintedformkeys.sort()
+ self.assertEquals(taintedformkeys, ['<tkeyoneitem>', 'tdeferalist',
+ 'tdeferatuple', 'tdeferdefersetrec', 'tdeferinitsetrec',
+ 'tdeferonerec', 'tinitalist', 'tinitatuple', 'tinitdefersetrec',
+ 'tinitinitsetrec', 'tinitonerec', 'toneitem', 'toneitemtuple'])
+
+ self._taintedKeysAlsoInForm(req)
+ self._onlyTaintedformHoldsTaintedStrings(req)
+
+ def testRecordsWithSequencesAndTainted(self):
+ inputs = (
+ ('tinitonerec.tokens:tokens:record', '<one> two'),
+ ('tdeferonerec.tokens:tokens:record', 'one <two>'),
+
+ ('tinitsetrec.name:records', 'first'),
+ ('tinitsetrec.ilist:list:records', '<1>'),
+ ('tinitsetrec.ilist:list:records', '2'),
+ ('tinitsetrec.ituple:tuple:int:records', '1'),
+ ('tinitsetrec.ituple:tuple:int:records', '2'),
+ ('tinitsetrec.name:records', 'second'),
+ ('tinitsetrec.ilist:list:records', '1'),
+ ('tinitsetrec.ilist:list:records', '2'),
+ ('tinitsetrec.ituple:tuple:int:records', '1'),
+ ('tinitsetrec.ituple:tuple:int:records', '2'),
+
+ ('tdeferfirstsetrec.name:records', 'first'),
+ ('tdeferfirstsetrec.ilist:list:records', '1'),
+ ('tdeferfirstsetrec.ilist:list:records', '<2>'),
+ ('tdeferfirstsetrec.ituple:tuple:int:records', '1'),
+ ('tdeferfirstsetrec.ituple:tuple:int:records', '2'),
+ ('tdeferfirstsetrec.name:records', 'second'),
+ ('tdeferfirstsetrec.ilist:list:records', '1'),
+ ('tdeferfirstsetrec.ilist:list:records', '2'),
+ ('tdeferfirstsetrec.ituple:tuple:int:records', '1'),
+ ('tdeferfirstsetrec.ituple:tuple:int:records', '2'),
+
+ ('tdefersecondsetrec.name:records', 'first'),
+ ('tdefersecondsetrec.ilist:list:records', '1'),
+ ('tdefersecondsetrec.ilist:list:records', '2'),
+ ('tdefersecondsetrec.ituple:tuple:int:records', '1'),
+ ('tdefersecondsetrec.ituple:tuple:int:records', '2'),
+ ('tdefersecondsetrec.name:records', 'second'),
+ ('tdefersecondsetrec.ilist:list:records', '1'),
+ ('tdefersecondsetrec.ilist:list:records', '<2>'),
+ ('tdefersecondsetrec.ituple:tuple:int:records', '1'),
+ ('tdefersecondsetrec.ituple:tuple:int:records', '2'),
+ )
+ req = self._processInputs(inputs)
+ self._noFormValuesInOther(req)
+
+ taintedformkeys = list(req.taintedform.keys())
+ taintedformkeys.sort()
+ self.assertEquals(taintedformkeys, ['tdeferfirstsetrec', 'tdeferonerec',
+ 'tdefersecondsetrec', 'tinitonerec', 'tinitsetrec'])
+
+ self._taintedKeysAlsoInForm(req)
+ self._onlyTaintedformHoldsTaintedStrings(req)
+
+ def testDefaultsWithTaints(self):
+ inputs = (
+ ('tfoo:default', '<5>'),
+
+ ('doesnnotapply:default', '<4>'),
+ ('doesnnotapply', '4'),
+
+ ('tinitlist:default', '3'),
+ ('tinitlist:default', '4'),
+ ('tinitlist:default', '5'),
+ ('tinitlist', '<1>'),
+ ('tinitlist', '2'),
+
+ ('tdeferlist:default', '3'),
+ ('tdeferlist:default', '<4>'),
+ ('tdeferlist:default', '5'),
+ ('tdeferlist', '1'),
+ ('tdeferlist', '2'),
+
+ ('tinitbar.spam:record:default', 'eggs'),
+ ('tinitbar.foo:record:default', 'foo'),
+ ('tinitbar.foo:record', '<baz>'),
+ ('tdeferbar.spam:record:default', '<eggs>'),
+ ('tdeferbar.foo:record:default', 'foo'),
+ ('tdeferbar.foo:record', 'baz'),
+
+ ('rdoesnotapply.spam:record:default', '<eggs>'),
+ ('rdoesnotapply.spam:record', 'eggs'),
+
+ ('tinitsetrec.spam:records:default', 'eggs'),
+ ('tinitsetrec.foo:records:default', 'foo'),
+ ('tinitsetrec.foo:records', '<baz>'),
+ ('tinitsetrec.foo:records', 'ham'),
+
+ ('tdefersetrec.spam:records:default', '<eggs>'),
+ ('tdefersetrec.foo:records:default', 'foo'),
+ ('tdefersetrec.foo:records', 'baz'),
+ ('tdefersetrec.foo:records', 'ham'),
+
+ ('srdoesnotapply.foo:records:default', '<eggs>'),
+ ('srdoesnotapply.foo:records', 'baz'),
+ ('srdoesnotapply.foo:records', 'ham'))
+ req = self._processInputs(inputs)
+ self._noFormValuesInOther(req)
+
+ taintedformkeys = list(req.taintedform.keys())
+ taintedformkeys.sort()
+ self.assertEquals(taintedformkeys, ['tdeferbar', 'tdeferlist',
+ 'tdefersetrec', 'tfoo', 'tinitbar', 'tinitlist', 'tinitsetrec'])
+
+ self._taintedKeysAlsoInForm(req)
+ self._onlyTaintedformHoldsTaintedStrings(req)
+
+ def testTaintedAttributeRaises(self):
+ input = ('taintedattr.here<be<taint:record', 'value',)
+
+ self.assertRaises(ValueError, self._processInputs, input)
+
+ def testNoTaintedExceptions(self):
+ # Feed tainted garbage to the conversion methods, and any exception
+ # returned should be HTML safe
+ from ZPublisher.Converters import type_converters
+ from DateTime import DateTime
+ for type, convert in type_converters.items():
+ try:
+ convert('<html garbage>')
+ except Exception, e:
+ self.failIf('<' in e.args,
+ '%s converter does not quote unsafe value!' % type)
+ except DateTime.SyntaxError, e:
+ self.failIf('<' in e,
+ '%s converter does not quote unsafe value!' % type)
def test_suite():