Skip to content

Commit

Permalink
Merge cd819ea into 7b5b437
Browse files Browse the repository at this point in the history
  • Loading branch information
dataflake committed Feb 3, 2020
2 parents 7b5b437 + cd819ea commit 002d826
Show file tree
Hide file tree
Showing 45 changed files with 4,631 additions and 13 deletions.
4 changes: 3 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ These are all the changes for Zope 5, starting with the alpha releases.
The change log for the previous version, Zope 4, is at
https://github.com/zopefoundation/Zope/blob/4.x/CHANGES.rst


5.0a1 (unreleased)
------------------

- Restore WebDAV support in Zope
(`#744 <https://github.com/zopefoundation/Zope/issues/744>`_)

- Remove deprecated module ``ZPublisher.maybe_lock``
(`#758 <https://github.com/zopefoundation/Zope/issues/758>`_)

Expand Down
4 changes: 4 additions & 0 deletions src/App/ApplicationManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from AccessControl.requestmethod import requestmethod
from Acquisition import Implicit
from App.config import getConfiguration
from App.DavLockManager import DavLockManager
from App.Management import Tabs
from App.special_dtml import DTMLFile
from App.Undo import UndoSupport
Expand Down Expand Up @@ -100,6 +101,7 @@ class ConfigurationViewer(Tabs, Traversable, Implicit):
{'label': 'Control Panel', 'action': '../manage_main'},
{'label': 'Databases', 'action': '../Database/manage_main'},
{'label': 'Configuration', 'action': 'manage_main'},
{'label': 'DAV Locks', 'action': '../DavLocks/manage_main'},
)
MANAGE_TABS_NO_BANNER = True

Expand Down Expand Up @@ -144,13 +146,15 @@ class ApplicationManager(Persistent, Tabs, Traversable, Implicit):

Database = DatabaseChooser()
Configuration = ConfigurationViewer()
DavLocks = DavLockManager()

manage = manage_main = DTMLFile('dtml/cpContents', globals())
manage_main._setName('manage_main')
manage_options = (
{'label': 'Control Panel', 'action': 'manage_main'},
{'label': 'Databases', 'action': 'Database/manage_main'},
{'label': 'Configuration', 'action': 'Configuration/manage_main'},
{'label': 'DAV Locks', 'action': 'DavLocks/manage_main'},
)
MANAGE_TABS_NO_BANNER = True

Expand Down
117 changes: 117 additions & 0 deletions src/App/DavLockManager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################

from AccessControl.class_init import InitializeClass
from AccessControl.SecurityInfo import ClassSecurityInfo
from Acquisition import Implicit
from Acquisition import aq_base
from App.special_dtml import DTMLFile
from OFS.Lockable import wl_isLocked
from OFS.SimpleItem import Item


manage_webdav_locks = 'Manage WebDAV Locks'


class DavLockManager(Item, Implicit):
id = 'DavLockManager'
name = title = 'WebDAV Lock Manager'
meta_type = 'WebDAV Lock Manager'
zmi_icon = 'fa fa-lock'

security = ClassSecurityInfo()

security.declareProtected(manage_webdav_locks, # NOQA: D001
'manage_davlocks')
manage_davlocks = manage_main = manage = DTMLFile(
'dtml/davLockManager', globals())
manage_davlocks._setName('manage_davlocks')
manage_options = ({'label': 'Write Locks', 'action': 'manage_main'}, )

@security.protected(manage_webdav_locks)
def findLockedObjects(self, frompath=''):
app = self.getPhysicalRoot()

if frompath:
if frompath[0] == '/':
frompath = frompath[1:]
# since the above will turn '/' into an empty string, check
# for truth before chopping a final slash
if frompath and frompath[-1] == '/':
frompath = frompath[:-1]

# Now we traverse to the node specified in the 'frompath' if
# the user chose to filter the search, and run a ZopeFind with
# the expression 'wl_isLocked()' to find locked objects.
obj = app.unrestrictedTraverse(frompath)
lockedobjs = self._findapply(obj, path=frompath)

return lockedobjs

@security.private
def unlockObjects(self, paths=[]):
app = self.getPhysicalRoot()

for path in paths:
ob = app.unrestrictedTraverse(path)
ob.wl_clearLocks()

@security.protected(manage_webdav_locks)
def manage_unlockObjects(self, paths=[], REQUEST=None):
" Management screen action to unlock objects. "
if paths:
self.unlockObjects(paths)
if REQUEST is not None:
m = '%s objects unlocked.' % len(paths)
return self.manage_davlocks(self, REQUEST, manage_tabs_message=m)

def _findapply(self, obj, result=None, path=''):
# recursive function to actually dig through and find the locked
# objects.

if result is None:
result = []
base = aq_base(obj)
if not hasattr(base, 'objectItems'):
return result
try:
items = obj.objectItems()
except Exception:
return result

addresult = result.append
for id, ob in items:
if path:
p = '%s/%s' % (path, id)
else:
p = id

dflag = hasattr(ob, '_p_changed') and (ob._p_changed is None)
bs = aq_base(ob)
if wl_isLocked(ob):
li = []
addlockinfo = li.append
for token, lock in ob.wl_lockItems():
addlockinfo({'owner': lock.getCreatorPath(),
'token': token})
addresult((p, li))
dflag = 0
if hasattr(bs, 'objectItems'):
self._findapply(ob, result, p)
if dflag:
ob._p_deactivate()

return result


InitializeClass(DavLockManager)
7 changes: 7 additions & 0 deletions src/App/ImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ def index_html(self, REQUEST, RESPONSE):
RESPONSE.setHeader('Content-Length', str(self.size).replace('L', ''))
return filestream_iterator(self.path, mode='rb')

@security.public
def HEAD(self, REQUEST, RESPONSE):
""" """
RESPONSE.setHeader('Content-Type', self.content_type)
RESPONSE.setHeader('Last-Modified', self.lmh)
return ''

def __len__(self):
# This is bogus and needed because of the way Python tests truth.
return 1
Expand Down
106 changes: 106 additions & 0 deletions src/App/dtml/davLockManager.dtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<dtml-var manage_page_header>
<dtml-var manage_tabs>

<main class="container-fluid">

<dtml-let from_path="REQUEST.form.get('frompath',None)"
lockedobjs="from_path and findLockedObjects(frompath=from_path) or []">

<p class="form-help">
Use the search form to locate locked items starting from the
provided path.
</p>

<form action="&dtml-URL0;" name="finderform">
<p class="form-help">Search path:
<input type="text" size="14" name="frompath"
value="&dtml.missing-frompath;"
class="form-element" />
<input class="btn btn-primary" type="submit" value="Go"/>
</p>
</form>

<form action="manage_unlockObjects" name="objectItems" method="post">
<input type="hidden" name="frompath" value="&dtml.missing-frompath;" />

<dtml-if lockedobjs>

<table class="table table-striped table-hover table-sm objectItems">

<thead class="thead-light">
<th scope="col" class="zmi-object-check text-right">
<input type="checkbox" id="checkAll" onclick="checkbox_all();" />
</th>
<th scope="col">Path</th>
<th scope="col">Locked by</th>
<th scope="col">Lock token</th>
</thead>

<tbody>
<dtml-in lockedobjs>
<tr>
<td class="zmi-object-check text-right"
onclick="$(this).children('input').trigger('click');">
<input type="checkbox" name="paths:list"
value="&dtml-sequence-key;"
class="checkbox-list-item"
id="cb_&dtml-sequence-index;"
onclick="event.stopPropagation();select_objectitem($(this));"/>
</td>
<td class="zmi-object-id">
<label for="cb_&dtml-sequence-index;">
<a href="/&dtml-sequence-key;/manage_workspace">
/&dtml-sequence-key;
</a>
</label>
</td>
<dtml-in sequence-item mapping>
<td class="zmi-object-id">&dtml-owner;</td>
<td class="zmi-object-id">&dtml-token;</td>
</dtml-in>
</td>
</tr>
</dtml-in lockedobjs>
</tbody>

</table>

<div class="form-group form-inline zmi-controls">
<div class="input-group">
<input class="btn btn-primary" type="submit"
value="Unlock objects" />
</div>
</div>
<dtml-else>
<dtml-if frompath>
<p class="form-help">
Found no locked items under path <em>&dtml-frompath;</em>.
</p>
</dtml-if>
</dtml-if>
</form>

</dtml-let>

</main>

<script>
// +++++++++++++++++++++++++++
// Item Selection
// +++++++++++++++++++++++++++
function checkbox_all() {
var checkboxes = document.getElementsByClassName('checkbox-list-item');
// Toggle Highlighting CSS-Class
if (document.getElementById('checkAll').checked) {
$('table.objectItems tbody tr').addClass('checked');
} else {
$('table.objectItems tbody tr').removeClass('checked');
};
// Set Checkbox like checkAll-Box
for (i = 0; i < checkboxes.length; i++) {
checkboxes[i].checked = document.getElementById('checkAll').checked;
}
};
</script>

<dtml-var manage_page_footer>
10 changes: 6 additions & 4 deletions src/App/dtml/manage_tabs.dtml
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@
<dtml-if "_['sequence-index']!=0 and last==False">
&nbsp;/&nbsp;
</dtml-if>
<dtml-if wl_isLocked
><span class="badge badge-warning"
title="This item has been locked by WebDAV"><i class="fa fa-lock"></i></span>
</dtml-if>
<dtml-if last>
<dtml-if wl_isLocked
><span class="badge badge-warning"
title="This item has been locked by WebDAV"><i class="fa fa-lock"></i></span>
</dtml-if>
</dtml-if>
</li>
</dtml-in>
</ol>
Expand Down
15 changes: 15 additions & 0 deletions src/OFS/Application.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
from OFS.metaconfigure import get_packages_to_initialize
from OFS.metaconfigure import package_initialized
from OFS.userfolder import UserFolder
from webdav.NullResource import NullResource
from zExceptions import Forbidden
from zExceptions import Redirect as RedirectException
from zope.interface import implementer

Expand Down Expand Up @@ -121,6 +123,9 @@ def __bobo_traverse__(self, REQUEST, name=None):

method = REQUEST.get('REQUEST_METHOD', 'GET')

if method not in ('GET', 'POST'):
return NullResource(self, name, REQUEST).__of__(self)

# Waaa. unrestrictedTraverse calls us with a fake REQUEST.
# There is probably a better fix for this.
try:
Expand All @@ -132,6 +137,16 @@ def ZopeTime(self, *args):
"""Utility function to return current date/time"""
return DateTime(*args)

def DELETE(self, REQUEST, RESPONSE):
"""Delete a resource object."""
self.dav__init(REQUEST, RESPONSE)
raise Forbidden('This resource cannot be deleted.')

def MOVE(self, REQUEST, RESPONSE):
"""Move a resource to a new location."""
self.dav__init(REQUEST, RESPONSE)
raise Forbidden('This resource cannot be moved.')

def absolute_url(self, relative=0):
"""The absolute URL of the root object is BASE1 or "/".
"""
Expand Down
13 changes: 13 additions & 0 deletions src/OFS/DTMLMethod.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,19 @@ def document_src(self, REQUEST=None, RESPONSE=None):
RESPONSE.setHeader('Content-Type', 'text/plain')
return self.read()

@security.protected(change_dtml_methods)
def PUT(self, REQUEST, RESPONSE):
""" Handle HTTP PUT requests.
"""
self.dav__init(REQUEST, RESPONSE)
self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
body = safe_file_data(REQUEST.get('BODY', ''))
self._validateProxy(REQUEST)
self.munge(body)
self.ZCacheable_invalidate()
RESPONSE.setStatus(204)
return RESPONSE

def manage_historyCompare(self, rev1, rev2, REQUEST,
historyComparisonResults=''):
return DTMLMethod.inheritedAttribute('manage_historyCompare')(
Expand Down
2 changes: 2 additions & 0 deletions src/OFS/Folder.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from OFS.role import RoleManager
from OFS.SimpleItem import Item
from OFS.SimpleItem import PathReprProvider
from webdav.Collection import Collection
from zope.interface import implementer


Expand Down Expand Up @@ -55,6 +56,7 @@ class Folder(
ObjectManager,
PropertyManager,
RoleManager,
Collection,
LockableItem,
Item,
FindSupport
Expand Down
19 changes: 19 additions & 0 deletions src/OFS/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,25 @@ def _read_data(self, file):

return (_next, size)

@security.protected(change_images_and_files)
def PUT(self, REQUEST, RESPONSE):
"""Handle HTTP PUT requests"""
self.dav__init(REQUEST, RESPONSE)
self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
type = REQUEST.get_header('content-type', None)

file = REQUEST['BODYFILE']

data, size = self._read_data(file)
if isinstance(data, str):
data = data.encode('UTF-8')
content_type = self._get_content_type(file, data, self.__name__,
type or self.content_type)
self.update_data(data, content_type, size)

RESPONSE.setStatus(204)
return RESPONSE

@security.protected(View)
def get_size(self):
# Get the size of a file or image.
Expand Down
Loading

0 comments on commit 002d826

Please sign in to comment.