• 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

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

17
import codecs
1✔
18
import html
1✔
19
import os
1✔
20
import random
1✔
21
import re
1✔
22
import time
1✔
23
from copy import deepcopy
1✔
24
from types import SimpleNamespace
1✔
25
from urllib.parse import parse_qsl
1✔
26
from urllib.parse import unquote
1✔
27
from urllib.parse import urlparse
1✔
28

29
from AccessControl.tainted import should_be_tainted
1✔
30
from AccessControl.tainted import taint_string
1✔
31
from multipart import Headers
1✔
32
from multipart import MultipartParser
1✔
33
from multipart import parse_options_header
1✔
34
from zExceptions import BadRequest
1✔
35
from zope.component import queryUtility
1✔
36
from zope.i18n.interfaces import IUserPreferredLanguages
1✔
37
from zope.i18n.locales import LoadLocaleError
1✔
38
from zope.i18n.locales import locales
1✔
39
from zope.interface import directlyProvidedBy
1✔
40
from zope.interface import directlyProvides
1✔
41
from zope.interface import implementer
1✔
42
from zope.publisher.base import DebugFlags
1✔
43
from zope.publisher.http import splitport
1✔
44
from zope.publisher.interfaces.browser import IBrowserRequest
1✔
45
from ZPublisher import xmlrpc
1✔
46
from ZPublisher.BaseRequest import BaseRequest
1✔
47
from ZPublisher.BaseRequest import quote
1✔
48
from ZPublisher.Converters import get_converter
1✔
49
from ZPublisher.interfaces import IXmlrpcChecker
1✔
50
from ZPublisher.utils import basic_auth_decode
1✔
51

52
from .cookie import getCookieValuePolicy
1✔
53

54

55
# DOS attack protection -- limiting the amount of memory for forms
56
# probably should become configurable
57
FORM_MEMORY_LIMIT = 2 ** 20  # memory limit for forms
1✔
58
FORM_DISK_LIMIT = 2 ** 30    # disk limit for forms
1✔
59
FORM_MEMFILE_LIMIT = 4000    # limit for `BytesIO` -> temporary file switch
1✔
60

61

62
# This may get overwritten during configuration
63
default_encoding = 'utf-8'
1✔
64

65
isCGI_NAMEs = {
1✔
66
    'SERVER_SOFTWARE': 1,
67
    'SERVER_NAME': 1,
68
    'GATEWAY_INTERFACE': 1,
69
    'SERVER_PROTOCOL': 1,
70
    'SERVER_PORT': 1,
71
    'REQUEST_METHOD': 1,
72
    'PATH_INFO': 1,
73
    'PATH_TRANSLATED': 1,
74
    'SCRIPT_NAME': 1,
75
    'QUERY_STRING': 1,
76
    'REMOTE_HOST': 1,
77
    'REMOTE_ADDR': 1,
78
    'AUTH_TYPE': 1,
79
    'REMOTE_USER': 1,
80
    'REMOTE_IDENT': 1,
81
    'CONTENT_TYPE': 1,
82
    'CONTENT_LENGTH': 1,
83
    'SERVER_URL': 1,
84
}
85

86
isCGI_NAME = isCGI_NAMEs.__contains__
1✔
87

88
hide_key = {'HTTP_AUTHORIZATION': 1, 'HTTP_CGI_AUTHORIZATION': 1}
1✔
89

90
default_port = {'http': 80, 'https': 443}
1✔
91

92
tainting_env = str(os.environ.get('ZOPE_DTML_REQUEST_AUTOQUOTE', '')).lower()
1✔
93
TAINTING_ENABLED = tainting_env not in ('disabled', '0', 'no')
1✔
94

95
search_type = re.compile(r'(:[a-zA-Z][-a-zA-Z0-9_]+|\.[xy])$').search
1✔
96

97
_marker = []
1✔
98

99
# The trusted_proxies configuration setting contains a sequence
100
# of front-end proxies that are trusted to supply an accurate
101
# X_FORWARDED_FOR header. If REMOTE_ADDR is one of the values in this list
102
# and it has set an X_FORWARDED_FOR header, ZPublisher copies REMOTE_ADDR
103
# into X_FORWARDED_BY, and the last element of the X_FORWARDED_FOR list
104
# into REMOTE_ADDR. X_FORWARDED_FOR is left unchanged.
105
# The ZConfig machinery may sets this attribute on initialization
106
# if any trusted-proxies are defined in the configuration file.
107

108
trusted_proxies = []
1✔
109

110

111
class NestedLoopExit(Exception):
1✔
112
    pass
1✔
113

114

115
@implementer(IBrowserRequest)
1✔
116
class HTTPRequest(BaseRequest):
1✔
117
    """ Model HTTP request data.
118

119
    This object provides access to request data.  This includes, the
120
    input headers, form data, server data, and cookies.
121

122
    Request objects are created by the object publisher and will be
123
    passed to published objects through the argument name, REQUEST.
124

125
    The request object is a mapping object that represents a
126
    collection of variable to value mappings.  In addition, variables
127
    are divided into five categories:
128

129
      - Environment variables
130

131
        These variables include input headers, server data, and other
132
        request-related data.  The variable names are as specified
133
        in the <a href="https://tools.ietf.org/html/rfc3875">CGI
134
        specification</a>
135

136
      - Form data
137

138
        These are data extracted from either a URL-encoded query
139
        string or body, if present.
140

141
      - Cookies
142

143
        These are the cookie data, if present.
144

145
      - Lazy Data
146

147
        These are callables which are deferred until explicitly
148
        referenced, at which point they are resolved and stored as
149
        application data.
150

151
      - Other
152

153
        Data that may be set by an application object.
154

155
    The form attribute of a request is actually a Field Storage
156
    object.  When file uploads are used, this provides a richer and
157
    more complex interface than is provided by accessing form data as
158
    items of the request.  See the FieldStorage class documentation
159
    for more details.
160

161
    The request object may be used as a mapping object, in which case
162
    values will be looked up in the order: environment variables,
163
    other variables, form data, and then cookies.
164
    """
165

166
    _hacked_path = None
1✔
167
    args = ()
1✔
168
    _file = None
1✔
169
    _urls = ()
1✔
170

171
    charset = default_encoding
1✔
172
    retry_max_count = 0
1✔
173

174
    def supports_retry(self):
1✔
175
        return self.retry_count < self.retry_max_count
1✔
176

177
    def delay_retry(self):
1✔
178
        # Insert a delay before retrying. Moved here from supports_retry.
179
        time.sleep(random.uniform(0, 2 ** (self.retry_count)))
1✔
180

181
    def retry(self):
1✔
182
        self.retry_count = self.retry_count + 1
1✔
183
        self.stdin.seek(0)
1✔
184
        r = self.__class__(stdin=self.stdin,
1✔
185
                           environ=self._orig_env,
186
                           response=self.response.retry())
187
        r.retry_count = self.retry_count
1✔
188
        return r
1✔
189

190
    def clear(self):
1✔
191
        # Clear all references to the input stream, possibly
192
        # removing tempfiles.
193
        self.stdin = None
1✔
194
        self._file = None
1✔
195
        self.form.clear()
1✔
196
        # we want to clear the lazy dict here because BaseRequests don't have
197
        # one.  Without this, there's the possibility of memory leaking
198
        # after every request.
199
        self._lazies = {}
1✔
200
        BaseRequest.clear(self)
1✔
201

202
    def setServerURL(self, protocol=None, hostname=None, port=None):
1✔
203
        """ Set the parts of generated URLs. """
204
        other = self.other
1✔
205
        server_url = other.get('SERVER_URL', '')
1✔
206
        if protocol is None and hostname is None and port is None:
1!
207
            return server_url
×
208
        old_url = urlparse(server_url)
1✔
209
        if protocol is None:
1!
210
            protocol = old_url.scheme
×
211
        if hostname is None:
1!
212
            hostname = old_url.hostname
×
213
        if port is None:
1✔
214
            port = old_url.port
1✔
215

216
        if (port is None or default_port[protocol] == port):
1✔
217
            host = hostname
1✔
218
        else:
219
            host = f'{hostname}:{port}'
1✔
220
        server_url = other['SERVER_URL'] = f'{protocol}://{host}'
1✔
221
        self._resetURLS()
1✔
222
        return server_url
1✔
223

224
    def setVirtualRoot(self, path, hard=0):
1✔
225
        """ Treat the current publishing object as a VirtualRoot """
226
        other = self.other
1✔
227
        if isinstance(path, str):
1✔
228
            path = path.split('/')
1✔
229
        self._script[:] = list(map(quote, [_p for _p in path if _p]))
1✔
230
        del self._steps[:]
1✔
231
        parents = other['PARENTS']
1✔
232
        if hard:
1!
233
            del parents[:-1]
×
234
        other['VirtualRootPhysicalPath'] = parents[-1].getPhysicalPath()
1✔
235
        self._resetURLS()
1✔
236

237
    def getVirtualRoot(self):
1✔
238
        """ Return a slash-separated virtual root.
239

240
        If it is same as the physical root, return ''.
241
        """
242
        return '/'.join([''] + self._script)
1✔
243

244
    def physicalPathToVirtualPath(self, path):
1✔
245
        """ Remove the path to the VirtualRoot from a physical path """
246
        if isinstance(path, str):
1!
247
            path = path.split('/')
×
248
        rpp = self.other.get('VirtualRootPhysicalPath', ('',))
1✔
249
        i = 0
1✔
250
        for name in rpp[:len(path)]:
1✔
251
            if path[i] == name:
1!
252
                i = i + 1
1✔
253
            else:
254
                break
×
255
        return path[i:]
1✔
256

257
    def physicalPathToURL(self, path, relative=0):
1✔
258
        """ Convert a physical path into a URL in the current context """
259
        path = self._script + list(
1✔
260
            map(quote, self.physicalPathToVirtualPath(path)))
261
        if relative:
1✔
262
            path.insert(0, '')
1✔
263
        else:
264
            path.insert(0, self['SERVER_URL'])
1✔
265
        return '/'.join(path)
1✔
266

267
    def physicalPathFromURL(self, URL):
1✔
268
        """ Convert a URL into a physical path in the current context.
269
            If the URL makes no sense in light of the current virtual
270
            hosting context, a ValueError is raised."""
271
        other = self.other
1✔
272
        path = [_p for _p in URL.split('/') if _p]
1✔
273

274
        if URL.find('://') >= 0:
1!
275
            path = path[2:]
1✔
276

277
        # Check the path against BASEPATH1
278
        vhbase = self._script
1✔
279
        vhbl = len(vhbase)
1✔
280
        if path[:vhbl] == vhbase:
1!
281
            path = path[vhbl:]
1✔
282
        else:
283
            raise ValueError('Url does not match virtual hosting context')
×
284
        vrpp = other.get('VirtualRootPhysicalPath', ('',))
1✔
285
        return list(vrpp) + list(map(unquote, path))
1✔
286

287
    def _resetURLS(self):
1✔
288
        other = self.other
1✔
289
        other['URL'] = '/'.join(
1✔
290
            [other['SERVER_URL']] + self._script + self._steps)
291
        for x in self._urls:
1!
292
            del self.other[x]
×
293
        self._urls = ()
1✔
294

295
    def getClientAddr(self):
1✔
296
        """ The IP address of the client.
297
        """
298
        return self._client_addr
1✔
299

300
    def setupLocale(self):
1✔
301
        envadapter = IUserPreferredLanguages(self, None)
1✔
302
        if envadapter is None:
1!
303
            self._locale = None
×
304
            return
×
305

306
        langs = envadapter.getPreferredLanguages()
1✔
307
        for httplang in langs:
1✔
308
            parts = (httplang.split('-') + [None, None])[:3]
1✔
309
            try:
1✔
310
                self._locale = locales.getLocale(*parts)
1✔
311
                return
1✔
312
            except LoadLocaleError:
1✔
313
                # Just try the next combination
314
                pass
1✔
315
        else:
316
            # No combination gave us an existing locale, so use the default,
317
            # which is guaranteed to exist
318
            self._locale = locales.getLocale(None, None, None)
1✔
319

320
    def __init__(self, stdin, environ, response, clean=0):
1✔
321
        self.__doc__ = None  # Make HTTPRequest objects unpublishable
1✔
322
        self._orig_env = environ
1✔
323
        # Avoid the overhead of scrubbing the environment in the
324
        # case of request cloning for traversal purposes. If the
325
        # clean flag is set, we know we can use the passed in
326
        # environ dict directly.
327
        if not clean:
1✔
328
            environ = sane_environment(environ)
1✔
329

330
        if 'HTTP_AUTHORIZATION' in environ:
1✔
331
            self._auth = environ['HTTP_AUTHORIZATION']
1✔
332
            response._auth = 1
1✔
333
            del environ['HTTP_AUTHORIZATION']
1✔
334

335
        self.stdin = stdin
1✔
336
        self.environ = environ
1✔
337
        get_env = environ.get
1✔
338
        self.response = response
1✔
339
        other = self.other = {'RESPONSE': response}
1✔
340
        self.form = {}
1✔
341
        self.taintedform = {}
1✔
342
        self.steps = []
1✔
343
        self._steps = []
1✔
344
        self._lazies = {}
1✔
345
        self._debug = DebugFlags()
1✔
346
        # We don't set up the locale initially but just on first access
347
        self._locale = _marker
1✔
348

349
        if 'REMOTE_ADDR' in environ:
1✔
350
            self._client_addr = environ['REMOTE_ADDR']
1✔
351
            if 'HTTP_X_FORWARDED_FOR' in environ and \
1✔
352
               self._client_addr in trusted_proxies:
353
                # REMOTE_ADDR is one of our trusted local proxies.
354
                # Not really very remote at all.  The proxy can tell us the
355
                # IP of the real remote client in the forwarded-for header
356
                # Skip the proxy-address itself though
357
                forwarded_for = [
1✔
358
                    e.strip()
359
                    for e in environ['HTTP_X_FORWARDED_FOR'].split(',')]
360
                forwarded_for.reverse()
1✔
361
                for entry in forwarded_for:
1!
362
                    if entry not in trusted_proxies:
1✔
363
                        self._client_addr = entry
1✔
364
                        break
1✔
365
        else:
366
            self._client_addr = ''
1✔
367

368
        ################################################################
369
        # Get base info first. This isn't likely to cause
370
        # errors and might be useful to error handlers.
371
        b = script = get_env('SCRIPT_NAME', '').strip()
1✔
372

373
        # _script and the other _names are meant for URL construction
374
        self._script = list(map(quote, [_s for _s in script.split('/') if _s]))
1✔
375

376
        while b and b[-1] == '/':
1!
377
            b = b[:-1]
×
378
        p = b.rfind('/')
1✔
379
        if p >= 0:
1!
380
            b = b[:p + 1]
×
381
        else:
382
            b = ''
1✔
383
        while b and b[0] == '/':
1!
384
            b = b[1:]
×
385

386
        server_url = get_env('SERVER_URL', None)
1✔
387
        if server_url is not None:
1✔
388
            other['SERVER_URL'] = server_url = server_url.strip()
1✔
389
        else:
390
            https_environ = environ.get('HTTPS', False)
1✔
391
            if https_environ and https_environ in ('on', 'ON', '1'):
1✔
392
                protocol = 'https'
1✔
393
            elif environ.get('SERVER_PORT_SECURE', None) == 1:
1✔
394
                protocol = 'https'
1✔
395
            elif (environ.get('REQUEST_SCHEME', '') or '').lower() == 'https':
1✔
396
                protocol = 'https'
1✔
397
            elif environ.get('wsgi.url_scheme') == 'https':
1✔
398
                protocol = 'https'
1✔
399
            else:
400
                protocol = 'http'
1✔
401

402
            if 'HTTP_HOST' in environ:
1✔
403
                host = environ['HTTP_HOST'].strip()
1✔
404
                hostname, port = splitport(host)
1✔
405

406
                # NOTE: some (DAV) clients manage to forget the port. This
407
                # can be fixed with the commented code below - the problem
408
                # is that it causes problems for virtual hosting. I've left
409
                # the commented code here in case we care enough to come
410
                # back and do anything with it later.
411
                #
412
                # if port is None and 'SERVER_PORT' in environ:
413
                #     s_port = environ['SERVER_PORT']
414
                #     if s_port not in ('80', '443'):
415
                #         port = s_port
416

417
            else:
418
                hostname = environ['SERVER_NAME'].strip()
1✔
419
                port = int(environ['SERVER_PORT'])
1✔
420
            self.setServerURL(protocol=protocol, hostname=hostname, port=port)
1✔
421
            server_url = other['SERVER_URL']
1✔
422

423
        if server_url[-1:] == '/':
1!
424
            server_url = server_url[:-1]
×
425

426
        if b:
1!
427
            self.base = f"{server_url}/{b}"
×
428
        else:
429
            self.base = server_url
1✔
430
        while script[:1] == '/':
1!
431
            script = script[1:]
×
432
        if script:
1!
433
            script = f"{server_url}/{script}"
×
434
        else:
435
            script = server_url
1✔
436
        other['URL'] = self.script = script
1✔
437
        other['method'] = environ.get('REQUEST_METHOD', 'GET').upper()
1✔
438

439
        # Make WEBDAV_SOURCE_PORT reachable with a simple REQUEST.get
440
        # to stay backwards-compatible
441
        if environ.get('WEBDAV_SOURCE_PORT'):
1✔
442
            other['WEBDAV_SOURCE_PORT'] = environ.get('WEBDAV_SOURCE_PORT')
1✔
443

444
        ################################################################
445
        # Cookie values should *not* be appended to existing form
446
        # vars with the same name - they are more like default values
447
        # for names not otherwise specified in the form.
448
        cookies = {}
1✔
449
        taintedcookies = {}
1✔
450
        k = get_env('HTTP_COOKIE', '')
1✔
451
        if k:
1✔
452
            parse_cookie(k, cookies)
1✔
453
            for k, v in cookies.items():
1✔
454
                istainted = 0
1✔
455
                if should_be_tainted(k):
1!
456
                    k = taint_string(k)
×
457
                    istainted = 1
×
458
                if should_be_tainted(v):
1!
459
                    v = taint_string(v)
×
460
                    istainted = 1
×
461
                if istainted:
1!
462
                    taintedcookies[k] = v
×
463
        self.cookies = cookies
1✔
464
        self.taintedcookies = taintedcookies
1✔
465

466
    def processInputs(
1✔
467
            self,
468
            # "static" variables that we want to be local for speed
469
            SEQUENCE=1,
470
            DEFAULT=2,
471
            RECORD=4,
472
            RECORDS=8,
473
            REC=12,  # RECORD | RECORDS
474
            EMPTY=16,
475
            CONVERTED=32,
476
            hasattr=hasattr,
477
            getattr=getattr,
478
            setattr=setattr):
479
        """Process request inputs
480

481
        See the `Zope Developer Guide Object Publishing chapter
482
        <https://zope.readthedocs.io/en/latest/zdgbook/ObjectPublishing.html>`_
483
        for a detailed explanation in the section `Marshalling Arguments from
484
        the Request`.
485

486
        We need to delay input parsing so that it is done under
487
        publisher control for error handling purposes.
488
        """
489
        response = self.response
1✔
490
        environ = self.environ
1✔
491
        method = environ.get('REQUEST_METHOD', 'GET')
1✔
492

493
        if method != 'GET':
1✔
494
            fp = self.stdin
1✔
495
        else:
496
            fp = None
1✔
497

498
        form = self.form
1✔
499
        other = self.other
1✔
500
        taintedform = self.taintedform
1✔
501

502
        # If 'QUERY_STRING' is not present in environ
503
        # FieldStorage will try to get it from sys.argv[1]
504
        # which is not what we need.
505
        if 'QUERY_STRING' not in environ:
1✔
506
            environ['QUERY_STRING'] = ''
1✔
507

508
        meth = None
1✔
509

510
        fs = ZopeFieldStorage(fp, environ)
1✔
511

512
        # Keep a reference to the FieldStorage. Otherwise it's
513
        # __del__ method is called too early and closing FieldStorage.file.
514
        self._hold(fs)
1✔
515

516
        if not hasattr(fs, 'list'):
1✔
517
            if 'HTTP_SOAPACTION' in environ:
1!
518
                # Stash XML request for interpretation by a SOAP-aware view
519
                other['SOAPXML'] = fs.value
×
520
            elif (method == 'POST'
1✔
521
                  and 'text/xml' in fs.headers.get('content-type', '')
522
                  and use_builtin_xmlrpc(self)):
523
                # Ye haaa, XML-RPC!
524
                meth, self.args = xmlrpc.parse_input(fs.value)
1✔
525
                response = xmlrpc.response(response)
1✔
526
                other['RESPONSE'] = self.response = response
1✔
527
                self.maybe_webdav_client = 0
1✔
528
            else:
529
                self._file = fs.file
1✔
530
        else:
531
            fslist = fs.list
1✔
532
            tuple_items = {}
1✔
533
            defaults = {}
1✔
534
            tainteddefaults = {}
1✔
535
            converter = None
1✔
536

537
            for item in fslist:  # form data
1✔
538
                # Note:
539
                # we want to support 2 use cases
540
                # 1. the form data has been created by the browser
541
                # 2. the form data is free standing
542
                # A browser internally works with character data,
543
                # which it encodes for transmission to the server --
544
                # usually with `self.charset`. Therefore, we
545
                # usually expect the form data to represent data
546
                # in this charset.
547
                # We make this same assumption also for free standing
548
                # form data, i.e. we expect the form creator to know
549
                # the server's charset. However, sometimes data cannot
550
                # be represented in this charset (e.g. arbitrary binary
551
                # data). To cover this case, we decode data
552
                # with the `surrogateescape` error handler (see PEP 383).
553
                # It allows to retrieve the original byte sequence.
554
                # With an encoding modifier, the form creator
555
                # can specify the correct encoding used by a form field value.
556
                # Note: we always expect the form field name
557
                # to be representable with `self.charset`. As those
558
                # names are expected to be `ASCII`, this should be no
559
                # big restriction.
560
                # Note: the use of `surrogateescape` can lead to delayed
561
                # problems when surrogates reach the application because
562
                # they cannot be encoded with a standard error handler.
563
                # We might want to prevent this.
564
                character_encoding = ''  # currently used encoding
1✔
565
                isFileUpload = 0
1✔
566
                key = item.name
1✔
567
                if key is None:
1!
568
                    continue
×
569
                key = item.name.encode("latin-1").decode(self.charset)
1✔
570

571
                if hasattr(item, 'file') and \
1✔
572
                   hasattr(item, 'filename') and \
573
                   hasattr(item, 'headers'):
574
                    item = FileUpload(item, self.charset)
1✔
575
                    isFileUpload = 1
1✔
576
                else:
577
                    character_encoding = self.charset
1✔
578
                    item = item.value.decode(
1✔
579
                        character_encoding, "surrogateescape")
580
                # from here on, `item` contains the field value
581
                # either as `FileUpload` or `str` with
582
                # `character_encoding` as encoding.
583
                # `key` the field name (`str`)
584

585
                flags = 0
1✔
586
                # Variables for potentially unsafe values.
587
                tainted = None
1✔
588
                converter_type = None
1✔
589

590
                # Loop through the different types and set
591
                # the appropriate flags
592

593
                # We'll search from the back to the front.
594
                # We'll do the search in two steps.  First, we'll
595
                # do a string search, and then we'll check it with
596
                # a re search.
597

598
                delim = key.rfind(':')
1✔
599
                if delim >= 0:
1✔
600
                    mo = search_type(key, delim)
1✔
601
                    if mo:
1!
602
                        delim = mo.start(0)
1✔
603
                    else:
604
                        delim = -1
×
605

606
                    while delim >= 0:
1!
607
                        type_name = key[delim + 1:]
1✔
608
                        key = key[:delim]
1✔
609
                        c = get_converter(type_name, None)
1✔
610

611
                        if c is not None:
1✔
612
                            converter = c
1✔
613
                            converter_type = type_name
1✔
614
                            flags = flags | CONVERTED
1✔
615
                        elif type_name == 'list':
1✔
616
                            flags = flags | SEQUENCE
1✔
617
                        elif type_name == 'tuple':
1✔
618
                            tuple_items[key] = 1
1✔
619
                            flags = flags | SEQUENCE
1✔
620
                        elif type_name == 'method' or type_name == 'action':
1✔
621
                            if delim:
1!
622
                                meth = key
1✔
623
                            else:
624
                                meth = item
×
625
                        elif (type_name == 'default_method'
1!
626
                              or type_name == 'default_action'):
627
                            if not meth:
×
628
                                if delim:
×
629
                                    meth = key
×
630
                                else:
631
                                    meth = item
×
632
                        elif type_name == 'default':
1✔
633
                            flags = flags | DEFAULT
1✔
634
                        elif type_name == 'record':
1✔
635
                            flags = flags | RECORD
1✔
636
                        elif type_name == 'records':
1✔
637
                            flags = flags | RECORDS
1✔
638
                        elif type_name == 'ignore_empty':
1!
639
                            if not item:
×
640
                                flags = flags | EMPTY
×
641
                        elif has_codec(type_name):
1!
642
                            # recode:
643
                            assert not isFileUpload, "cannot recode files"
1✔
644
                            item = item.encode(
1✔
645
                                character_encoding, "surrogateescape")
646
                            character_encoding = type_name
1✔
647
                            # we do not use `surrogateescape` as
648
                            # we immediately want to determine
649
                            # an incompatible encoding modifier
650
                            item = item.decode(character_encoding)
1✔
651

652
                        delim = key.rfind(':')
1✔
653
                        if delim < 0:
1✔
654
                            break
1✔
655
                        mo = search_type(key, delim)
1✔
656
                        if mo:
1!
657
                            delim = mo.start(0)
1✔
658
                        else:
659
                            delim = -1
×
660

661
                # Filter out special names from form:
662
                if key in isCGI_NAMEs or key.startswith('HTTP_'):
1!
663
                    continue
×
664

665
                # If the key is tainted, mark it so as well.
666
                tainted_key = key
1✔
667
                if should_be_tainted(key):
1✔
668
                    tainted_key = taint_string(key)
1✔
669

670
                if flags:
1✔
671

672
                    # skip over empty fields
673
                    if flags & EMPTY:
1!
674
                        continue
×
675

676
                    # Split the key and its attribute
677
                    if flags & REC:
1✔
678
                        key = key.split(".")
1✔
679
                        key, attr = ".".join(key[:-1]), key[-1]
1✔
680

681
                        # Update the tainted_key if necessary
682
                        tainted_key = key
1✔
683
                        if should_be_tainted(key):
1!
684
                            tainted_key = taint_string(key)
×
685

686
                        # Attributes cannot hold a <.
687
                        if should_be_tainted(attr):
1!
688
                            raise ValueError(
×
689
                                "%s is not a valid record attribute name" %
690
                                html.escape(attr, True))
691

692
                    # defer conversion
693
                    if flags & CONVERTED:
1✔
694
                        try:
1✔
695
                            if character_encoding and \
1✔
696
                               getattr(converter, "binary", False):
697
                                item = item.encode(character_encoding,
1✔
698
                                                   "surrogateescape")
699
                            item = converter(item)
1✔
700

701
                            # Flag potentially unsafe values
702
                            if converter_type in ('string', 'required', 'text',
1✔
703
                                                  'ustring', 'utext'):
704
                                if not isFileUpload and \
1✔
705
                                   should_be_tainted(item):
706
                                    tainted = taint_string(item)
1✔
707
                            elif converter_type in ('tokens', 'lines',
1✔
708
                                                    'utokens', 'ulines'):
709
                                is_tainted = 0
1✔
710
                                tainted = item[:]
1✔
711
                                for i in range(len(tainted)):
1✔
712
                                    if should_be_tainted(tainted[i]):
1✔
713
                                        is_tainted = 1
1✔
714
                                        tainted[i] = taint_string(tainted[i])
1✔
715
                                if not is_tainted:
1✔
716
                                    tainted = None
1✔
717

718
                        except Exception:
×
719
                            if not item and \
×
720
                               not (flags & DEFAULT) and \
721
                               key in defaults:
722
                                item = defaults[key]
×
723
                                if flags & RECORD:
×
724
                                    item = getattr(item, attr)
×
725
                                if flags & RECORDS:
×
726
                                    item = getattr(item[-1], attr)
×
727
                                if tainted_key in tainteddefaults:
×
728
                                    tainted = tainteddefaults[tainted_key]
×
729
                                    if flags & RECORD:
×
730
                                        tainted = getattr(tainted, attr)
×
731
                                    if flags & RECORDS:
×
732
                                        tainted = getattr(tainted[-1], attr)
×
733
                            else:
734
                                raise
×
735

736
                    elif not isFileUpload and should_be_tainted(item):
1✔
737
                        # Flag potentially unsafe values
738
                        tainted = taint_string(item)
1✔
739

740
                    # If the key is tainted, we need to store stuff in the
741
                    # tainted dict as well, even if the value is safe.
742
                    if should_be_tainted(tainted_key) and tainted is None:
1✔
743
                        tainted = item
1✔
744

745
                    # Determine which dictionary to use
746
                    if flags & DEFAULT:
1✔
747
                        mapping_object = defaults
1✔
748
                        tainted_mapping = tainteddefaults
1✔
749
                    else:
750
                        mapping_object = form
1✔
751
                        tainted_mapping = taintedform
1✔
752

753
                    # Insert in dictionary
754
                    if key in mapping_object:
1✔
755
                        if flags & RECORDS:
1✔
756
                            # Get the list and the last record
757
                            # in the list. reclist is mutable.
758
                            reclist = mapping_object[key]
1✔
759
                            x = reclist[-1]
1✔
760

761
                            if tainted:
1✔
762
                                # Store a tainted copy as well
763
                                if tainted_key not in tainted_mapping:
1!
764
                                    tainted_mapping[tainted_key] = deepcopy(
1✔
765
                                        reclist)
766
                                treclist = tainted_mapping[tainted_key]
1✔
767
                                lastrecord = treclist[-1]
1✔
768

769
                                if not hasattr(lastrecord, attr):
1✔
770
                                    if flags & SEQUENCE:
1✔
771
                                        tainted = [tainted]
1✔
772
                                    setattr(lastrecord, attr, tainted)
1✔
773
                                else:
774
                                    if flags & SEQUENCE:
1✔
775
                                        getattr(
1✔
776
                                            lastrecord, attr).append(tainted)
777
                                    else:
778
                                        newrec = record()
1✔
779
                                        setattr(newrec, attr, tainted)
1✔
780
                                        treclist.append(newrec)
1✔
781

782
                            elif tainted_key in tainted_mapping:
1✔
783
                                # If we already put a tainted value into this
784
                                # recordset, we need to make sure the whole
785
                                # recordset is built.
786
                                treclist = tainted_mapping[tainted_key]
1✔
787
                                lastrecord = treclist[-1]
1✔
788
                                copyitem = item
1✔
789

790
                                if not hasattr(lastrecord, attr):
1✔
791
                                    if flags & SEQUENCE:
1✔
792
                                        copyitem = [copyitem]
1✔
793
                                    setattr(lastrecord, attr, copyitem)
1✔
794
                                else:
795
                                    if flags & SEQUENCE:
1✔
796
                                        getattr(
1✔
797
                                            lastrecord, attr).append(copyitem)
798
                                    else:
799
                                        newrec = record()
1✔
800
                                        setattr(newrec, attr, copyitem)
1✔
801
                                        treclist.append(newrec)
1✔
802

803
                            if not hasattr(x, attr):
1✔
804
                                # If the attribute does not
805
                                # exist, set it
806
                                if flags & SEQUENCE:
1✔
807
                                    item = [item]
1✔
808
                                setattr(x, attr, item)
1✔
809
                            else:
810
                                if flags & SEQUENCE:
1✔
811
                                    # If the attribute is a
812
                                    # sequence, append the item
813
                                    # to the existing attribute
814
                                    y = getattr(x, attr)
1✔
815
                                    y.append(item)
1✔
816
                                    setattr(x, attr, y)
1✔
817
                                else:
818
                                    # Create a new record and add
819
                                    # it to the list
820
                                    n = record()
1✔
821
                                    setattr(n, attr, item)
1✔
822
                                    mapping_object[key].append(n)
1✔
823
                        elif flags & RECORD:
1✔
824
                            b = mapping_object[key]
1✔
825
                            if flags & SEQUENCE:
1!
826
                                item = [item]
×
827
                                if not hasattr(b, attr):
×
828
                                    # if it does not have the
829
                                    # attribute, set it
830
                                    setattr(b, attr, item)
×
831
                                else:
832
                                    # it has the attribute so
833
                                    # append the item to it
834
                                    setattr(b, attr, getattr(b, attr) + item)
×
835
                            else:
836
                                # it is not a sequence so
837
                                # set the attribute
838
                                setattr(b, attr, item)
1✔
839

840
                            # Store a tainted copy as well if necessary
841
                            if tainted:
1✔
842
                                if tainted_key not in tainted_mapping:
1!
843
                                    tainted_mapping[tainted_key] = deepcopy(
1✔
844
                                        mapping_object[key])
845
                                b = tainted_mapping[tainted_key]
1✔
846
                                if flags & SEQUENCE:
1!
847
                                    seq = getattr(b, attr, [])
×
848
                                    seq.append(tainted)
×
849
                                    setattr(b, attr, seq)
×
850
                                else:
851
                                    setattr(b, attr, tainted)
1✔
852

853
                            elif tainted_key in tainted_mapping:
1✔
854
                                # If we already put a tainted value into this
855
                                # record, we need to make sure the whole record
856
                                # is built.
857
                                b = tainted_mapping[tainted_key]
1✔
858
                                if flags & SEQUENCE:
1!
859
                                    seq = getattr(b, attr, [])
×
860
                                    seq.append(item)
×
861
                                    setattr(b, attr, seq)
×
862
                                else:
863
                                    setattr(b, attr, item)
1✔
864

865
                        else:
866
                            # it is not a record or list of records
867
                            found = mapping_object[key]
1✔
868

869
                            if tainted:
1✔
870
                                # Store a tainted version if necessary
871
                                if tainted_key not in tainted_mapping:
1!
872
                                    copied = deepcopy(found)
1✔
873
                                    if isinstance(copied, list):
1✔
874
                                        tainted_mapping[tainted_key] = copied
1✔
875
                                    else:
876
                                        tainted_mapping[tainted_key] = [copied]
1✔
877
                                tainted_mapping[tainted_key].append(tainted)
1✔
878

879
                            elif tainted_key in tainted_mapping:
1✔
880
                                # We may already have encountered a tainted
881
                                # value for this key, and the tainted_mapping
882
                                # needs to hold all the values.
883
                                tfound = tainted_mapping[tainted_key]
1✔
884
                                if isinstance(tfound, list):
1!
885
                                    tainted_mapping[tainted_key].append(item)
1✔
886
                                else:
887
                                    tainted_mapping[tainted_key] = [tfound,
×
888
                                                                    item]
889

890
                            if isinstance(found, list):
1✔
891
                                found.append(item)
1✔
892
                            else:
893
                                found = [found, item]
1✔
894
                                mapping_object[key] = found
1✔
895
                    else:
896
                        # The dictionary does not have the key
897
                        if flags & RECORDS:
1✔
898
                            # Create a new record, set its attribute
899
                            # and put it in the dictionary as a list
900
                            a = record()
1✔
901
                            if flags & SEQUENCE:
1!
902
                                item = [item]
×
903
                            setattr(a, attr, item)
1✔
904
                            mapping_object[key] = [a]
1✔
905

906
                            if tainted:
1✔
907
                                # Store a tainted copy if necessary
908
                                a = record()
1✔
909
                                if flags & SEQUENCE:
1!
910
                                    tainted = [tainted]
×
911
                                setattr(a, attr, tainted)
1✔
912
                                tainted_mapping[tainted_key] = [a]
1✔
913

914
                        elif flags & RECORD:
1✔
915
                            # Create a new record, set its attribute
916
                            # and put it in the dictionary
917
                            if flags & SEQUENCE:
1!
918
                                item = [item]
×
919
                            r = mapping_object[key] = record()
1✔
920
                            setattr(r, attr, item)
1✔
921

922
                            if tainted:
1✔
923
                                # Store a tainted copy if necessary
924
                                if flags & SEQUENCE:
1!
925
                                    tainted = [tainted]
×
926
                                r = tainted_mapping[tainted_key] = record()
1✔
927
                                setattr(r, attr, tainted)
1✔
928
                        else:
929
                            # it is not a record or list of records
930
                            if flags & SEQUENCE:
1✔
931
                                item = [item]
1✔
932
                            mapping_object[key] = item
1✔
933

934
                            if tainted:
1✔
935
                                # Store a tainted copy if necessary
936
                                if flags & SEQUENCE:
1✔
937
                                    tainted = [tainted]
1✔
938
                                tainted_mapping[tainted_key] = tainted
1✔
939

940
                else:
941
                    # This branch is for case when no type was specified.
942
                    mapping_object = form
1✔
943

944
                    if not isFileUpload and should_be_tainted(item):
1✔
945
                        tainted = taint_string(item)
1✔
946
                    elif should_be_tainted(key):
1✔
947
                        tainted = item
1✔
948

949
                    # Insert in dictionary
950
                    if key in mapping_object:
1✔
951
                        # it is not a record or list of records
952
                        found = mapping_object[key]
1✔
953

954
                        if tainted:
1✔
955
                            # Store a tainted version if necessary
956
                            if tainted_key not in taintedform:
1✔
957
                                copied = deepcopy(found)
1✔
958
                                if isinstance(copied, list):
1!
959
                                    taintedform[tainted_key] = copied
×
960
                                else:
961
                                    taintedform[tainted_key] = [copied]
1✔
962
                            elif not isinstance(
1!
963
                                    taintedform[tainted_key], list):
964
                                taintedform[tainted_key] = [
1✔
965
                                    taintedform[tainted_key]]
966
                            taintedform[tainted_key].append(tainted)
1✔
967

968
                        elif tainted_key in taintedform:
1✔
969
                            # We may already have encountered a tainted value
970
                            # for this key, and the taintedform needs to hold
971
                            # all the values.
972
                            tfound = taintedform[tainted_key]
1✔
973
                            if isinstance(tfound, list):
1!
974
                                taintedform[tainted_key].append(item)
×
975
                            else:
976
                                taintedform[tainted_key] = [tfound, item]
1✔
977

978
                        if isinstance(found, list):
1!
979
                            found.append(item)
×
980
                        else:
981
                            found = [found, item]
1✔
982
                            mapping_object[key] = found
1✔
983
                    else:
984
                        mapping_object[key] = item
1✔
985
                        if tainted:
1✔
986
                            taintedform[tainted_key] = tainted
1✔
987

988
            # insert defaults into form dictionary
989
            if defaults:
1✔
990
                for key, value in defaults.items():
1✔
991
                    tainted_key = key
1✔
992
                    if should_be_tainted(key):
1!
993
                        tainted_key = taint_string(key)
×
994

995
                    if key not in form:
1✔
996
                        # if the form does not have the key,
997
                        # set the default
998
                        form[key] = value
1✔
999

1000
                        if tainted_key in tainteddefaults:
1!
1001
                            taintedform[tainted_key] = \
1✔
1002
                                tainteddefaults[tainted_key]
1003
                    else:
1004
                        # The form has the key
1005
                        tdefault = tainteddefaults.get(tainted_key, value)
1✔
1006
                        if isinstance(value, record):
1✔
1007
                            # if the key is mapped to a record, get the
1008
                            # record
1009
                            r = form[key]
1✔
1010

1011
                            # First deal with tainted defaults.
1012
                            if tainted_key in taintedform:
1✔
1013
                                tainted = taintedform[tainted_key]
1✔
1014
                                for k, v in tdefault.__dict__.items():
1✔
1015
                                    if not hasattr(tainted, k):
1✔
1016
                                        setattr(tainted, k, v)
1✔
1017

1018
                            elif tainted_key in tainteddefaults:
1✔
1019
                                # Find out if any of the tainted default
1020
                                # attributes needs to be copied over.
1021
                                missesdefault = 0
1✔
1022
                                for k, v in tdefault.__dict__.items():
1✔
1023
                                    if not hasattr(r, k):
1✔
1024
                                        missesdefault = 1
1✔
1025
                                        break
1✔
1026
                                if missesdefault:
1✔
1027
                                    tainted = deepcopy(r)
1✔
1028
                                    for k, v in tdefault.__dict__.items():
1✔
1029
                                        if not hasattr(tainted, k):
1✔
1030
                                            setattr(tainted, k, v)
1✔
1031
                                    taintedform[tainted_key] = tainted
1✔
1032

1033
                            for k, v in value.__dict__.items():
1✔
1034
                                # loop through the attributes and value
1035
                                # in the default dictionary
1036
                                if not hasattr(r, k):
1✔
1037
                                    # if the form dictionary doesn't have
1038
                                    # the attribute, set it to the default
1039
                                    setattr(r, k, v)
1✔
1040
                            form[key] = r
1✔
1041

1042
                        elif isinstance(value, list):
1!
1043
                            # the default value is a list
1044
                            val = form[key]
1✔
1045
                            if not isinstance(val, list):
1!
1046
                                val = [val]
×
1047

1048
                            # First deal with tainted copies
1049
                            if tainted_key in taintedform:
1✔
1050
                                tainted = taintedform[tainted_key]
1✔
1051
                                if not isinstance(tainted, list):
1!
1052
                                    tainted = [tainted]
×
1053
                                for defitem in tdefault:
1✔
1054
                                    if isinstance(defitem, record):
1✔
1055
                                        for k, v in defitem.__dict__.items():
1✔
1056
                                            for origitem in tainted:
1✔
1057
                                                if not hasattr(origitem, k):
1✔
1058
                                                    setattr(origitem, k, v)
1✔
1059
                                    else:
1060
                                        if defitem not in tainted:
1!
1061
                                            tainted.append(defitem)
1✔
1062
                                taintedform[tainted_key] = tainted
1✔
1063

1064
                            elif tainted_key in tainteddefaults:
1✔
1065
                                missesdefault = 0
1✔
1066
                                for defitem in tdefault:
1✔
1067
                                    if isinstance(defitem, record):
1✔
1068
                                        try:
1✔
1069
                                            for k, v in \
1✔
1070
                                                    defitem.__dict__.items():
1071
                                                for origitem in val:
1✔
1072
                                                    if not hasattr(
1✔
1073
                                                            origitem, k):
1074
                                                        missesdefault = 1
1✔
1075
                                                        raise NestedLoopExit
1✔
1076
                                        except NestedLoopExit:
1✔
1077
                                            break
1✔
1078
                                    else:
1079
                                        if defitem not in val:
1!
1080
                                            missesdefault = 1
1✔
1081
                                            break
1✔
1082
                                if missesdefault:
1✔
1083
                                    tainted = deepcopy(val)
1✔
1084
                                    for defitem in tdefault:
1✔
1085
                                        if isinstance(defitem, record):
1✔
1086
                                            for k, v in (
1✔
1087
                                                    defitem.__dict__.items()):
1088
                                                for origitem in tainted:
1✔
1089
                                                    if not hasattr(
1✔
1090
                                                            origitem, k):
1091
                                                        setattr(origitem, k, v)
1✔
1092
                                        else:
1093
                                            if defitem not in tainted:
1!
1094
                                                tainted.append(defitem)
1✔
1095
                                    taintedform[tainted_key] = tainted
1✔
1096

1097
                            for x in value:
1✔
1098
                                # for each x in the list
1099
                                if isinstance(x, record):
1✔
1100
                                    # if the x is a record
1101
                                    for k, v in x.__dict__.items():
1✔
1102

1103
                                        # loop through each
1104
                                        # attribute and value in
1105
                                        # the record
1106

1107
                                        for y in val:
1✔
1108

1109
                                            # loop through each
1110
                                            # record in the form
1111
                                            # list if it doesn't
1112
                                            # have the attributes
1113
                                            # in the default
1114
                                            # dictionary, set them
1115

1116
                                            if not hasattr(y, k):
1✔
1117
                                                setattr(y, k, v)
1✔
1118
                                else:
1119
                                    # x is not a record
1120
                                    if x not in val:
1!
1121
                                        val.append(x)
1✔
1122
                            form[key] = val
1✔
1123
                        else:
1124
                            # The form has the key, the key is not mapped
1125
                            # to a record or sequence so do nothing
1126
                            pass
1✔
1127

1128
            # Convert to tuples
1129
            if tuple_items:
1✔
1130
                for key in tuple_items.keys():
1✔
1131
                    # Split the key and get the attr
1132
                    k = key.split(".")
1✔
1133
                    k, attr = '.'.join(k[:-1]), k[-1]
1✔
1134
                    a = attr
1✔
1135
                    new = ''
1✔
1136
                    # remove any type_names in the attr
1137
                    while not a == '':
1✔
1138
                        a = a.split(":")
1✔
1139
                        a, new = ':'.join(a[:-1]), a[-1]
1✔
1140
                    attr = new
1✔
1141
                    if k in form:
1✔
1142
                        # If the form has the split key get its value
1143
                        tainted_split_key = k
1✔
1144
                        if should_be_tainted(k):
1!
1145
                            tainted_split_key = taint_string(k)
×
1146
                        item = form[k]
1✔
1147
                        if isinstance(item, record):
1!
1148
                            # if the value is mapped to a record, check if it
1149
                            # has the attribute, if it has it, convert it to
1150
                            # a tuple and set it
1151
                            if hasattr(item, attr):
×
1152
                                value = tuple(getattr(item, attr))
×
1153
                                setattr(item, attr, value)
×
1154
                        else:
1155
                            # It is mapped to a list of  records
1156
                            for x in item:
1✔
1157
                                # loop through the records
1158
                                if hasattr(x, attr):
1!
1159
                                    # If the record has the attribute
1160
                                    # convert it to a tuple and set it
1161
                                    value = tuple(getattr(x, attr))
1✔
1162
                                    setattr(x, attr, value)
1✔
1163

1164
                        # Do the same for the tainted counterpart
1165
                        if tainted_split_key in taintedform:
1✔
1166
                            tainted = taintedform[tainted_split_key]
1✔
1167
                            if isinstance(item, record):
1!
1168
                                seq = tuple(getattr(tainted, attr))
×
1169
                                setattr(tainted, attr, seq)
×
1170
                            else:
1171
                                for trec in tainted:
1✔
1172
                                    if hasattr(trec, attr):
1!
1173
                                        seq = getattr(trec, attr)
1✔
1174
                                        seq = tuple(seq)
1✔
1175
                                        setattr(trec, attr, seq)
1✔
1176
                    else:
1177
                        # the form does not have the split key
1178
                        tainted_key = key
1✔
1179
                        if should_be_tainted(key):
1!
1180
                            tainted_key = taint_string(key)
×
1181
                        if key in form:
1✔
1182
                            # if it has the original key, get the item
1183
                            # convert it to a tuple
1184
                            item = form[key]
1✔
1185
                            item = tuple(form[key])
1✔
1186
                            form[key] = item
1✔
1187

1188
                        if tainted_key in taintedform:
1✔
1189
                            tainted = tuple(taintedform[tainted_key])
1✔
1190
                            taintedform[tainted_key] = tainted
1✔
1191

1192
        if meth:
1✔
1193
            if 'PATH_INFO' in environ:
1✔
1194
                path = environ['PATH_INFO']
1✔
1195
                while path[-1:] == '/':
1!
1196
                    path = path[:-1]
×
1197
            else:
1198
                path = ''
1✔
1199
            other['PATH_INFO'] = f"{path}/{meth}"
1✔
1200
            self._hacked_path = 1
1✔
1201

1202
    def resolve_url(self, url):
1✔
1203
        # Attempt to resolve a url into an object in the Zope
1204
        # namespace. The url must be a fully-qualified url. The
1205
        # method will return the requested object if it is found
1206
        # or raise the same HTTP error that would be raised in
1207
        # the case of a real web request. If the passed in url
1208
        # does not appear to describe an object in the system
1209
        # namespace (e.g. the host, port or script name don't
1210
        # match that of the current request), a ValueError will
1211
        # be raised.
1212
        if url.find(self.script) != 0:
1!
1213
            raise ValueError('Different namespace.')
×
1214
        path = url[len(self.script):]
1✔
1215
        while path and path[0] == '/':
1✔
1216
            path = path[1:]
1✔
1217
        while path and path[-1] == '/':
1!
1218
            path = path[:-1]
×
1219
        req = self.clone()
1✔
1220
        rsp = req.response
1✔
1221
        req['PATH_INFO'] = path
1✔
1222
        object = None
1✔
1223

1224
        # Try to traverse to get an object. Note that we call
1225
        # the exception method on the response, but we don't
1226
        # want to actually abort the current transaction
1227
        # (which is usually the default when the exception
1228
        # method is called on the response).
1229
        try:
1✔
1230
            object = req.traverse(path)
1✔
1231
        except Exception as exc:
1✔
1232
            rsp.exception()
1✔
1233
            req.clear()
1✔
1234
            raise exc.__class__(rsp.errmsg)
1✔
1235

1236
        # The traversal machinery may return a "default object"
1237
        # like an index_html document. This is not appropriate
1238
        # in the context of the resolve_url method so we need
1239
        # to ensure we are getting the actual object named by
1240
        # the given url, and not some kind of default object.
1241
        if hasattr(object, 'id'):
1!
1242
            if callable(object.id):
×
1243
                name = object.id()
×
1244
            else:
1245
                name = object.id
×
1246
        elif hasattr(object, '__name__'):
1!
1247
            name = object.__name__
×
1248
        else:
1249
            name = ''
1✔
1250
        if name != os.path.split(path)[-1]:
1!
1251
            object = req.PARENTS[0]
×
1252

1253
        req.clear()
1✔
1254
        return object
1✔
1255

1256
    def clone(self):
1✔
1257
        # Return a clone of the current request object
1258
        # that may be used to perform object traversal.
1259
        environ = self.environ.copy()
1✔
1260
        environ['REQUEST_METHOD'] = 'GET'
1✔
1261
        if self._auth:
1✔
1262
            environ['HTTP_AUTHORIZATION'] = self._auth
1✔
1263
        if self.response is not None:
1✔
1264
            response = self.response.__class__()
1✔
1265
        else:
1266
            response = None
1✔
1267
        clone = self.__class__(None, environ, response, clean=1)
1✔
1268
        clone['PARENTS'] = [self['PARENTS'][-1]]
1✔
1269
        directlyProvides(clone, *directlyProvidedBy(self))
1✔
1270
        return clone
1✔
1271

1272
    def getHeader(self, name, default=None, literal=False):
1✔
1273
        """Return the named HTTP header, or an optional default
1274
        argument or None if the header is not found. Note that
1275
        both original and CGI-ified header names are recognized,
1276
        e.g. 'Content-Type', 'CONTENT_TYPE' and 'HTTP_CONTENT_TYPE'
1277
        should all return the Content-Type header, if available.
1278
        """
1279
        environ = self.environ
1✔
1280
        if not literal:
1✔
1281
            name = name.replace('-', '_').upper()
1✔
1282
        val = environ.get(name, None)
1✔
1283
        if val is not None:
1✔
1284
            return val
1✔
1285
        if name[:5] != 'HTTP_':
1!
1286
            name = 'HTTP_%s' % name
1✔
1287
        return environ.get(name, default)
1✔
1288

1289
    get_header = getHeader  # BBB
1✔
1290

1291
    def get(self, key, default=None, returnTaints=0,
1✔
1292
            URLmatch=re.compile('URL(PATH)?([0-9]+)$').match,
1293
            BASEmatch=re.compile('BASE(PATH)?([0-9]+)$').match,
1294
            ):
1295
        """Get a variable value
1296

1297
        Return a value for the variable key, or default if not found.
1298

1299
        If key is "REQUEST", return the request.
1300
        Otherwise, the value will be looked up from one of the request data
1301
        categories. The search order is:
1302
        other (the target for explicitly set variables),
1303
        the special URL and BASE variables,
1304
        environment variables,
1305
        common variables (defined by the request class),
1306
        lazy variables (set with set_lazy),
1307
        form data and cookies.
1308

1309
        If returnTaints has a true value, then the access to
1310
        form and cookie variables returns values with special
1311
        protection against embedded HTML fragments to counter
1312
        some cross site scripting attacks.
1313
        """
1314

1315
        if key == 'REQUEST':
1✔
1316
            return self
1✔
1317

1318
        other = self.other
1✔
1319
        if key in other:
1✔
1320
            return other[key]
1✔
1321

1322
        if key[:1] == 'U':
1✔
1323
            match = URLmatch(key)
1✔
1324
            if match is not None:
1!
1325
                pathonly, n = match.groups()
1✔
1326
                path = self._script + self._steps
1✔
1327
                n = len(path) - int(n)
1✔
1328
                if n < 0:
1✔
1329
                    raise KeyError(key)
1✔
1330
                if pathonly:
1✔
1331
                    path = [''] + path[:n]
1✔
1332
                else:
1333
                    path = [other['SERVER_URL']] + path[:n]
1✔
1334
                URL = '/'.join(path)
1✔
1335
                if 'PUBLISHED' in other:
1✔
1336
                    # Don't cache URLs until publishing traversal is done.
1337
                    other[key] = URL
1✔
1338
                    self._urls = self._urls + (key,)
1✔
1339
                return URL
1✔
1340

1341
        if key in isCGI_NAMEs or key[:5] == 'HTTP_':
1✔
1342
            environ = self.environ
1✔
1343
            if key in environ and (key not in hide_key):
1✔
1344
                return environ[key]
1✔
1345
            return ''
1✔
1346

1347
        if key[:1] == 'B':
1✔
1348
            match = BASEmatch(key)
1✔
1349
            if match is not None:
1✔
1350
                pathonly, n = match.groups()
1✔
1351
                path = self._steps
1✔
1352
                n = int(n)
1✔
1353
                if n:
1✔
1354
                    n = n - 1
1✔
1355
                    if len(path) < n:
1✔
1356
                        raise KeyError(key)
1✔
1357

1358
                    v = self._script + path[:n]
1✔
1359
                else:
1360
                    v = self._script[:-1]
1✔
1361
                if pathonly:
1✔
1362
                    v.insert(0, '')
1✔
1363
                else:
1364
                    v.insert(0, other['SERVER_URL'])
1✔
1365
                URL = '/'.join(v)
1✔
1366
                if 'PUBLISHED' in other:
1✔
1367
                    # Don't cache URLs until publishing traversal is done.
1368
                    other[key] = URL
1✔
1369
                    self._urls = self._urls + (key,)
1✔
1370
                return URL
1✔
1371

1372
            if key == 'BODY' and self._file is not None:
1✔
1373
                p = self._file.tell()
1✔
1374
                self._file.seek(0)
1✔
1375
                v = self._file.read()
1✔
1376
                self._file.seek(p)
1✔
1377
                self.other[key] = v
1✔
1378
                return v
1✔
1379

1380
            if key == 'BODYFILE' and self._file is not None:
1!
1381
                v = self._file
1✔
1382
                self.other[key] = v
1✔
1383
                return v
1✔
1384

1385
        v = self.common.get(key, _marker)
1✔
1386
        if v is not _marker:
1!
1387
            return v
×
1388

1389
        if self._lazies:
1!
1390
            v = self._lazies.get(key, _marker)
×
1391
            if v is not _marker:
×
1392
                if callable(v):
×
1393
                    v = v()
×
1394
                self[key] = v  # Promote lazy value
×
1395
                del self._lazies[key]
×
1396
                return v
×
1397

1398
        # Return tainted data first (marked as suspect)
1399
        if returnTaints:
1✔
1400
            v = self.taintedform.get(key, _marker)
1✔
1401
            if v is not _marker:
1!
1402
                return v
×
1403

1404
        # Untrusted data *after* trusted data
1405
        v = self.form.get(key, _marker)
1✔
1406
        if v is not _marker:
1✔
1407
            return v
1✔
1408

1409
        # Return tainted data first (marked as suspect)
1410
        if returnTaints:
1✔
1411
            v = self.taintedcookies.get(key, _marker)
1✔
1412
            if v is not _marker:
1!
1413
                return v
×
1414

1415
        # Untrusted data *after* trusted data
1416
        v = self.cookies.get(key, _marker)
1✔
1417
        if v is not _marker:
1!
1418
            return v
×
1419

1420
        return default
1✔
1421

1422
    def __getitem__(self, key, default=_marker, returnTaints=0):
1✔
1423
        v = self.get(key, default, returnTaints=returnTaints)
1✔
1424
        if v is _marker:
1✔
1425
            raise KeyError(key)
1✔
1426
        return v
1✔
1427

1428
    # Using the getattr protocol to retrieve form values and similar
1429
    # is discouraged and is likely to be deprecated in the future.
1430
    # request.get(key) or request[key] should be used instead
1431
    def __getattr__(self, key, default=_marker, returnTaints=0):
1✔
1432
        v = self.get(key, default, returnTaints=returnTaints)
1✔
1433
        if v is _marker:
1✔
1434
            if key == 'locale':
1✔
1435
                # we only create the _locale on first access, as setting it
1436
                # up might be slow and we don't want to slow down every
1437
                # request
1438
                if self._locale is _marker:
1✔
1439
                    self.setupLocale()
1✔
1440
                return self._locale
1✔
1441
            if key == 'debug':
1✔
1442
                return self._debug
1✔
1443
            raise AttributeError(key)
1✔
1444
        return v
1✔
1445

1446
    def set_lazy(self, key, callable):
1✔
1447
        self._lazies[key] = callable
×
1448

1449
    def __contains__(self, key, returnTaints=0):
1✔
1450
        return self.has_key(key, returnTaints=returnTaints)  # NOQA
1✔
1451

1452
    def has_key(self, key, returnTaints=0):
1✔
1453
        try:
1✔
1454
            self.__getitem__(key, returnTaints=returnTaints)
1✔
1455
        except Exception:
1✔
1456
            return 0
1✔
1457
        else:
1458
            return 1
1✔
1459

1460
    def keys(self, returnTaints=0):
1✔
1461
        keys = {}
1✔
1462
        keys.update(self.common)
1✔
1463
        keys.update(self._lazies)
1✔
1464

1465
        for key in self.environ.keys():
1✔
1466
            if (key in isCGI_NAMEs or key[:5] == 'HTTP_') and \
1✔
1467
               (key not in hide_key):
1468
                keys[key] = 1
1✔
1469

1470
        # Cache URLN and BASEN in self.other.
1471
        # This relies on a side effect of has_key.
1472
        n = 0
1✔
1473
        while 1:
1474
            n = n + 1
1✔
1475
            key = "URL%s" % n
1✔
1476
            if key not in self:  # NOQA
1✔
1477
                break
1✔
1478

1479
        n = 0
1✔
1480
        while 1:
1481
            n = n + 1
1✔
1482
            key = "BASE%s" % n
1✔
1483
            if key not in self:  # NOQA
1✔
1484
                break
1✔
1485

1486
        keys.update(self.other)
1✔
1487
        keys.update(self.cookies)
1✔
1488
        if returnTaints:
1!
1489
            keys.update(self.taintedcookies)
×
1490
        keys.update(self.form)
1✔
1491
        if returnTaints:
1!
1492
            keys.update(self.taintedform)
×
1493

1494
        keys = list(keys.keys())
1✔
1495
        keys.sort()
1✔
1496

1497
        return keys
1✔
1498

1499
    def __str__(self):
1✔
1500
        result = "<h3>form</h3><table>"
1✔
1501
        row = '<tr valign="top" align="left"><th>%s</th><td>%s</td></tr>'
1✔
1502
        for k, v in _filterPasswordFields(self.form.items()):
1✔
1503
            result = result + row % (
1✔
1504
                html.escape(k, False), html.escape(repr(v), False))
1505
        result = result + "</table><h3>cookies</h3><table>"
1✔
1506
        for k, v in _filterPasswordFields(self.cookies.items()):
1!
1507
            result = result + row % (
×
1508
                html.escape(k, False), html.escape(repr(v), False))
1509
        result = result + "</table><h3>lazy items</h3><table>"
1✔
1510
        for k, v in _filterPasswordFields(self._lazies.items()):
1!
1511
            result = result + row % (
×
1512
                html.escape(k, False), html.escape(repr(v), False))
1513
        result = result + "</table><h3>other</h3><table>"
1✔
1514
        for k, v in _filterPasswordFields(self.other.items()):
1✔
1515
            if k in ('PARENTS', 'RESPONSE'):
1✔
1516
                continue
1✔
1517
            result = result + row % (
1✔
1518
                html.escape(k, False), html.escape(repr(v), False))
1519

1520
        for n in "0123456789":
1✔
1521
            key = "URL%s" % n
1✔
1522
            try:
1✔
1523
                result = result + row % (key, html.escape(self[key], False))
1✔
1524
            except KeyError:
1✔
1525
                pass
1✔
1526
        for n in "0123456789":
1✔
1527
            key = "BASE%s" % n
1✔
1528
            try:
1✔
1529
                result = result + row % (key, html.escape(self[key], False))
1✔
1530
            except KeyError:
1✔
1531
                pass
1✔
1532

1533
        result = result + "</table><h3>environ</h3><table>"
1✔
1534
        for k, v in self.environ.items():
1✔
1535
            if k not in hide_key:
1!
1536
                result = result + row % (
1✔
1537
                    html.escape(k, False), html.escape(repr(v), False))
1538
        return result + "</table>"
1✔
1539

1540
    def __repr__(self):
1✔
1541
        return f"<{self.__class__.__name__}, URL={self.get('URL')}>"
1✔
1542

1543
    def text(self):
1✔
1544
        result = "FORM\n\n"
1✔
1545
        row = '%-20s %s\n'
1✔
1546
        for k, v in _filterPasswordFields(self.form.items()):
1✔
1547
            result = result + row % (k, repr(v))
1✔
1548
        result = result + "\nCOOKIES\n\n"
1✔
1549
        for k, v in _filterPasswordFields(self.cookies.items()):
1!
1550
            result = result + row % (k, repr(v))
×
1551
        result = result + "\nLAZY ITEMS\n\n"
1✔
1552
        for k, v in _filterPasswordFields(self._lazies.items()):
1!
1553
            result = result + row % (k, repr(v))
×
1554
        result = result + "\nOTHER\n\n"
1✔
1555
        for k, v in _filterPasswordFields(self.other.items()):
1✔
1556
            if k in ('PARENTS', 'RESPONSE'):
1✔
1557
                continue
1✔
1558
            result = result + row % (k, repr(v))
1✔
1559

1560
        for n in "0123456789":
1✔
1561
            key = "URL%s" % n
1✔
1562
            try:
1✔
1563
                result = result + row % (key, self[key])
1✔
1564
            except KeyError:
1✔
1565
                pass
1✔
1566
        for n in "0123456789":
1✔
1567
            key = "BASE%s" % n
1✔
1568
            try:
1✔
1569
                result = result + row % (key, self[key])
1✔
1570
            except KeyError:
1✔
1571
                pass
1✔
1572

1573
        result = result + "\nENVIRON\n\n"
1✔
1574
        for k, v in self.environ.items():
1✔
1575
            if k not in hide_key:
1!
1576
                result = result + row % (k, v)
1✔
1577
        return result
1✔
1578

1579
    def _authUserPW(self):
1✔
1580
        # Can return None
1581
        return basic_auth_decode(self._auth)
1✔
1582

1583
    def taintWrapper(self, enabled=TAINTING_ENABLED):
1✔
1584
        return enabled and TaintRequestWrapper(self) or self
1✔
1585

1586
    # Original version: zope.publisher.http.HTTPRequest.shiftNameToApplication
1587
    def shiftNameToApplication(self):
1✔
1588
        """see zope.publisher.interfaces.http.IVirtualHostRequest"""
1589
        if len(self._steps) == 1:
1!
1590
            self._script.append(self._steps.pop())
1✔
1591
            self._resetURLS()
1✔
1592
            return
1✔
1593

1594
        raise ValueError("Can only shift leading traversal "
×
1595
                         "names to application names")
1596

1597
    def getURL(self):
1✔
1598
        return self.URL
×
1599

1600

1601
class WSGIRequest(HTTPRequest):
1✔
1602
    # A request object for WSGI, no docstring to avoid being publishable.
1603
    pass
1✔
1604

1605

1606
class TaintRequestWrapper:
1✔
1607

1608
    def __init__(self, req):
1✔
1609
        self._req = req
1✔
1610

1611
    def __contains__(self, *args, **kw):
1✔
1612
        return TaintMethodWrapper(self._req.__contains__)(*args, **kw)
×
1613

1614
    def __getattr__(self, key, *args, **kw):
1✔
1615
        if key not in self._req.keys():
1✔
1616
            item = getattr(self._req, key, _marker)
1✔
1617
            if item is not _marker:
1!
1618
                return item
1✔
1619
        return TaintMethodWrapper(self._req.__getattr__)(key, *args, **kw)
1✔
1620

1621
    def __getitem__(self, *args, **kw):
1✔
1622
        return TaintMethodWrapper(self._req.__getitem__)(*args, **kw)
1✔
1623

1624
    def __len__(self):
1✔
1625
        return len(self._req)
1✔
1626

1627
    def get(self, *args, **kw):
1✔
1628
        return TaintMethodWrapper(self._req.get)(*args, **kw)
1✔
1629

1630
    def has_key(self, *args, **kw):
1✔
1631
        return TaintMethodWrapper(self._req.has_key)(*args, **kw)
×
1632

1633
    def keys(self, *args, **kw):
1✔
1634
        return TaintMethodWrapper(self._req.keys)(*args, **kw)
×
1635

1636

1637
class TaintMethodWrapper:
1✔
1638

1639
    def __init__(self, method):
1✔
1640
        self._method = method
1✔
1641

1642
    def __call__(self, *args, **kw):
1✔
1643
        kw['returnTaints'] = 1
1✔
1644
        return self._method(*args, **kw)
1✔
1645

1646

1647
def has_codec(x):
1✔
1648
    try:
1✔
1649
        codecs.lookup(x)
1✔
1650
    except (LookupError, SystemError):
×
1651
        return 0
×
1652
    else:
1653
        return 1
1✔
1654

1655

1656
def sane_environment(env):
1✔
1657
    # return an environment mapping which has been cleaned of
1658
    # funny business such as REDIRECT_ prefixes added by Apache
1659
    # or HTTP_CGI_AUTHORIZATION hacks.
1660
    dict = {}
1✔
1661
    for key, val in env.items():
1✔
1662
        while key[:9] == 'REDIRECT_':
1!
1663
            key = key[9:]
×
1664
        dict[key] = val
1✔
1665
    if 'HTTP_CGI_AUTHORIZATION' in dict:
1!
1666
        dict['HTTP_AUTHORIZATION'] = dict['HTTP_CGI_AUTHORIZATION']
×
1667
        try:
×
1668
            del dict['HTTP_CGI_AUTHORIZATION']
×
1669
        except Exception:
×
1670
            pass
×
1671
    return dict
1✔
1672

1673

1674
class ValueDescriptor:
1✔
1675
    """(non data) descriptor to compute `value` from `file`."""
1676
    def __get__(self, inst, owner=None):
1✔
1677
        if inst is None:
1!
1678
            return self
×
1679
        file = inst.file
1✔
1680
        try:
1✔
1681
            fpos = file.tell()
1✔
1682
        except Exception:
×
1683
            fpos = None
×
1684
        try:
1✔
1685
            return file.read()
1✔
1686
        finally:
1687
            if fpos is not None:
1!
1688
                file.seek(fpos)
1✔
1689

1690

1691
class ValueAccessor:
1✔
1692
    value = ValueDescriptor()
1✔
1693

1694

1695
class FormField(SimpleNamespace, ValueAccessor):
1✔
1696
    """represent a single form field.
1697

1698
    Typical attributes:
1699
    name
1700
      the field name
1701
    value
1702
      the field value (`bytes`)
1703

1704
    File fields additionally have the attributes:
1705
    file
1706
      a binary file containing the file content
1707
    filename
1708
      the file's name as reported by the client
1709
    headers
1710
      a case insensitive dict with header information;
1711
      usually `content-type` and `content-disposition`.
1712

1713
    Unless otherwise noted, `latin-1` decoded bytes
1714
    are used to represent textual data.
1715
    """
1716

1717

1718
class ZopeFieldStorage(ValueAccessor):
1✔
1719
    def __init__(self, fp, environ):
1✔
1720
        self.file = fp
1✔
1721
        method = environ.get("REQUEST_METHOD", "GET").upper()
1✔
1722
        qs = environ.get("QUERY_STRING", "")
1✔
1723
        hl = []
1✔
1724
        content_type = environ.get("CONTENT_TYPE",
1✔
1725
                                   "application/x-www-form-urlencoded")
1726
        content_type = content_type
1✔
1727
        hl.append(("content-type", content_type))
1✔
1728
        content_disposition = environ.get("CONTENT_DISPOSITION")
1✔
1729
        if content_disposition is not None:
1!
1730
            hl.append(("content-disposition", content_disposition))
×
1731
        self.headers = Headers(hl)
1✔
1732
        parts = ()
1✔
1733
        if method == "POST":
1✔
1734
            try:
1✔
1735
                fpos = fp.tell()
1✔
1736
            except Exception:
1✔
1737
                fpos = None
1✔
1738
            if content_type.startswith("multipart/form-data"):
1✔
1739
                ct, options = parse_options_header(content_type)
1✔
1740
                parts = MultipartParser(
1✔
1741
                    fp, options["boundary"],
1742
                    mem_limit=FORM_MEMORY_LIMIT,
1743
                    disk_limit=FORM_DISK_LIMIT,
1744
                    memfile_limit=FORM_MEMFILE_LIMIT,
1745
                    charset="latin-1").parts()
1746
            elif content_type == "application/x-www-form-urlencoded":
1✔
1747
                if qs:
1✔
1748
                    qs += "&"
1✔
1749
                qs += fp.read(FORM_MEMORY_LIMIT).decode("latin-1")
1✔
1750
                if fp.read(1):
1✔
1751
                    raise BadRequest("form data processing "
1✔
1752
                                     "requires too much memory")
1753
            else:
1754
                # `processInputs` currently expects either
1755
                # form values or a response body, not both.
1756
                # reset `qs` to fulfill this expectation.
1757
                qs = ""
1✔
1758
            if fpos is not None:
1✔
1759
                fp.seek(fpos)
1✔
1760
        elif method not in ("GET", "HEAD"):
1✔
1761
            # `processInputs` currently expects either
1762
            # form values or a response body, not both.
1763
            # reset `qs` to fulfill this expectation.
1764
            qs = ""
1✔
1765
        fl = []
1✔
1766
        add_field = fl.append
1✔
1767
        for name, val in parse_qsl(
1✔
1768
           qs,  # noqa: E121
1769
           keep_blank_values=True, encoding="latin-1"):
1770
            add_field(FormField(
1✔
1771
                name=name, value=val.encode("latin-1")))
1772
        for part in parts:
1✔
1773
            if part.filename:
1✔
1774
                # a file
1775
                field = FormField(
1✔
1776
                    name=part.name,
1777
                    file=part.file,
1778
                    filename=part.filename,
1779
                    headers=part.headers)
1780
            else:
1781
                field = FormField(name=part.name, value=part.raw)
1✔
1782
            add_field(field)
1✔
1783
        if fl:
1✔
1784
            self.list = fl
1✔
1785

1786

1787
# Original version: zope.publisher.browser.FileUpload
1788
class FileUpload:
1✔
1789
    '''File upload objects
1790

1791
    File upload objects are used to represent file-uploaded data.
1792

1793
    File upload objects can be used just like files.
1794

1795
    In addition, they have a 'headers' attribute that is a dictionary
1796
    containing the file-upload headers, and a 'filename' attribute
1797
    containing the name of the uploaded file.
1798
    '''
1799

1800
    # Allow access to attributes such as headers and filename so
1801
    # that protected code can use DTML to work with FileUploads.
1802
    __allow_access_to_unprotected_subobjects__ = 1
1✔
1803

1804
    def __init__(self, aFieldStorage, charset):
1✔
1805
        self.file = aFieldStorage.file
1✔
1806
        self.headers = aFieldStorage.headers
1✔
1807
        self.filename = aFieldStorage.filename\
1✔
1808
            .encode("latin-1").decode(charset)
1809
        self.name = aFieldStorage.name.encode("latin-1").decode(charset)
1✔
1810

1811
        # Add an assertion to the rfc822.Message object that implements
1812
        # self.headers so that managed code can access them.
1813
        try:
1✔
1814
            self.headers.__allow_access_to_unprotected_subobjects__ = 1
1✔
1815
        except Exception:
×
1816
            pass
×
1817

1818
    def __getattribute__(self, key):
1✔
1819
        if key in ('close', 'closed', 'detach', 'fileno', 'flush',
1✔
1820
                   'getbuffer', 'getvalue', 'isatty', 'read', 'read1',
1821
                   'readable', 'readinto', 'readline', 'readlines',
1822
                   'seek', 'seekable', 'tell', 'truncate', 'writable',
1823
                   'write', 'writelines', 'name'):
1824
            file = object.__getattribute__(self, 'file')
1✔
1825
            func = getattr(file, key, _marker)
1✔
1826
            if func is not _marker:
1!
1827
                return func
1✔
1828
        # Always fall back to looking things up on self
1829
        return object.__getattribute__(self, key)
1✔
1830

1831
    def __iter__(self):
1✔
1832
        return self.file.__iter__()
1✔
1833

1834
    def __bool__(self):
1✔
1835
        """FileUpload objects are considered false if their
1836
           filename is empty.
1837
        """
1838
        return bool(self.filename)
1✔
1839

1840
    def __next__(self):
1✔
1841
        return self.file.__next__()
1✔
1842

1843

1844
QPARMRE = re.compile(
1✔
1845
    '([\x00- ]*([^\x00- ;,="]+)="([^"]*)"([\x00- ]*[;,])?[\x00- ]*)')
1846
PARMRE = re.compile(
1✔
1847
    '([\x00- ]*([^\x00- ;,="]+)=([^;]*)([\x00- ]*[;,])?[\x00- ]*)')
1848
PARAMLESSRE = re.compile(
1✔
1849
    '([\x00- ]*([^\x00- ;,="]+)[\x00- ]*[;,][\x00- ]*)')
1850

1851

1852
def parse_cookie(text,
1✔
1853
                 result=None,
1854
                 qparmre=QPARMRE,
1855
                 parmre=PARMRE,
1856
                 paramlessre=PARAMLESSRE,
1857
                 ):
1858

1859
    if result is None:
1!
1860
        result = {}
×
1861

1862
    mo_q = qparmre.match(text)
1✔
1863

1864
    if mo_q:
1✔
1865
        # Match quoted correct cookies
1866
        c_len = len(mo_q.group(1))
1✔
1867
        name = mo_q.group(2)
1✔
1868
        value = mo_q.group(3)
1✔
1869

1870
    else:
1871
        # Match evil MSIE cookies ;)
1872
        mo_p = parmre.match(text)
1✔
1873

1874
        if mo_p:
1✔
1875
            c_len = len(mo_p.group(1))
1✔
1876
            name = mo_p.group(2)
1✔
1877
            value = mo_p.group(3)
1✔
1878
        else:
1879
            # Broken Cookie without = nor value.
1880
            broken_p = paramlessre.match(text)
1✔
1881
            if broken_p:
1✔
1882
                c_len = len(broken_p.group(1))
1✔
1883
                name = broken_p.group(2)
1✔
1884
                value = ''
1✔
1885
            else:
1886
                return result
1✔
1887

1888
    if name not in result:
1!
1889
        result[name] = getCookieValuePolicy().load(name, value)
1✔
1890

1891
    return parse_cookie(text[c_len:], result)
1✔
1892

1893

1894
class record:
1✔
1895

1896
    # Allow access to record methods and values from DTML
1897
    __allow_access_to_unprotected_subobjects__ = 1
1✔
1898
    _guarded_writes = 1
1✔
1899

1900
    def __getattr__(self, key, default=None):
1✔
1901
        if key in ('get',
1✔
1902
                   'keys',
1903
                   'items',
1904
                   'values',
1905
                   'copy'):
1906
            return getattr(self.__dict__, key)
1✔
1907
        raise AttributeError(key)
1✔
1908

1909
    def __contains__(self, key):
1✔
1910
        return key in self.__dict__
1✔
1911

1912
    def __getitem__(self, key):
1✔
1913
        return self.__dict__[key]
1✔
1914

1915
    def __iter__(self):
1✔
1916
        return iter(self.__dict__)
1✔
1917

1918
    def __len__(self):
1✔
1919
        return len(self.__dict__)
1✔
1920

1921
    def __str__(self):
1✔
1922
        return ", ".join("%s: %s" % item for item in
1✔
1923
                         sorted(self.__dict__.items()))
1924

1925
    def __repr__(self):
1✔
1926
        # return repr( self.__dict__ )
1927
        return '{%s}' % ', '.join(
1✔
1928
            f"'{key}': {value!r}"
1929
            for key, value in sorted(self.__dict__.items()))
1930

1931
    def __eq__(self, other):
1✔
1932
        if not isinstance(other, record):
1!
1933
            return False
×
1934
        return sorted(self.__dict__.items()) == sorted(other.__dict__.items())
1✔
1935

1936

1937
def _filterPasswordFields(items):
1✔
1938
    # Collector #777:  filter out request fields which contain 'passw'
1939

1940
    result = []
1✔
1941

1942
    for k, v in items:
1✔
1943

1944
        if 'passw' in k.lower():
1✔
1945
            v = '<password obscured>'
1✔
1946

1947
        result.append((k, v))
1✔
1948

1949
    return result
1✔
1950

1951

1952
def use_builtin_xmlrpc(request):
1✔
1953
    checker = queryUtility(IXmlrpcChecker)
1✔
1954
    return checker is None or checker(request)
1✔
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