• 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

72.24
/src/OFS/CopySupport.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
"""Copy interface
1✔
14
"""
15

16
import base64
1✔
17
import logging
1✔
18
import re
1✔
19
import tempfile
1✔
20
import warnings
1✔
21
from json import dumps
1✔
22
from json import loads
1✔
23
from zlib import compress
1✔
24
from zlib import decompressobj
1✔
25

26
import transaction
1✔
27
from AccessControl import ClassSecurityInfo
1✔
28
from AccessControl import getSecurityManager
1✔
29
from AccessControl.class_init import InitializeClass
1✔
30
from AccessControl.Permissions import copy_or_move
1✔
31
from AccessControl.Permissions import delete_objects
1✔
32
from AccessControl.Permissions import view_management_screens
1✔
33
from Acquisition import aq_base
1✔
34
from Acquisition import aq_inner
1✔
35
from Acquisition import aq_parent
1✔
36
from App.special_dtml import DTMLFile
1✔
37
from ExtensionClass import Base
1✔
38
from OFS.event import ObjectClonedEvent
1✔
39
from OFS.event import ObjectWillBeMovedEvent
1✔
40
from OFS.interfaces import ICopyContainer
1✔
41
from OFS.interfaces import ICopySource
1✔
42
from OFS.Moniker import Moniker
1✔
43
from OFS.Moniker import loadMoniker
1✔
44
from OFS.subscribers import compatibilityCall
1✔
45
from zExceptions import BadRequest
1✔
46
from zExceptions import ResourceLockedError
1✔
47
from zExceptions import Unauthorized
1✔
48
from ZODB.POSException import ConflictError
1✔
49
from zope.container.contained import notifyContainerModified
1✔
50
from zope.event import notify
1✔
51
from zope.interface import implementer
1✔
52
from zope.lifecycleevent import ObjectCopiedEvent
1✔
53
from zope.lifecycleevent import ObjectMovedEvent
1✔
54

55

56
class CopyError(Exception):
1✔
57
    pass
1✔
58

59

60
copy_re = re.compile('^copy([0-9]*)_of_(.*)')
1✔
61
logger = logging.getLogger('OFS')
1✔
62
_marker = []
1✔
63

64

65
@implementer(ICopyContainer)
1✔
66
class CopyContainer(Base):
1✔
67

68
    """Interface for containerish objects which allow cut/copy/paste"""
69

70
    security = ClassSecurityInfo()
1✔
71

72
    # The following three methods should be overridden to store sub-objects
73
    # as non-attributes.
74
    def _setOb(self, id, object):
1✔
75
        setattr(self, id, object)
×
76

77
    def _delOb(self, id):
1✔
78
        delattr(self, id)
×
79

80
    def _getOb(self, id, default=_marker):
1✔
81
        if hasattr(aq_base(self), id):
×
82
            return getattr(self, id)
×
83
        if default is _marker:
×
84
            raise AttributeError(id)
×
85
        return default
×
86

87
    def manage_CopyContainerFirstItem(self, REQUEST):
1✔
88
        return self._getOb(REQUEST['ids'][0])
×
89

90
    def manage_CopyContainerAllItems(self, REQUEST):
1✔
91
        return [self._getOb(i) for i in REQUEST['ids']]
×
92

93
    @security.protected(delete_objects)
1✔
94
    def manage_cutObjects(self, ids=None, REQUEST=None):
1✔
95
        """Put a reference to the objects named in ids in the clip board"""
96
        if ids is None and REQUEST is not None:
1!
97
            raise BadRequest('No items specified')
×
98
        elif ids is None:
1!
99
            raise ValueError('ids must be specified')
×
100

101
        if isinstance(ids, str):
1✔
102
            ids = [ids]
1✔
103
        oblist = []
1✔
104
        for id in ids:
1✔
105
            ob = self._getOb(id)
1✔
106

107
            if ob.wl_isLocked():
1!
108
                raise ResourceLockedError('Object "%s" is locked' % ob.getId())
×
109

110
            if not ob.cb_isMoveable():
1!
111
                raise CopyError('Not Supported')
×
112
            m = Moniker(ob)
1✔
113
            oblist.append(m.dump())
1✔
114
        cp = (1, oblist)
1✔
115
        cp = _cb_encode(cp)
1✔
116
        if REQUEST is not None:
1!
117
            resp = REQUEST['RESPONSE']
×
118
            resp.setCookie('__cp', cp, path='%s' % cookie_path(REQUEST))
×
119
            REQUEST['__cp'] = cp
×
120
            return self.manage_main(self, REQUEST)
×
121
        return cp
1✔
122

123
    @security.protected(view_management_screens)
1✔
124
    def manage_copyObjects(self, ids=None, REQUEST=None, RESPONSE=None):
1✔
125
        """Put a reference to the objects named in ids in the clip board"""
126
        if ids is None and REQUEST is not None:
1!
127
            raise BadRequest('No items specified')
×
128
        elif ids is None:
1!
129
            raise ValueError('ids must be specified')
×
130

131
        if isinstance(ids, str):
1✔
132
            ids = [ids]
1✔
133
        oblist = []
1✔
134
        for id in ids:
1✔
135
            ob = self._getOb(id)
1✔
136
            if not ob.cb_isCopyable():
1!
137
                raise CopyError('Not Supported')
×
138
            m = Moniker(ob)
1✔
139
            oblist.append(m.dump())
1✔
140
        cp = (0, oblist)
1✔
141
        cp = _cb_encode(cp)
1✔
142
        if REQUEST is not None:
1!
143
            resp = REQUEST['RESPONSE']
×
144
            resp.setCookie('__cp', cp, path='%s' % cookie_path(REQUEST))
×
145
            REQUEST['__cp'] = cp
×
146
            return self.manage_main(self, REQUEST)
×
147
        return cp
1✔
148

149
    def _get_id(self, id):
1✔
150
        # Allow containers to override the generation of
151
        # object copy id by attempting to call its _get_id
152
        # method, if it exists.
153
        match = copy_re.match(id)
1✔
154
        if match:
1✔
155
            n = int(match.group(1) or '1')
1✔
156
            orig_id = match.group(2)
1✔
157
        else:
158
            n = 0
1✔
159
            orig_id = id
1✔
160
        while 1:
161
            if self._getOb(id, None) is None:
1✔
162
                return id
1✔
163
            id = 'copy{}_of_{}'.format(n and n + 1 or '', orig_id)
1✔
164
            n = n + 1
1✔
165

166
    def _pasteObjects(self, cp, cb_maxsize=0):
1✔
167
        """Paste previously copied objects into the current object.
168

169
        ``cp`` is the list of objects for paste as encoded by ``_cb_encode``.
170
        If calling _pasteObjects from python code, pass the result of a
171
        previous call to manage_cutObjects or manage_copyObjects as the first
172
        argument.
173

174
        ``cb_maxsize`` is the maximum size of the JSON representation of the
175
        object list. Set it to a non-zero value to prevent DoS attacks with
176
        huge object lists or zlib bombs.
177

178
        This method sends IObjectCopiedEvent and IObjectClonedEvent
179
        or IObjectWillBeMovedEvent and IObjectMovedEvent.
180

181
        Returns tuple of (operator, list of {'id': orig_id, 'new_id': new_id}).
182
        Where `operator` is 0 for a copy operation and 1 for a move operation.
183
        """
184
        if cp is None:
1✔
185
            raise CopyError('No clipboard data found.')
1✔
186

187
        try:
1✔
188
            op, mdatas = _cb_decode(cp, cb_maxsize)
1✔
189
        except Exception as e:
1✔
190
            raise CopyError('Clipboard Error') from e
1✔
191

192
        oblist = []
1✔
193
        app = self.getPhysicalRoot()
1✔
194
        for mdata in mdatas:
1✔
195
            m = loadMoniker(mdata)
1✔
196
            try:
1✔
197
                ob = m.bind(app)
1✔
198
            except ConflictError:
1!
199
                raise
×
200
            except Exception:
1✔
201
                raise CopyError('Item Not Found')
1✔
202
            self._verifyObjectPaste(ob, validate_src=op + 1)
1✔
203
            oblist.append(ob)
1✔
204

205
        result = []
1✔
206
        if op == 0:
1✔
207
            # Copy operation
208
            for ob in oblist:
1✔
209
                orig_id = ob.getId()
1✔
210
                if not ob.cb_isCopyable():
1!
211
                    raise CopyError('Not Supported')
×
212

213
                try:
1✔
214
                    ob._notifyOfCopyTo(self, op=0)
1✔
215
                except ConflictError:
×
216
                    raise
×
217
                except Exception:
×
218
                    raise CopyError('Copy Error')
×
219

220
                id = self._get_id(orig_id)
1✔
221
                result.append({'id': orig_id, 'new_id': id})
1✔
222

223
                orig_ob = ob
1✔
224
                ob = ob._getCopy(self)
1✔
225
                ob._setId(id)
1✔
226
                notify(ObjectCopiedEvent(ob, orig_ob))
1✔
227

228
                self._setObject(id, ob)
1✔
229
                ob = self._getOb(id)
1✔
230
                ob.wl_clearLocks()
1✔
231

232
                ob._postCopy(self, op=0)
1✔
233

234
                compatibilityCall('manage_afterClone', ob, ob)
1✔
235

236
                notify(ObjectClonedEvent(ob))
1✔
237

238
        elif op == 1:
1!
239
            # Move operation
240
            for ob in oblist:
1✔
241
                orig_id = ob.getId()
1✔
242
                if not ob.cb_isMoveable():
1!
243
                    raise CopyError('Not Supported')
×
244

245
                try:
1✔
246
                    ob._notifyOfCopyTo(self, op=1)
1✔
247
                except ConflictError:
×
248
                    raise
×
249
                except Exception:
×
250
                    raise CopyError('Move Error')
×
251

252
                if not sanity_check(self, ob):
1!
253
                    raise CopyError("This object cannot be pasted into itself")
×
254

255
                orig_container = aq_parent(aq_inner(ob))
1✔
256
                if aq_base(orig_container) is aq_base(self):
1✔
257
                    id = orig_id
1✔
258
                else:
259
                    id = self._get_id(orig_id)
1✔
260
                result.append({'id': orig_id, 'new_id': id})
1✔
261

262
                notify(ObjectWillBeMovedEvent(ob, orig_container, orig_id,
1✔
263
                                              self, id))
264

265
                # try to make ownership explicit so that it gets carried
266
                # along to the new location if needed.
267
                ob.manage_changeOwnershipType(explicit=1)
1✔
268

269
                try:
1✔
270
                    orig_container._delObject(orig_id, suppress_events=True)
1✔
271
                except TypeError:
×
272
                    orig_container._delObject(orig_id)
×
273
                    warnings.warn(
×
274
                        "%s._delObject without suppress_events is discouraged."
275
                        % orig_container.__class__.__name__,
276
                        DeprecationWarning)
277
                ob = aq_base(ob)
1✔
278
                ob._setId(id)
1✔
279

280
                try:
1✔
281
                    self._setObject(id, ob, set_owner=0, suppress_events=True)
1✔
282
                except TypeError:
×
283
                    self._setObject(id, ob, set_owner=0)
×
284
                    warnings.warn(
×
285
                        "%s._setObject without suppress_events is discouraged."
286
                        % self.__class__.__name__, DeprecationWarning)
287
                ob = self._getOb(id)
1✔
288

289
                notify(ObjectMovedEvent(ob, orig_container, orig_id, self, id))
1✔
290
                notifyContainerModified(orig_container)
1✔
291
                if aq_base(orig_container) is not aq_base(self):
1✔
292
                    notifyContainerModified(self)
1✔
293

294
                ob._postCopy(self, op=1)
1✔
295
                # try to make ownership implicit if possible
296
                ob.manage_changeOwnershipType(explicit=0)
1✔
297

298
        return op, result
1✔
299

300
    @security.protected(view_management_screens)
1✔
301
    def manage_pasteObjects(self, cb_copy_data=None, REQUEST=None):
1✔
302
        """Paste previously copied objects into the current object.
303

304
        If calling manage_pasteObjects from python code, pass the result of a
305
        previous call to manage_cutObjects or manage_copyObjects as the first
306
        argument.
307

308
        Also sends IObjectCopiedEvent and IObjectClonedEvent
309
        or IObjectWillBeMovedEvent and IObjectMovedEvent.
310

311
        If `REQUEST` is None it returns a
312
        list of dicts {'id': orig_id, 'new_id': new_id} otherwise it renders
313
        a HTML page.
314
        """
315
        if cb_copy_data is None and REQUEST is not None and '__cp' in REQUEST:
1!
316
            cb_copy_data = REQUEST['__cp']
×
317
        op, result = self._pasteObjects(cb_copy_data, cb_maxsize=8192)
1✔
318

319
        if REQUEST is not None:
1!
320
            if op == 0:
×
321
                cb_valid = 1
×
322
            elif op == 1:
×
323
                REQUEST['RESPONSE'].setCookie(
×
324
                    '__cp', 'deleted',
325
                    path='%s' % cookie_path(REQUEST),
326
                    expires='Wed, 31-Dec-97 23:59:59 GMT')
327
                REQUEST['__cp'] = None
×
328
                cb_valid = 0
×
329
            return self.manage_main(self, REQUEST, cb_dataValid=cb_valid)
×
330

331
        return result
1✔
332

333
    security.declareProtected(view_management_screens, 'manage_renameForm')  # NOQA: D001,E501
1✔
334
    manage_renameForm = DTMLFile('dtml/renameForm', globals())
1✔
335

336
    @security.protected(view_management_screens)
1✔
337
    def manage_renameObjects(self, ids=[], new_ids=[], REQUEST=None):
1✔
338
        """Rename several sub-objects"""
339
        if len(ids) != len(new_ids):
1!
340
            raise BadRequest('Please rename each listed object.')
×
341
        for i in range(len(ids)):
1✔
342
            if ids[i] != new_ids[i]:
1!
343
                self.manage_renameObject(ids[i], new_ids[i], REQUEST)
1✔
344
        if REQUEST is not None:
1!
345
            return self.manage_main(self, REQUEST)
×
346

347
    @security.protected(view_management_screens)
1✔
348
    def manage_renameObject(self, id, new_id, REQUEST=None):
1✔
349
        """Rename a particular sub-object.
350
        """
351
        try:
1✔
352
            self._checkId(new_id)
1✔
353
        except Exception:
×
354
            raise CopyError('Invalid Id')
×
355

356
        ob = self._getOb(id)
1✔
357

358
        if ob.wl_isLocked():
1!
359
            raise ResourceLockedError('Object "%s" is locked' % ob.getId())
×
360
        if not ob.cb_isMoveable():
1!
361
            raise CopyError('Not Supported')
×
362
        self._verifyObjectPaste(ob)
1✔
363

364
        try:
1✔
365
            ob._notifyOfCopyTo(self, op=1)
1✔
366
        except ConflictError:
×
367
            raise
×
368
        except Exception:
×
369
            raise CopyError('Rename Error')
×
370

371
        notify(ObjectWillBeMovedEvent(ob, self, id, self, new_id))
1✔
372

373
        try:
1✔
374
            self._delObject(id, suppress_events=True)
1✔
375
        except TypeError:
×
376
            self._delObject(id)
×
377
            warnings.warn(
×
378
                "%s._delObject without suppress_events is discouraged." %
379
                self.__class__.__name__, DeprecationWarning)
380
        ob = aq_base(ob)
1✔
381
        ob._setId(new_id)
1✔
382

383
        # Note - because a rename always keeps the same context, we
384
        # can just leave the ownership info unchanged.
385
        try:
1✔
386
            self._setObject(new_id, ob, set_owner=0, suppress_events=True)
1✔
387
        except TypeError:
×
388
            self._setObject(new_id, ob, set_owner=0)
×
389
            warnings.warn(
×
390
                "%s._setObject without suppress_events is discouraged." %
391
                self.__class__.__name__, DeprecationWarning)
392
        ob = self._getOb(new_id)
1✔
393

394
        notify(ObjectMovedEvent(ob, self, id, self, new_id))
1✔
395
        notifyContainerModified(self)
1✔
396

397
        ob._postCopy(self, op=1)
1✔
398

399
        if REQUEST is not None:
1!
400
            return self.manage_main(self, REQUEST)
×
401

402
    @security.public
1✔
403
    def manage_clone(self, ob, id, REQUEST=None):
1✔
404
        """Clone an object, creating a new object with the given id.
405
        """
406
        if not ob.cb_isCopyable():
1!
407
            raise CopyError('Not Supported')
×
408
        try:
1✔
409
            self._checkId(id)
1✔
410
        except Exception:
×
411
            raise CopyError('Invalid Id')
×
412

413
        self._verifyObjectPaste(ob)
1✔
414

415
        try:
1✔
416
            ob._notifyOfCopyTo(self, op=0)
1✔
417
        except ConflictError:
×
418
            raise
×
419
        except Exception:
×
420
            raise CopyError('Clone Error')
×
421

422
        orig_ob = ob
1✔
423
        ob = ob._getCopy(self)
1✔
424
        ob._setId(id)
1✔
425
        notify(ObjectCopiedEvent(ob, orig_ob))
1✔
426

427
        self._setObject(id, ob)
1✔
428
        ob = self._getOb(id)
1✔
429

430
        ob._postCopy(self, op=0)
1✔
431

432
        compatibilityCall('manage_afterClone', ob, ob)
1✔
433

434
        notify(ObjectClonedEvent(ob))
1✔
435

436
        return ob
1✔
437

438
    def cb_dataValid(self):
1✔
439
        # Return true if clipboard data seems valid.
440
        try:
1✔
441
            _cb_decode(self.REQUEST['__cp'])
1✔
442
        except Exception:
1✔
443
            return 0
1✔
444
        return 1
×
445

446
    def cb_dataItems(self):
1✔
447
        # List of objects in the clip board
448
        try:
×
449
            cp = _cb_decode(self.REQUEST['__cp'])
×
450
        except Exception:
×
451
            return []
×
452
        oblist = []
×
453

454
        app = self.getPhysicalRoot()
×
455
        for mdata in cp[1]:
×
456
            m = loadMoniker(mdata)
×
457
            oblist.append(m.bind(app))
×
458
        return oblist
×
459

460
    validClipData = cb_dataValid
1✔
461

462
    def _verifyObjectPaste(self, object, validate_src=1):
1✔
463
        # Verify whether the current user is allowed to paste the
464
        # passed object into self. This is determined by checking
465
        # to see if the user could create a new object of the same
466
        # meta_type of the object passed in and checking that the
467
        # user actually is allowed to access the passed in object
468
        # in its existing context.
469
        #
470
        # Passing a false value for the validate_src argument will skip
471
        # checking the passed in object in its existing context. This is
472
        # mainly useful for situations where the passed in object has no
473
        # existing context, such as checking an object during an import
474
        # (the object will not yet have been connected to the acquisition
475
        # heirarchy).
476

477
        if not hasattr(object, 'meta_type'):
1!
478
            raise CopyError('Not Supported')
×
479

480
        if not hasattr(self, 'all_meta_types'):
1!
481
            raise CopyError('Cannot paste into this object.')
×
482

483
        mt_permission = None
1✔
484
        meta_types = absattr(self.all_meta_types)
1✔
485

486
        for d in meta_types:
1✔
487
            if d['name'] == object.meta_type:
1✔
488
                mt_permission = d.get('permission')
1✔
489
                break
1✔
490

491
        if mt_permission is not None:
1✔
492
            sm = getSecurityManager()
1✔
493

494
            if sm.checkPermission(mt_permission, self):
1✔
495
                if validate_src:
1✔
496
                    # Ensure the user is allowed to access the object on the
497
                    # clipboard.
498
                    try:
1✔
499
                        parent = aq_parent(aq_inner(object))
1✔
500
                    except Exception:
×
501
                        parent = None
×
502

503
                    if not sm.validate(None, parent, None, object):
1!
504
                        raise Unauthorized(absattr(object.id))
×
505

506
                    if validate_src == 2:  # moving
1✔
507
                        if not sm.checkPermission(delete_objects, parent):
1✔
508
                            raise Unauthorized('Delete not allowed.')
1✔
509
            else:
510
                raise CopyError('Insufficient privileges')
1✔
511
        else:
512
            raise CopyError('Not Supported')
1✔
513

514

515
InitializeClass(CopyContainer)
1✔
516

517

518
@implementer(ICopySource)
1✔
519
class CopySource(Base):
1✔
520

521
    """Interface for objects which allow themselves to be copied."""
522

523
    # declare a dummy permission for Copy or Move here that we check
524
    # in cb_isCopyable.
525
    security = ClassSecurityInfo()
1✔
526
    security.setPermissionDefault(copy_or_move, ('Anonymous', 'Manager'))
1✔
527

528
    def _canCopy(self, op=0):
1✔
529
        """Called to make sure this object is copyable.
530

531
        The op var is 0 for a copy, 1 for a move.
532
        """
533
        return 1
1✔
534

535
    def _notifyOfCopyTo(self, container, op=0):
1✔
536
        """Overide this to be pickly about where you go!
537

538
        If you dont want to go there, raise an exception. The op variable is 0
539
        for a copy, 1 for a move.
540
        """
541
        pass
1✔
542

543
    def _getCopy(self, container):
1✔
544
        # Commit a subtransaction to:
545
        # 1) Make sure the data about to be exported is current
546
        # 2) Ensure self._p_jar and container._p_jar are set even if
547
        #    either one is a new object
548
        transaction.savepoint(optimistic=True)
1✔
549

550
        if self._p_jar is None:
1!
551
            raise CopyError(
×
552
                'Object "%r" needs to be in the database to be copied' % self)
553
        if container._p_jar is None:
1!
554
            raise CopyError(
×
555
                'Container "%r" needs to be in the database' % container)
556

557
        # Ask an object for a new copy of itself.
558
        with tempfile.TemporaryFile() as f:
1✔
559
            self._p_jar.exportFile(self._p_oid, f)
1✔
560
            f.seek(0)
1✔
561
            ob = container._p_jar.importFile(f)
1✔
562

563
        # Cleanup the copy.  It may contain private objects that the current
564
        # user is not allowed to see.
565
        sm = getSecurityManager()
1✔
566
        if not sm.checkPermission('View', self):
1✔
567
            # The user is not allowed to view the object that is currently
568
            # being copied, so it makes no sense to check any of its sub
569
            # objects.  It probably means we are in a test.
570
            return ob
1✔
571
        return self._cleanupCopy(ob, container)
1✔
572

573
    def _cleanupCopy(self, cp, container):
1✔
574
        sm = getSecurityManager()
1✔
575
        ob = aq_base(self)
1✔
576
        if hasattr(ob, 'objectIds'):
1!
577
            for k in self.objectIds():
1✔
578
                v = self._getOb(k)
1✔
579
                if not sm.checkPermission('View', v):
1✔
580
                    # We do not use cp._delObject, because this would fire
581
                    # events that are needless for objects that are not even in
582
                    # an Acquisition chain yet.
583
                    logger.warning(
1✔
584
                        'While copying %s to %s, removed %s from copy '
585
                        'because user is not allowed to view the original.',
586
                        '/'.join(self.getPhysicalPath()),
587
                        '/'.join(container.getPhysicalPath()),
588
                        '/'.join(v.getPhysicalPath())
589
                    )
590
                    cp._delOb(k)
1✔
591
                    # We need to cleanup the internal objects list, even when
592
                    # in some implementations this is always an empty tuple.
593
                    cp._objects = tuple([
1✔
594
                        i for i in cp._objects if i['id'] != k])
595
                else:
596
                    # recursively check
597
                    v._cleanupCopy(cp._getOb(k), container)
1✔
598
        return cp
1✔
599

600
    def _postCopy(self, container, op=0):
1✔
601
        # Called after the copy is finished to accomodate special cases.
602
        # The op var is 0 for a copy, 1 for a move.
603
        pass
1✔
604

605
    def _setId(self, id):
1✔
606
        # Called to set the new id of a copied object.
607
        self.id = id
1✔
608

609
    def cb_isCopyable(self):
1✔
610
        # Is object copyable? Returns 0 or 1
611
        if not (hasattr(self, '_canCopy') and self._canCopy(0)):
1!
612
            return 0
×
613
        if not self.cb_userHasCopyOrMovePermission():
1!
614
            return 0
×
615
        return 1
1✔
616

617
    def cb_isMoveable(self):
1✔
618
        # Is object moveable? Returns 0 or 1
619
        if not (hasattr(self, '_canCopy') and self._canCopy(1)):
1!
620
            return 0
×
621
        if hasattr(self, '_p_jar') and self._p_jar is None:
1!
622
            return 0
×
623
        try:
1✔
624
            n = aq_parent(aq_inner(self))._reserved_names
1✔
625
        except Exception:
×
626
            n = ()
×
627
        if absattr(self.id) in n:
1!
628
            return 0
×
629
        if not self.cb_userHasCopyOrMovePermission():
1!
630
            return 0
×
631
        return 1
1✔
632

633
    def cb_userHasCopyOrMovePermission(self):
1✔
634
        if getSecurityManager().checkPermission(copy_or_move, self):
1!
635
            return 1
1✔
636

637

638
InitializeClass(CopySource)
1✔
639

640

641
def sanity_check(c, ob):
1✔
642
    # This is called on cut/paste operations to make sure that
643
    # an object is not cut and pasted into itself or one of its
644
    # subobjects, which is an undefined situation.
645
    ob = aq_base(ob)
1✔
646
    while 1:
647
        if aq_base(c) is ob:
1!
648
            return 0
×
649
        inner = aq_inner(c)
1✔
650
        if aq_parent(inner) is None:
1✔
651
            return 1
1✔
652
        c = aq_parent(inner)
1✔
653

654

655
def absattr(attr):
1✔
656
    if callable(attr):
1✔
657
        return attr()
1✔
658
    return attr
1✔
659

660

661
def _cb_encode(d):
1✔
662
    """Encode a list of IDs for storage in a cookie.
663

664
    ``d`` must be a list or tuple of text IDs.
665

666
    Return text.
667
    """
668
    json_bytes = dumps(d).encode('utf-8')
1✔
669
    squashed_bytes = compress(json_bytes, 2)  # -> bytes w/ useful encoding
1✔
670
    # quote for embeding in cookie
671
    return base64.encodebytes(squashed_bytes)
1✔
672

673

674
def _cb_decode(s, maxsize=8192):
1✔
675
    """Decode a list of IDs from storage in a cookie.
676

677
    ``s`` is text as encoded by ``_cb_encode``.
678
    ``maxsize`` is the maximum size of uncompressed data. ``0`` means no limit.
679

680
    Return a list of text IDs.
681
    """
682
    dec = decompressobj()
1✔
683
    if isinstance(s, str):
1!
684
        s = s.encode('latin-1')
×
685
    data = dec.decompress(base64.decodebytes(s), maxsize)
1✔
686
    if dec.unconsumed_tail:
1✔
687
        raise ValueError
1✔
688
    json_bytes = data.decode('utf-8')
1✔
689
    return loads(json_bytes)
1✔
690

691

692
def cookie_path(request):
1✔
693
    # Return a "path" value for use in a cookie that refers
694
    # to the root of the Zope object space.
695
    return request['BASEPATH1'] or "/"
×
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