• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / Zope / 3956162881

pending completion
3956162881

push

github

Michael Howitz
Update to deprecation warning free releases.

4401 of 7036 branches covered (62.55%)

Branch coverage included in aggregate %.

27161 of 31488 relevant lines covered (86.26%)

0.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.04
/src/ZPublisher/tests/testHTTPRequest.py
1
##############################################################################
2
#
3
# Copyright (c) 2002 Zope Foundation and Contributors.
4
#
5
# This software is subject to the provisions of the Zope Public License,
6
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
7
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10
# FOR A PARTICULAR PURPOSE.
11
#
12
##############################################################################
13

14
import sys
1✔
15
import unittest
1✔
16
import warnings
1✔
17
from contextlib import contextmanager
1✔
18
from io import BytesIO
1✔
19
from unittest.mock import patch
1✔
20

21
from AccessControl.tainted import should_be_tainted
1✔
22
from zExceptions import NotFound
1✔
23
from zope.component import getGlobalSiteManager
1✔
24
from zope.component import provideAdapter
1✔
25
from zope.i18n.interfaces import IUserPreferredLanguages
1✔
26
from zope.i18n.interfaces.locales import ILocale
1✔
27
from zope.publisher.browser import BrowserLanguages
1✔
28
from zope.publisher.interfaces.http import IHTTPRequest
1✔
29
from zope.testing.cleanup import cleanUp
1✔
30
from ZPublisher.HTTPRequest import BadRequest
1✔
31
from ZPublisher.HTTPRequest import search_type
1✔
32
from ZPublisher.interfaces import IXmlrpcChecker
1✔
33
from ZPublisher.tests.testBaseRequest import TestRequestViewsBase
1✔
34
from ZPublisher.utils import basic_auth_encode
1✔
35
from ZPublisher.xmlrpc import is_xmlrpc_response
1✔
36

37

38
class RecordTests(unittest.TestCase):
1✔
39

40
    def _makeOne(self):
1✔
41
        from ZPublisher.HTTPRequest import record
1✔
42
        return record()
1✔
43

44
    def test_dict_methods(self):
1✔
45
        rec = self._makeOne()
1✔
46
        rec.a = 1
1✔
47
        self.assertEqual(rec['a'], 1)
1✔
48
        self.assertEqual(rec.get('a'), 1)
1✔
49
        self.assertEqual(list(rec.keys()), ['a'])
1✔
50
        self.assertEqual(list(rec.values()), [1])
1✔
51
        self.assertEqual(list(rec.items()), [('a', 1)])
1✔
52

53
    def test_dict_special_methods(self):
1✔
54
        rec = self._makeOne()
1✔
55
        rec.a = 1
1✔
56
        self.assertTrue('a' in rec)
1✔
57
        self.assertFalse('b' in rec)
1✔
58
        self.assertEqual(len(rec), 1)
1✔
59
        self.assertEqual(list(iter(rec)), ['a'])
1✔
60

61
    def test_copy(self):
1✔
62
        rec = self._makeOne()
1✔
63
        rec.a = 1
1✔
64
        rec.b = 'foo'
1✔
65
        new_rec = rec.copy()
1✔
66
        self.assertIsInstance(new_rec, dict)
1✔
67
        self.assertEqual(new_rec, {'a': 1, 'b': 'foo'})
1✔
68

69
    def test_eq(self):
1✔
70
        rec1 = self._makeOne()
1✔
71
        self.assertFalse(rec1, {})
1✔
72
        rec2 = self._makeOne()
1✔
73
        self.assertEqual(rec1, rec2)
1✔
74
        rec1.a = 1
1✔
75
        self.assertNotEqual(rec1, rec2)
1✔
76
        rec2.a = 1
1✔
77
        self.assertEqual(rec1, rec2)
1✔
78
        rec2.b = 'foo'
1✔
79
        self.assertNotEqual(rec1, rec2)
1✔
80

81
    def test__str__returns_native_string(self):
1✔
82
        rec = self._makeOne()
1✔
83
        rec.a = b'foo'
1✔
84
        rec.b = 8
1✔
85
        rec.c = 'bar'
1✔
86
        self.assertIsInstance(str(rec), str)
1✔
87

88
    def test_str(self):
1✔
89
        rec = self._makeOne()
1✔
90
        rec.a = 1
1✔
91
        self.assertEqual(str(rec), 'a: 1')
1✔
92

93
    def test_repr(self):
1✔
94
        rec = self._makeOne()
1✔
95
        rec.a = 1
1✔
96
        rec.b = 'foo'
1✔
97
        r = repr(rec)
1✔
98
        d = eval(r)
1✔
99
        self.assertEqual(d, rec.__dict__)
1✔
100

101

102
class HTTPRequestFactoryMixin:
1✔
103

104
    def tearDown(self):
1✔
105
        cleanUp()
×
106

107
    def _getTargetClass(self):
1✔
108
        from ZPublisher.HTTPRequest import HTTPRequest
1✔
109
        return HTTPRequest
1✔
110

111
    def _makePostEnviron(self, body=b'', multipart=True):
1✔
112
        environ = TEST_POST_ENVIRON.copy()
1✔
113
        environ["CONTENT_TYPE"] = \
1✔
114
            multipart and 'multipart/form-data; boundary=12345' \
115
            or 'application/x-www-form-urlencoded'
116
        environ['CONTENT_LENGTH'] = str(len(body))
1✔
117
        return environ
1✔
118

119
    def _makeOne(self, stdin=None, environ=None, response=None, clean=1):
1✔
120
        from ZPublisher.HTTPResponse import HTTPResponse
1✔
121
        if stdin is None:
1✔
122
            stdin = BytesIO()
1✔
123

124
        if environ is None:
1✔
125
            environ = {}
1✔
126

127
        if 'REQUEST_METHOD' not in environ:
1✔
128
            environ['REQUEST_METHOD'] = 'GET'
1✔
129

130
        if 'SERVER_NAME' not in environ:
1✔
131
            environ['SERVER_NAME'] = 'localhost'
1✔
132

133
        if 'SERVER_PORT' not in environ:
1✔
134
            environ['SERVER_PORT'] = '8080'
1✔
135

136
        if response is None:
1✔
137
            response = HTTPResponse(stdout=BytesIO())
1✔
138

139
        return self._getTargetClass()(stdin, environ, response, clean)
1✔
140

141

142
class HTTPRequestTests(unittest.TestCase, HTTPRequestFactoryMixin):
1✔
143

144
    def _processInputs(self, inputs):
1✔
145
        from urllib.parse import quote_plus
1✔
146

147
        # Have the inputs processed, and return a HTTPRequest object
148
        # holding the result.
149
        # inputs is expected to be a list of (key, value) tuples, no CGI
150
        # encoding is required.
151

152
        query_string = []
1✔
153
        add = query_string.append
1✔
154
        for key, val in inputs:
1✔
155
            add(f"{quote_plus(key)}={quote_plus(val)}")
1✔
156
        query_string = '&'.join(query_string)
1✔
157

158
        env = {'SERVER_NAME': 'testingharnas', 'SERVER_PORT': '80'}
1✔
159
        env['QUERY_STRING'] = query_string
1✔
160
        req = self._makeOne(environ=env)
1✔
161
        req.processInputs()
1✔
162
        self._noFormValuesInOther(req)
1✔
163
        return req
1✔
164

165
    def _noTaintedValues(self, req):
1✔
166
        self.assertFalse(list(req.taintedform.keys()))
1✔
167

168
    def _valueIsOrHoldsTainted(self, val):
1✔
169
        # Recursively searches a structure for a TaintedString and returns 1
170
        # when one is found.
171
        # Also raises an Assertion if a string which *should* have been
172
        # tainted is found, or when a tainted string is not deemed dangerous.
173
        from AccessControl.tainted import TaintedString
1✔
174
        from ZPublisher.HTTPRequest import record
1✔
175

176
        retval = 0
1✔
177

178
        if isinstance(val, TaintedString):
1✔
179
            self.assertTrue(
1✔
180
                should_be_tainted(val._value),
181
                "%r is not dangerous, no taint required." % val)
182
            retval = 1
1✔
183

184
        elif isinstance(val, record):
1✔
185
            for attr, value in list(val.__dict__.items()):
1✔
186
                rval = self._valueIsOrHoldsTainted(attr)
1✔
187
                if rval:
1!
188
                    retval = 1
×
189
                rval = self._valueIsOrHoldsTainted(value)
1✔
190
                if rval:
1✔
191
                    retval = 1
1✔
192

193
        elif type(val) in (list, tuple):
1✔
194
            for entry in val:
1✔
195
                rval = self._valueIsOrHoldsTainted(entry)
1✔
196
                if rval:
1✔
197
                    retval = 1
1✔
198

199
        elif isinstance(val, str):
1✔
200
            self.assertFalse(
1✔
201
                should_be_tainted(val),
202
                "'%s' is dangerous and should have been tainted." % val)
203

204
        return retval
1✔
205

206
    def _noFormValuesInOther(self, req):
1✔
207
        for key in list(req.taintedform.keys()):
1✔
208
            self.assertFalse(
1✔
209
                key in req.other,
210
                'REQUEST.other should not hold tainted values at first!')
211

212
        for key in list(req.form.keys()):
1✔
213
            self.assertFalse(
1✔
214
                key in req.other,
215
                'REQUEST.other should not hold form values at first!')
216

217
    def _onlyTaintedformHoldsTaintedStrings(self, req):
1✔
218
        for key, val in list(req.taintedform.items()):
1✔
219
            self.assertTrue(
1✔
220
                self._valueIsOrHoldsTainted(key)
221
                or self._valueIsOrHoldsTainted(val),
222
                'Tainted form holds item %s that is not tainted' % key)
223

224
        for key, val in list(req.form.items()):
1✔
225
            if key in req.taintedform:
1✔
226
                continue
1✔
227
            self.assertFalse(
1✔
228
                self._valueIsOrHoldsTainted(key)
229
                or self._valueIsOrHoldsTainted(val),
230
                'Normal form holds item %s that is tainted' % key)
231

232
    def _taintedKeysAlsoInForm(self, req):
1✔
233
        for key in list(req.taintedform.keys()):
1✔
234
            self.assertTrue(
1✔
235
                key in req.form,
236
                "Found tainted %s not in form" % key)
237
            self.assertEqual(
1✔
238
                req.form[key], req.taintedform[key],
239
                "Key %s not correctly reproduced in tainted; expected %r, "
240
                "got %r" % (key, req.form[key], req.taintedform[key]))
241

242
    def test_webdav_source_port_available(self):
1✔
243
        req = self._makeOne()
1✔
244
        self.assertFalse(req.get('WEBDAV_SOURCE_PORT'))
1✔
245

246
        req = self._makeOne(environ={'WEBDAV_SOURCE_PORT': 1})
1✔
247
        self.assertTrue(req.get('WEBDAV_SOURCE_PORT'))
1✔
248

249
    def test_no_docstring_on_instance(self):
1✔
250
        env = {'SERVER_NAME': 'testingharnas', 'SERVER_PORT': '80'}
1✔
251
        req = self._makeOne(environ=env)
1✔
252
        self.assertTrue(req.__doc__ is None)
1✔
253

254
    def test___bobo_traverse___raises(self):
1✔
255
        env = {'SERVER_NAME': 'testingharnas', 'SERVER_PORT': '80'}
1✔
256
        req = self._makeOne(environ=env)
1✔
257
        self.assertRaises(KeyError, req.__bobo_traverse__, 'REQUEST')
1✔
258
        self.assertRaises(KeyError, req.__bobo_traverse__, 'BODY')
1✔
259
        self.assertRaises(KeyError, req.__bobo_traverse__, 'BODYFILE')
1✔
260
        self.assertRaises(KeyError, req.__bobo_traverse__, 'RESPONSE')
1✔
261

262
    def test_processInputs_wo_query_string(self):
1✔
263
        env = {'SERVER_NAME': 'testingharnas', 'SERVER_PORT': '80'}
1✔
264
        req = self._makeOne(environ=env)
1✔
265
        req.processInputs()
1✔
266
        self._noFormValuesInOther(req)
1✔
267
        self.assertEqual(req.form, {})
1✔
268

269
    def test_processInputs_wo_marshalling(self):
1✔
270
        inputs = (
1✔
271
            ('foo', 'bar'), ('spam', 'eggs'),
272
            ('number', '1'),
273
            ('spacey key', 'val'), ('key', 'spacey val'),
274
            ('multi', '1'), ('multi', '2'))
275
        req = self._processInputs(inputs)
1✔
276

277
        formkeys = list(req.form.keys())
1✔
278
        formkeys.sort()
1✔
279
        self.assertEqual(
1✔
280
            formkeys,
281
            ['foo', 'key', 'multi', 'number', 'spacey key', 'spam'])
282
        self.assertEqual(req['number'], '1')
1✔
283
        self.assertEqual(req['multi'], ['1', '2'])
1✔
284
        self.assertEqual(req['spacey key'], 'val')
1✔
285
        self.assertEqual(req['key'], 'spacey val')
1✔
286

287
        self._noTaintedValues(req)
1✔
288
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
289

290
    def test_processInputs_w_simple_marshalling(self):
1✔
291
        from DateTime.DateTime import DateTime
1✔
292
        inputs = (
1✔
293
            ('num:int', '42'), ('fract:float', '4.2'), ('bign:long', '45'),
294
            ('words:string', 'Some words'), ('2tokens:tokens', 'one two'),
295
            ('aday:date', '2002/07/23'),
296
            ('accountedfor:required', 'yes'),
297
            ('multiline:lines', 'one\ntwo'),
298
            ('morewords:text', 'one\ntwo\n'))
299
        req = self._processInputs(inputs)
1✔
300

301
        formkeys = list(req.form.keys())
1✔
302
        formkeys.sort()
1✔
303
        self.assertEqual(
1✔
304
            formkeys,
305
            ['2tokens', 'accountedfor', 'aday', 'bign',
306
             'fract', 'morewords', 'multiline', 'num', 'words'])
307

308
        self.assertEqual(req['2tokens'], ['one', 'two'])
1✔
309
        self.assertEqual(req['accountedfor'], 'yes')
1✔
310
        self.assertEqual(req['aday'], DateTime('2002/07/23'))
1✔
311
        self.assertEqual(req['bign'], 45)
1✔
312
        self.assertEqual(req['fract'], 4.2)
1✔
313
        self.assertEqual(req['morewords'], 'one\ntwo\n')
1✔
314
        self.assertEqual(req['multiline'], ['one', 'two'])
1✔
315
        self.assertEqual(req['num'], 42)
1✔
316
        self.assertEqual(req['words'], 'Some words')
1✔
317

318
        self._noTaintedValues(req)
1✔
319
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
320

321
    def test_processInputs_w_unicode_conversions(self):
1✔
322
        # This tests native strings.
323
        reg_char = '\xae'
1✔
324
        inputs = (('ustring:ustring:utf8', 'test' + reg_char),
1✔
325
                  ('utext:utext:utf8',
326
                   'test' + reg_char + '\ntest' + reg_char + '\n'),
327
                  ('utokens:utokens:utf8',
328
                   'test' + reg_char + ' test' + reg_char),
329
                  ('ulines:ulines:utf8',
330
                   'test' + reg_char + '\ntest' + reg_char),
331
                  ('nouconverter:string:utf8', 'test' + reg_char))
332
        # unicode converters will go away with Zope 6
333
        # ignore deprecation warning for test run
334
        with warnings.catch_warnings():
1✔
335
            warnings.simplefilter('ignore')
1✔
336
            req = self._processInputs(inputs)
1✔
337

338
        formkeys = list(req.form.keys())
1✔
339
        formkeys.sort()
1✔
340
        self.assertEqual(
1✔
341
            formkeys,
342
            ['nouconverter', 'ulines', 'ustring', 'utext', 'utokens'])
343

344
        self.assertEqual(req['ustring'], 'test\u00AE')
1✔
345
        self.assertEqual(req['utext'], 'test\u00AE\ntest\u00AE\n')
1✔
346
        self.assertEqual(req['utokens'], ['test\u00AE', 'test\u00AE'])
1✔
347
        self.assertEqual(req['ulines'], ['test\u00AE', 'test\u00AE'])
1✔
348

349
        # expect a utf-8 encoded version
350
        self.assertEqual(req['nouconverter'], 'test' + reg_char)
1✔
351

352
        self._noTaintedValues(req)
1✔
353
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
354

355
    def test_processInputs_w_simple_containers(self):
1✔
356
        inputs = (
1✔
357
            ('oneitem:list', 'one'),
358
            ('alist:list', 'one'), ('alist:list', 'two'),
359
            ('oneitemtuple:tuple', 'one'),
360
            ('atuple:tuple', 'one'), ('atuple:tuple', 'two'),
361
            ('onerec.foo:record', 'foo'), ('onerec.bar:record', 'bar'),
362
            ('setrec.foo:records', 'foo'), ('setrec.bar:records', 'bar'),
363
            ('setrec.foo:records', 'spam'), ('setrec.bar:records', 'eggs'))
364
        req = self._processInputs(inputs)
1✔
365

366
        formkeys = list(req.form.keys())
1✔
367
        formkeys.sort()
1✔
368
        self.assertEqual(
1✔
369
            formkeys,
370
            ['alist', 'atuple', 'oneitem', 'oneitemtuple', 'onerec', 'setrec'])
371

372
        self.assertEqual(req['oneitem'], ['one'])
1✔
373
        self.assertEqual(req['oneitemtuple'], ('one',))
1✔
374
        self.assertEqual(req['alist'], ['one', 'two'])
1✔
375
        self.assertEqual(req['atuple'], ('one', 'two'))
1✔
376
        self.assertEqual(req['onerec'].foo, 'foo')
1✔
377
        self.assertEqual(req['onerec'].bar, 'bar')
1✔
378
        self.assertEqual(len(req['setrec']), 2)
1✔
379
        self.assertEqual(req['setrec'][0].foo, 'foo')
1✔
380
        self.assertEqual(req['setrec'][0].bar, 'bar')
1✔
381
        self.assertEqual(req['setrec'][1].foo, 'spam')
1✔
382
        self.assertEqual(req['setrec'][1].bar, 'eggs')
1✔
383

384
        self._noTaintedValues(req)
1✔
385
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
386

387
    def test_processInputs_w_marshalling_into_sequences(self):
1✔
388
        inputs = (
1✔
389
            ('ilist:int:list', '1'), ('ilist:int:list', '2'),
390
            ('ilist:list:int', '3'),
391
            ('ftuple:float:tuple', '1.0'), ('ftuple:float:tuple', '1.1'),
392
            ('ftuple:tuple:float', '1.2'),
393
            ('tlist:tokens:list', 'one two'), ('tlist:list:tokens', '3 4'))
394
        req = self._processInputs(inputs)
1✔
395

396
        formkeys = list(req.form.keys())
1✔
397
        formkeys.sort()
1✔
398
        self.assertEqual(formkeys, ['ftuple', 'ilist', 'tlist'])
1✔
399

400
        self.assertEqual(req['ilist'], [1, 2, 3])
1✔
401
        self.assertEqual(req['ftuple'], (1.0, 1.1, 1.2))
1✔
402
        self.assertEqual(req['tlist'], [['one', 'two'], ['3', '4']])
1✔
403

404
        self._noTaintedValues(req)
1✔
405
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
406

407
    def test_processInputs_w_records_w_sequences(self):
1✔
408
        inputs = (
1✔
409
            ('onerec.name:record', 'foo'),
410
            ('onerec.tokens:tokens:record', 'one two'),
411
            ('onerec.ints:int:record', '1'),
412
            ('onerec.ints:int:record', '2'),
413

414
            ('setrec.name:records', 'first'),
415
            ('setrec.ilist:list:int:records', '1'),
416
            ('setrec.ilist:list:int:records', '2'),
417
            ('setrec.ituple:tuple:int:records', '1'),
418
            ('setrec.ituple:tuple:int:records', '2'),
419
            ('setrec.name:records', 'second'),
420
            ('setrec.ilist:list:int:records', '1'),
421
            ('setrec.ilist:list:int:records', '2'),
422
            ('setrec.ituple:tuple:int:records', '1'),
423
            ('setrec.ituple:tuple:int:records', '2'))
424
        req = self._processInputs(inputs)
1✔
425

426
        formkeys = list(req.form.keys())
1✔
427
        formkeys.sort()
1✔
428
        self.assertEqual(formkeys, ['onerec', 'setrec'])
1✔
429

430
        self.assertEqual(req['onerec'].name, 'foo')
1✔
431
        self.assertEqual(req['onerec'].tokens, ['one', 'two'])
1✔
432
        # Implicit sequences and records don't mix.
433
        self.assertEqual(req['onerec'].ints, 2)
1✔
434

435
        self.assertEqual(len(req['setrec']), 2)
1✔
436
        self.assertEqual(req['setrec'][0].name, 'first')
1✔
437
        self.assertEqual(req['setrec'][1].name, 'second')
1✔
438

439
        for i in range(2):
1✔
440
            self.assertEqual(req['setrec'][i].ilist, [1, 2])
1✔
441
            self.assertEqual(req['setrec'][i].ituple, (1, 2))
1✔
442

443
        self._noTaintedValues(req)
1✔
444
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
445

446
    def test_processInputs_w_defaults(self):
1✔
447
        inputs = (
1✔
448
            ('foo:default:int', '5'),
449

450
            ('alist:int:default', '3'),
451
            ('alist:int:default', '4'),
452
            ('alist:int:default', '5'),
453
            ('alist:int', '1'),
454
            ('alist:int', '2'),
455

456
            ('explicitlist:int:list:default', '3'),
457
            ('explicitlist:int:list:default', '4'),
458
            ('explicitlist:int:list:default', '5'),
459
            ('explicitlist:int:list', '1'),
460
            ('explicitlist:int:list', '2'),
461

462
            ('bar.spam:record:default', 'eggs'),
463
            ('bar.foo:record:default', 'foo'),
464
            ('bar.foo:record', 'baz'),
465

466
            ('setrec.spam:records:default', 'eggs'),
467
            ('setrec.foo:records:default', 'foo'),
468
            ('setrec.foo:records', 'baz'),
469
            ('setrec.foo:records', 'ham'),
470
        )
471
        req = self._processInputs(inputs)
1✔
472

473
        formkeys = list(req.form.keys())
1✔
474
        formkeys.sort()
1✔
475
        self.assertEqual(
1✔
476
            formkeys, ['alist', 'bar', 'explicitlist', 'foo', 'setrec'])
477

478
        self.assertEqual(req['alist'], [1, 2, 3, 4, 5])
1✔
479
        self.assertEqual(req['explicitlist'], [1, 2, 3, 4, 5])
1✔
480

481
        self.assertEqual(req['foo'], 5)
1✔
482
        self.assertEqual(req['bar'].spam, 'eggs')
1✔
483
        self.assertEqual(req['bar'].foo, 'baz')
1✔
484

485
        self.assertEqual(len(req['setrec']), 2)
1✔
486
        self.assertEqual(req['setrec'][0].spam, 'eggs')
1✔
487
        self.assertEqual(req['setrec'][0].foo, 'baz')
1✔
488
        self.assertEqual(req['setrec'][1].spam, 'eggs')
1✔
489
        self.assertEqual(req['setrec'][1].foo, 'ham')
1✔
490

491
        self._noTaintedValues(req)
1✔
492
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
493

494
    def test_processInputs_wo_marshalling_w_Taints(self):
1✔
495
        inputs = (
1✔
496
            ('foo', 'bar'), ('spam', 'eggs'),
497
            ('number', '1'),
498
            ('tainted', '<tainted value>'),
499
            ('<tainted key>', 'value'),
500
            ('spacey key', 'val'), ('key', 'spacey val'),
501
            ('tinitmulti', '<1>'), ('tinitmulti', '2'),
502
            ('tdefermulti', '1'), ('tdefermulti', '<2>'),
503
            ('tallmulti', '<1>'), ('tallmulti', '<2>'))
504
        req = self._processInputs(inputs)
1✔
505

506
        taintedformkeys = list(req.taintedform.keys())
1✔
507
        taintedformkeys.sort()
1✔
508
        self.assertEqual(
1✔
509
            taintedformkeys,
510
            ['<tainted key>', 'tainted',
511
             'tallmulti', 'tdefermulti', 'tinitmulti'])
512

513
        self._taintedKeysAlsoInForm(req)
1✔
514
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
515

516
    def test_processInputs_w_simple_marshalling_w_taints(self):
1✔
517
        inputs = (
1✔
518
            ('<tnum>:int', '42'), ('<tfract>:float', '4.2'),
519
            ('<tbign>:long', '45'),
520
            ('twords:string', 'Some <words>'),
521
            ('t2tokens:tokens', 'one <two>'),
522
            ('<taday>:date', '2002/07/23'),
523
            ('taccountedfor:required', '<yes>'),
524
            ('tmultiline:lines', '<one\ntwo>'),
525
            ('tmorewords:text', '<one\ntwo>\n'))
526
        req = self._processInputs(inputs)
1✔
527

528
        taintedformkeys = list(req.taintedform.keys())
1✔
529
        taintedformkeys.sort()
1✔
530
        self.assertEqual(
1✔
531
            taintedformkeys,
532
            ['<taday>', '<tbign>', '<tfract>',
533
             '<tnum>', 't2tokens', 'taccountedfor', 'tmorewords', 'tmultiline',
534
             'twords'])
535

536
        self._taintedKeysAlsoInForm(req)
1✔
537
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
538

539
    def test_processInputs_w_unicode_w_taints(self):
1✔
540
        inputs = (
1✔
541
            ('tustring:ustring:utf8', '<test\xc2\xae>'),
542
            ('tutext:utext:utf8', '<test\xc2\xae>\n<test\xc2\xae\n>'),
543

544
            ('tinitutokens:utokens:utf8', '<test\xc2\xae> test\xc2\xae'),
545
            ('tinitulines:ulines:utf8', '<test\xc2\xae>\ntest\xc2\xae'),
546

547
            ('tdeferutokens:utokens:utf8', 'test\xc2\xae <test\xc2\xae>'),
548
            ('tdeferulines:ulines:utf8', 'test\xc2\xae\n<test\xc2\xae>'),
549

550
            ('tnouconverter:string:utf8', '<test\xc2\xae>'),
551
        )
552

553
        # unicode converters will go away with Zope 6
554
        # ignore deprecation warning for test run
555
        with warnings.catch_warnings():
1✔
556
            warnings.simplefilter('ignore')
1✔
557
            req = self._processInputs(inputs)
1✔
558

559
        taintedformkeys = list(req.taintedform.keys())
1✔
560
        taintedformkeys.sort()
1✔
561
        self.assertEqual(
1✔
562
            taintedformkeys,
563
            ['tdeferulines', 'tdeferutokens',
564
             'tinitulines', 'tinitutokens', 'tnouconverter', 'tustring',
565
             'tutext'])
566

567
        self._taintedKeysAlsoInForm(req)
1✔
568
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
569

570
    def test_processInputs_w_simple_containers_w_taints(self):
1✔
571
        inputs = (
1✔
572
            ('toneitem:list', '<one>'),
573
            ('<tkeyoneitem>:list', 'one'),
574
            ('tinitalist:list', '<one>'), ('tinitalist:list', 'two'),
575
            ('tdeferalist:list', 'one'), ('tdeferalist:list', '<two>'),
576

577
            ('toneitemtuple:tuple', '<one>'),
578
            ('tinitatuple:tuple', '<one>'), ('tinitatuple:tuple', 'two'),
579
            ('tdeferatuple:tuple', 'one'), ('tdeferatuple:tuple', '<two>'),
580

581
            ('tinitonerec.foo:record', '<foo>'),
582
            ('tinitonerec.bar:record', 'bar'),
583
            ('tdeferonerec.foo:record', 'foo'),
584
            ('tdeferonerec.bar:record', '<bar>'),
585

586
            ('tinitinitsetrec.foo:records', '<foo>'),
587
            ('tinitinitsetrec.bar:records', 'bar'),
588
            ('tinitinitsetrec.foo:records', 'spam'),
589
            ('tinitinitsetrec.bar:records', 'eggs'),
590

591
            ('tinitdefersetrec.foo:records', 'foo'),
592
            ('tinitdefersetrec.bar:records', '<bar>'),
593
            ('tinitdefersetrec.foo:records', 'spam'),
594
            ('tinitdefersetrec.bar:records', 'eggs'),
595

596
            ('tdeferinitsetrec.foo:records', 'foo'),
597
            ('tdeferinitsetrec.bar:records', 'bar'),
598
            ('tdeferinitsetrec.foo:records', '<spam>'),
599
            ('tdeferinitsetrec.bar:records', 'eggs'),
600

601
            ('tdeferdefersetrec.foo:records', 'foo'),
602
            ('tdeferdefersetrec.bar:records', 'bar'),
603
            ('tdeferdefersetrec.foo:records', 'spam'),
604
            ('tdeferdefersetrec.bar:records', '<eggs>'))
605
        req = self._processInputs(inputs)
1✔
606

607
        taintedformkeys = list(req.taintedform.keys())
1✔
608
        taintedformkeys.sort()
1✔
609
        self.assertEqual(
1✔
610
            taintedformkeys,
611
            ['<tkeyoneitem>', 'tdeferalist',
612
             'tdeferatuple', 'tdeferdefersetrec', 'tdeferinitsetrec',
613
             'tdeferonerec', 'tinitalist', 'tinitatuple', 'tinitdefersetrec',
614
             'tinitinitsetrec', 'tinitonerec', 'toneitem', 'toneitemtuple'])
615

616
        self._taintedKeysAlsoInForm(req)
1✔
617
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
618

619
    def test_processInputs_w_records_w_sequences_tainted(self):
1✔
620
        inputs = (
1✔
621
            ('tinitonerec.tokens:tokens:record', '<one> two'),
622
            ('tdeferonerec.tokens:tokens:record', 'one <two>'),
623

624
            ('tinitsetrec.name:records', 'first'),
625
            ('tinitsetrec.ilist:list:records', '<1>'),
626
            ('tinitsetrec.ilist:list:records', '2'),
627
            ('tinitsetrec.ituple:tuple:int:records', '1'),
628
            ('tinitsetrec.ituple:tuple:int:records', '2'),
629
            ('tinitsetrec.name:records', 'second'),
630
            ('tinitsetrec.ilist:list:records', '1'),
631
            ('tinitsetrec.ilist:list:records', '2'),
632
            ('tinitsetrec.ituple:tuple:int:records', '1'),
633
            ('tinitsetrec.ituple:tuple:int:records', '2'),
634

635
            ('tdeferfirstsetrec.name:records', 'first'),
636
            ('tdeferfirstsetrec.ilist:list:records', '1'),
637
            ('tdeferfirstsetrec.ilist:list:records', '<2>'),
638
            ('tdeferfirstsetrec.ituple:tuple:int:records', '1'),
639
            ('tdeferfirstsetrec.ituple:tuple:int:records', '2'),
640
            ('tdeferfirstsetrec.name:records', 'second'),
641
            ('tdeferfirstsetrec.ilist:list:records', '1'),
642
            ('tdeferfirstsetrec.ilist:list:records', '2'),
643
            ('tdeferfirstsetrec.ituple:tuple:int:records', '1'),
644
            ('tdeferfirstsetrec.ituple:tuple:int:records', '2'),
645

646
            ('tdefersecondsetrec.name:records', 'first'),
647
            ('tdefersecondsetrec.ilist:list:records', '1'),
648
            ('tdefersecondsetrec.ilist:list:records', '2'),
649
            ('tdefersecondsetrec.ituple:tuple:int:records', '1'),
650
            ('tdefersecondsetrec.ituple:tuple:int:records', '2'),
651
            ('tdefersecondsetrec.name:records', 'second'),
652
            ('tdefersecondsetrec.ilist:list:records', '1'),
653
            ('tdefersecondsetrec.ilist:list:records', '<2>'),
654
            ('tdefersecondsetrec.ituple:tuple:int:records', '1'),
655
            ('tdefersecondsetrec.ituple:tuple:int:records', '2'),
656
        )
657
        req = self._processInputs(inputs)
1✔
658

659
        taintedformkeys = list(req.taintedform.keys())
1✔
660
        taintedformkeys.sort()
1✔
661
        self.assertEqual(
1✔
662
            taintedformkeys,
663
            ['tdeferfirstsetrec', 'tdeferonerec',
664
             'tdefersecondsetrec', 'tinitonerec', 'tinitsetrec'])
665

666
        self._taintedKeysAlsoInForm(req)
1✔
667
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
668

669
    def test_processInputs_w_defaults_w_taints(self):
1✔
670
        inputs = (
1✔
671
            ('tfoo:default', '<5>'),
672

673
            ('doesnnotapply:default', '<4>'),
674
            ('doesnnotapply', '4'),
675

676
            ('tinitlist:default', '3'),
677
            ('tinitlist:default', '4'),
678
            ('tinitlist:default', '5'),
679
            ('tinitlist', '<1>'),
680
            ('tinitlist', '2'),
681

682
            ('tdeferlist:default', '3'),
683
            ('tdeferlist:default', '<4>'),
684
            ('tdeferlist:default', '5'),
685
            ('tdeferlist', '1'),
686
            ('tdeferlist', '2'),
687

688
            ('tinitbar.spam:record:default', 'eggs'),
689
            ('tinitbar.foo:record:default', 'foo'),
690
            ('tinitbar.foo:record', '<baz>'),
691
            ('tdeferbar.spam:record:default', '<eggs>'),
692
            ('tdeferbar.foo:record:default', 'foo'),
693
            ('tdeferbar.foo:record', 'baz'),
694

695
            ('rdoesnotapply.spam:record:default', '<eggs>'),
696
            ('rdoesnotapply.spam:record', 'eggs'),
697

698
            ('tinitsetrec.spam:records:default', 'eggs'),
699
            ('tinitsetrec.foo:records:default', 'foo'),
700
            ('tinitsetrec.foo:records', '<baz>'),
701
            ('tinitsetrec.foo:records', 'ham'),
702

703
            ('tdefersetrec.spam:records:default', '<eggs>'),
704
            ('tdefersetrec.foo:records:default', 'foo'),
705
            ('tdefersetrec.foo:records', 'baz'),
706
            ('tdefersetrec.foo:records', 'ham'),
707

708
            ('srdoesnotapply.foo:records:default', '<eggs>'),
709
            ('srdoesnotapply.foo:records', 'baz'),
710
            ('srdoesnotapply.foo:records', 'ham'))
711
        req = self._processInputs(inputs)
1✔
712

713
        taintedformkeys = list(req.taintedform.keys())
1✔
714
        taintedformkeys.sort()
1✔
715
        self.assertEqual(
1✔
716
            taintedformkeys,
717
            ['tdeferbar', 'tdeferlist',
718
             'tdefersetrec', 'tfoo', 'tinitbar', 'tinitlist', 'tinitsetrec'])
719

720
        self._taintedKeysAlsoInForm(req)
1✔
721
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
722

723
    def test_processInputs_w_tainted_attribute_raises(self):
1✔
724
        input = ('taintedattr.here<be<taint:record', 'value',)
1✔
725

726
        self.assertRaises(ValueError, self._processInputs, input)
1✔
727

728
    def test_processInputs_w_tainted_values_cleans_exceptions(self):
1✔
729
        # Feed tainted garbage to the conversion methods, and any exception
730
        # returned should be HTML safe
731
        from DateTime.interfaces import SyntaxError
1✔
732
        from ZPublisher.Converters import type_converters
1✔
733
        for type, convert in list(type_converters.items()):
1✔
734
            try:
1✔
735
                # unicode converters will go away with Zope 6
736
                # ignore deprecation warning for test run
737
                with warnings.catch_warnings():
1✔
738
                    warnings.simplefilter('ignore')
1✔
739
                    convert('<html garbage>')
1✔
740
            except Exception as e:
1!
741
                self.assertFalse(
1✔
742
                    '<' in e.args,
743
                    '%s converter does not quote unsafe value!' % type)
744
            except SyntaxError as e:
×
745
                self.assertFalse(
×
746
                    '<' in e,
747
                    '%s converter does not quote unsafe value!' % type)
748

749
    def test_processInputs_w_dotted_name_as_tuple(self):
1✔
750
        # Collector #500
751
        inputs = (
1✔
752
            ('name.:tuple', 'name with dot as tuple'),)
753
        req = self._processInputs(inputs)
1✔
754

755
        formkeys = list(req.form.keys())
1✔
756
        formkeys.sort()
1✔
757
        self.assertEqual(formkeys, ['name.'])
1✔
758

759
        self.assertEqual(req['name.'], ('name with dot as tuple',))
1✔
760

761
        self._noTaintedValues(req)
1✔
762
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
763

764
    def test_processInputs_w_cookie_parsing(self):
1✔
765
        env = {'SERVER_NAME': 'testingharnas', 'SERVER_PORT': '80'}
1✔
766

767
        env['HTTP_COOKIE'] = 'foo=bar; baz=gee'
1✔
768
        req = self._makeOne(environ=env)
1✔
769
        self.assertEqual(req.cookies['foo'], 'bar')
1✔
770
        self.assertEqual(req.cookies['baz'], 'gee')
1✔
771

772
        env['HTTP_COOKIE'] = 'foo=bar; baz="gee, like, e=mc^2"'
1✔
773
        req = self._makeOne(environ=env)
1✔
774
        self.assertEqual(req.cookies['foo'], 'bar')
1✔
775
        self.assertEqual(req.cookies['baz'], 'gee, like, e=mc^2')
1✔
776

777
        # Collector #1498: empty cookies
778
        env['HTTP_COOKIE'] = 'foo=bar; hmm; baz=gee'
1✔
779
        req = self._makeOne(environ=env)
1✔
780
        self.assertEqual(req.cookies['foo'], 'bar')
1✔
781
        self.assertEqual(req.cookies['hmm'], '')
1✔
782
        self.assertEqual(req.cookies['baz'], 'gee')
1✔
783

784
        # Unquoted multi-space cookies
785
        env['HTTP_COOKIE'] = 'single=cookie data; ' \
1✔
786
                             'quoted="cookie data with unquoted spaces"; ' \
787
                             'multi=cookie data with unquoted spaces; ' \
788
                             'multi2=cookie data with unquoted spaces'
789
        req = self._makeOne(environ=env)
1✔
790
        self.assertEqual(req.cookies['single'], 'cookie data')
1✔
791
        self.assertEqual(req.cookies['quoted'],
1✔
792
                         'cookie data with unquoted spaces')
793
        self.assertEqual(req.cookies['multi'],
1✔
794
                         'cookie data with unquoted spaces')
795
        self.assertEqual(req.cookies['multi2'],
1✔
796
                         'cookie data with unquoted spaces')
797

798
    def test_processInputs_xmlrpc(self):
1✔
799
        TEST_METHOD_CALL = (
1✔
800
            b'<?xml version="1.0"?>'
801
            b'<methodCall><methodName>test</methodName></methodCall>'
802
        )
803
        environ = self._makePostEnviron(body=TEST_METHOD_CALL)
1✔
804
        environ['CONTENT_TYPE'] = 'text/xml'
1✔
805
        req = self._makeOne(stdin=BytesIO(TEST_METHOD_CALL), environ=environ)
1✔
806
        req.processInputs()
1✔
807
        self.assertEqual(req.PATH_INFO, '/test')
1✔
808
        self.assertEqual(req.args, ())
1✔
809

810
    def test_processInputs_w_urlencoded_and_qs(self):
1✔
811
        body = b'foo=1'
1✔
812
        environ = {
1✔
813
            'CONTENT_TYPE': 'application/x-www-form-urlencoded',
814
            'CONTENT_LENGTH': len(body),
815
            'QUERY_STRING': 'bar=2',
816
            'REQUEST_METHOD': 'POST',
817
        }
818
        req = self._makeOne(stdin=BytesIO(body), environ=environ)
1✔
819
        req.processInputs()
1✔
820
        self.assertEqual(req.form['foo'], '1')
1✔
821
        self.assertEqual(req.form['bar'], '2')
1✔
822

823
    def test_close_removes_stdin_references(self):
1✔
824
        # Verifies that all references to the input stream go away on
825
        # request.close().  Otherwise a tempfile may stick around.
826
        s = BytesIO(TEST_FILE_DATA)
1✔
827
        start_count = sys.getrefcount(s)
1✔
828

829
        environ = self._makePostEnviron(body=TEST_FILE_DATA)
1✔
830
        req = self._makeOne(stdin=s, environ=environ)
1✔
831
        req.processInputs()
1✔
832
        self.assertNotEqual(start_count, sys.getrefcount(s))  # Precondition
1✔
833
        req.close()
1✔
834
        self.assertEqual(start_count, sys.getrefcount(s))  # The test
1✔
835

836
    def test_processInputs_w_large_input_gets_tempfile(self):
1✔
837
        # checks fileupload object supports the filename
838
        s = BytesIO(TEST_LARGEFILE_DATA)
1✔
839

840
        environ = self._makePostEnviron(body=TEST_LARGEFILE_DATA)
1✔
841
        req = self._makeOne(stdin=s, environ=environ)
1✔
842
        req.processInputs()
1✔
843
        f = req.form.get('largefile')
1✔
844
        self.assertTrue(f.name)
1✔
845
        self.assertEqual(4006, len(f.file.read()))
1✔
846

847
    def test_processInputs_with_file_upload_gets_iterator(self):
1✔
848
        # checks fileupload object supports the iterator protocol
849
        # collector entry 1837
850
        s = BytesIO(TEST_FILE_DATA)
1✔
851

852
        environ = self._makePostEnviron(body=TEST_FILE_DATA)
1✔
853
        req = self._makeOne(stdin=s, environ=environ)
1✔
854
        req.processInputs()
1✔
855
        f = req.form.get('smallfile')
1✔
856
        self.assertEqual(list(f), [b'test\n'])
1✔
857
        f.seek(0)
1✔
858
        self.assertEqual(next(f), b'test\n')
1✔
859

860
    def test__authUserPW_simple(self):
1✔
861
        user_id = 'user'
1✔
862
        password = 'password'
1✔
863
        auth_header = basic_auth_encode(user_id, password)
1✔
864

865
        environ = {'HTTP_AUTHORIZATION': auth_header}
1✔
866
        request = self._makeOne(environ=environ)
1✔
867

868
        user_id_x, password_x = request._authUserPW()
1✔
869

870
        self.assertEqual(user_id_x, user_id)
1✔
871
        self.assertEqual(password_x, password)
1✔
872

873
    def test__authUserPW_with_embedded_colon(self):
1✔
874
        user_id = 'user'
1✔
875
        password = 'embedded:colon'
1✔
876
        auth_header = basic_auth_encode(user_id, password)
1✔
877

878
        environ = {'HTTP_AUTHORIZATION': auth_header}
1✔
879
        request = self._makeOne(environ=environ)
1✔
880

881
        user_id_x, password_x = request._authUserPW()
1✔
882

883
        self.assertEqual(user_id_x, user_id)
1✔
884
        self.assertEqual(password_x, password)
1✔
885

886
    def test__authUserPW_non_ascii(self):
1✔
887
        user_id = 'usèr'
1✔
888
        password = 'pàssword'
1✔
889
        auth_header = basic_auth_encode(user_id, password)
1✔
890

891
        environ = {'HTTP_AUTHORIZATION': auth_header}
1✔
892
        request = self._makeOne(environ=environ)
1✔
893

894
        user_id_x, password_x = request._authUserPW()
1✔
895

896
        self.assertEqual(user_id_x, user_id)
1✔
897
        self.assertEqual(password_x, password)
1✔
898

899
    def test_debug_not_in_qs_still_gets_attr(self):
1✔
900
        from zope.publisher.base import DebugFlags
1✔
901

902
        # when accessing request.debug we will see the DebugFlags instance
903
        request = self._makeOne()
1✔
904
        self.assertIsInstance(request.debug, DebugFlags)
1✔
905
        # It won't be available through dictonary lookup, though
906
        self.assertTrue(request.get('debug') is None)
1✔
907

908
    def test_debug_in_qs_gets_form_var(self):
1✔
909
        env = {'QUERY_STRING': 'debug=1'}
1✔
910

911
        # request.debug will actually yield a 'debug' form variable
912
        # if it exists
913
        request = self._makeOne(environ=env)
1✔
914
        request.processInputs()
1✔
915
        self.assertEqual(request.debug, '1')
1✔
916
        self.assertEqual(request.get('debug'), '1')
1✔
917
        self.assertEqual(request['debug'], '1')
1✔
918

919
        # we can still override request.debug with a form variable or directly
920

921
    def test_debug_override_via_form_other(self):
1✔
922
        request = self._makeOne()
1✔
923
        request.processInputs()
1✔
924
        request.form['debug'] = '1'
1✔
925
        self.assertEqual(request.debug, '1')
1✔
926
        request['debug'] = '2'
1✔
927
        self.assertEqual(request.debug, '2')
1✔
928

929
    def test_locale_property_accessor(self):
1✔
930
        from ZPublisher.HTTPRequest import _marker
1✔
931

932
        provideAdapter(BrowserLanguages, [IHTTPRequest],
1✔
933
                       IUserPreferredLanguages)
934

935
        env = {'HTTP_ACCEPT_LANGUAGE': 'en'}
1✔
936
        request = self._makeOne(environ=env)
1✔
937

938
        # before accessing request.locale for the first time, request._locale
939
        # is still a marker
940
        self.assertTrue(request._locale is _marker)
1✔
941

942
        # when accessing request.locale we will see an ILocale
943
        self.assertTrue(ILocale.providedBy(request.locale))
1✔
944

945
        # and request._locale has been set
946
        self.assertTrue(request._locale is request.locale)
1✔
947

948
        # It won't be available through dictonary lookup, though
949
        self.assertTrue(request.get('locale') is None)
1✔
950

951
    def test_locale_in_qs(self):
1✔
952
        provideAdapter(BrowserLanguages, [IHTTPRequest],
1✔
953
                       IUserPreferredLanguages)
954

955
        # request.locale will actually yield a 'locale' form variable
956
        # if it exists
957
        env = {'HTTP_ACCEPT_LANGUAGE': 'en', 'QUERY_STRING': 'locale=1'}
1✔
958
        request = self._makeOne(environ=env)
1✔
959
        request.processInputs()
1✔
960

961
        self.assertEqual(request.locale, '1')
1✔
962
        self.assertEqual(request.get('locale'), '1')
1✔
963
        self.assertEqual(request['locale'], '1')
1✔
964

965
    def test_locale_property_override_via_form_other(self):
1✔
966
        provideAdapter(BrowserLanguages, [IHTTPRequest],
1✔
967
                       IUserPreferredLanguages)
968
        env = {'HTTP_ACCEPT_LANGUAGE': 'en'}
1✔
969

970
        # we can still override request.locale with a form variable
971
        request = self._makeOne(environ=env)
1✔
972
        request.processInputs()
1✔
973

974
        self.assertTrue(ILocale.providedBy(request.locale))
1✔
975

976
        request.form['locale'] = '1'
1✔
977
        self.assertEqual(request.locale, '1')
1✔
978

979
        request['locale'] = '2'
1✔
980
        self.assertEqual(request.locale, '2')
1✔
981

982
    def test_locale_semantics(self):
1✔
983
        provideAdapter(BrowserLanguages, [IHTTPRequest],
1✔
984
                       IUserPreferredLanguages)
985
        env_ = {'HTTP_ACCEPT_LANGUAGE': 'en'}
1✔
986

987
        # we should also test the correct semantics of the locale
988
        for httplang in ('it', 'it-ch', 'it-CH', 'IT', 'IT-CH', 'IT-ch'):
1✔
989
            env = env_.copy()
1✔
990
            env['HTTP_ACCEPT_LANGUAGE'] = httplang
1✔
991
            request = self._makeOne(environ=env)
1✔
992
            locale = request.locale
1✔
993
            self.assertTrue(ILocale.providedBy(locale))
1✔
994
            parts = httplang.split('-')
1✔
995
            lang = parts.pop(0).lower()
1✔
996
            territory = variant = None
1✔
997
            if parts:
1✔
998
                territory = parts.pop(0).upper()
1✔
999
            if parts:
1!
1000
                variant = parts.pop(0).upper()
×
1001
            self.assertEqual(locale.id.language, lang)
1✔
1002
            self.assertEqual(locale.id.territory, territory)
1✔
1003
            self.assertEqual(locale.id.variant, variant)
1✔
1004

1005
    def test_locale_fallback(self):
1✔
1006
        provideAdapter(BrowserLanguages, [IHTTPRequest],
1✔
1007
                       IUserPreferredLanguages)
1008

1009
        env = {'HTTP_ACCEPT_LANGUAGE': 'xx'}
1✔
1010

1011
        # Now test for non-existant locale fallback
1012
        request = self._makeOne(environ=env)
1✔
1013
        locale = request.locale
1✔
1014

1015
        self.assertTrue(ILocale.providedBy(locale))
1✔
1016
        self.assertTrue(locale.id.language is None)
1✔
1017
        self.assertTrue(locale.id.territory is None)
1✔
1018
        self.assertTrue(locale.id.variant is None)
1✔
1019

1020
    def test_method_GET(self):
1✔
1021
        env = {'REQUEST_METHOD': 'GET'}
1✔
1022
        request = self._makeOne(environ=env)
1✔
1023
        self.assertEqual(request.method, 'GET')
1✔
1024

1025
    def test_method_POST(self):
1✔
1026
        env = {'REQUEST_METHOD': 'POST'}
1✔
1027
        request = self._makeOne(environ=env)
1✔
1028
        self.assertEqual(request.method, 'POST')
1✔
1029

1030
    def test_getClientAddr_wo_trusted_proxy(self):
1✔
1031
        env = {'REMOTE_ADDR': '127.0.0.1',
1✔
1032
               'HTTP_X_FORWARDED_FOR': '10.1.20.30, 192.168.1.100'}
1033
        request = self._makeOne(environ=env)
1✔
1034
        self.assertEqual(request.getClientAddr(), '127.0.0.1')
1✔
1035

1036
    def test_getClientAddr_one_trusted_proxy(self):
1✔
1037
        from ZPublisher.HTTPRequest import trusted_proxies
1✔
1038
        env = {'REMOTE_ADDR': '127.0.0.1',
1✔
1039
               'HTTP_X_FORWARDED_FOR': '10.1.20.30, 192.168.1.100'}
1040

1041
        orig = trusted_proxies[:]
1✔
1042
        try:
1✔
1043
            trusted_proxies.append('127.0.0.1')
1✔
1044
            request = self._makeOne(environ=env)
1✔
1045
            self.assertEqual(request.getClientAddr(), '192.168.1.100')
1✔
1046
        finally:
1047
            trusted_proxies[:] = orig
1✔
1048

1049
    def test_getClientAddr_trusted_proxy_last(self):
1✔
1050
        from ZPublisher.HTTPRequest import trusted_proxies
1✔
1051
        env = {'REMOTE_ADDR': '192.168.1.100',
1✔
1052
               'HTTP_X_FORWARDED_FOR': '10.1.20.30, 192.168.1.100'}
1053

1054
        orig = trusted_proxies[:]
1✔
1055
        try:
1✔
1056
            trusted_proxies.append('192.168.1.100')
1✔
1057
            request = self._makeOne(environ=env)
1✔
1058
            self.assertEqual(request.getClientAddr(), '10.1.20.30')
1✔
1059
        finally:
1060
            trusted_proxies[:] = orig
1✔
1061

1062
    def test_getClientAddr_trusted_proxy_no_REMOTE_ADDR(self):
1✔
1063
        from ZPublisher.HTTPRequest import trusted_proxies
1✔
1064
        env = {'HTTP_X_FORWARDED_FOR': '10.1.20.30, 192.168.1.100'}
1✔
1065

1066
        orig = trusted_proxies[:]
1✔
1067
        try:
1✔
1068
            trusted_proxies.append('192.168.1.100')
1✔
1069
            request = self._makeOne(environ=env)
1✔
1070
            self.assertEqual(request.getClientAddr(), '')
1✔
1071
        finally:
1072
            trusted_proxies[:] = orig
1✔
1073

1074
    def test_getHeader_exact(self):
1✔
1075
        environ = self._makePostEnviron()
1✔
1076
        request = self._makeOne(environ=environ)
1✔
1077
        self.assertEqual(request.getHeader('content-type'),
1✔
1078
                         'multipart/form-data; boundary=12345')
1079

1080
    def test_getHeader_case_insensitive(self):
1✔
1081
        environ = self._makePostEnviron()
1✔
1082
        request = self._makeOne(environ=environ)
1✔
1083
        self.assertEqual(request.getHeader('Content-Type'),
1✔
1084
                         'multipart/form-data; boundary=12345')
1085

1086
    def test_getHeader_underscore_is_dash(self):
1✔
1087
        environ = self._makePostEnviron()
1✔
1088
        request = self._makeOne(environ=environ)
1✔
1089
        self.assertEqual(request.getHeader('content_type'),
1✔
1090
                         'multipart/form-data; boundary=12345')
1091

1092
    def test_getHeader_literal_turns_off_case_normalization(self):
1✔
1093
        environ = self._makePostEnviron()
1✔
1094
        request = self._makeOne(environ=environ)
1✔
1095
        self.assertEqual(request.getHeader('Content-Type', literal=True), None)
1✔
1096

1097
    def test_getHeader_nonesuch(self):
1✔
1098
        environ = self._makePostEnviron()
1✔
1099
        request = self._makeOne(environ=environ)
1✔
1100
        self.assertEqual(request.getHeader('none-such'), None)
1✔
1101

1102
    def test_getHeader_nonesuch_with_default(self):
1✔
1103
        environ = self._makePostEnviron()
1✔
1104
        request = self._makeOne(environ=environ)
1✔
1105
        self.assertEqual(request.getHeader('Not-existant', default='Whatever'),
1✔
1106
                         'Whatever')
1107

1108
    def test_clone_updates_method_to_GET(self):
1✔
1109
        request = self._makeOne(environ={'REQUEST_METHOD': 'POST'})
1✔
1110
        request['PARENTS'] = [object()]
1✔
1111
        clone = request.clone()
1✔
1112
        self.assertEqual(clone.method, 'GET')
1✔
1113

1114
    def test_clone_keeps_preserves__auth(self):
1✔
1115
        request = self._makeOne()
1✔
1116
        request['PARENTS'] = [object()]
1✔
1117
        request._auth = 'foobar'
1✔
1118
        clone = request.clone()
1✔
1119
        self.assertEqual(clone._auth, 'foobar')
1✔
1120

1121
    def test_clone_doesnt_re_clean_environ(self):
1✔
1122
        request = self._makeOne()
1✔
1123
        request.environ['HTTP_CGI_AUTHORIZATION'] = 'lalalala'
1✔
1124
        request['PARENTS'] = [object()]
1✔
1125
        clone = request.clone()
1✔
1126
        self.assertEqual(clone.environ['HTTP_CGI_AUTHORIZATION'], 'lalalala')
1✔
1127

1128
    def test_clone_keeps_only_last_PARENT(self):
1✔
1129
        PARENTS = [object(), object()]
1✔
1130
        request = self._makeOne()
1✔
1131
        request['PARENTS'] = PARENTS
1✔
1132
        clone = request.clone()
1✔
1133
        self.assertEqual(clone['PARENTS'], PARENTS[1:])
1✔
1134

1135
    def test_clone_preserves_response_class(self):
1✔
1136
        class DummyResponse:
1✔
1137
            pass
1✔
1138
        environ = self._makePostEnviron()
1✔
1139
        request = self._makeOne(None, environ, DummyResponse())
1✔
1140
        request['PARENTS'] = [object()]
1✔
1141
        clone = request.clone()
1✔
1142
        self.assertIsInstance(clone.response, DummyResponse)
1✔
1143

1144
    def test_clone_preserves_request_subclass(self):
1✔
1145
        class SubRequest(self._getTargetClass()):
1✔
1146
            pass
1✔
1147
        environ = self._makePostEnviron()
1✔
1148
        request = SubRequest(None, environ, None)
1✔
1149
        request['PARENTS'] = [object()]
1✔
1150
        clone = request.clone()
1✔
1151
        self.assertIsInstance(clone, SubRequest)
1✔
1152

1153
    def test_clone_preserves_direct_interfaces(self):
1✔
1154
        from zope.interface import Interface
1✔
1155
        from zope.interface import directlyProvides
1✔
1156

1157
        class IFoo(Interface):
1✔
1158
            pass
1✔
1159
        request = self._makeOne()
1✔
1160
        request['PARENTS'] = [object()]
1✔
1161
        directlyProvides(request, IFoo)
1✔
1162
        clone = request.clone()
1✔
1163
        self.assertTrue(IFoo.providedBy(clone))
1✔
1164

1165
    def test_resolve_url_doesnt_send_endrequestevent(self):
1✔
1166
        import zope.event
1✔
1167
        events = []
1✔
1168
        zope.event.subscribers.append(events.append)
1✔
1169
        request = self._makeOne()
1✔
1170
        request['PARENTS'] = [object()]
1✔
1171
        try:
1✔
1172
            request.resolve_url(request.script + '/')
1✔
1173
        finally:
1174
            zope.event.subscribers.remove(events.append)
1✔
1175
        self.assertFalse(
1✔
1176
            len(events),
1177
            "HTTPRequest.resolve_url should not emit events")
1178

1179
    def test_resolve_url_errorhandling(self):
1✔
1180
        # Check that resolve_url really raises the same error
1181
        # it received from ZPublisher.BaseRequest.traverse
1182
        request = self._makeOne()
1✔
1183
        request['PARENTS'] = [object()]
1✔
1184
        self.assertRaises(
1✔
1185
            NotFound, request.resolve_url, request.script + '/does_not_exist')
1186

1187
    def test_parses_json_cookies(self):
1✔
1188
        # https://bugs.launchpad.net/zope2/+bug/563229
1189
        # reports cookies in the wild with embedded double quotes (e.g,
1190
        # JSON-encoded data structures.
1191
        env = {
1✔
1192
            'SERVER_NAME': 'testingharnas',
1193
            'SERVER_PORT': '80',
1194
            'HTTP_COOKIE': 'json={"intkey":123,"stringkey":"blah"}; '
1195
                           'anothercookie=boring; baz'
1196
        }
1197
        req = self._makeOne(environ=env)
1✔
1198
        self.assertEqual(req.cookies['json'],
1✔
1199
                         '{"intkey":123,"stringkey":"blah"}')
1200
        self.assertEqual(req.cookies['anothercookie'], 'boring')
1✔
1201

1202
    def test_getVirtualRoot(self):
1✔
1203
        # https://bugs.launchpad.net/zope2/+bug/193122
1204
        req = self._makeOne()
1✔
1205

1206
        req._script = []
1✔
1207
        self.assertEqual(req.getVirtualRoot(), '')
1✔
1208

1209
        req._script = ['foo', 'bar']
1✔
1210
        self.assertEqual(req.getVirtualRoot(), '/foo/bar')
1✔
1211

1212
    def test__str__returns_native_string(self):
1✔
1213
        r = self._makeOne()
1✔
1214
        self.assertIsInstance(str(r), str)
1✔
1215

1216
    def test___str____password_field(self):
1✔
1217
        # It obscures password fields.
1218
        req = self._makeOne()
1✔
1219
        req.form['passwd'] = 'secret'
1✔
1220

1221
        self.assertNotIn('secret', str(req))
1✔
1222
        self.assertIn('password obscured', str(req))
1✔
1223

1224
    def test_text__password_field(self):
1✔
1225
        # It obscures password fields.
1226
        req = self._makeOne()
1✔
1227
        req.form['passwd'] = 'secret'
1✔
1228

1229
        self.assertNotIn('secret', req.text())
1✔
1230
        self.assertIn('password obscured', req.text())
1✔
1231

1232
    _xmlrpc_call = b"""<?xml version="1.0"?>
1✔
1233
    <methodCall>
1234
      <methodName>examples.getStateName</methodName>
1235
      <params>
1236
         <param>
1237
            <value><i4>41</i4></value>
1238
            </param>
1239
         </params>
1240
      </methodCall>
1241
    """
1242

1243
    def test_processInputs_xmlrpc_with_args(self):
1✔
1244
        req = self._makeOne(
1✔
1245
            stdin=BytesIO(self._xmlrpc_call),
1246
            environ=dict(REQUEST_METHOD="POST", CONTENT_TYPE="text/xml"))
1247
        req.processInputs()
1✔
1248
        self.assertTrue(is_xmlrpc_response(req.response))
1✔
1249
        self.assertEqual(req.args, (41,))
1✔
1250
        self.assertEqual(req.other["PATH_INFO"], "/examples/getStateName")
1✔
1251

1252
    def test_processInputs_xmlrpc_controlled_allowed(self):
1✔
1253
        req = self._makeOne(
1✔
1254
            stdin=BytesIO(self._xmlrpc_call),
1255
            environ=dict(REQUEST_METHOD="POST", CONTENT_TYPE="text/xml"))
1256
        with self._xmlrpc_control(lambda request: True):
1✔
1257
            req.processInputs()
1✔
1258
        self.assertTrue(is_xmlrpc_response(req.response))
1✔
1259

1260
    def test_processInputs_xmlrpc_controlled_disallowed(self):
1✔
1261
        req = self._makeOne(
1✔
1262
            environ=dict(REQUEST_METHOD="POST", CONTENT_TYPE="text/xml"))
1263
        with self._xmlrpc_control(lambda request: False):
1✔
1264
            req.processInputs()
1✔
1265
        self.assertFalse(is_xmlrpc_response(req.response))
1✔
1266

1267
    @contextmanager
1✔
1268
    def _xmlrpc_control(self, allow):
1✔
1269
        gsm = getGlobalSiteManager()
1✔
1270
        gsm.registerUtility(allow, IXmlrpcChecker)
1✔
1271
        yield
1✔
1272
        gsm.unregisterUtility(allow, IXmlrpcChecker)
1✔
1273

1274
    def test_url_scheme(self):
1✔
1275
        # The default is http
1276
        env = {'SERVER_NAME': 'myhost', 'SERVER_PORT': 80}
1✔
1277
        req = self._makeOne(environ=env)
1✔
1278
        self.assertEqual(req['SERVER_URL'], 'http://myhost')
1✔
1279

1280
        # If we bang a SERVER_URL into the environment it is retained
1281
        env = {'SERVER_URL': 'https://anotherserver:8443'}
1✔
1282
        req = self._makeOne(environ=env)
1✔
1283
        self.assertEqual(req['SERVER_URL'], 'https://anotherserver:8443')
1✔
1284

1285
        # Now go through the various environment values that signal
1286
        # a request uses the https URL scheme
1287
        for val in ('on', 'ON', '1'):
1✔
1288
            env = {'SERVER_NAME': 'myhost', 'SERVER_PORT': 443, 'HTTPS': val}
1✔
1289
            req = self._makeOne(environ=env)
1✔
1290
            self.assertEqual(req['SERVER_URL'], 'https://myhost')
1✔
1291

1292
        env = {'SERVER_NAME': 'myhost', 'SERVER_PORT': 443,
1✔
1293
               'SERVER_PORT_SECURE': 1}
1294
        req = self._makeOne(environ=env)
1✔
1295
        self.assertEqual(req['SERVER_URL'], 'https://myhost')
1✔
1296

1297
        env = {'SERVER_NAME': 'myhost', 'SERVER_PORT': 443,
1✔
1298
               'REQUEST_SCHEME': 'HTTPS'}
1299
        req = self._makeOne(environ=env)
1✔
1300
        self.assertEqual(req['SERVER_URL'], 'https://myhost')
1✔
1301

1302
        env = {'SERVER_NAME': 'myhost', 'SERVER_PORT': 443,
1✔
1303
               'wsgi.url_scheme': 'https'}
1304
        req = self._makeOne(environ=env)
1✔
1305
        self.assertEqual(req['SERVER_URL'], 'https://myhost')
1✔
1306

1307
    def test_form_urlencoded(self):
1✔
1308
        body = b"a=1"
1✔
1309
        env = self._makePostEnviron(body, False)
1✔
1310
        req = self._makeOne(stdin=BytesIO(body), environ=env)
1✔
1311
        req.processInputs()
1✔
1312
        self.assertEqual(req.form["a"], "1")
1✔
1313
        req = self._makeOne(stdin=BytesIO(body), environ=env)
1✔
1314
        with patch("ZPublisher.HTTPRequest.FORM_MEMORY_LIMIT", 1):
1✔
1315
            with self.assertRaises(BadRequest):
1✔
1316
                req.processInputs()
1✔
1317

1318
    def test_bytes_converter(self):
1✔
1319
        val = "äöü".encode("latin-1")
1✔
1320
        body = b"a:bytes:latin-1=" + val
1✔
1321
        env = self._makePostEnviron(body, False)
1✔
1322
        req = self._makeOne(stdin=BytesIO(body), environ=env)
1✔
1323
        req.processInputs()
1✔
1324
        self.assertEqual(req.form["a"], val)
1✔
1325

1326

1327
class TestHTTPRequestZope3Views(TestRequestViewsBase):
1✔
1328

1329
    def _makeOne(self, root):
1✔
1330
        from zope.interface import directlyProvides
1✔
1331
        from zope.publisher.browser import IDefaultBrowserLayer
1✔
1332
        request = HTTPRequestFactoryMixin()._makeOne()
1✔
1333
        request['PARENTS'] = [root]
1✔
1334
        # The request needs to implement the proper interface
1335
        directlyProvides(request, IDefaultBrowserLayer)
1✔
1336
        return request
1✔
1337

1338
    def test_no_traversal_of_view_request_attribute(self):
1✔
1339
        # make sure views don't accidentally publish the 'request' attribute
1340
        root, _ = self._makeRootAndFolder()
1✔
1341

1342
        # make sure the view itself is traversable:
1343
        view = self._makeOne(root).traverse('folder/@@meth')
1✔
1344
        from ZPublisher.HTTPRequest import HTTPRequest
1✔
1345
        self.assertEqual(view.request.__class__, HTTPRequest,)
1✔
1346

1347
        # but not the request:
1348
        self.assertRaises(
1✔
1349
            NotFound,
1350
            self._makeOne(root).traverse, 'folder/@@meth/request'
1351
        )
1352

1353

1354
class TestSearchType(unittest.TestCase):
1✔
1355
    """Test `ZPublisher.HTTPRequest.search_type`
1356

1357
    see "https://github.com/zopefoundation/Zope/pull/512"
1358
    """
1359
    def check(self, val, expect):
1✔
1360
        mo = search_type(val)
1✔
1361
        if expect is None:
1✔
1362
            self.assertIsNone(mo)
1✔
1363
        else:
1364
            self.assertIsNotNone(mo)
1✔
1365
            self.assertEqual(mo.group(), expect)
1✔
1366

1367
    def test_image_control(self):
1✔
1368
        self.check("abc.x", ".x")
1✔
1369
        self.check("abc.y", ".y")
1✔
1370
        self.check("abc.xy", None)
1✔
1371

1372
    def test_type(self):
1✔
1373
        self.check("abc:int", ":int")
1✔
1374

1375
    def test_leftmost(self):
1✔
1376
        self.check("abc:int:record", ":record")
1✔
1377

1378
    def test_special(self):
1✔
1379
        self.check("abc:a-_0b", ":a-_0b")
1✔
1380

1381

1382
TEST_POST_ENVIRON = {
1✔
1383
    'CONTENT_LENGTH': None,
1384
    'REQUEST_METHOD': 'POST',
1385
    'SERVER_NAME': 'localhost',
1386
    'SERVER_PORT': '80',
1387
}
1388

1389
TEST_FILE_DATA = b'''
1✔
1390
--12345
1391
Content-Disposition: form-data; name="smallfile"; filename="smallfile"
1392
Content-Type: application/octet-stream
1393

1394
test
1395

1396
--12345--
1397
'''
1398

1399
TEST_LARGEFILE_DATA = b'''
1✔
1400
--12345
1401
Content-Disposition: form-data; name="largefile"; filename="largefile"
1402
Content-Type: application/octet-stream
1403

1404
test %s
1405

1406
--12345--
1407
''' % (b'test' * 1000)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc