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

zopefoundation / Zope / 3979553512

pending completion
3979553512

push

github

GitHub
merge PR 1097 (#1098)

4269 of 6877 branches covered (62.08%)

Branch coverage included in aggregate %.

62 of 62 new or added lines in 2 files covered. (100.0%)

27025 of 31325 relevant lines covered (86.27%)

0.86 hits per line

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

99.06
/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 TaintedString
1✔
22
from AccessControl.tainted import should_be_tainted
1✔
23
from zExceptions import NotFound
1✔
24
from zope.component import getGlobalSiteManager
1✔
25
from zope.component import provideAdapter
1✔
26
from zope.i18n.interfaces import IUserPreferredLanguages
1✔
27
from zope.i18n.interfaces.locales import ILocale
1✔
28
from zope.publisher.browser import BrowserLanguages
1✔
29
from zope.publisher.interfaces.http import IHTTPRequest
1✔
30
from zope.testing.cleanup import cleanUp
1✔
31
from ZPublisher.HTTPRequest import BadRequest
1✔
32
from ZPublisher.HTTPRequest import FileUpload
1✔
33
from ZPublisher.HTTPRequest import search_type
1✔
34
from ZPublisher.interfaces import IXmlrpcChecker
1✔
35
from ZPublisher.tests.testBaseRequest import TestRequestViewsBase
1✔
36
from ZPublisher.utils import basic_auth_encode
1✔
37
from ZPublisher.xmlrpc import is_xmlrpc_response
1✔
38

39

40
class RecordTests(unittest.TestCase):
1✔
41

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

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

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

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

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

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

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

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

103

104
class HTTPRequestFactoryMixin:
1✔
105

106
    def tearDown(self):
1✔
107
        cleanUp()
×
108

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

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

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

126
        if environ is None:
1✔
127
            environ = {}
1✔
128

129
        if 'REQUEST_METHOD' not in environ:
1✔
130
            environ['REQUEST_METHOD'] = 'GET'
1✔
131

132
        if 'SERVER_NAME' not in environ:
1✔
133
            environ['SERVER_NAME'] = 'localhost'
1✔
134

135
        if 'SERVER_PORT' not in environ:
1✔
136
            environ['SERVER_PORT'] = '8080'
1✔
137

138
        if response is None:
1✔
139
            response = HTTPResponse(stdout=BytesIO())
1✔
140

141
        return self._getTargetClass()(stdin, environ, response, clean)
1✔
142

143

144
class HTTPRequestTests(unittest.TestCase, HTTPRequestFactoryMixin):
1✔
145

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

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

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

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

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

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

178
        retval = 0
1✔
179

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

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

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

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

206
        return retval
1✔
207

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

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

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

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

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

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

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

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

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

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

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

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

289
        self._noTaintedValues(req)
1✔
290
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
291

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

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

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

320
        self._noTaintedValues(req)
1✔
321
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
322

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

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

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

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

354
        self._noTaintedValues(req)
1✔
355
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
356

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

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

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

386
        self._noTaintedValues(req)
1✔
387
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
388

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

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

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

406
        self._noTaintedValues(req)
1✔
407
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
408

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

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

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

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

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

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

445
        self._noTaintedValues(req)
1✔
446
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
447

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

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

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

464
            ('bar.spam:record:default', 'eggs'),
465
            ('bar.foo:record:default', 'foo'),
466
            ('bar.foo:record', 'baz'),
467

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

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

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

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

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

493
        self._noTaintedValues(req)
1✔
494
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
495

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

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

515
        self._taintedKeysAlsoInForm(req)
1✔
516
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
517

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

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

538
        self._taintedKeysAlsoInForm(req)
1✔
539
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
540

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

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

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

552
            ('tnouconverter:string:utf8', '<test\xc2\xae>'),
553
        )
554

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

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

569
        self._taintedKeysAlsoInForm(req)
1✔
570
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
571

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

579
            ('toneitemtuple:tuple', '<one>'),
580
            ('tinitatuple:tuple', '<one>'), ('tinitatuple:tuple', 'two'),
581
            ('tdeferatuple:tuple', 'one'), ('tdeferatuple:tuple', '<two>'),
582

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

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

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

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

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

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

618
        self._taintedKeysAlsoInForm(req)
1✔
619
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
620

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

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

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

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

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

668
        self._taintedKeysAlsoInForm(req)
1✔
669
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
670

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

675
            ('doesnnotapply:default', '<4>'),
676
            ('doesnnotapply', '4'),
677

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

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

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

697
            ('rdoesnotapply.spam:record:default', '<eggs>'),
698
            ('rdoesnotapply.spam:record', 'eggs'),
699

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

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

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

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

722
        self._taintedKeysAlsoInForm(req)
1✔
723
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
724

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

728
        self.assertRaises(ValueError, self._processInputs, input)
1✔
729

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

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

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

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

763
        self._noTaintedValues(req)
1✔
764
        self._onlyTaintedformHoldsTaintedStrings(req)
1✔
765

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

870
        user_id_x, password_x = request._authUserPW()
1✔
871

872
        self.assertEqual(user_id_x, user_id)
1✔
873
        self.assertEqual(password_x, password)
1✔
874

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

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

883
        user_id_x, password_x = request._authUserPW()
1✔
884

885
        self.assertEqual(user_id_x, user_id)
1✔
886
        self.assertEqual(password_x, password)
1✔
887

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

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

896
        user_id_x, password_x = request._authUserPW()
1✔
897

898
        self.assertEqual(user_id_x, user_id)
1✔
899
        self.assertEqual(password_x, password)
1✔
900

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

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

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

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

921
        # we can still override request.debug with a form variable or directly
922

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

931
    def test_locale_property_accessor(self):
1✔
932
        from ZPublisher.HTTPRequest import _marker
1✔
933

934
        provideAdapter(BrowserLanguages, [IHTTPRequest],
1✔
935
                       IUserPreferredLanguages)
936

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

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

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

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

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

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

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

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

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

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

976
        self.assertTrue(ILocale.providedBy(request.locale))
1✔
977

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

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

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

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

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

1011
        env = {'HTTP_ACCEPT_LANGUAGE': 'xx'}
1✔
1012

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1167
    def test_resolve_url_doesnt_send_endrequestevent(self):
1✔
1168
        # The following imports are necessary:
1169
        #  They happen implicitely in `request.resolve_url`
1170
        #  They creates `zope.schema` events
1171
        # Doing them here avoids those unrelated events
1172
        import OFS.PropertyManager  # noqa: F401
1✔
1173
        import OFS.SimpleItem  # noqa: F401
1✔
1174
        #
1175
        import zope.event
1✔
1176
        events = []
1✔
1177
        zope.event.subscribers.append(events.append)
1✔
1178
        request = self._makeOne()
1✔
1179
        request['PARENTS'] = [object()]
1✔
1180
        try:
1✔
1181
            request.resolve_url(request.script + '/')
1✔
1182
        finally:
1183
            zope.event.subscribers.remove(events.append)
1✔
1184
        self.assertFalse(
1✔
1185
            len(events),
1186
            "HTTPRequest.resolve_url should not emit events")
1187

1188
    def test_resolve_url_errorhandling(self):
1✔
1189
        # Check that resolve_url really raises the same error
1190
        # it received from ZPublisher.BaseRequest.traverse
1191
        request = self._makeOne()
1✔
1192
        request['PARENTS'] = [object()]
1✔
1193
        self.assertRaises(
1✔
1194
            NotFound, request.resolve_url, request.script + '/does_not_exist')
1195

1196
    def test_parses_json_cookies(self):
1✔
1197
        # https://bugs.launchpad.net/zope2/+bug/563229
1198
        # reports cookies in the wild with embedded double quotes (e.g,
1199
        # JSON-encoded data structures.
1200
        env = {
1✔
1201
            'SERVER_NAME': 'testingharnas',
1202
            'SERVER_PORT': '80',
1203
            'HTTP_COOKIE': 'json={"intkey":123,"stringkey":"blah"}; '
1204
                           'anothercookie=boring; baz'
1205
        }
1206
        req = self._makeOne(environ=env)
1✔
1207
        self.assertEqual(req.cookies['json'],
1✔
1208
                         '{"intkey":123,"stringkey":"blah"}')
1209
        self.assertEqual(req.cookies['anothercookie'], 'boring')
1✔
1210

1211
    def test_getVirtualRoot(self):
1✔
1212
        # https://bugs.launchpad.net/zope2/+bug/193122
1213
        req = self._makeOne()
1✔
1214

1215
        req._script = []
1✔
1216
        self.assertEqual(req.getVirtualRoot(), '')
1✔
1217

1218
        req._script = ['foo', 'bar']
1✔
1219
        self.assertEqual(req.getVirtualRoot(), '/foo/bar')
1✔
1220

1221
    def test__str__returns_native_string(self):
1✔
1222
        r = self._makeOne()
1✔
1223
        self.assertIsInstance(str(r), str)
1✔
1224

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

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

1233
    def test_text__password_field(self):
1✔
1234
        # It obscures password fields.
1235
        req = self._makeOne()
1✔
1236
        req.form['passwd'] = 'secret'
1✔
1237

1238
        self.assertNotIn('secret', req.text())
1✔
1239
        self.assertIn('password obscured', req.text())
1✔
1240

1241
    _xmlrpc_call = b"""<?xml version="1.0"?>
1✔
1242
    <methodCall>
1243
      <methodName>examples.getStateName</methodName>
1244
      <params>
1245
         <param>
1246
            <value><i4>41</i4></value>
1247
            </param>
1248
         </params>
1249
      </methodCall>
1250
    """
1251

1252
    def test_processInputs_xmlrpc_with_args(self):
1✔
1253
        req = self._makeOne(
1✔
1254
            stdin=BytesIO(self._xmlrpc_call),
1255
            environ=dict(REQUEST_METHOD="POST", CONTENT_TYPE="text/xml"))
1256
        req.processInputs()
1✔
1257
        self.assertTrue(is_xmlrpc_response(req.response))
1✔
1258
        self.assertEqual(req.args, (41,))
1✔
1259
        self.assertEqual(req.other["PATH_INFO"], "/examples/getStateName")
1✔
1260

1261
    def test_processInputs_xmlrpc_controlled_allowed(self):
1✔
1262
        req = self._makeOne(
1✔
1263
            stdin=BytesIO(self._xmlrpc_call),
1264
            environ=dict(REQUEST_METHOD="POST", CONTENT_TYPE="text/xml"))
1265
        with self._xmlrpc_control(lambda request: True):
1✔
1266
            req.processInputs()
1✔
1267
        self.assertTrue(is_xmlrpc_response(req.response))
1✔
1268

1269
    def test_processInputs_xmlrpc_controlled_disallowed(self):
1✔
1270
        req = self._makeOne(
1✔
1271
            environ=dict(REQUEST_METHOD="POST", CONTENT_TYPE="text/xml"))
1272
        with self._xmlrpc_control(lambda request: False):
1✔
1273
            req.processInputs()
1✔
1274
        self.assertFalse(is_xmlrpc_response(req.response))
1✔
1275

1276
    @contextmanager
1✔
1277
    def _xmlrpc_control(self, allow):
1✔
1278
        gsm = getGlobalSiteManager()
1✔
1279
        gsm.registerUtility(allow, IXmlrpcChecker)
1✔
1280
        yield
1✔
1281
        gsm.unregisterUtility(allow, IXmlrpcChecker)
1✔
1282

1283
    def test_url_scheme(self):
1✔
1284
        # The default is http
1285
        env = {'SERVER_NAME': 'myhost', 'SERVER_PORT': 80}
1✔
1286
        req = self._makeOne(environ=env)
1✔
1287
        self.assertEqual(req['SERVER_URL'], 'http://myhost')
1✔
1288

1289
        # If we bang a SERVER_URL into the environment it is retained
1290
        env = {'SERVER_URL': 'https://anotherserver:8443'}
1✔
1291
        req = self._makeOne(environ=env)
1✔
1292
        self.assertEqual(req['SERVER_URL'], 'https://anotherserver:8443')
1✔
1293

1294
        # Now go through the various environment values that signal
1295
        # a request uses the https URL scheme
1296
        for val in ('on', 'ON', '1'):
1✔
1297
            env = {'SERVER_NAME': 'myhost', 'SERVER_PORT': 443, 'HTTPS': val}
1✔
1298
            req = self._makeOne(environ=env)
1✔
1299
            self.assertEqual(req['SERVER_URL'], 'https://myhost')
1✔
1300

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

1306
        env = {'SERVER_NAME': 'myhost', 'SERVER_PORT': 443,
1✔
1307
               'REQUEST_SCHEME': 'HTTPS'}
1308
        req = self._makeOne(environ=env)
1✔
1309
        self.assertEqual(req['SERVER_URL'], 'https://myhost')
1✔
1310

1311
        env = {'SERVER_NAME': 'myhost', 'SERVER_PORT': 443,
1✔
1312
               'wsgi.url_scheme': 'https'}
1313
        req = self._makeOne(environ=env)
1✔
1314
        self.assertEqual(req['SERVER_URL'], 'https://myhost')
1✔
1315

1316
    def test_form_urlencoded(self):
1✔
1317
        body = b"a=1"
1✔
1318
        env = self._makePostEnviron(body, False)
1✔
1319
        req = self._makeOne(stdin=BytesIO(body), environ=env)
1✔
1320
        req.processInputs()
1✔
1321
        self.assertEqual(req.form["a"], "1")
1✔
1322
        req = self._makeOne(stdin=BytesIO(body), environ=env)
1✔
1323
        with patch("ZPublisher.HTTPRequest.FORM_MEMORY_LIMIT", 1):
1✔
1324
            with self.assertRaises(BadRequest):
1✔
1325
                req.processInputs()
1✔
1326

1327
    def test_bytes_converter(self):
1✔
1328
        val = "äöü".encode("latin-1")
1✔
1329
        body = b"a:bytes:latin-1=" + val
1✔
1330
        env = self._makePostEnviron(body, False)
1✔
1331
        req = self._makeOne(stdin=BytesIO(body), environ=env)
1✔
1332
        req.processInputs()
1✔
1333
        self.assertEqual(req.form["a"], val)
1✔
1334

1335
    def test_issue_1095(self):
1✔
1336
        body = TEST_ISSUE_1095_DATA
1✔
1337
        env = self._makePostEnviron(body)
1✔
1338
        req = self._makeOne(BytesIO(body), env)
1✔
1339
        req.processInputs()
1✔
1340
        r = req["r"]
1✔
1341
        self.assertEqual(len(r), 2)
1✔
1342
        self.assertIsInstance(r[0].x, FileUpload)
1✔
1343
        self.assertIsInstance(r[1].x, str)
1✔
1344
        r = req.taintedform["r"]
1✔
1345
        self.assertIsInstance(r[0].x, FileUpload)
1✔
1346
        self.assertIsInstance(r[1].x, TaintedString)
1✔
1347

1348

1349
class TestHTTPRequestZope3Views(TestRequestViewsBase):
1✔
1350

1351
    def _makeOne(self, root):
1✔
1352
        from zope.interface import directlyProvides
1✔
1353
        from zope.publisher.browser import IDefaultBrowserLayer
1✔
1354
        request = HTTPRequestFactoryMixin()._makeOne()
1✔
1355
        request['PARENTS'] = [root]
1✔
1356
        # The request needs to implement the proper interface
1357
        directlyProvides(request, IDefaultBrowserLayer)
1✔
1358
        return request
1✔
1359

1360
    def test_no_traversal_of_view_request_attribute(self):
1✔
1361
        # make sure views don't accidentally publish the 'request' attribute
1362
        root, _ = self._makeRootAndFolder()
1✔
1363

1364
        # make sure the view itself is traversable:
1365
        view = self._makeOne(root).traverse('folder/@@meth')
1✔
1366
        from ZPublisher.HTTPRequest import HTTPRequest
1✔
1367
        self.assertEqual(view.request.__class__, HTTPRequest,)
1✔
1368

1369
        # but not the request:
1370
        self.assertRaises(
1✔
1371
            NotFound,
1372
            self._makeOne(root).traverse, 'folder/@@meth/request'
1373
        )
1374

1375

1376
class TestSearchType(unittest.TestCase):
1✔
1377
    """Test `ZPublisher.HTTPRequest.search_type`
1378

1379
    see "https://github.com/zopefoundation/Zope/pull/512"
1380
    """
1381
    def check(self, val, expect):
1✔
1382
        mo = search_type(val)
1✔
1383
        if expect is None:
1✔
1384
            self.assertIsNone(mo)
1✔
1385
        else:
1386
            self.assertIsNotNone(mo)
1✔
1387
            self.assertEqual(mo.group(), expect)
1✔
1388

1389
    def test_image_control(self):
1✔
1390
        self.check("abc.x", ".x")
1✔
1391
        self.check("abc.y", ".y")
1✔
1392
        self.check("abc.xy", None)
1✔
1393

1394
    def test_type(self):
1✔
1395
        self.check("abc:int", ":int")
1✔
1396

1397
    def test_leftmost(self):
1✔
1398
        self.check("abc:int:record", ":record")
1✔
1399

1400
    def test_special(self):
1✔
1401
        self.check("abc:a-_0b", ":a-_0b")
1✔
1402

1403

1404
TEST_POST_ENVIRON = {
1✔
1405
    'CONTENT_LENGTH': None,
1406
    'REQUEST_METHOD': 'POST',
1407
    'SERVER_NAME': 'localhost',
1408
    'SERVER_PORT': '80',
1409
}
1410

1411
TEST_FILE_DATA = b'''
1✔
1412
--12345
1413
Content-Disposition: form-data; name="smallfile"; filename="smallfile"
1414
Content-Type: application/octet-stream
1415

1416
test
1417

1418
--12345--
1419
'''
1420

1421
TEST_LARGEFILE_DATA = b'''
1✔
1422
--12345
1423
Content-Disposition: form-data; name="largefile"; filename="largefile"
1424
Content-Type: application/octet-stream
1425

1426
test %s
1427

1428
--12345--
1429
''' % (b'test' * 1000)
1430

1431
TEST_ISSUE_1095_DATA = b'''
1✔
1432
--12345
1433
Content-Disposition: form-data; name="r.x:records"; filename="fn"
1434
Content-Type: application/octet-stream
1435

1436
test
1437

1438
--12345
1439
Content-Disposition: form-data; name="r.x:records"
1440
Content-Type: text/html
1441

1442
<body>abc</body>
1443

1444
--12345--
1445
'''
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