Skip to content

Commit

Permalink
Enable WebDAV support without having to use ZServer (#787)
Browse files Browse the repository at this point in the history
* - Enable WebDAV support independent of ``ZServer``

* - mention PR in change log

* - found a much better place to hook into

* - remove unused variable

* - reset global variables changed in the tests

* - quick Appveyor experiment

* - reorder Appveyor tests again

* - try shorter config as found in the docs

* - shorten config more

* - Appveyor cont

* - remove commented configurations
  • Loading branch information
dataflake authored Feb 25, 2020
1 parent fd3a742 commit 6ecb56c
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 25 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ https://zope.readthedocs.io/en/2.13/CHANGES.html
4.3 (unreleased)
----------------

- Enable WebDAV support independent of ``ZServer``
(`#787 <https://github.com/zopefoundation/Zope/pull/787>`_)

- Only use ``wsgi.file_wrapper`` for response bodies with a ``read`` method
(`#763 <https://github.com/zopefoundation/Zope/issues/763>`_)

Expand Down
32 changes: 8 additions & 24 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,20 @@
environment:
WIN_COV: ' ' # some whitespaces to have a trueish value.
matrix:
- PROFILE: py37
PYTHON_VERSION: 3.7"
TOXENV: "py37"
- PROFILE: py37-conventions
PYTHON_VERSION: 3.7"
TOXENV: "lint"
- PROFILE: py27
PYTHON_VERSION: 2.7"
TOXENV: "py27"
- PROFILE: py35
PYTHON_VERSION: 3.5"
TOXENV: "py35"
- PROFILE: py36
PYTHON_VERSION: 3.6"
TOXENV: "py36"

cache:
- '%LOCALAPPDATA%\pip\Cache'

version: '{branch}.{build}'
- TOXENV: py38
- TOXENV: py37
- TOXENV: py36
- TOXENV: py35
- TOXENV: lint-py36
- TOXENV: py27

install:
- "python.exe -m pip install tox"
- pip install tox

build: off

max_jobs: 2

matrix:
fast_finish: true

test_script:
- "tox.exe"
- tox
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
5 changes: 5 additions & 0 deletions src/ZPublisher/HTTPRequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,11 @@ def __init__(self, stdin, environ, response, clean=0):
other['URL'] = self.script = script
other['method'] = environ.get('REQUEST_METHOD', 'GET').upper()

# Make WEBDAV_SOURCE_PORT reachable with a simple REQUEST.get
# to stay backwards-compatible
if environ.get('WEBDAV_SOURCE_PORT'):
other['WEBDAV_SOURCE_PORT'] = environ.get('WEBDAV_SOURCE_PORT')

################################################################
# Cookie values should *not* be appended to existing form
# vars with the same name - they are more like default values
Expand Down
21 changes: 21 additions & 0 deletions src/ZPublisher/WSGIPublisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
_DEFAULT_REALM = None
_MODULE_LOCK = allocate_lock()
_MODULES = {}
_WEBDAV_SOURCE_PORT = 0


def call_object(obj, args, request):
Expand All @@ -81,6 +82,11 @@ def set_default_debug_exceptions(debug_exceptions):
_DEFAULT_DEBUG_EXCEPTIONS = debug_exceptions


def set_webdav_source_port(port):
global _WEBDAV_SOURCE_PORT
_WEBDAV_SOURCE_PORT = port


def get_debug_exceptions():
global _DEFAULT_DEBUG_EXCEPTIONS
return _DEFAULT_DEBUG_EXCEPTIONS
Expand Down Expand Up @@ -313,6 +319,21 @@ def publish_module(environ, start_response,
path_info = path_info.decode('utf-8')

environ['PATH_INFO'] = path_info

# See if this should be be marked up as WebDAV request.
try:
server_port = int(environ['SERVER_PORT'])
except (KeyError, ValueError):
server_port = 0

if _WEBDAV_SOURCE_PORT and _WEBDAV_SOURCE_PORT == server_port:
environ['WEBDAV_SOURCE_PORT'] = 1

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

with closing(BytesIO()) as stdout, closing(BytesIO()) as stderr:
new_response = (
_response
Expand Down
7 changes: 7 additions & 0 deletions src/ZPublisher/tests/testHTTPRequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,13 @@ def _taintedKeysAlsoInForm(self, req):
"Key %s not correctly reproduced in tainted; expected %r, "
"got %r" % (key, req.form[key], req.taintedform[key]))

def test_webdav_source_port_available(self):
req = self._makeOne()
self.assertFalse(req.get('WEBDAV_SOURCE_PORT'))

req = self._makeOne(environ={'WEBDAV_SOURCE_PORT': 1})
self.assertTrue(req.get('WEBDAV_SOURCE_PORT'))

def test_no_docstring_on_instance(self):
env = {'SERVER_NAME': 'testingharnas', 'SERVER_PORT': '80'}
req = self._makeOne(environ=env)
Expand Down
46 changes: 46 additions & 0 deletions src/ZPublisher/tests/test_WSGIPublisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,52 @@ def test_set_REMOTE_USER_environ(self):
self._callFUT(environ, start_response, _publish)
self.assertFalse('REMOTE_USER' in environ)

def test_webdav_source_port(self):
from ZPublisher import WSGIPublisher
old_webdav_source_port = WSGIPublisher._WEBDAV_SOURCE_PORT
start_response = DummyCallable()
_response = DummyResponse()
_publish = DummyCallable()
_publish._result = _response

# WebDAV source port not configured
environ = self._makeEnviron(PATH_INFO='/test')
self.assertNotIn('WEBDAV_SOURCE_PORT', environ)
self.assertEqual(WSGIPublisher._WEBDAV_SOURCE_PORT, 0)
self._callFUT(environ, start_response, _publish)
self.assertNotIn('WEBDAV_SOURCE_PORT', environ)
self.assertEqual(environ['PATH_INFO'], '/test')

# Configuring the port
WSGIPublisher.set_webdav_source_port(9800)
self.assertEqual(WSGIPublisher._WEBDAV_SOURCE_PORT, 9800)

# Coming through the wrong port
environ = self._makeEnviron(SERVER_PORT=8080, PATH_INFO='/test')
self._callFUT(environ, start_response, _publish)
self.assertNotIn('WEBDAV_SOURCE_PORT', environ)
self.assertEqual(environ['PATH_INFO'], '/test')

# Using the wrong request method, environ gets marked
# but the path doesn't get changed
environ = self._makeEnviron(SERVER_PORT=9800,
PATH_INFO='/test',
REQUEST_METHOD='POST')
self._callFUT(environ, start_response, _publish)
self.assertIn('WEBDAV_SOURCE_PORT', environ)
self.assertEqual(environ['PATH_INFO'], '/test')

# All stars aligned
environ = self._makeEnviron(SERVER_PORT=9800,
PATH_INFO='/test',
REQUEST_METHOD='GET')
self._callFUT(environ, start_response, _publish)
self.assertTrue(environ['WEBDAV_SOURCE_PORT'])
self.assertEqual(environ['PATH_INFO'], '/test/manage_DAVget')

# Clean up
WSGIPublisher.set_webdav_source_port(old_webdav_source_port)


class ExcViewCreatedTests(ZopeTestCase):

Expand Down
1 change: 1 addition & 0 deletions src/Zope2/Startup/starter.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def setupPublisher(self):
WSGIPublisher.set_default_debug_exceptions(self.cfg.debug_exceptions)
WSGIPublisher.set_default_authentication_realm(
self.cfg.http_realm)
WSGIPublisher.set_webdav_source_port(self.cfg.webdav_source_port)
if self.cfg.trusted_proxies:
mapped = []
for name in self.cfg.trusted_proxies:
Expand Down
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
20 changes: 20 additions & 0 deletions src/Zope2/Startup/tests/test_starter.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,26 @@ def testSetupConflictRetries(self):
root_wsgi_handler(conf)
self.assertEqual(HTTPRequest.retry_max_count, 25)

def test_webdav_source_port(self):
from ZPublisher import WSGIPublisher

# If no value is provided, the default is 0
conf = self.load_config_text("""
instancehome <<INSTANCE_HOME>>""")
starter = self.get_starter(conf)
starter.setupPublisher()
self.assertEqual(WSGIPublisher._WEBDAV_SOURCE_PORT, 0)

conf = self.load_config_text("""
instancehome <<INSTANCE_HOME>>
webdav-source-port 9800""")
starter = self.get_starter(conf)
starter.setupPublisher()
self.assertEqual(WSGIPublisher._WEBDAV_SOURCE_PORT, 9800)

# Cleanup
WSGIPublisher.set_webdav_source_port(0)

@unittest.skipUnless(six.PY2, 'Python 2 specific checkinterval test.')
def testConfigureInterpreter(self):
oldcheckinterval = sys.getcheckinterval()
Expand Down
21 changes: 21 additions & 0 deletions src/Zope2/Startup/wsgischema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,27 @@
</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.

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
23 changes: 23 additions & 0 deletions src/Zope2/utilities/skel/etc/zope.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,29 @@ instancehome $INSTANCE
# http-realm Slipknot


# Directive: webdav-source-port
#
# Description:
# This value designates a network port number as WebDAV source port.
#
# 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.
#
# 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.
#
# Default: Off
#
# Example:
#
# webdav-source-port 9800


# Directive: zmi-bookmarkable-urls
#
# Description:
Expand Down

0 comments on commit 6ecb56c

Please sign in to comment.