Skip to content

Commit

Permalink
- Enable WebDAV support independent of ZServer
Browse files Browse the repository at this point in the history
  • Loading branch information
dataflake committed Feb 16, 2020
1 parent 42ef7d7 commit ab5adce
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 3 deletions.
6 changes: 4 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ The change log for the previous version, Zope 2.13, is at
https://zope.readthedocs.io/en/2.13/CHANGES.html


4.2.2 (unreleased)
------------------
4.3 (unreleased)
----------------

- Enable WebDAV support independent of ``ZServer``

- Deprecate unused ``postProcessInputs`` request method for removal in Zope 5
(`#782 <https://github.com/zopefoundation/Zope/issues/782>`_)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def _read_file(filename):
README = _read_file('README.rst')
CHANGES = _read_file('CHANGES.rst')

version = '4.2.2.dev0'
version = '4.3.dev0'


setup(
Expand Down
25 changes: 25 additions & 0 deletions src/Products/SiteAccess/VirtualHostMonster.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from AccessControl.Permissions import view as View # NOQA
from AccessControl.SecurityInfo import ClassSecurityInfo
from Acquisition import Implicit
from App.config import getConfiguration
from App.special_dtml import DTMLFile
from OFS.SimpleItem import Item
from Persistence import Persistent
Expand All @@ -18,6 +19,9 @@
from ZPublisher.BeforeTraverse import unregisterBeforeTraverse


ZOPE_CONFIG = getConfiguration()


class VirtualHostMonster(Persistent, Item, Implicit):
"""Provide a simple drop-in solution for virtual hosting.
"""
Expand Down Expand Up @@ -140,6 +144,27 @@ def manage_afterAdd(self, item, container):

def __call__(self, client, request, response=None):
'''Traversing at home'''

# Before starting on the normal processing, see if this request
# must be marked up as WebDAV request.
try:
server_port = int(request['SERVER_PORT'])
except ValueError:
server_port = 0
webdav_source_port = getattr(ZOPE_CONFIG, 'webdav_source_port', 0)

if webdav_source_port and webdav_source_port == server_port:
request['WEBDAV_SOURCE_PORT'] = 1

# GET needs special treatment. Traversal is forced to the
# manage_DAVget method to get the unrendered sources.
if (request.get('method', '') or '').upper() == 'GET':
request['PATH_INFO'] = (
'%s/manage_DAVget' % request['PATH_INFO'])
request['TraversalRequestNameStack'] = (
['manage_DAVget'] + request['TraversalRequestNameStack'])

# Returning to the "real" virtual hosting processing
vh_used = 0
stack = request['TraversalRequestNameStack']
path = None
Expand Down
81 changes: 81 additions & 0 deletions src/Products/SiteAccess/tests/testVirtualHostMonster.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,84 @@ def test_add_manage_addVirtualHostMonster(self):
self.assertTrue(VirtualHostMonster.id in self.root.objectIds())
hook = queryBeforeTraverse(self.root, VirtualHostMonster.meta_type)
self.assertTrue(hook)


class VHMWebDAV(unittest.TestCase):
"""Test the WebDAV special treatment"""

def setUp(self):
import transaction
from Testing.makerequest import makerequest
from Testing.ZopeTestCase.ZopeLite import app
transaction.begin()
self.app = makerequest(app())
if 'virtual_hosting' not in self.app.objectIds():
# If ZopeLite was imported, we have no default virtual
# host monster
from Products.SiteAccess.VirtualHostMonster \
import manage_addVirtualHostMonster
manage_addVirtualHostMonster(self.app, 'virtual_hosting')
self.app.manage_addFolder('folder')
self.app.folder.manage_addDTMLMethod('doc', '')
self.app.REQUEST.set('PARENTS', [self.app])
self.traverse = self.app.REQUEST.traverse

def tearDown(self):
import transaction
transaction.abort()
self.app._p_jar.close()

def test_no_port_set(self):
req = self.app.REQUEST
req['method'] = 'GET'

self.traverse('/folder/doc')
self.assertFalse(req.get('WEBDAV_SOURCE_PORT'))

def test_wrong_port(self):
from ..VirtualHostMonster import ZOPE_CONFIG
setattr(ZOPE_CONFIG, 'webdav_source_port', 9800)
req = self.app.REQUEST
req['method'] = 'GET'
req['SERVER_PORT'] = 8080

self.traverse('/folder/doc')
self.assertFalse(req.get('WEBDAV_SOURCE_PORT'))

del ZOPE_CONFIG.webdav_source_port

def test_correct_port_wrong_method(self):
from ..VirtualHostMonster import ZOPE_CONFIG
setattr(ZOPE_CONFIG, 'webdav_source_port', 9800)
req = self.app.REQUEST
req['method'] = 'POST'
req['SERVER_PORT'] = 9800

# This won't raise an Unauthorized error because it never
# gets forced to go to manage_DAVget
self.traverse('/folder/doc')
self.assertEqual(req['PATH_INFO'], '')
# But it is still marked as a WebDAV source port request
self.assertTrue(req.get('WEBDAV_SOURCE_PORT'))

del ZOPE_CONFIG.webdav_source_port

def test_correct_port_correct_method(self):
from zExceptions import Unauthorized
from ..VirtualHostMonster import ZOPE_CONFIG
setattr(ZOPE_CONFIG, 'webdav_source_port', 9800)
req = self.app.REQUEST
req['method'] = 'GET'
req['SERVER_PORT'] = 9800

# The traversal will raise Unauthorizd because the WebDAV handling
# will force the request to go to /folder/doc/manage_DAVget,
# which is protected.
with self.assertRaises(Unauthorized):
self.traverse('/folder/doc')
# We only see /manage_DAVget here because PATH_INFO gets consumed
# during traversal
self.assertEqual(req['PATH_INFO'], '/manage_DAVget')
self.assertTrue(req.get('WEBDAV_SOURCE_PORT'))

del ZOPE_CONFIG.webdav_source_port
14 changes: 14 additions & 0 deletions src/Zope2/Startup/tests/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,20 @@ def test_automatically_quote_dtml_request_data(self):
self.assertEqual(os.environ.get('ZOPE_DTML_REQUEST_AUTOQUOTE', ''),
'0')

def test_webdav_source_port(self):
conf, handler = self.load_config_text(u"""\
instancehome <<INSTANCE_HOME>>
""")
handleWSGIConfig(None, handler)
self.assertEqual(conf.webdav_source_port, 0)

conf, handler = self.load_config_text(u"""\
instancehome <<INSTANCE_HOME>>
webdav-source-port 9800
""")
handleWSGIConfig(None, handler)
self.assertEqual(conf.webdav_source_port, 9800)

def test_ms_public_header(self):
import webdav

Expand Down
24 changes: 24 additions & 0 deletions src/Zope2/Startup/wsgischema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,30 @@
</description>
</multisection>

<key name="webdav-source-port" datatype="integer" default="0">
<description>
This value designates a network port number as WebDAV source port.

WebDAV requires special handling for GET requests. A WebDAV
client expects to receive the un-rendered source in the returned
response body, not the rendered result a web browser would get.

If this value is set to a positive integer, any GET request coming into
Zope via the designated port will be marked up to signal that this is a
WebDAV request. This request markup resembles what ZServer did for
requests coming though its designated WebDAV source server port, so it is
backwards-compatible for existing code that offers WebDAV handling under
ZServer.

The support is integrated into the Virtual Host Monster code, so make
sure you instantiate a Virtual Host Monster in your Zope root folder.

Please note that Zope itself has no server capabilities and cannot open
network ports. You need to configure your WSGI server to listen on the
designated port.
</description>
</key>

<key name="enable-ms-public-header"
datatype="boolean"
handler="enable_ms_public_header"
Expand Down

0 comments on commit ab5adce

Please sign in to comment.