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

zopefoundation / Products.CMFCore / 6246931310

20 Sep 2023 09:54AM UTC coverage: 86.008% (-0.3%) from 86.266%
6246931310

Pull #131

github

mauritsvanrees
gha: don't need setup-python on 27 as we use the 27 container.
Pull Request #131: Make decodeFolderFilter and encodeFolderFilter non-public.

2466 of 3689 branches covered (0.0%)

Branch coverage included in aggregate %.

6 of 6 new or added lines in 1 file covered. (100.0%)

17297 of 19289 relevant lines covered (89.67%)

0.9 hits per line

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

66.42
/src/Products/CMFCore/utils.py
1
##############################################################################
2
#
3
# Copyright (c) 2001 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
""" Utility functions.
1✔
14
"""
15

16
import base64
1✔
17
import re
1✔
18
import sys
1✔
19
from copy import deepcopy
1✔
20
from os import path as os_path
1✔
21
from os.path import abspath
1✔
22
from warnings import warn
1✔
23

24
import pkg_resources
1✔
25
import six
1✔
26
from six.moves._thread import allocate_lock
1✔
27

28
from AccessControl.class_init import InitializeClass
1✔
29
from AccessControl.Permission import Permission
1✔
30
from AccessControl.PermissionRole import rolesForPermissionOn
1✔
31
from AccessControl.rolemanager import gather_permissions
1✔
32
from AccessControl.SecurityInfo import ClassSecurityInfo
1✔
33
from AccessControl.SecurityInfo import ModuleSecurityInfo
1✔
34
from AccessControl.SecurityManagement import getSecurityManager
1✔
35
from Acquisition import Implicit
1✔
36
from Acquisition import aq_get
1✔
37
from Acquisition import aq_parent
1✔
38
from Acquisition.interfaces import IAcquirer
1✔
39
from App.Common import package_home
1✔
40
from App.ImageFile import ImageFile
1✔
41
from App.special_dtml import HTMLFile
1✔
42
from DateTime.DateTime import DateTime
1✔
43
from DateTime.interfaces import DateTimeError
1✔
44
from ExtensionClass import Base
1✔
45
from OFS.misc_ import Misc_ as MiscImage
1✔
46
from OFS.misc_ import misc_ as misc_images
1✔
47
from OFS.ObjectManager import UNIQUE
1✔
48
from OFS.PropertyManager import PropertyManager
1✔
49
from OFS.SimpleItem import SimpleItem
1✔
50
from zope.component import getUtility
1✔
51
from zope.component import queryUtility
1✔
52
from zope.datetime import rfc1123_date
1✔
53
from zope.dottedname.resolve import resolve as resolve_dotted_name
1✔
54
from zope.i18nmessageid import MessageFactory
1✔
55
from zope.interface.interfaces import ComponentLookupError
1✔
56

57
import Products
1✔
58

59
from .exceptions import AccessControl_Unauthorized
1✔
60
from .exceptions import NotFound
1✔
61
from .interfaces import ICachingPolicyManager
1✔
62

63

64
HAS_ZSERVER = True
1✔
65
try:
1✔
66
    dist = pkg_resources.get_distribution('ZServer')
1✔
67
except pkg_resources.DistributionNotFound:
1✔
68
    HAS_ZSERVER = False
1✔
69

70
SUBTEMPLATE = '__SUBTEMPLATE__'
1✔
71
ProductsPath = [abspath(ppath) for ppath in Products.__path__]
1✔
72
security = ModuleSecurityInfo('Products.CMFCore.utils')
1✔
73

74
_globals = globals()
1✔
75
_dtmldir = os_path.join(package_home(globals()), 'dtml')
1✔
76
_wwwdir = os_path.join(package_home(globals()), 'www')
1✔
77

78
#
79
#   Simple utility functions, callable from restricted code.
80
#
81
_marker = []  # Create a new marker object.
1✔
82

83
_tool_interface_registry = {}
1✔
84

85

86
@security.private
1✔
87
def registerToolInterface(tool_id, tool_interface):
1✔
88
    """ Register a tool ID for an interface
89

90
    This method can go away when getToolByName is going away
91
    """
92
    global _tool_interface_registry
93
    _tool_interface_registry[tool_id] = tool_interface
1✔
94

95

96
@security.private
1✔
97
def getToolInterface(tool_id):
1✔
98
    """ Get the interface registered for a tool ID
99
    """
100
    global _tool_interface_registry
101
    return _tool_interface_registry.get(tool_id, None)
×
102

103

104
@security.public
1✔
105
def getToolByName(obj, name, default=_marker):
1✔
106

107
    """ Get the tool, 'toolname', by acquiring it.
108

109
    o Application code should use this method, rather than simply
110
      acquiring the tool by name, to ease forward migration (e.g.,
111
      to Zope3).
112
    """
113
    tool_interface = _tool_interface_registry.get(name)
1✔
114

115
    if tool_interface is not None:
1✔
116
        try:
1✔
117
            utility = getUtility(tool_interface)
1✔
118
            # Site managers, except for five.localsitemanager, return unwrapped
119
            # utilities. If the result is something which is
120
            # acquisition-unaware but unwrapped we wrap it on the context.
121
            if IAcquirer.providedBy(obj) and \
1!
122
                    aq_parent(utility) is None and \
123
                    IAcquirer.providedBy(utility):
124
                utility = utility.__of__(obj)
1✔
125
            return utility
1✔
126
        except ComponentLookupError:
1✔
127
            # behave in backwards-compatible way
128
            # fall through to old implementation
129
            pass
1✔
130

131
    try:
1✔
132
        tool = aq_get(obj, name, default, 1)
1✔
133
    except AttributeError:
×
134
        if default is _marker:
×
135
            raise
×
136
        return default
×
137
    else:
138
        if tool is _marker:
1!
139
            raise AttributeError(name)
×
140
        return tool
1✔
141

142

143
@security.public
1✔
144
def getUtilityByInterfaceName(dotted_name, default=_marker):
1✔
145
    """ Get a tool by its fully-qualified dotted interface path
146

147
    This method replaces getToolByName for use in untrusted code.
148
    Trusted code should use zope.component.getUtility instead.
149
    """
150
    try:
×
151
        iface = resolve_dotted_name(dotted_name)
×
152
    except ImportError:
153
        if default is _marker:
154
            raise ComponentLookupError(dotted_name)
155
        return default
156

157
    try:
×
158
        return getUtility(iface)
×
159
    except ComponentLookupError:
×
160
        if default is _marker:
×
161
            raise
×
162
        return default
×
163

164

165
@security.public
1✔
166
def cookString(text):
1✔
167

168
    """ Make a Zope-friendly ID from 'text'.
169

170
    o Remove any spaces
171

172
    o Lowercase the ID.
173
    """
174
    rgx = re.compile(r'(^_|[^a-zA-Z0-9-_~\,\.])')
×
175
    cooked = re.sub(rgx, '', text).lower()
×
176
    return cooked
×
177

178

179
@security.public
1✔
180
def tuplize(valueName, value):
1✔
181

182
    """ Make a tuple from 'value'.
183

184
    o Use 'valueName' to generate appropriate error messages.
185
    """
186
    if isinstance(value, tuple):
×
187
        return value
×
188
    if isinstance(value, list):
×
189
        return tuple(value)
×
190
    if isinstance(value, six.string_types):
×
191
        return tuple(value.split())
×
192
    raise ValueError('%s of unsupported type' % valueName)
×
193

194

195
#
196
#   Security utilities, callable only from unrestricted code.
197
#
198
# deprecated alias
199
@security.private
1✔
200
def _getAuthenticatedUser(self):
1✔
201
    return getSecurityManager().getUser()
×
202

203

204
@security.private
1✔
205
def _checkPermission(permission, obj):
1✔
206
    if not isinstance(permission, six.string_types):
1!
207
        permission = permission.decode()
×
208
    return getSecurityManager().checkPermission(permission, obj)
1✔
209

210

211
# If Zope ever provides a call to getRolesInContext() through
212
# the SecurityManager API, the method below needs to be updated.
213
@security.private
1✔
214
def _limitGrantedRoles(roles, context, special_roles=()):
1✔
215
    # Only allow a user to grant roles already possessed by that user,
216
    # with the exception that all special_roles can also be granted.
217
    user = getSecurityManager().getUser()
×
218
    if user is None:
×
219
        user_roles = ()
×
220
    else:
221
        user_roles = user.getRolesInContext(context)
×
222
    if 'Manager' in user_roles:
×
223
        # Assume all other roles are allowed.
224
        return
×
225
    for role in roles:
×
226
        if role not in special_roles and role not in user_roles:
×
227
            raise AccessControl_Unauthorized('Too many roles specified.')
×
228

229

230
@security.private
1✔
231
def _mergedLocalRoles(object):
1✔
232
    """Returns a merging of object and its ancestors'
233
    __ac_local_roles__."""
234
    # Modified from AccessControl.User.getRolesInContext().
235
    merged = {}
1✔
236
    object = getattr(object, 'aq_inner', object)
1✔
237
    while 1:
238
        if hasattr(object, '__ac_local_roles__'):
1✔
239
            dict = object.__ac_local_roles__ or {}
1✔
240
            if callable(dict):
1!
241
                dict = dict()
×
242
            for k, v in dict.items():
1✔
243
                if k in merged:
1!
244
                    merged[k] = merged[k] + v
×
245
                else:
246
                    merged[k] = v
1✔
247
        if hasattr(object, 'aq_parent'):
1!
248
            object = object.aq_parent
×
249
            object = getattr(object, 'aq_inner', object)
×
250
            continue
×
251
        if hasattr(object, '__self__'):
1!
252
            object = object.__self__
1✔
253
            object = getattr(object, 'aq_inner', object)
1✔
254
            continue
1✔
255
        break
×
256

257
    return deepcopy(merged)
1✔
258

259

260
@security.private
1✔
261
def _ac_inherited_permissions(ob, all=0):
1✔
262
    # Get all permissions not defined in ourself that are inherited
263
    # This will be a sequence of tuples with a name as the first item and
264
    # an empty tuple as the second.
265
    d = {}
×
266
    perms = getattr(ob, '__ac_permissions__', ())
×
267
    for p in perms:
×
268
        d[p[0]] = None
×
269
    r = gather_permissions(ob.__class__, [], d)
×
270
    if all:
×
271
        if hasattr(ob, '_subobject_permissions'):
×
272
            for p in ob._subobject_permissions():
×
273
                pname = p[0]
×
274
                if pname not in d:
×
275
                    d[pname] = 1
×
276
                    r.append(p)
×
277
        r = list(perms) + r
×
278
    return r
×
279

280

281
@security.private
1✔
282
def _modifyPermissionMappings(ob, map):
1✔
283
    """
284
    Modifies multiple role to permission mappings.
285
    """
286
    # This mimics what AccessControl/Role.py does.
287
    # Needless to say, it's crude. :-(
288
    something_changed = 0
×
289
    perm_info = _ac_inherited_permissions(ob, 1)
×
290
    for name, settings in map.items():
×
291
        cur_roles = rolesForPermissionOn(name, ob)
×
292
        if isinstance(cur_roles, six.string_types):
×
293
            cur_roles = [cur_roles]
×
294
        else:
295
            cur_roles = list(cur_roles)
×
296
        changed = 0
×
297
        for (role, allow) in settings.items():
×
298
            if not allow:
×
299
                if role in cur_roles:
×
300
                    changed = 1
×
301
                    cur_roles.remove(role)
×
302
            else:
303
                if role not in cur_roles:
×
304
                    changed = 1
×
305
                    cur_roles.append(role)
×
306
        if changed:
×
307
            data = ()  # The list of methods using this permission.
×
308
            for perm in perm_info:
×
309
                n, d = perm[:2]
×
310
                if n == name:
×
311
                    data = d
×
312
                    break
×
313
            p = Permission(name, data, ob)
×
314
            p.setRoles(tuple(cur_roles))
×
315
            something_changed = 1
×
316
    return something_changed
×
317

318

319
class FakeExecutableObject:
1✔
320

321
    """Fake ExecutableObject used to set proxy roles in trusted code.
322
    """
323

324
    def __init__(self, proxy_roles):
1✔
325
        self._proxy_roles = tuple(proxy_roles)
1✔
326

327
    def getOwner(self):
1✔
328
        return None
1✔
329

330
    getWrappedOwner = getOwner
1✔
331

332

333
# Parse a string of etags from an If-None-Match header
334
# Code follows ZPublisher.HTTPRequest.parse_cookie
335
parse_etags_lock = allocate_lock()
1✔
336

337

338
def parse_etags(text,
1✔
339
                result=None,
340
                # quoted etags (assumed separated by whitespace + a comma)
341
                etagre_quote=re.compile(r'(\s*\"([^\"]*)\"\s*,{0,1})'),
342
                # non-quoted etags (assumed separated by whitespace + a comma)
343
                etagre_noquote=re.compile(r'(\s*([^,]*)\s*,{0,1})'),
344
                acquire=parse_etags_lock.acquire,
345
                release=parse_etags_lock.release):
346

347
    if result is None:
1✔
348
        result = []
1✔
349
    if not len(text):
1✔
350
        return result
1✔
351

352
    acquire()
1✔
353
    try:
1✔
354
        m = etagre_quote.match(text)
1✔
355
        if m:
1✔
356
            # Match quoted etag (spec-observing client)
357
            tl = len(m.group(1))
1✔
358
            value = m.group(2)
1✔
359
        else:
360
            # Match non-quoted etag (lazy client)
361
            m = etagre_noquote.match(text)
1✔
362
            if m:
1!
363
                tl = len(m.group(1))
1✔
364
                value = m.group(2)
1✔
365
            else:
366
                return result
×
367
    finally:
368
        release()
1!
369

370
    if value:
1!
371
        result.append(value)
1✔
372
    return parse_etags(*(text[tl:], result))
1✔
373

374

375
def _checkConditionalGET(obj, extra_context):
1✔
376
    """A conditional GET is done using one or both of the request
377
       headers:
378

379
       If-Modified-Since: Date
380
       If-None-Match: list ETags (comma delimited, sometimes quoted)
381

382
       If both conditions are present, both must be satisfied.
383

384
       This method checks the caching policy manager to see if
385
       a content object's Last-modified date and ETag satisfy
386
       the conditional GET headers.
387

388
       Returns the tuple (last_modified, etag) if the conditional
389
       GET requirements are met and None if not.
390

391
       It is possible for one of the tuple elements to be None.
392
       For example, if there is no If-None-Match header and
393
       the caching policy does not specify an ETag, we will
394
       just return (last_modified, None).
395
       """
396

397
    REQUEST = getattr(obj, 'REQUEST', None)
1✔
398
    if REQUEST is None:
1!
399
        return False
×
400

401
    # check whether we need to suppress subtemplates
402
    call_count = getattr(REQUEST, SUBTEMPLATE, 0)
1✔
403
    setattr(REQUEST, SUBTEMPLATE, call_count + 1)
1✔
404
    if call_count != 0:
1✔
405
        return False
1✔
406

407
    if_modified_since = REQUEST.getHeader('If-Modified-Since', None)
1✔
408
    if_none_match = REQUEST.getHeader('If-None-Match', None)
1✔
409

410
    if if_modified_since is None and if_none_match is None:
1✔
411
        # not a conditional GET
412
        return False
1✔
413

414
    manager = queryUtility(ICachingPolicyManager)
1✔
415
    if manager is None:
1✔
416
        return False
1✔
417

418
    ret = manager.getModTimeAndETag(aq_parent(obj), obj.getId(), extra_context)
1✔
419
    if ret is None:
1✔
420
        # no appropriate policy or 304s not enabled
421
        return False
1✔
422

423
    (content_mod_time, content_etag, set_last_modified_header) = ret
1✔
424
    if content_mod_time:
1✔
425
        mod_time_secs = int(content_mod_time.timeTime())
1✔
426
    else:
427
        mod_time_secs = None
1✔
428

429
    if if_modified_since:
1✔
430
        # from CMFCore/FSFile.py:
431
        if_modified_since = if_modified_since.split(';')[0]
1✔
432
        # Some proxies seem to send invalid date strings for this
433
        # header. If the date string is not valid, we ignore it
434
        # rather than raise an error to be generally consistent
435
        # with common servers such as Apache (which can usually
436
        # understand the screwy date string as a lucky side effect
437
        # of the way they parse it).
438
        try:
1✔
439
            if_modified_since = int(DateTime(if_modified_since).timeTime())
1✔
440
        except Exception:
×
441
            if_modified_since = None
×
442

443
    client_etags = None
1✔
444
    if if_none_match:
1✔
445
        client_etags = parse_etags(if_none_match)
1✔
446

447
    if not if_modified_since and not client_etags:
1!
448
        # not a conditional GET, or headers are messed up
449
        return False
×
450

451
    if if_modified_since:
1✔
452
        if not content_mod_time or \
1✔
453
           mod_time_secs < 0 or \
454
           mod_time_secs > if_modified_since:
455
            return False
1✔
456

457
    if client_etags:
1✔
458
        if not content_etag or \
1✔
459
           (content_etag not in client_etags and '*' not in client_etags):
460
            return False
1✔
461
    else:
462
        # If we generate an ETag, don't validate the conditional GET unless
463
        # the client supplies an ETag
464
        # This may be more conservative than the spec requires, but we are
465
        # already _way_ more conservative.
466
        if content_etag:
1✔
467
            return False
1✔
468

469
    response = REQUEST.RESPONSE
1✔
470
    if content_mod_time and set_last_modified_header:
1!
471
        response.setHeader('Last-modified', str(content_mod_time))
1✔
472
    if content_etag:
1✔
473
        response.setHeader('ETag', content_etag, literal=1)
1✔
474
    response.setStatus(304)
1✔
475
    delattr(REQUEST, SUBTEMPLATE)
1✔
476

477
    return True
1✔
478

479

480
@security.private
1✔
481
def _setCacheHeaders(obj, extra_context):
1✔
482
    """Set cache headers according to cache policy manager for the obj."""
483
    REQUEST = getattr(obj, 'REQUEST', None)
1✔
484

485
    if REQUEST is not None:
1!
486
        call_count = getattr(REQUEST, SUBTEMPLATE, 1) - 1
1✔
487
        setattr(REQUEST, SUBTEMPLATE, call_count)
1✔
488
        if call_count != 0:
1✔
489
            return
1✔
490

491
        # cleanup
492
        delattr(REQUEST, SUBTEMPLATE)
1✔
493

494
        content = aq_parent(obj)
1✔
495
        manager = queryUtility(ICachingPolicyManager)
1✔
496
        if manager is None:
1✔
497
            return
1✔
498

499
        view_name = obj.getId()
1✔
500
        headers = manager.getHTTPCachingHeaders(
1✔
501
                          content, view_name, extra_context)
502
        RESPONSE = REQUEST['RESPONSE']
1✔
503
        for key, value in headers:
1✔
504
            if key == 'ETag':
1✔
505
                RESPONSE.setHeader(key, value, literal=1)
1✔
506
            else:
507
                RESPONSE.setHeader(key, value)
1✔
508
        if headers:
1✔
509
            RESPONSE.setHeader('X-Cache-Headers-Set-By',
1✔
510
                               'CachingPolicyManager: %s' %
511
                               '/'.join(manager.getPhysicalPath()))
512

513

514
class _ViewEmulator(Implicit):
1✔
515
    """Auxiliary class used to adapt FSFile and FSImage
516
    for caching_policy_manager
517
    """
518
    def __init__(self, view_name=''):
1✔
519
        self._view_name = view_name
1✔
520

521
    def getId(self):
1✔
522
        return self._view_name
1✔
523

524

525
#
526
#   Base classes for tools
527
#
528
class ImmutableId(Base):
1✔
529

530
    """ Base class for objects which cannot be renamed.
531
    """
532

533
    def _setId(self, id):
1✔
534
        """ Never allow renaming!
535
        """
536
        if id != self.getId():
1✔
537
            raise ValueError('Changing the id of this object is forbidden: %s'
1✔
538
                             % self.getId())
539

540

541
class UniqueObject(ImmutableId):
1✔
542

543
    """ Base class for objects which cannot be "overridden" / shadowed.
544
    """
545
    zmi_icon = 'fas fa-wrench'
1✔
546

547
    def _getUNIQUE(self):
1✔
548
        return UNIQUE
×
549

550
    __replaceable__ = property(_getUNIQUE)
1✔
551

552

553
class SimpleItemWithProperties(PropertyManager, SimpleItem):
1✔
554
    """
555
    A common base class for objects with configurable
556
    properties in a fixed schema.
557
    """
558
    manage_options = (
1✔
559
        PropertyManager.manage_options +
560
        SimpleItem.manage_options)
561

562
    security = ClassSecurityInfo()
1✔
563
    security.declarePrivate('manage_addProperty')  # NOQA: flake8: D001
1✔
564
    security.declarePrivate('manage_delProperties')  # NOQA: flake8: D001
1✔
565
    security.declarePrivate('manage_changePropertyTypes')  # NOQA: flake8: D001
1✔
566

567
    def manage_propertiesForm(self, REQUEST, *args, **kw):
1✔
568
        """ An override that makes the schema fixed.
569
        """
570
        my_kw = kw.copy()
×
571
        my_kw['property_extensible_schema__'] = 0
×
572
        form = PropertyManager.manage_propertiesForm.__of__(self)
×
573
        return form(self, REQUEST, *args, **my_kw)
×
574

575

576
InitializeClass(SimpleItemWithProperties)
1✔
577

578

579
#
580
#   "Omnibus" factory framework for tools.
581
#
582
class ToolInit:
1✔
583

584
    """ Utility class for generating the factories for several tools.
585
    """
586
    __name__ = 'toolinit'
1✔
587

588
    security = ClassSecurityInfo()
1✔
589
    security.declareObjectPrivate()     # equivalent of __roles__ = ()
1✔
590

591
    def __init__(self, meta_type, tools, product_name=None, icon=None):
1✔
592
        self.meta_type = meta_type
1✔
593
        self.tools = tools
1✔
594
        if product_name is not None:
1!
595
            warn('The product_name parameter of ToolInit is now ignored',
×
596
                 DeprecationWarning, stacklevel=2)
597
        self.icon = icon
1✔
598

599
    def initialize(self, context):
1✔
600
        # Add only one meta type to the folder add list.
601
        productObject = context._ProductContext__prod
1✔
602
        self.product_name = productObject.id
1✔
603
        context.registerClass(
1✔
604
            meta_type=self.meta_type,
605
            # This is a little sneaky: we add self to the
606
            # FactoryDispatcher under the name "toolinit".
607
            # manage_addTool() can then grab it.
608
            constructors=(manage_addToolForm, manage_addTool, self),
609
            icon=self.icon)
610

611
        if self.icon:
1!
612
            icon = os_path.split(self.icon)[1]
1✔
613
        else:
614
            icon = None
×
615
        for tool in self.tools:
1✔
616
            tool.__factory_meta_type__ = self.meta_type
1✔
617
            tool.icon = 'misc_/%s/%s' % (self.product_name, icon)
1✔
618

619

620
InitializeClass(ToolInit)
1✔
621

622
addInstanceForm = HTMLFile('dtml/addInstance', globals())
1✔
623

624

625
def manage_addToolForm(self, REQUEST):
1✔
626

627
    """ Show the add tool form.
628
    """
629
    # self is a FactoryDispatcher.
630
    toolinit = self.toolinit
×
631
    tl = []
×
632
    for tool in toolinit.tools:
×
633
        tl.append(tool.meta_type)
×
634
    return addInstanceForm(addInstanceForm, self, REQUEST,
×
635
                           factory_action='manage_addTool',
636
                           factory_meta_type=toolinit.meta_type,
637
                           factory_product_name=toolinit.product_name,
638
                           factory_icon=toolinit.icon,
639
                           factory_types_list=tl,
640
                           factory_need_id=0)
641

642

643
def manage_addTool(self, type, REQUEST=None):
1✔
644

645
    """ Add the tool specified by name.
646
    """
647
    # self is a FactoryDispatcher.
648
    toolinit = self.toolinit
×
649
    obj = None
×
650
    for tool in toolinit.tools:
×
651
        if tool.meta_type == type:
×
652
            obj = tool()
×
653
            break
×
654
    if obj is None:
×
655
        raise NotFound(type)
×
656
    self._setObject(obj.getId(), obj)
×
657
    if REQUEST is not None:
×
658
        return self.manage_main(self, REQUEST)
×
659

660

661
#
662
#   Now, do the same for creating content factories.
663
#
664
class ContentInit:
1✔
665

666
    """ Utility class for generating factories for several content types.
667
    """
668
    __name__ = 'contentinit'
1✔
669

670
    security = ClassSecurityInfo()
1✔
671
    security.declareObjectPrivate()
1✔
672

673
    def __init__(self, meta_type, content_types, permission=None,
1✔
674
                 extra_constructors=(), fti=(), visibility='Global'):
675
        # BBB: fti argument is ignored
676
        self.meta_type = meta_type
1✔
677
        self.content_types = content_types
1✔
678
        self.permission = permission
1✔
679
        self.extra_constructors = extra_constructors
1✔
680
        self.visibility = visibility
1✔
681

682
    def initialize(self, context):
1✔
683
        # Add only one meta type to the folder add list.
684
        context.registerClass(
1✔
685
            meta_type=self.meta_type,
686
            # This is a little sneaky: we add self to the
687
            # FactoryDispatcher under the name "contentinit".
688
            # manage_addContentType() can then grab it.
689
            constructors=(manage_addContentForm, manage_addContent,
690
                          self) + self.extra_constructors,
691
            permission=self.permission,
692
            visibility=self.visibility)
693

694
        for ct in self.content_types:
1!
695
            ct.__factory_meta_type__ = self.meta_type
×
696

697

698
InitializeClass(ContentInit)
1✔
699

700

701
def manage_addContentForm(self, REQUEST):
1✔
702
    """ Show the add content type form.
703
    """
704
    # self is a FactoryDispatcher.
705
    ci = self.contentinit
×
706
    tl = []
×
707
    for t in ci.content_types:
×
708
        tl.append(t.meta_type)
×
709
    return addInstanceForm(addInstanceForm, self, REQUEST,
×
710
                           factory_action='manage_addContent',
711
                           factory_meta_type=ci.meta_type,
712
                           factory_icon=None,
713
                           factory_types_list=tl,
714
                           factory_need_id=1)
715

716

717
def manage_addContent(self, id, type, REQUEST=None):
1✔
718
    """ Add the content type specified by name.
719
    """
720
    # self is a FactoryDispatcher.
721
    contentinit = self.contentinit
×
722
    obj = None
×
723
    for content_type in contentinit.content_types:
×
724
        if content_type.meta_type == type:
×
725
            obj = content_type(id)
×
726
            break
×
727
    if obj is None:
×
728
        raise NotFound(type)
×
729
    self._setObject(id, obj)
×
730
    if REQUEST is not None:
×
731
        return self.manage_main(self, REQUEST)
×
732

733

734
def registerIcon(klass, iconspec, _prefix=None):
1✔
735

736
    """ Make an icon available for a given class.
737

738
    o 'klass' is the class being decorated.
739

740
    o 'iconspec' is the path within the product where the icon lives.
741
    """
742
    modname = klass.__module__
1✔
743
    pid = modname.split('.')[1]
1✔
744
    name = os_path.split(iconspec)[1]
1✔
745
    klass.icon = 'misc_/%s/%s' % (pid, name)
1✔
746
    icon = ImageFile(iconspec, _prefix)
1✔
747
    icon.__roles__ = None
1✔
748
    if not hasattr(misc_images, pid):
1✔
749
        setattr(misc_images, pid, MiscImage(pid, {}))
1✔
750
    getattr(misc_images, pid)[name] = icon
1✔
751

752

753
#
754
#   Metadata Keyword splitter utilities
755
#
756
KEYSPLITRE = re.compile(r'[,;]')
1✔
757

758

759
@security.public
1✔
760
def keywordsplitter(headers, names=('Subject', 'Keywords'),
1✔
761
                    splitter=KEYSPLITRE.split):
762
    """ Split keywords out of headers, keyed on names.  Returns list.
763
    """
764
    out = []
1✔
765
    for head in names:
1✔
766
        keylist = splitter(headers.get(head, ''))
1✔
767
        keylist = [x.strip() for x in keylist]
1✔
768
        out.extend([key for key in keylist if key])
1✔
769
    return out
1✔
770

771

772
#
773
#   Metadata Contributors splitter utilities
774
#
775
CONTRIBSPLITRE = re.compile(r';')
1✔
776

777

778
@security.public
1✔
779
def contributorsplitter(headers, names=('Contributors',),
1✔
780
                        splitter=CONTRIBSPLITRE.split):
781
    """ Split contributors out of headers, keyed on names.  Returns list.
782
    """
783
    return keywordsplitter(headers, names, splitter)
1✔
784

785

786
#
787
#   Directory-handling utilities
788
#
789
@security.public
1✔
790
def normalize(p):
1✔
791
    # the first .replace is needed to help normpath when dealing with Windows
792
    # paths under *nix, the second to normalize to '/'
793
    return os_path.normpath(p.replace('\\', '/')).replace('\\', '/')
1✔
794

795

796
@security.private
1✔
797
def getContainingPackage(module):
1✔
798
    parts = module.split('.')
1✔
799
    while parts:
1!
800
        name = '.'.join(parts)
1✔
801
        mod = sys.modules[name]
1✔
802
        if '__init__' in mod.__file__:
1✔
803
            return name
1✔
804
        parts = parts[:-1]
1✔
805

806
    raise ValueError('Unable to find package for module %s' % module)
×
807

808

809
@security.private
1✔
810
def getPackageLocation(module):
1✔
811
    """ Return the filesystem location of a module.
812

813
    This is a simple wrapper around the global package_home method which
814
    tricks it into working with just a module name.
815
    """
816
    package = getContainingPackage(module)
1✔
817
    return package_home({'__name__': package})
1✔
818

819

820
@security.private
1✔
821
def getPackageName(globals_):
1✔
822
    module = globals_['__name__']
1✔
823
    return getContainingPackage(module)
1✔
824

825

826
def _OldCacheHeaders(obj):
1✔
827
    # Old-style checking of modified headers
828

829
    REQUEST = getattr(obj, 'REQUEST', None)
1✔
830
    if REQUEST is None:
1!
831
        return False
×
832

833
    RESPONSE = REQUEST.RESPONSE
1✔
834
    header = REQUEST.getHeader('If-Modified-Since', None)
1✔
835
    last_mod = int(obj.modified().timeTime())
1✔
836

837
    if header is not None:
1!
838
        header = header.split(';')[0]
1✔
839
        # Some proxies seem to send invalid date strings for this
840
        # header. If the date string is not valid, we ignore it
841
        # rather than raise an error to be generally consistent
842
        # with common servers such as Apache (which can usually
843
        # understand the screwy date string as a lucky side effect
844
        # of the way they parse it).
845
        try:
1✔
846
            mod_since = DateTime(header)
1✔
847
            mod_since = int(mod_since.timeTime())
1✔
848
        except (TypeError, DateTimeError):
1✔
849
            mod_since = None
1✔
850

851
        if mod_since is not None:
1✔
852
            if last_mod > 0 and last_mod <= mod_since:
1✔
853
                RESPONSE.setStatus(304)
1✔
854
                return True
1✔
855

856
    # Last-Modified will get stomped on by a cache policy if there is
857
    # one set....
858
    RESPONSE.setHeader('Last-Modified', rfc1123_date(last_mod))
1✔
859

860

861
def _FSCacheHeaders(obj):
1✔
862
    # Old-style setting of modified headers for FS-based objects
863

864
    REQUEST = getattr(obj, 'REQUEST', None)
1✔
865
    if REQUEST is None:
1!
866
        return False
×
867

868
    RESPONSE = REQUEST.RESPONSE
1✔
869
    header = REQUEST.getHeader('If-Modified-Since', None)
1✔
870
    # Reduce resolution to one second, else if-modified-since would
871
    # always be older if system resolution is higher
872
    last_mod = int(obj._file_mod_time)
1✔
873

874
    if header is not None:
1✔
875
        header = header.split(';')[0]
1✔
876
        # Some proxies seem to send invalid date strings for this
877
        # header. If the date string is not valid, we ignore it
878
        # rather than raise an error to be generally consistent
879
        # with common servers such as Apache (which can usually
880
        # understand the screwy date string as a lucky side effect
881
        # of the way they parse it).
882
        try:
1✔
883
            mod_since = DateTime(header)
1✔
884
            mod_since = int(mod_since.timeTime())
1✔
885
        except (TypeError, DateTimeError):
1✔
886
            mod_since = None
1✔
887

888
        if mod_since is not None:
1✔
889
            if last_mod > 0 and last_mod <= mod_since:
1✔
890
                RESPONSE.setStatus(304)
1✔
891
                return True
1✔
892

893
    # Last-Modified will get stomped on by a cache policy if there is
894
    # one set....
895
    RESPONSE.setHeader('Last-Modified', rfc1123_date(last_mod))
1✔
896

897

898
class SimpleRecord:
1✔
899
    """ record-like class """
900

901
    def __init__(self, **kw):
1✔
902
        self.__dict__.update(kw)
×
903

904

905
def base64_encode(text):
1✔
906
    # Helper method to avoid deprecation warning under Python 3
907
    if six.PY2:
1!
908
        return base64.encodestring(text).rstrip()
×
909
    return base64.encodebytes(text).rstrip()
1✔
910

911

912
def base64_decode(text):
1✔
913
    # Helper method to avoid deprecation warning under Python 3
914
    if six.PY2:
×
915
        return base64.decodestring(text)
×
916
    return base64.decodebytes(text)
×
917

918

919
security.declarePublic('Message')  # NOQA: flake8: D001
1✔
920
Message = MessageFactory('cmf_default')
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