• 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

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

16
import html
1✔
17
from urllib.parse import quote
1✔
18
from urllib.parse import unquote
1✔
19

20
from AccessControl import getSecurityManager
1✔
21
from AccessControl.unauthorized import Unauthorized
1✔
22
from AccessControl.ZopeGuards import guarded_getitem
1✔
23
from DateTime.DateTime import DateTime
1✔
24
from ZTUtils.Batch import Batch
1✔
25
from ZTUtils.Lazy import Lazy
1✔
26
from ZTUtils.SimpleTree import SimpleTreeMaker
1✔
27
from ZTUtils.Tree import TreeMaker
1✔
28
from ZTUtils.Tree import decodeExpansion
1✔
29
from ZTUtils.Tree import encodeExpansion
1✔
30

31

32
class LazyFilter(Lazy):
1✔
33
    # A LazyFilter that checks with the security policy
34

35
    def __init__(self, seq, test=None, skip=None):
1✔
36
        self._seq = seq
1✔
37
        self._data = []
1✔
38
        self._eindex = -1
1✔
39
        self._test = test
1✔
40
        if not (skip is None or str(skip) == skip):
1!
41
            raise TypeError('Skip must be None or a string')
×
42
        self._skip = skip
1✔
43

44
    def __getitem__(self, index):
1✔
45
        data = self._data
1✔
46
        try:
1✔
47
            s = self._seq
1✔
48
        except AttributeError:
1✔
49
            return data[index]
1✔
50

51
        i = index
1✔
52
        if i < 0:
1!
53
            i = len(self) + i
×
54
        if i < 0:
1!
55
            raise IndexError(index)
×
56

57
        ind = len(data)
1✔
58
        if i < ind:
1✔
59
            return data[i]
1✔
60
        ind = ind - 1
1✔
61

62
        test = self._test
1✔
63
        e = self._eindex
1✔
64
        skip = self._skip
1✔
65
        while i > ind:
1✔
66
            e = e + 1
1✔
67
            try:
1✔
68
                try:
1✔
69
                    v = guarded_getitem(s, e)
1✔
70
                except Unauthorized as vv:
1✔
71
                    if skip is None:
×
72
                        self._eindex = e
×
73
                        msg = f'(item {index}): {vv}'
×
74
                        raise Unauthorized(msg)
×
75
                    skip_this = 1
×
76
                else:
77
                    skip_this = 0
1✔
78
            except IndexError:
1✔
79
                del self._test
1✔
80
                del self._seq
1✔
81
                del self._eindex
1✔
82
                raise IndexError(index)
1✔
83
            if skip_this:
1!
84
                continue
×
85
            if skip and not getSecurityManager().checkPermission(skip, v):
1!
86
                continue
×
87
            if test is None or test(v):
1!
88
                data.append(v)
1✔
89
                ind = ind + 1
1✔
90
        self._eindex = e
1✔
91
        return data[i]
1✔
92

93

94
class TreeSkipMixin:
1✔
95
    '''Mixin class to make trees test security, and allow
96
    skipping of unauthorized objects. '''
97
    skip = None
1✔
98

99
    def setSkip(self, skip):
1✔
100
        self.skip = skip
×
101
        return self
×
102

103
    def getChildren(self, object):
1✔
104
        return LazyFilter(self._getChildren(object), skip=self.skip)
×
105

106
    def filterChildren(self, children):
1✔
107
        if self._values_filter:
×
108
            return self._values_filter(LazyFilter(children, skip=self.skip))
×
109
        return children
×
110

111

112
class TreeMaker(TreeSkipMixin, TreeMaker):
1✔
113
    _getChildren = TreeMaker.getChildren
1✔
114

115

116
class SimpleTreeMaker(TreeSkipMixin, SimpleTreeMaker):
1✔
117
    _getChildren = SimpleTreeMaker.getChildren
1✔
118

119
    def cookieTree(self, root_object, default_state=None):
1✔
120
        '''Make a tree with state stored in a cookie.'''
121
        tree_pre = self.tree_pre
×
122
        state_name = '%s-state' % tree_pre
×
123
        set_name = '%s-setstate' % tree_pre
×
124

125
        req = root_object.REQUEST
×
126
        state = req.get(state_name)
×
127
        if state:
×
128
            setst = req.form.get(set_name)
×
129
            if setst:
×
130
                st, pn, expid = setst.split(',')
×
131
                state, (m, obid) = decodeExpansion(state, int(pn))
×
132
                if m is None:
×
133
                    pass
×
134
                elif st == 'e':
×
135
                    if m[obid] is None:
×
136
                        m[obid] = {expid: None}
×
137
                    else:
138
                        m[obid][expid] = None
×
139
                elif st == 'c' and m is not state and obid == expid:
×
140
                    del m[obid]
×
141
            else:
142
                state = decodeExpansion(state)
×
143
        else:
144
            state = default_state
×
145
        tree = self.tree(root_object, state)
×
146
        rows = tree.flat()
×
147
        req.RESPONSE.setCookie(state_name, encodeExpansion(rows))
×
148
        return tree, rows
×
149

150

151
# Make the Batch class test security, and let it skip unauthorized.
152
_Batch = Batch
1✔
153

154

155
class Batch(Batch):
1✔
156
    def __init__(self, sequence, size, start=0, end=0,
1✔
157
                 orphan=0, overlap=0, skip_unauthorized=None):
158
        sequence = LazyFilter(sequence, skip=skip_unauthorized)
1✔
159
        _Batch.__init__(self, sequence, size, start, end,
1✔
160
                        orphan, overlap)
161

162
# These functions are meant to be used together in templates that use
163
# trees or batches.  For example, given a batch with a 'bstart' query
164
# argument, you would use "url_query(request, omit='bstart')" to get
165
# the base for the batching links, then append
166
# "make_query(bstart=batch.previous.first)" to one and
167
# "make_query(bstart=batch.end)" to the other.
168

169

170
# Do not do this at import time.
171
# Call '_default_encoding()' at run time to retrieve it from config, if present
172
# If not configured, will be 'utf8' by default.
173
_DEFAULT_ENCODING = None
1✔
174

175

176
def _default_encoding():
1✔
177
    ''' Retrieve default encoding from config '''
178
    global _DEFAULT_ENCODING
179
    if _DEFAULT_ENCODING is None:
×
180
        from App.config import getConfiguration
×
181
        config = getConfiguration()
×
182
        try:
×
183
            _DEFAULT_ENCODING = config.zpublisher_default_encoding
×
184
        except AttributeError:
×
185
            _DEFAULT_ENCODING = 'utf8'
×
186
    return _DEFAULT_ENCODING
×
187

188

189
def make_query(*args, **kwargs):
1✔
190
    '''Construct a URL query string, with marshalling markup.
191

192
    If there are positional arguments, they must be dictionaries.
193
    They are combined with the dictionary of keyword arguments to form
194
    a dictionary of query names and values.
195

196
    Query names (the keys) must be strings.  Values may be strings,
197
    integers, floats, or DateTimes, and they may also be lists or
198
    namespaces containing these types.  Names and string values
199
    should not be URL-quoted.  All arguments are marshalled with
200
    complex_marshal().
201
    '''
202

203
    d = {}
1✔
204
    for arg in args:
1!
205
        d.update(arg)
×
206
    d.update(kwargs)
1✔
207

208
    qlist = complex_marshal(list(d.items()))
1✔
209
    for i in range(len(qlist)):
1✔
210
        k, m, v = qlist[i]
1✔
211
        qlist[i] = f'{quote(k)}{m}={quote(str(v))}'
1✔
212

213
    return '&'.join(qlist)
1✔
214

215

216
def make_hidden_input(*args, **kwargs):
1✔
217
    '''Construct a set of hidden input elements, with marshalling markup.
218

219
    If there are positional arguments, they must be dictionaries.
220
    They are combined with the dictionary of keyword arguments to form
221
    a dictionary of query names and values.
222

223
    Query names (the keys) must be strings.  Values may be strings,
224
    integers, floats, or DateTimes, and they may also be lists or
225
    namespaces containing these types.  All arguments are marshalled with
226
    complex_marshal().
227
    '''
228

229
    d = {}
1✔
230
    for arg in args:
1!
231
        d.update(arg)
×
232
    d.update(kwargs)
1✔
233

234
    def hq(x):
1✔
235
        return html.escape(x, quote=True)
1✔
236

237
    qlist = complex_marshal(list(d.items()))
1✔
238
    for i in range(len(qlist)):
1✔
239
        k, m, v = qlist[i]
1✔
240
        qlist[i] = ('<input type="hidden" name="%s%s" value="%s">'
1✔
241
                    % (hq(k), m, hq(str(v))))
242

243
    return '\n'.join(qlist)
1✔
244

245

246
def complex_marshal(pairs):
1✔
247
    '''Add request marshalling information to a list of name-value pairs.
248

249
    Names must be strings.  Values may be strings,
250
    integers, floats, or DateTimes, and they may also be lists or
251
    namespaces containing these types.
252

253
    The list is edited in place so that each (name, value) pair
254
    becomes a (name, marshal, value) triple.  The middle value is the
255
    request marshalling string.  Integer, float, and DateTime values
256
    will have ":int", ":float", or ":date" as their marshal string.
257
    Lists will be flattened, and the elements given ":list" in
258
    addition to their simple marshal string.  Dictionaries will be
259
    flattened and marshalled using ":record".
260
    '''
261
    i = len(pairs)
1✔
262
    while i > 0:
1✔
263
        i = i - 1
1✔
264
        k, v = pairs[i]
1✔
265
        m = ''
1✔
266
        sublist = None
1✔
267
        if isinstance(v, str):
1✔
268
            pass
1✔
269
        elif hasattr(v, 'items'):
1✔
270
            sublist = []
1✔
271
            for sk, sv in v.items():
1✔
272
                if isinstance(sv, list):
1✔
273
                    for ssv in sv:
1✔
274
                        sm = simple_marshal(ssv)
1✔
275
                        sublist.append((f'{k}.{sk}',
1✔
276
                                        '%s:list:record' % sm, ssv))
277
                else:
278
                    sm = simple_marshal(sv)
1✔
279
                    sublist.append((f'{k}.{sk}', '%s:record' % sm, sv))
1✔
280
        elif isinstance(v, list):
1✔
281
            sublist = []
1✔
282
            for sv in v:
1✔
283
                sm = simple_marshal(sv)
1✔
284
                sublist.append((k, '%s:list' % sm, sv))
1✔
285
        else:
286
            m = simple_marshal(v)
1✔
287
        if sublist is None:
1✔
288
            pairs[i] = (k, m, v)
1✔
289
        else:
290
            pairs[i:i + 1] = sublist
1✔
291

292
    return pairs
1✔
293

294

295
def simple_marshal(v):
1✔
296
    if isinstance(v, str):
1✔
297
        return ''
1✔
298
    if isinstance(v, bytes):
1!
299
        return ':bytes'
×
300
    if isinstance(v, bool):
1✔
301
        return ':boolean'
1✔
302
    if isinstance(v, int):
1✔
303
        return ':int'
1✔
304
    if isinstance(v, float):
1✔
305
        return ':float'
1✔
306
    if isinstance(v, DateTime):
1!
307
        return ':date'
1✔
308
    return ''
×
309

310

311
def url_query(request, req_name="URL", omit=None):
1✔
312
    '''Construct a URL with a query string, using the current request.
313

314
    request: the request object
315
    req_name: the name, such as "URL1" or "BASEPATH1", to get from request
316
    omit: sequence of name of query arguments to omit.  If a name
317
    contains a colon, it is treated literally.  Otherwise, it will
318
    match each argument name that starts with the name and a period or colon.
319
    '''
320

321
    base = request[req_name]
×
322
    qs = request.get('QUERY_STRING', '')
×
323

324
    if qs and omit:
×
325
        qsparts = qs.split('&')
×
326

327
        if isinstance(omit, str):
×
328
            omits = {omit: None}
×
329
        else:
330
            omits = {}
×
331
            for name in omit:
×
332
                omits[name] = None
×
333

334
        for i in range(len(qsparts)):
×
335
            name = unquote(qsparts[i].split('=', 1)[0])
×
336
            if name in omits:
×
337
                qsparts[i] = ''
×
338
            name = name.split(':', 1)[0]
×
339
            if name in omits:
×
340
                qsparts[i] = ''
×
341
            name = name.split('.', 1)[0]
×
342
            if name in omits:
×
343
                qsparts[i] = ''
×
344

345
        qs = '&'.join([part for part in qsparts if part])
×
346

347
    # We always append '?' since arguments will be appended to the URL
348
    return f'{base}?{qs}'
×
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