[Zope-Checkins] CVS: Zope/lib/python/ZPublisher/tests - testTaintedString.py:1.1 testHTTPRequest.py:1.5

Martijn Pieters mj@zope.com
Thu, 1 Aug 2002 12:00:42 -0400


Update of /cvs-repository/Zope/lib/python/ZPublisher/tests
In directory cvs.zope.org:/tmp/cvs-serv9325/lib/python/ZPublisher/tests

Modified Files:
	testHTTPRequest.py 
Added Files:
	testTaintedString.py 
Log Message:
Big change

- 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 unittest

class TestTaintedString(unittest.TestCase):
    def setUp(self):
        self.unquoted = '<test attr="&">'
        self.quoted = '&lt;test attr=&quot;&amp;&quot;&gt;'
        self.tainted = self._getClass()(self.unquoted)

    def _getClass(self):
        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.4 => 1.5 ===
         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 testUnicodeConversions(self):
         inputs = (('ustring:ustring:utf8', 'test\xc2\xae'),
                   ('utext:utext:utf8', 'test\xc2\xae\ntest\xc2\xae\n'),
@@ -88,6 +163,7 @@
                   
                   ('nouconverter:string:utf8', 'test\xc2\xae'))
         req = self._processInputs(inputs)
+        self._noFormValuesInOther(req)
 
         formkeys = list(req.form.keys())
         formkeys.sort()
@@ -99,8 +175,12 @@
         self.assertEquals(req['utokens'], [u'test\u00AE', u'test\u00AE'])
         self.assertEquals(req['ulines'], [u'test\u00AE', u'test\u00AE'])
 
+        # expect a latin1 encoded version
         self.assertEquals(req['nouconverter'], 'test\xae')
 
+        self._noTaintedValues(req)
+        self._onlyTaintedformHoldsTaintedStrings(req)
+
     def testSimpleContainers(self):
         inputs = (
             ('oneitem:list', 'one'),
@@ -111,6 +191,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()
@@ -129,6 +210,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'),
@@ -137,6 +221,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()
@@ -146,6 +231,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'),
@@ -164,6 +252,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()
@@ -182,6 +271,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'), 
@@ -208,6 +300,7 @@
             ('setrec.foo:records', 'ham'),
             )
         req = self._processInputs(inputs)
+        self._noFormValuesInOther(req)
 
         formkeys = list(req.form.keys())
         formkeys.sort()
@@ -226,6 +319,240 @@
         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 testUnicodeWithTaints(self):
+        inputs = (('tustring:ustring:utf8', '<test\xc2\xae>'),
+                  ('tutext:utext:utf8', '<test\xc2\xae>\n<test\xc2\xae\n>'),
+
+                  ('tinitutokens:utokens:utf8', '<test\xc2\xae> test\xc2\xae'),
+                  ('tinitulines:ulines:utf8', '<test\xc2\xae>\ntest\xc2\xae'),
+                  
+                  ('tdeferutokens:utokens:utf8', 'test\xc2\xae <test\xc2\xae>'),
+                  ('tdeferulines:ulines:utf8', 'test\xc2\xae\n<test\xc2\xae>'),
+                  
+                  ('tnouconverter:string:utf8', '<test\xc2\xae>'))
+        req = self._processInputs(inputs)
+        self._noFormValuesInOther(req)
+
+        taintedformkeys = list(req.taintedform.keys())
+        taintedformkeys.sort()
+        self.assertEquals(taintedformkeys, ['tdeferulines', 'tdeferutokens',
+            'tinitulines', 'tinitutokens', 'tnouconverter', 'tustring',
+            'tutext'])
+
+        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():