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

zopefoundation / Zope / 4359661191

pending completion
4359661191

push

github

Maurits van Rees
FileUpload: use latin-1 as charset when nothing is passed.

4270 of 6879 branches covered (62.07%)

Branch coverage included in aggregate %.

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

27027 of 31329 relevant lines covered (86.27%)

0.86 hits per line

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

86.16
/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 types import SimpleNamespace
1✔
24
from urllib.parse import parse_qsl
1✔
25
from urllib.parse import unquote
1✔
26
from urllib.parse import urlparse
1✔
27

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

51
from .cookie import getCookieValuePolicy
1✔
52

53

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

60

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

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

85
isCGI_NAME = isCGI_NAMEs.__contains__
1✔
86

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

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

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

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

96
_marker = []
1✔
97

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

107
trusted_proxies = []
1✔
108

109

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

113

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

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

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

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

128
      - Environment variables
129

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

135
      - Form data
136

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

140
      - Cookies
141

142
        These are the cookie data, if present.
143

144
      - Lazy Data
145

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

150
      - Other
151

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

443
        ################################################################
444
        # Cookie values should *not* be appended to existing form
445
        # vars with the same name - they are more like default values
446
        # for names not otherwise specified in the form.
447
        cookies = {}
1✔
448
        k = get_env('HTTP_COOKIE', '')
1✔
449
        if k:
1✔
450
            parse_cookie(k, cookies)
1✔
451
        self.cookies = cookies
1✔
452
        self.taintedcookies = taint(cookies)
1✔
453

454
    def processInputs(
1✔
455
            self,
456
            # "static" variables that we want to be local for speed
457
            SEQUENCE=1,
458
            DEFAULT=2,
459
            RECORD=4,
460
            RECORDS=8,
461
            REC=12,  # RECORD | RECORDS
462
            EMPTY=16,
463
            CONVERTED=32,
464
            hasattr=hasattr,
465
            getattr=getattr,
466
            setattr=setattr):
467
        """Process request inputs
468

469
        See the `Zope Developer Guide Object Publishing chapter
470
        <https://zope.readthedocs.io/en/latest/zdgbook/ObjectPublishing.html>`_
471
        for a detailed explanation in the section `Marshalling Arguments from
472
        the Request`.
473

474
        We need to delay input parsing so that it is done under
475
        publisher control for error handling purposes.
476
        """
477
        response = self.response
1✔
478
        environ = self.environ
1✔
479
        method = environ.get('REQUEST_METHOD', 'GET')
1✔
480

481
        if method != 'GET':
1✔
482
            fp = self.stdin
1✔
483
        else:
484
            fp = None
1✔
485

486
        form = self.form
1✔
487
        other = self.other
1✔
488

489
        # If 'QUERY_STRING' is not present in environ
490
        # FieldStorage will try to get it from sys.argv[1]
491
        # which is not what we need.
492
        if 'QUERY_STRING' not in environ:
1✔
493
            environ['QUERY_STRING'] = ''
1✔
494

495
        meth = None
1✔
496

497
        fs = ZopeFieldStorage(fp, environ)
1✔
498

499
        # Keep a reference to the FieldStorage. Otherwise it's
500
        # __del__ method is called too early and closing FieldStorage.file.
501
        self._hold(fs)
1✔
502

503
        if not hasattr(fs, 'list'):
1✔
504
            if 'HTTP_SOAPACTION' in environ:
1!
505
                # Stash XML request for interpretation by a SOAP-aware view
506
                other['SOAPXML'] = fs.value
×
507
            elif (method == 'POST'
1✔
508
                  and 'text/xml' in fs.headers.get('content-type', '')
509
                  and use_builtin_xmlrpc(self)):
510
                # Ye haaa, XML-RPC!
511
                meth, self.args = xmlrpc.parse_input(fs.value)
1✔
512
                response = xmlrpc.response(response)
1✔
513
                other['RESPONSE'] = self.response = response
1✔
514
                self.maybe_webdav_client = 0
1✔
515
            else:
516
                self._file = fs.file
1✔
517
        else:
518
            fslist = fs.list
1✔
519
            tuple_items = {}
1✔
520
            defaults = {}
1✔
521
            converter = None
1✔
522

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

556
                if hasattr(item, 'file') and \
1✔
557
                   hasattr(item, 'filename') and \
558
                   hasattr(item, 'headers'):
559
                    item = FileUpload(item, self.charset)
1✔
560
                else:
561
                    character_encoding = self.charset
1✔
562
                    item = item.value.decode(
1✔
563
                        character_encoding, "surrogateescape")
564
                # from here on, `item` contains the field value
565
                # either as `FileUpload` or `str` with
566
                # `character_encoding` as encoding.
567
                # `key` the field name (`str`)
568

569
                flags = 0
1✔
570
                # Loop through the different types and set
571
                # the appropriate flags
572

573
                # We'll search from the back to the front.
574
                # We'll do the search in two steps.  First, we'll
575
                # do a string search, and then we'll check it with
576
                # a re search.
577

578
                delim = key.rfind(':')
1✔
579
                if delim >= 0:
1✔
580
                    mo = search_type(key, delim)
1✔
581
                    if mo:
1!
582
                        delim = mo.start(0)
1✔
583
                    else:
584
                        delim = -1
×
585

586
                    while delim >= 0:
1!
587
                        type_name = key[delim + 1:]
1✔
588
                        key = key[:delim]
1✔
589
                        c = get_converter(type_name, None)
1✔
590

591
                        if c is not None:
1✔
592
                            converter = c
1✔
593
                            flags = flags | CONVERTED
1✔
594
                        elif type_name == 'list':
1✔
595
                            flags = flags | SEQUENCE
1✔
596
                        elif type_name == 'tuple':
1✔
597
                            tuple_items[key] = 1
1✔
598
                            flags = flags | SEQUENCE
1✔
599
                        elif type_name == 'method' or type_name == 'action':
1✔
600
                            if delim:
1!
601
                                meth = key
1✔
602
                            else:
603
                                meth = item
×
604
                        elif (type_name == 'default_method'
1!
605
                              or type_name == 'default_action'):
606
                            if not meth:
×
607
                                if delim:
×
608
                                    meth = key
×
609
                                else:
610
                                    meth = item
×
611
                        elif type_name == 'default':
1✔
612
                            flags = flags | DEFAULT
1✔
613
                        elif type_name == 'record':
1✔
614
                            flags = flags | RECORD
1✔
615
                        elif type_name == 'records':
1✔
616
                            flags = flags | RECORDS
1✔
617
                        elif type_name == 'ignore_empty':
1!
618
                            if not item:
×
619
                                flags = flags | EMPTY
×
620
                        elif has_codec(type_name):
1!
621
                            # recode:
622
                            assert not isinstance(item, FileUpload), \
1✔
623
                                   "cannot recode files"
624
                            item = item.encode(
1✔
625
                                character_encoding, "surrogateescape")
626
                            character_encoding = type_name
1✔
627
                            # we do not use `surrogateescape` as
628
                            # we immediately want to determine
629
                            # an incompatible encoding modifier
630
                            item = item.decode(character_encoding)
1✔
631

632
                        delim = key.rfind(':')
1✔
633
                        if delim < 0:
1✔
634
                            break
1✔
635
                        mo = search_type(key, delim)
1✔
636
                        if mo:
1!
637
                            delim = mo.start(0)
1✔
638
                        else:
639
                            delim = -1
×
640

641
                # Filter out special names from form:
642
                if key in isCGI_NAMEs or key.startswith('HTTP_'):
1!
643
                    continue
×
644

645
                if flags:
1✔
646

647
                    # skip over empty fields
648
                    if flags & EMPTY:
1!
649
                        continue
×
650

651
                    # Split the key and its attribute
652
                    if flags & REC:
1✔
653
                        key = key.split(".")
1✔
654
                        key, attr = ".".join(key[:-1]), key[-1]
1✔
655

656
                    # defer conversion
657
                    if flags & CONVERTED:
1✔
658
                        try:
1✔
659
                            if character_encoding and \
1✔
660
                               getattr(converter, "binary", False):
661
                                item = item.encode(character_encoding,
1✔
662
                                                   "surrogateescape")
663
                            item = converter(item)
1✔
664

665
                        except Exception:
×
666
                            if not item and \
×
667
                               not (flags & DEFAULT) and \
668
                               key in defaults:
669
                                item = defaults[key]
×
670
                                if flags & RECORD:
×
671
                                    item = getattr(item, attr)
×
672
                                if flags & RECORDS:
×
673
                                    item = getattr(item[-1], attr)
×
674
                            else:
675
                                raise
×
676

677
                    # Determine which dictionary to use
678
                    if flags & DEFAULT:
1✔
679
                        mapping_object = defaults
1✔
680
                    else:
681
                        mapping_object = form
1✔
682

683
                    # Insert in dictionary
684
                    if key in mapping_object:
1✔
685
                        if flags & RECORDS:
1✔
686
                            # Get the list and the last record
687
                            # in the list. reclist is mutable.
688
                            reclist = mapping_object[key]
1✔
689
                            x = reclist[-1]
1✔
690
                            if not hasattr(x, attr):
1✔
691
                                # If the attribute does not
692
                                # exist, set it
693
                                if flags & SEQUENCE:
1✔
694
                                    item = [item]
1✔
695
                                setattr(x, attr, item)
1✔
696
                            else:
697
                                if flags & SEQUENCE:
1✔
698
                                    # If the attribute is a
699
                                    # sequence, append the item
700
                                    # to the existing attribute
701
                                    y = getattr(x, attr)
1✔
702
                                    y.append(item)
1✔
703
                                    setattr(x, attr, y)
1✔
704
                                else:
705
                                    # Create a new record and add
706
                                    # it to the list
707
                                    n = record()
1✔
708
                                    setattr(n, attr, item)
1✔
709
                                    mapping_object[key].append(n)
1✔
710
                        elif flags & RECORD:
1✔
711
                            b = mapping_object[key]
1✔
712
                            if flags & SEQUENCE:
1!
713
                                item = [item]
×
714
                                if not hasattr(b, attr):
×
715
                                    # if it does not have the
716
                                    # attribute, set it
717
                                    setattr(b, attr, item)
×
718
                                else:
719
                                    # it has the attribute so
720
                                    # append the item to it
721
                                    setattr(b, attr, getattr(b, attr) + item)
×
722
                            else:
723
                                # it is not a sequence so
724
                                # set the attribute
725
                                setattr(b, attr, item)
1✔
726
                        else:
727
                            # it is not a record or list of records
728
                            found = mapping_object[key]
1✔
729
                            if isinstance(found, list):
1✔
730
                                found.append(item)
1✔
731
                            else:
732
                                found = [found, item]
1✔
733
                                mapping_object[key] = found
1✔
734
                    else:
735
                        # The dictionary does not have the key
736
                        if flags & RECORDS:
1✔
737
                            # Create a new record, set its attribute
738
                            # and put it in the dictionary as a list
739
                            a = record()
1✔
740
                            if flags & SEQUENCE:
1!
741
                                item = [item]
×
742
                            setattr(a, attr, item)
1✔
743
                            mapping_object[key] = [a]
1✔
744
                        elif flags & RECORD:
1✔
745
                            # Create a new record, set its attribute
746
                            # and put it in the dictionary
747
                            if flags & SEQUENCE:
1!
748
                                item = [item]
×
749
                            r = mapping_object[key] = record()
1✔
750
                            setattr(r, attr, item)
1✔
751
                        else:
752
                            # it is not a record or list of records
753
                            if flags & SEQUENCE:
1✔
754
                                item = [item]
1✔
755
                            mapping_object[key] = item
1✔
756

757
                else:
758
                    # This branch is for case when no type was specified.
759
                    mapping_object = form
1✔
760

761
                    # Insert in dictionary
762
                    if key in mapping_object:
1✔
763
                        # it is not a record or list of records
764
                        found = mapping_object[key]
1✔
765
                        if isinstance(found, list):
1!
766
                            found.append(item)
×
767
                        else:
768
                            found = [found, item]
1✔
769
                            mapping_object[key] = found
1✔
770
                    else:
771
                        mapping_object[key] = item
1✔
772

773
            # insert defaults into form dictionary
774
            if defaults:
1✔
775
                for key, value in defaults.items():
1✔
776
                    if key not in form:
1✔
777
                        # if the form does not have the key,
778
                        # set the default
779
                        form[key] = value
1✔
780
                    else:
781
                        # The form has the key
782
                        if isinstance(value, record):
1✔
783
                            # if the key is mapped to a record, get the
784
                            # record
785
                            r = form[key]
1✔
786
                            for k, v in value.__dict__.items():
1✔
787
                                # loop through the attributes and value
788
                                # in the default dictionary
789
                                if not hasattr(r, k):
1✔
790
                                    # if the form dictionary doesn't have
791
                                    # the attribute, set it to the default
792
                                    setattr(r, k, v)
1✔
793
                            form[key] = r
1✔
794

795
                        elif isinstance(value, list):
1!
796
                            # the default value is a list
797
                            val = form[key]
1✔
798
                            if not isinstance(val, list):
1!
799
                                val = [val]
×
800
                            for x in value:
1✔
801
                                # for each x in the list
802
                                if isinstance(x, record):
1✔
803
                                    # if the x is a record
804
                                    for k, v in x.__dict__.items():
1✔
805

806
                                        # loop through each
807
                                        # attribute and value in
808
                                        # the record
809

810
                                        for y in val:
1✔
811

812
                                            # loop through each
813
                                            # record in the form
814
                                            # list if it doesn't
815
                                            # have the attributes
816
                                            # in the default
817
                                            # dictionary, set them
818

819
                                            if not hasattr(y, k):
1✔
820
                                                setattr(y, k, v)
1✔
821
                                else:
822
                                    # x is not a record
823
                                    if x not in val:
1!
824
                                        val.append(x)
1✔
825
                            form[key] = val
1✔
826
                        else:
827
                            # The form has the key, the key is not mapped
828
                            # to a record or sequence so do nothing
829
                            pass
1✔
830

831
            # Convert to tuples
832
            if tuple_items:
1✔
833
                for key in tuple_items.keys():
1✔
834
                    # Split the key and get the attr
835
                    k = key.split(".")
1✔
836
                    k, attr = '.'.join(k[:-1]), k[-1]
1✔
837
                    a = attr
1✔
838
                    new = ''
1✔
839
                    # remove any type_names in the attr
840
                    while not a == '':
1✔
841
                        a = a.split(":")
1✔
842
                        a, new = ':'.join(a[:-1]), a[-1]
1✔
843
                    attr = new
1✔
844
                    if k in form:
1✔
845
                        # If the form has the split key get its value
846
                        item = form[k]
1✔
847
                        if isinstance(item, record):
1!
848
                            # if the value is mapped to a record, check if it
849
                            # has the attribute, if it has it, convert it to
850
                            # a tuple and set it
851
                            if hasattr(item, attr):
×
852
                                value = tuple(getattr(item, attr))
×
853
                                setattr(item, attr, value)
×
854
                        else:
855
                            # It is mapped to a list of  records
856
                            for x in item:
1✔
857
                                # loop through the records
858
                                if hasattr(x, attr):
1!
859
                                    # If the record has the attribute
860
                                    # convert it to a tuple and set it
861
                                    value = tuple(getattr(x, attr))
1✔
862
                                    setattr(x, attr, value)
1✔
863
                    else:
864
                        # the form does not have the split key
865
                        if key in form:
1✔
866
                            # if it has the original key, get the item
867
                            # convert it to a tuple
868
                            item = form[key]
1✔
869
                            item = tuple(form[key])
1✔
870
                            form[key] = item
1✔
871

872
            self.taintedform = taint(self.form)
1✔
873

874
        if meth:
1✔
875
            if 'PATH_INFO' in environ:
1✔
876
                path = environ['PATH_INFO']
1✔
877
                while path[-1:] == '/':
1!
878
                    path = path[:-1]
×
879
            else:
880
                path = ''
1✔
881
            other['PATH_INFO'] = f"{path}/{meth}"
1✔
882
            self._hacked_path = 1
1✔
883

884
    def resolve_url(self, url):
1✔
885
        # Attempt to resolve a url into an object in the Zope
886
        # namespace. The url must be a fully-qualified url. The
887
        # method will return the requested object if it is found
888
        # or raise the same HTTP error that would be raised in
889
        # the case of a real web request. If the passed in url
890
        # does not appear to describe an object in the system
891
        # namespace (e.g. the host, port or script name don't
892
        # match that of the current request), a ValueError will
893
        # be raised.
894
        if url.find(self.script) != 0:
1!
895
            raise ValueError('Different namespace.')
×
896
        path = url[len(self.script):]
1✔
897
        while path and path[0] == '/':
1✔
898
            path = path[1:]
1✔
899
        while path and path[-1] == '/':
1!
900
            path = path[:-1]
×
901
        req = self.clone()
1✔
902
        rsp = req.response
1✔
903
        req['PATH_INFO'] = path
1✔
904
        object = None
1✔
905

906
        # Try to traverse to get an object. Note that we call
907
        # the exception method on the response, but we don't
908
        # want to actually abort the current transaction
909
        # (which is usually the default when the exception
910
        # method is called on the response).
911
        try:
1✔
912
            object = req.traverse(path)
1✔
913
        except Exception as exc:
1✔
914
            rsp.exception()
1✔
915
            req.clear()
1✔
916
            raise exc.__class__(rsp.errmsg)
1✔
917

918
        # The traversal machinery may return a "default object"
919
        # like an index_html document. This is not appropriate
920
        # in the context of the resolve_url method so we need
921
        # to ensure we are getting the actual object named by
922
        # the given url, and not some kind of default object.
923
        if hasattr(object, 'id'):
1!
924
            if callable(object.id):
×
925
                name = object.id()
×
926
            else:
927
                name = object.id
×
928
        elif hasattr(object, '__name__'):
1!
929
            name = object.__name__
×
930
        else:
931
            name = ''
1✔
932
        if name != os.path.split(path)[-1]:
1!
933
            object = req.PARENTS[0]
×
934

935
        req.clear()
1✔
936
        return object
1✔
937

938
    def clone(self):
1✔
939
        # Return a clone of the current request object
940
        # that may be used to perform object traversal.
941
        environ = self.environ.copy()
1✔
942
        environ['REQUEST_METHOD'] = 'GET'
1✔
943
        if self._auth:
1✔
944
            environ['HTTP_AUTHORIZATION'] = self._auth
1✔
945
        if self.response is not None:
1✔
946
            response = self.response.__class__()
1✔
947
        else:
948
            response = None
1✔
949
        clone = self.__class__(None, environ, response, clean=1)
1✔
950
        clone['PARENTS'] = [self['PARENTS'][-1]]
1✔
951
        directlyProvides(clone, *directlyProvidedBy(self))
1✔
952
        return clone
1✔
953

954
    def getHeader(self, name, default=None, literal=False):
1✔
955
        """Return the named HTTP header, or an optional default
956
        argument or None if the header is not found. Note that
957
        both original and CGI-ified header names are recognized,
958
        e.g. 'Content-Type', 'CONTENT_TYPE' and 'HTTP_CONTENT_TYPE'
959
        should all return the Content-Type header, if available.
960
        """
961
        environ = self.environ
1✔
962
        if not literal:
1✔
963
            name = name.replace('-', '_').upper()
1✔
964
        val = environ.get(name, None)
1✔
965
        if val is not None:
1✔
966
            return val
1✔
967
        if name[:5] != 'HTTP_':
1!
968
            name = 'HTTP_%s' % name
1✔
969
        return environ.get(name, default)
1✔
970

971
    get_header = getHeader  # BBB
1✔
972

973
    def get(self, key, default=None, returnTaints=0,
1✔
974
            URLmatch=re.compile('URL(PATH)?([0-9]+)$').match,
975
            BASEmatch=re.compile('BASE(PATH)?([0-9]+)$').match,
976
            ):
977
        """Get a variable value
978

979
        Return a value for the variable key, or default if not found.
980

981
        If key is "REQUEST", return the request.
982
        Otherwise, the value will be looked up from one of the request data
983
        categories. The search order is:
984
        other (the target for explicitly set variables),
985
        the special URL and BASE variables,
986
        environment variables,
987
        common variables (defined by the request class),
988
        lazy variables (set with set_lazy),
989
        form data and cookies.
990

991
        If returnTaints has a true value, then the access to
992
        form and cookie variables returns values with special
993
        protection against embedded HTML fragments to counter
994
        some cross site scripting attacks.
995
        """
996

997
        if key == 'REQUEST':
1✔
998
            return self
1✔
999

1000
        other = self.other
1✔
1001
        if key in other:
1✔
1002
            return other[key]
1✔
1003

1004
        if key[:1] == 'U':
1✔
1005
            match = URLmatch(key)
1✔
1006
            if match is not None:
1!
1007
                pathonly, n = match.groups()
1✔
1008
                path = self._script + self._steps
1✔
1009
                n = len(path) - int(n)
1✔
1010
                if n < 0:
1✔
1011
                    raise KeyError(key)
1✔
1012
                if pathonly:
1✔
1013
                    path = [''] + path[:n]
1✔
1014
                else:
1015
                    path = [other['SERVER_URL']] + path[:n]
1✔
1016
                URL = '/'.join(path)
1✔
1017
                if 'PUBLISHED' in other:
1✔
1018
                    # Don't cache URLs until publishing traversal is done.
1019
                    other[key] = URL
1✔
1020
                    self._urls = self._urls + (key,)
1✔
1021
                return URL
1✔
1022

1023
        if key in isCGI_NAMEs or key[:5] == 'HTTP_':
1✔
1024
            environ = self.environ
1✔
1025
            if key in environ and (key not in hide_key):
1✔
1026
                return environ[key]
1✔
1027
            return ''
1✔
1028

1029
        if key[:1] == 'B':
1✔
1030
            match = BASEmatch(key)
1✔
1031
            if match is not None:
1✔
1032
                pathonly, n = match.groups()
1✔
1033
                path = self._steps
1✔
1034
                n = int(n)
1✔
1035
                if n:
1✔
1036
                    n = n - 1
1✔
1037
                    if len(path) < n:
1✔
1038
                        raise KeyError(key)
1✔
1039

1040
                    v = self._script + path[:n]
1✔
1041
                else:
1042
                    v = self._script[:-1]
1✔
1043
                if pathonly:
1✔
1044
                    v.insert(0, '')
1✔
1045
                else:
1046
                    v.insert(0, other['SERVER_URL'])
1✔
1047
                URL = '/'.join(v)
1✔
1048
                if 'PUBLISHED' in other:
1✔
1049
                    # Don't cache URLs until publishing traversal is done.
1050
                    other[key] = URL
1✔
1051
                    self._urls = self._urls + (key,)
1✔
1052
                return URL
1✔
1053

1054
            if key == 'BODY' and self._file is not None:
1✔
1055
                p = self._file.tell()
1✔
1056
                self._file.seek(0)
1✔
1057
                v = self._file.read()
1✔
1058
                self._file.seek(p)
1✔
1059
                self.other[key] = v
1✔
1060
                return v
1✔
1061

1062
            if key == 'BODYFILE' and self._file is not None:
1!
1063
                v = self._file
1✔
1064
                self.other[key] = v
1✔
1065
                return v
1✔
1066

1067
        v = self.common.get(key, _marker)
1✔
1068
        if v is not _marker:
1!
1069
            return v
×
1070

1071
        if self._lazies:
1!
1072
            v = self._lazies.get(key, _marker)
×
1073
            if v is not _marker:
×
1074
                if callable(v):
×
1075
                    v = v()
×
1076
                self[key] = v  # Promote lazy value
×
1077
                del self._lazies[key]
×
1078
                return v
×
1079

1080
        # Return tainted data first (marked as suspect)
1081
        if returnTaints:
1✔
1082
            v = self.taintedform.get(key, _marker)
1✔
1083
            if v is not _marker:
1!
1084
                return v
×
1085

1086
        # Untrusted data *after* trusted data
1087
        v = self.form.get(key, _marker)
1✔
1088
        if v is not _marker:
1✔
1089
            return v
1✔
1090

1091
        # Return tainted data first (marked as suspect)
1092
        if returnTaints:
1✔
1093
            v = self.taintedcookies.get(key, _marker)
1✔
1094
            if v is not _marker:
1!
1095
                return v
×
1096

1097
        # Untrusted data *after* trusted data
1098
        v = self.cookies.get(key, _marker)
1✔
1099
        if v is not _marker:
1!
1100
            return v
×
1101

1102
        return default
1✔
1103

1104
    def __getitem__(self, key, default=_marker, returnTaints=0):
1✔
1105
        v = self.get(key, default, returnTaints=returnTaints)
1✔
1106
        if v is _marker:
1✔
1107
            raise KeyError(key)
1✔
1108
        return v
1✔
1109

1110
    # Using the getattr protocol to retrieve form values and similar
1111
    # is discouraged and is likely to be deprecated in the future.
1112
    # request.get(key) or request[key] should be used instead
1113
    def __getattr__(self, key, default=_marker, returnTaints=0):
1✔
1114
        v = self.get(key, default, returnTaints=returnTaints)
1✔
1115
        if v is _marker:
1✔
1116
            if key == 'locale':
1✔
1117
                # we only create the _locale on first access, as setting it
1118
                # up might be slow and we don't want to slow down every
1119
                # request
1120
                if self._locale is _marker:
1✔
1121
                    self.setupLocale()
1✔
1122
                return self._locale
1✔
1123
            if key == 'debug':
1✔
1124
                return self._debug
1✔
1125
            raise AttributeError(key)
1✔
1126
        return v
1✔
1127

1128
    def set_lazy(self, key, callable):
1✔
1129
        self._lazies[key] = callable
×
1130

1131
    def __contains__(self, key, returnTaints=0):
1✔
1132
        return self.has_key(key, returnTaints=returnTaints)  # NOQA
1✔
1133

1134
    def has_key(self, key, returnTaints=0):
1✔
1135
        try:
1✔
1136
            self.__getitem__(key, returnTaints=returnTaints)
1✔
1137
        except Exception:
1✔
1138
            return 0
1✔
1139
        else:
1140
            return 1
1✔
1141

1142
    def keys(self, returnTaints=0):
1✔
1143
        keys = {}
1✔
1144
        keys.update(self.common)
1✔
1145
        keys.update(self._lazies)
1✔
1146

1147
        for key in self.environ.keys():
1✔
1148
            if (key in isCGI_NAMEs or key[:5] == 'HTTP_') and \
1✔
1149
               (key not in hide_key):
1150
                keys[key] = 1
1✔
1151

1152
        # Cache URLN and BASEN in self.other.
1153
        # This relies on a side effect of has_key.
1154
        n = 0
1✔
1155
        while 1:
1156
            n = n + 1
1✔
1157
            key = "URL%s" % n
1✔
1158
            if key not in self:  # NOQA
1✔
1159
                break
1✔
1160

1161
        n = 0
1✔
1162
        while 1:
1163
            n = n + 1
1✔
1164
            key = "BASE%s" % n
1✔
1165
            if key not in self:  # NOQA
1✔
1166
                break
1✔
1167

1168
        keys.update(self.other)
1✔
1169
        keys.update(self.cookies)
1✔
1170
        if returnTaints:
1!
1171
            keys.update(self.taintedcookies)
×
1172
        keys.update(self.form)
1✔
1173
        if returnTaints:
1!
1174
            keys.update(self.taintedform)
×
1175

1176
        keys = list(keys.keys())
1✔
1177
        keys.sort()
1✔
1178

1179
        return keys
1✔
1180

1181
    def __str__(self):
1✔
1182
        result = "<h3>form</h3><table>"
1✔
1183
        row = '<tr valign="top" align="left"><th>%s</th><td>%s</td></tr>'
1✔
1184
        for k, v in _filterPasswordFields(self.form.items()):
1✔
1185
            result = result + row % (
1✔
1186
                html.escape(k, False), html.escape(repr(v), False))
1187
        result = result + "</table><h3>cookies</h3><table>"
1✔
1188
        for k, v in _filterPasswordFields(self.cookies.items()):
1!
1189
            result = result + row % (
×
1190
                html.escape(k, False), html.escape(repr(v), False))
1191
        result = result + "</table><h3>lazy items</h3><table>"
1✔
1192
        for k, v in _filterPasswordFields(self._lazies.items()):
1!
1193
            result = result + row % (
×
1194
                html.escape(k, False), html.escape(repr(v), False))
1195
        result = result + "</table><h3>other</h3><table>"
1✔
1196
        for k, v in _filterPasswordFields(self.other.items()):
1✔
1197
            if k in ('PARENTS', 'RESPONSE'):
1✔
1198
                continue
1✔
1199
            result = result + row % (
1✔
1200
                html.escape(k, False), html.escape(repr(v), False))
1201

1202
        for n in "0123456789":
1✔
1203
            key = "URL%s" % n
1✔
1204
            try:
1✔
1205
                result = result + row % (key, html.escape(self[key], False))
1✔
1206
            except KeyError:
1✔
1207
                pass
1✔
1208
        for n in "0123456789":
1✔
1209
            key = "BASE%s" % n
1✔
1210
            try:
1✔
1211
                result = result + row % (key, html.escape(self[key], False))
1✔
1212
            except KeyError:
1✔
1213
                pass
1✔
1214

1215
        result = result + "</table><h3>environ</h3><table>"
1✔
1216
        for k, v in self.environ.items():
1✔
1217
            if k not in hide_key:
1!
1218
                result = result + row % (
1✔
1219
                    html.escape(k, False), html.escape(repr(v), False))
1220
        return result + "</table>"
1✔
1221

1222
    def __repr__(self):
1✔
1223
        return f"<{self.__class__.__name__}, URL={self.get('URL')}>"
1✔
1224

1225
    def text(self):
1✔
1226
        result = "FORM\n\n"
1✔
1227
        row = '%-20s %s\n'
1✔
1228
        for k, v in _filterPasswordFields(self.form.items()):
1✔
1229
            result = result + row % (k, repr(v))
1✔
1230
        result = result + "\nCOOKIES\n\n"
1✔
1231
        for k, v in _filterPasswordFields(self.cookies.items()):
1!
1232
            result = result + row % (k, repr(v))
×
1233
        result = result + "\nLAZY ITEMS\n\n"
1✔
1234
        for k, v in _filterPasswordFields(self._lazies.items()):
1!
1235
            result = result + row % (k, repr(v))
×
1236
        result = result + "\nOTHER\n\n"
1✔
1237
        for k, v in _filterPasswordFields(self.other.items()):
1✔
1238
            if k in ('PARENTS', 'RESPONSE'):
1✔
1239
                continue
1✔
1240
            result = result + row % (k, repr(v))
1✔
1241

1242
        for n in "0123456789":
1✔
1243
            key = "URL%s" % n
1✔
1244
            try:
1✔
1245
                result = result + row % (key, self[key])
1✔
1246
            except KeyError:
1✔
1247
                pass
1✔
1248
        for n in "0123456789":
1✔
1249
            key = "BASE%s" % n
1✔
1250
            try:
1✔
1251
                result = result + row % (key, self[key])
1✔
1252
            except KeyError:
1✔
1253
                pass
1✔
1254

1255
        result = result + "\nENVIRON\n\n"
1✔
1256
        for k, v in self.environ.items():
1✔
1257
            if k not in hide_key:
1!
1258
                result = result + row % (k, v)
1✔
1259
        return result
1✔
1260

1261
    def _authUserPW(self):
1✔
1262
        # Can return None
1263
        return basic_auth_decode(self._auth)
1✔
1264

1265
    def taintWrapper(self, enabled=TAINTING_ENABLED):
1✔
1266
        return enabled and TaintRequestWrapper(self) or self
1✔
1267

1268
    # Original version: zope.publisher.http.HTTPRequest.shiftNameToApplication
1269
    def shiftNameToApplication(self):
1✔
1270
        """see zope.publisher.interfaces.http.IVirtualHostRequest"""
1271
        if len(self._steps) == 1:
1!
1272
            self._script.append(self._steps.pop())
1✔
1273
            self._resetURLS()
1✔
1274
            return
1✔
1275

1276
        raise ValueError("Can only shift leading traversal "
×
1277
                         "names to application names")
1278

1279
    def getURL(self):
1✔
1280
        return self.URL
×
1281

1282

1283
class WSGIRequest(HTTPRequest):
1✔
1284
    # A request object for WSGI, no docstring to avoid being publishable.
1285
    pass
1✔
1286

1287

1288
class TaintRequestWrapper:
1✔
1289

1290
    def __init__(self, req):
1✔
1291
        self._req = req
1✔
1292

1293
    def __contains__(self, *args, **kw):
1✔
1294
        return TaintMethodWrapper(self._req.__contains__)(*args, **kw)
×
1295

1296
    def __getattr__(self, key, *args, **kw):
1✔
1297
        if key not in self._req.keys():
1✔
1298
            item = getattr(self._req, key, _marker)
1✔
1299
            if item is not _marker:
1!
1300
                return item
1✔
1301
        return TaintMethodWrapper(self._req.__getattr__)(key, *args, **kw)
1✔
1302

1303
    def __getitem__(self, *args, **kw):
1✔
1304
        return TaintMethodWrapper(self._req.__getitem__)(*args, **kw)
1✔
1305

1306
    def __len__(self):
1✔
1307
        return len(self._req)
1✔
1308

1309
    def get(self, *args, **kw):
1✔
1310
        return TaintMethodWrapper(self._req.get)(*args, **kw)
1✔
1311

1312
    def has_key(self, *args, **kw):
1✔
1313
        return TaintMethodWrapper(self._req.has_key)(*args, **kw)
×
1314

1315
    def keys(self, *args, **kw):
1✔
1316
        return TaintMethodWrapper(self._req.keys)(*args, **kw)
×
1317

1318

1319
class TaintMethodWrapper:
1✔
1320

1321
    def __init__(self, method):
1✔
1322
        self._method = method
1✔
1323

1324
    def __call__(self, *args, **kw):
1✔
1325
        kw['returnTaints'] = 1
1✔
1326
        return self._method(*args, **kw)
1✔
1327

1328

1329
def has_codec(x):
1✔
1330
    try:
1✔
1331
        codecs.lookup(x)
1✔
1332
    except (LookupError, SystemError):
×
1333
        return 0
×
1334
    else:
1335
        return 1
1✔
1336

1337

1338
def sane_environment(env):
1✔
1339
    # return an environment mapping which has been cleaned of
1340
    # funny business such as REDIRECT_ prefixes added by Apache
1341
    # or HTTP_CGI_AUTHORIZATION hacks.
1342
    dict = {}
1✔
1343
    for key, val in env.items():
1✔
1344
        while key[:9] == 'REDIRECT_':
1!
1345
            key = key[9:]
×
1346
        dict[key] = val
1✔
1347
    if 'HTTP_CGI_AUTHORIZATION' in dict:
1!
1348
        dict['HTTP_AUTHORIZATION'] = dict['HTTP_CGI_AUTHORIZATION']
×
1349
        try:
×
1350
            del dict['HTTP_CGI_AUTHORIZATION']
×
1351
        except Exception:
×
1352
            pass
×
1353
    return dict
1✔
1354

1355

1356
class ValueDescriptor:
1✔
1357
    """(non data) descriptor to compute `value` from `file`."""
1358
    def __get__(self, inst, owner=None):
1✔
1359
        if inst is None:
1!
1360
            return self
×
1361
        file = inst.file
1✔
1362
        try:
1✔
1363
            fpos = file.tell()
1✔
1364
        except Exception:
×
1365
            fpos = None
×
1366
        try:
1✔
1367
            return file.read()
1✔
1368
        finally:
1369
            if fpos is not None:
1!
1370
                file.seek(fpos)
1✔
1371

1372

1373
class ValueAccessor:
1✔
1374
    value = ValueDescriptor()
1✔
1375

1376

1377
class FormField(SimpleNamespace, ValueAccessor):
1✔
1378
    """represent a single form field.
1379

1380
    Typical attributes:
1381
    name
1382
      the field name
1383
    value
1384
      the field value (`bytes`)
1385

1386
    File fields additionally have the attributes:
1387
    file
1388
      a binary file containing the file content
1389
    filename
1390
      the file's name as reported by the client
1391
    headers
1392
      a case insensitive dict with header information;
1393
      usually `content-type` and `content-disposition`.
1394

1395
    Unless otherwise noted, `latin-1` decoded bytes
1396
    are used to represent textual data.
1397
    """
1398

1399

1400
class ZopeFieldStorage(ValueAccessor):
1✔
1401
    def __init__(self, fp, environ):
1✔
1402
        self.file = fp
1✔
1403
        method = environ.get("REQUEST_METHOD", "GET").upper()
1✔
1404
        qs = environ.get("QUERY_STRING", "")
1✔
1405
        hl = []
1✔
1406
        content_type = environ.get("CONTENT_TYPE",
1✔
1407
                                   "application/x-www-form-urlencoded")
1408
        content_type = content_type
1✔
1409
        hl.append(("content-type", content_type))
1✔
1410
        content_disposition = environ.get("CONTENT_DISPOSITION")
1✔
1411
        if content_disposition is not None:
1!
1412
            hl.append(("content-disposition", content_disposition))
×
1413
        self.headers = Headers(hl)
1✔
1414
        parts = ()
1✔
1415
        if method == "POST":
1✔
1416
            try:
1✔
1417
                fpos = fp.tell()
1✔
1418
            except Exception:
1✔
1419
                fpos = None
1✔
1420
            if content_type.startswith("multipart/form-data"):
1✔
1421
                ct, options = parse_options_header(content_type)
1✔
1422
                parts = MultipartParser(
1✔
1423
                    fp, options["boundary"],
1424
                    mem_limit=FORM_MEMORY_LIMIT,
1425
                    disk_limit=FORM_DISK_LIMIT,
1426
                    memfile_limit=FORM_MEMFILE_LIMIT,
1427
                    charset="latin-1").parts()
1428
            elif content_type == "application/x-www-form-urlencoded":
1✔
1429
                if qs:
1✔
1430
                    qs += "&"
1✔
1431
                qs += fp.read(FORM_MEMORY_LIMIT).decode("latin-1")
1✔
1432
                if fp.read(1):
1✔
1433
                    raise BadRequest("form data processing "
1✔
1434
                                     "requires too much memory")
1435
            else:
1436
                # `processInputs` currently expects either
1437
                # form values or a response body, not both.
1438
                # reset `qs` to fulfill this expectation.
1439
                qs = ""
1✔
1440
            if fpos is not None:
1✔
1441
                fp.seek(fpos)
1✔
1442
        elif method not in ("GET", "HEAD"):
1✔
1443
            # `processInputs` currently expects either
1444
            # form values or a response body, not both.
1445
            # reset `qs` to fulfill this expectation.
1446
            qs = ""
1✔
1447
        fl = []
1✔
1448
        add_field = fl.append
1✔
1449
        for name, val in parse_qsl(
1✔
1450
           qs,  # noqa: E121
1451
           keep_blank_values=True, encoding="latin-1"):
1452
            add_field(FormField(
1✔
1453
                name=name, value=val.encode("latin-1")))
1454
        for part in parts:
1✔
1455
            if part.filename:
1✔
1456
                # a file
1457
                field = FormField(
1✔
1458
                    name=part.name,
1459
                    file=part.file,
1460
                    filename=part.filename,
1461
                    headers=part.headers)
1462
            else:
1463
                field = FormField(name=part.name, value=part.raw)
1✔
1464
            add_field(field)
1✔
1465
        if fl:
1✔
1466
            self.list = fl
1✔
1467

1468

1469
# Original version: zope.publisher.browser.FileUpload
1470
class FileUpload:
1✔
1471
    '''File upload objects
1472

1473
    File upload objects are used to represent file-uploaded data.
1474

1475
    File upload objects can be used just like files.
1476

1477
    In addition, they have a 'headers' attribute that is a dictionary
1478
    containing the file-upload headers, and a 'filename' attribute
1479
    containing the name of the uploaded file.
1480
    '''
1481

1482
    # Allow access to attributes such as headers and filename so
1483
    # that protected code can use DTML to work with FileUploads.
1484
    __allow_access_to_unprotected_subobjects__ = 1
1✔
1485

1486
    def __init__(self, aFieldStorage, charset="latin-1"):
1✔
1487
        self.file = aFieldStorage.file
1✔
1488
        self.headers = aFieldStorage.headers
1✔
1489
        # Prevent needless encode-decode when both charsets are the same.
1490
        if charset != "latin-1":
1!
1491
            self.filename = aFieldStorage.filename\
1✔
1492
                .encode("latin-1").decode(charset)
1493
            self.name = aFieldStorage.name.encode("latin-1").decode(charset)
1✔
1494
        else:
1495
            self.filename = aFieldStorage.filename
×
1496
            self.name = aFieldStorage.name
×
1497

1498
        # Add an assertion to the rfc822.Message object that implements
1499
        # self.headers so that managed code can access them.
1500
        try:
1✔
1501
            self.headers.__allow_access_to_unprotected_subobjects__ = 1
1✔
1502
        except Exception:
×
1503
            pass
×
1504

1505
    def __getattribute__(self, key):
1✔
1506
        if key in ('close', 'closed', 'detach', 'fileno', 'flush',
1✔
1507
                   'getbuffer', 'getvalue', 'isatty', 'read', 'read1',
1508
                   'readable', 'readinto', 'readline', 'readlines',
1509
                   'seek', 'seekable', 'tell', 'truncate', 'writable',
1510
                   'write', 'writelines', 'name'):
1511
            file = object.__getattribute__(self, 'file')
1✔
1512
            func = getattr(file, key, _marker)
1✔
1513
            if func is not _marker:
1!
1514
                return func
1✔
1515
        # Always fall back to looking things up on self
1516
        return object.__getattribute__(self, key)
1✔
1517

1518
    def __iter__(self):
1✔
1519
        return self.file.__iter__()
1✔
1520

1521
    def __bool__(self):
1✔
1522
        """FileUpload objects are considered false if their
1523
           filename is empty.
1524
        """
1525
        return bool(self.filename)
1✔
1526

1527
    def __next__(self):
1✔
1528
        return self.file.__next__()
1✔
1529

1530

1531
QPARMRE = re.compile(
1✔
1532
    '([\x00- ]*([^\x00- ;,="]+)="([^"]*)"([\x00- ]*[;,])?[\x00- ]*)')
1533
PARMRE = re.compile(
1✔
1534
    '([\x00- ]*([^\x00- ;,="]+)=([^;]*)([\x00- ]*[;,])?[\x00- ]*)')
1535
PARAMLESSRE = re.compile(
1✔
1536
    '([\x00- ]*([^\x00- ;,="]+)[\x00- ]*[;,][\x00- ]*)')
1537

1538

1539
def parse_cookie(text,
1✔
1540
                 result=None,
1541
                 qparmre=QPARMRE,
1542
                 parmre=PARMRE,
1543
                 paramlessre=PARAMLESSRE,
1544
                 ):
1545

1546
    if result is None:
1!
1547
        result = {}
×
1548

1549
    mo_q = qparmre.match(text)
1✔
1550

1551
    if mo_q:
1✔
1552
        # Match quoted correct cookies
1553
        c_len = len(mo_q.group(1))
1✔
1554
        name = mo_q.group(2)
1✔
1555
        value = mo_q.group(3)
1✔
1556

1557
    else:
1558
        # Match evil MSIE cookies ;)
1559
        mo_p = parmre.match(text)
1✔
1560

1561
        if mo_p:
1✔
1562
            c_len = len(mo_p.group(1))
1✔
1563
            name = mo_p.group(2)
1✔
1564
            value = mo_p.group(3)
1✔
1565
        else:
1566
            # Broken Cookie without = nor value.
1567
            broken_p = paramlessre.match(text)
1✔
1568
            if broken_p:
1✔
1569
                c_len = len(broken_p.group(1))
1✔
1570
                name = broken_p.group(2)
1✔
1571
                value = ''
1✔
1572
            else:
1573
                return result
1✔
1574

1575
    if name not in result:
1!
1576
        result[name] = getCookieValuePolicy().load(name, value)
1✔
1577

1578
    return parse_cookie(text[c_len:], result)
1✔
1579

1580

1581
class record:
1✔
1582

1583
    # Allow access to record methods and values from DTML
1584
    __allow_access_to_unprotected_subobjects__ = 1
1✔
1585
    _guarded_writes = 1
1✔
1586

1587
    def __getattr__(self, key, default=None):
1✔
1588
        if key in ('get',
1✔
1589
                   'keys',
1590
                   'items',
1591
                   'values',
1592
                   'copy'):
1593
            return getattr(self.__dict__, key)
1✔
1594
        raise AttributeError(key)
1✔
1595

1596
    def __contains__(self, key):
1✔
1597
        return key in self.__dict__
1✔
1598

1599
    def __getitem__(self, key):
1✔
1600
        return self.__dict__[key]
1✔
1601

1602
    def __iter__(self):
1✔
1603
        return iter(self.__dict__)
1✔
1604

1605
    def __len__(self):
1✔
1606
        return len(self.__dict__)
1✔
1607

1608
    def __str__(self):
1✔
1609
        return ", ".join("%s: %s" % item for item in
1✔
1610
                         sorted(self.__dict__.items()))
1611

1612
    def __repr__(self):
1✔
1613
        # return repr( self.__dict__ )
1614
        return '{%s}' % ', '.join(
1✔
1615
            f"'{key}': {value!r}"
1616
            for key, value in sorted(self.__dict__.items()))
1617

1618
    def __eq__(self, other):
1✔
1619
        if not isinstance(other, record):
1!
1620
            return False
×
1621
        return sorted(self.__dict__.items()) == sorted(other.__dict__.items())
1✔
1622

1623

1624
def _filterPasswordFields(items):
1✔
1625
    # Collector #777:  filter out request fields which contain 'passw'
1626

1627
    result = []
1✔
1628

1629
    for k, v in items:
1✔
1630

1631
        if 'passw' in k.lower():
1✔
1632
            v = '<password obscured>'
1✔
1633

1634
        result.append((k, v))
1✔
1635

1636
    return result
1✔
1637

1638

1639
def use_builtin_xmlrpc(request):
1✔
1640
    checker = queryUtility(IXmlrpcChecker)
1✔
1641
    return checker is None or checker(request)
1✔
1642

1643

1644
def taint(d):
1✔
1645
    """return as ``dict`` the items from *d* which require tainting.
1646

1647
    *d* must be a ``dict`` with ``str`` keys and values recursively
1648
    build from elementary values, ``list``, ``tuple``, ``record`` and
1649
    ``dict``.
1650
    """
1651
    def should_taint(v):
1✔
1652
        """check whether *v* needs tainting."""
1653
        if isinstance(v, (list, tuple)):
1✔
1654
            return any(should_taint(x) for x in v)
1✔
1655
        if isinstance(v, (record, dict)):
1✔
1656
            for k in v:
1✔
1657
                if should_taint(k):
1!
1658
                    return True
×
1659
                if should_taint(v[k]):
1✔
1660
                    return True
1✔
1661
        return should_be_tainted(v)
1✔
1662

1663
    def _taint(v):
1✔
1664
        """return a copy of *v* with tainted replacements.
1665

1666
        In the copy, each ``str`` which requires tainting
1667
        is replaced by the corresponding ``taint_string``.
1668
        """
1669
        __traceback_info__ = v
1✔
1670
        if isinstance(v, (bytes, str)) and should_be_tainted(v):
1✔
1671
            return taint_string(v)
1✔
1672
        if isinstance(v, (list, tuple)):
1✔
1673
            return v.__class__(_taint(x) for x in v)
1✔
1674
        if isinstance(v, record):
1✔
1675
            rn = record()
1✔
1676
            for k in v:
1✔
1677
                __traceback_info__ = v, k
1✔
1678
                if should_be_tainted(k):
1!
1679
                    raise ValueError("Cannot taint `record` attribute names")
×
1680
                setattr(rn, k, _taint(v[k]))
1✔
1681
            return rn
1✔
1682
        if isinstance(v, dict):
1!
1683
            return v.__class__((_taint(k), _taint(v[k])) for k in v)
×
1684
        return v
1✔
1685
    td = {}
1✔
1686
    for k in d:
1✔
1687
        v = d[k]
1✔
1688
        if should_taint(k) or should_taint(v):
1✔
1689
            td[_taint(k)] = _taint(v)
1✔
1690
    return td
1✔
1691

1692

1693
def should_be_tainted(v):
1✔
1694
    """Work around ``AccessControl`` weakness."""
1695
    if isinstance(v, FileUpload):
1✔
1696
        # `base_should_be_tainted` would read `v` as a side effect
1697
        return False
1✔
1698
    try:
1✔
1699
        return base_should_be_tainted(v)
1✔
1700
    except Exception:
1✔
1701
        return False
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