Commit ee997b7d authored by Andreas Zeidler's avatar Andreas Zeidler

merged r76174:78105 from the zope 2.10 branch

parents d94b202e 8457b66d
......@@ -8,10 +8,38 @@ Zope Changes
- Backported feature from Zope 2.11 to support named temporary files.
Zope 2.10.4 (unreleased)
Zope 2.10.4 (23.06.2007)
Other changes
- updated to ZODB 3.7.1
- updated to Zope 3.3.2
Bugs fixed
- Collector #1306: Missing acquisition context on local roles screen.
- Collector #2153: Supporting unquoted cookies with spaces.
- The REQUEST no longer accepts holds after it has been closed.
- Collector #1441: WebDAV compatibility with Windows Web Folders
restored by adding a configuration variable that controls the
sending of the non-standard MS-Author-Via and Public
headers. Thanks for PatrickD for the the hard work coming up
with an initial patch.
(http://zope.org/Collectors/Zope/1441)
- Fixed bug in ZPublisher.BaseRequest with persistent site managers. An
EndRequestEvent was thrown after the ZODB connection was already
closed and thus the site manager not being available anymore.
- Collector #2295: Comments in PythonScripts could lead to syntax
errors
- Collector #2307: ObjectCopiedEvent not dispatched to sublocations.
- Collector #2304: fixed markup issue in ptEdit.zpt
- Collector #2260: fixed Examples.zexp
......@@ -1314,9 +1342,9 @@ Zope Changes
x86_64 systems
- ZReST: the charset used in the rendered HTML was not set to the
corresponding output_encoding property of the ZReST instance. In addition
changing the encodings through the Properties tab did not re-render
the HTML.
corresponding output_encoding property of the ZReST
instance. In addition changing the encodings through the
Properties tab did not re-render the HTML.
- Collector #1234: an exception triple passed to LOG() was not
propagated properly to the logging module of Python
......
ZOPEVERSION = 2.10.3-final
ZOPEVERSION = 2.10.4-final
ZOPEDIRNAME := Zope-$(ZOPEVERSION)
ZOPE_REQUIRED_FILES=tmp/$(ZOPEDIRNAME).tgz
......
ZOPE_MAJOR_VERSION = '2.10'
ZOPE_MINOR_VERSION = '3'
ZOPE_MINOR_VERSION = '4'
ZOPE_BRANCH_NAME = '$Name$'[6:] or 'no-branch'
# always start prerelease branches with '0' to avoid upgrade
......
......@@ -345,11 +345,12 @@ class RoleManager(ExtensionClass.Base, PermissionMapping.RoleManager):
if mlu < 0: raise OverflowError
un = getattr(aclu, 'user_names', _notfound)
if un is not _notfound:
un = aclu.__of__(item).user_names # rewrap
unl = un()
# maxlistusers of 0 is list all
if len(unl) > mlu and mlu != 0:
raise OverflowError
for name in un():
for name in unl:
dict[name]=1
item = getattr(item, 'aq_parent', _notfound)
if item is _notfound:
......
......@@ -633,6 +633,11 @@ def install_products(app):
install_product(app, product_dir, product_name, meta_types,
folder_permissions, raise_exc=debug_mode)
# Delayed install of packages-as-products
for module, init_func in Products._packages_to_initialize:
install_package(app, module, init_func, raise_exc=debug_mode)
Products._packages_to_initialize = []
Products.meta_types=Products.meta_types+tuple(meta_types)
InitializeClass(Folder.Folder)
......@@ -861,6 +866,34 @@ def install_product(app, product_dir, product_name, meta_types,
if raise_exc:
raise
def install_package(app, module, init_func, raise_exc=False, log_exc=True):
"""Installs a Python package like a product."""
try:
product = App.Product.initializeProduct(module,
module.__name__,
module.__path__[0],
app)
product.package_name = module.__name__
if init_func is not None:
newContext = ProductContext(product, app, module)
init_func(newContext)
if not doInstall():
transaction.abort()
else:
transaction.get().note('Installed package %s' % module.__name__)
transaction.commit()
except:
if log_exc:
LOG.error("Couldn't install %s" % module.__name__,
exc_info=True)
transaction.abort()
if raise_exc:
raise
def install_standards(app):
# Check to see if we've already done this before
# Don't do it twice (Casey)
......
......@@ -31,6 +31,7 @@ import zope.interface
import zope.location.interfaces
from zope.app.container.contained import dispatchToSublocations
from zope.app.container.interfaces import IObjectMovedEvent
from zope.lifecycleevent.interfaces import IObjectCopiedEvent
deprecatedManageAddDeleteClasses = []
......@@ -130,6 +131,14 @@ def dispatchObjectClonedEvent(ob, event):
if OFS.interfaces.IObjectManager.providedBy(ob):
dispatchToSublocations(ob, event)
@zope.component.adapter(OFS.interfaces.IItem, IObjectCopiedEvent)
def dispatchObjectCopiedEvent(ob, event):
"""Multi-subscriber for IItem + IObjectCopiedEvent.
"""
# Dispatch to sublocations
if OFS.interfaces.IObjectManager.providedBy(ob):
dispatchToSublocations(ob, event)
def callManageAfterAdd(ob, item, container):
"""Compatibility subscriber for manage_afterAdd.
......
......@@ -224,7 +224,7 @@ class TestCopySupportSublocation(EventTest):
self.subfolder.manage_clone(self.folder.myfolder, 'myfolder')
self.assertEqual(eventlog.called(),
[('myfolder', 'ObjectCopiedEvent'),
#('mydoc', 'ObjectCopiedEvent'),
('mydoc', 'ObjectCopiedEvent'),
('myfolder', 'ObjectWillBeAddedEvent'),
('mydoc', 'ObjectWillBeAddedEvent'),
('myfolder', 'ObjectAddedEvent'),
......@@ -240,7 +240,7 @@ class TestCopySupportSublocation(EventTest):
self.subfolder.manage_pasteObjects(cb)
self.assertEqual(eventlog.called(),
[('myfolder', 'ObjectCopiedEvent'),
#('mydoc', 'ObjectCopiedEvent'),
('mydoc', 'ObjectCopiedEvent'),
('myfolder', 'ObjectWillBeAddedEvent'),
('mydoc', 'ObjectWillBeAddedEvent'),
('myfolder', 'ObjectAddedEvent'),
......@@ -282,7 +282,7 @@ class TestCopySupportSublocation(EventTest):
self.folder.myfolder.COPY(req, req.RESPONSE)
self.assertEqual(eventlog.called(),
[('myfolder', 'ObjectCopiedEvent'),
#('mydoc', 'ObjectCopiedEvent'),
('mydoc', 'ObjectCopiedEvent'),
('myfolder', 'ObjectWillBeAddedEvent'),
('mydoc', 'ObjectWillBeAddedEvent'),
('myfolder', 'ObjectAddedEvent'),
......
......@@ -83,6 +83,26 @@ def boboAwareZopeTraverse(object, path_items, econtext):
request=request)
return object
def trustedBoboAwareZopeTraverse(object, path_items, econtext):
"""Traverses a sequence of names, first trying attributes then items.
This uses Zope 3 path traversal where possible and interacts
correctly with objects providing OFS.interface.ITraversable when
necessary (bobo-awareness).
"""
request = getattr(econtext, 'request', None)
path_items = list(path_items)
path_items.reverse()
while path_items:
name = path_items.pop()
if OFS.interfaces.ITraversable.providedBy(object):
object = object.unrestrictedTraverse(name)
else:
object = traversePathElement(object, name, path_items,
request=request)
return object
def render(ob, ns):
"""Calls the object, possibly a document template, or just returns
it if not callable. (From DT_Util.py)
......@@ -108,11 +128,13 @@ def render(ob, ns):
class ZopePathExpr(PathExpr):
_TRAVERSER = staticmethod(boboAwareZopeTraverse)
def __init__(self, name, expr, engine):
if not expr.strip():
expr = 'nothing'
super(ZopePathExpr, self).__init__(name, expr, engine,
boboAwareZopeTraverse)
self._TRAVERSER)
# override this to support different call metrics (see bottom of
# method) and Zope 2's traversal exceptions (ZopeUndefs instead of
......@@ -150,6 +172,9 @@ class ZopePathExpr(PathExpr):
return 1
return 0
class TrustedZopePathExpr(ZopePathExpr):
_TRAVERSER = staticmethod(trustedBoboAwareZopeTraverse)
class SafeMapping(MultiMapping):
"""Mapping with security declarations and limited method exposure.
......@@ -335,11 +360,11 @@ class PathIterator(ZopeIterator):
return False
return ob1 == ob2
def createZopeEngine():
def createZopeEngine(zpe=ZopePathExpr):
e = ZopeEngine()
e.iteratorFactory = PathIterator
for pt in ZopePathExpr._default_type_names:
e.registerType(pt, ZopePathExpr)
for pt in zpe._default_type_names:
e.registerType(pt, zpe)
e.registerType('string', StringExpr)
e.registerType('python', ZRPythonExpr.PythonExpr)
e.registerType('not', NotExpr)
......@@ -352,7 +377,7 @@ def createZopeEngine():
def createTrustedZopeEngine():
# same as createZopeEngine, but use non-restricted Python
# expression evaluator
e = createZopeEngine()
e = createZopeEngine(TrustedZopePathExpr)
e.types['python'] = PythonExpr
return e
......
......@@ -128,6 +128,9 @@ class TestPythonScriptNoAq(PythonScriptTestBase):
res = self._newPS('return 1 * 5 + 4 / 2 - 6')()
self.assertEqual(res, 1)
def testCollector2295(self):
res = self._newPS('if False:\n pass\n#hi')
def testReduce(self):
res = self._newPS('return reduce(lambda x, y: x + y, [1,3,5,7])')()
self.assertEqual(res, 16)
......
......@@ -11,3 +11,8 @@
#
##############################################################################
__ac_permissions__=()
# This is used to keep track of packages which need to be initialized as
# products. These will be processed during the usual product installation
# in OFS.Application
_packages_to_initialize = []
\ No newline at end of file
......@@ -43,7 +43,7 @@ class RestrictedCompileMode(AbstractCompileMode):
def __init__(self, source, filename):
if source:
source = '\n'.join(source.splitlines())
source = '\n'.join(source.splitlines()) + '\n'
self.rm = RestrictionMutator()
AbstractCompileMode.__init__(self, source, filename)
......@@ -209,7 +209,7 @@ class RFunction(RModule):
def __init__(self, p, body, name, filename, globals):
self.params = p
if body:
body = '\n'.join(body.splitlines())
body = '\n'.join(body.splitlines()) + '\n'
self.body = body
self.name = name
self.globals = globals or []
......
......@@ -100,7 +100,10 @@ class PortalTestCase(base.TestCase):
self.portal.clearCurrentSkin()
else: # CMF 1.4
self.portal._v_skindata = None
self.portal.setupCurrentSkin()
try:
self.portal.setupCurrentSkin(self.app.REQUEST)
except TypeError:
self.portal.setupCurrentSkin()
# Portal interface
......
......@@ -132,12 +132,14 @@ if not Zope2._began_startup:
# Allow test authors to install Zope products into the test environment. Note
# that installProduct() must be called at module level -- never from tests.
from OFS.Application import get_folder_permissions, get_products, install_product
from OFS.Application import get_folder_permissions, get_products
from OFS.Application import install_product, install_package
from OFS.Folder import Folder
import Products
_theApp = Zope2.app()
_installedProducts = {}
_installedPackages = {}
def hasProduct(name):
'''Checks if a product can be found along Products.__path__'''
......@@ -164,6 +166,27 @@ def installProduct(name, quiet=0):
if name != 'SomeProduct': # Ignore the skeleton tests :-P
if not quiet: _print('Installing %s ... NOT FOUND\n' % name)
def hasPackage(name):
'''Checks if a package has been registered with five:registerPackage.'''
return name in [m.__name__ for m in getattr(Products, '_registered_packages', [])]
def installPackage(name, quiet=0):
'''Installs a registered Python package like a Zope product.'''
start = time.time()
if _patched and not _installedPackages.has_key(name):
for module, init_func in Products._packages_to_initialize:
if module.__name__ == name:
if not quiet: _print('Installing %s ... ' % module.__name__)
# We want to fail immediately if a package throws an exception
# during install, so we set the raise_exc flag.
install_package(_theApp, module, init_func, raise_exc=1)
_installedPackages[module.__name__] = 1
Products._packages_to_initialize.remove((module, init_func))
if not quiet: _print('done (%.3fs)\n' % (time.time() - start))
break
else:
if not quiet: _print('Installing %s ... NOT FOUND\n' % name)
def _load_control_panel():
# Loading the Control_Panel of an existing ZODB may take
# a while; print another dot if it does.
......@@ -193,6 +216,7 @@ DB = Zope2.DB
configure = Zope2.configure
def startup(): pass
Zope = Zope2
active = _patched
# ZODB sandbox factory
from ZODB.DemoStorage import DemoStorage
......
......@@ -20,6 +20,8 @@ import utils
from ZopeLite import hasProduct
from ZopeLite import installProduct
from ZopeLite import hasPackage
from ZopeLite import installPackage
from ZopeLite import _print
from ZopeTestCase import folder_name
......
......@@ -14,6 +14,9 @@
publish_module(). Thanks to Andreas Zeidler.
- Fixed doctestsuite factory to copy layers from test_class to the suite.
Thanks to Whit Morris.
- Added hasPackage and installPackage functions for dealing with "products"
registered via five:registerPackage.
- Provide access to test globs in doctest setUp and tearDown.
0.9.8 (Zope 2.8 edition)
- Renamed 'doctest' package to 'zopedoctest' because of name-shadowing
......
def initialize(context):
print 'testpackage.initialize called'
......@@ -253,6 +253,7 @@ class ZopeSuiteFactory:
if hasattr(test_instance, 'portal'):
test.globs['portal'] = test_instance.portal
test.globs['portal_name'] = test_instance.portal.getId()
test_instance.globs = test.globs
if kwsetUp is not None:
kwsetUp(test_instance)
......
......@@ -38,6 +38,9 @@ def setUp(self):
Content-Type: text/plain; charset=...
<BLANKLINE>
index
>>> foo
1
'''
self.folder.addDTMLDocument('index_html', file='index')
......@@ -55,6 +58,8 @@ def setUp(self):
</dtml-in>'''
self.folder.addDTMLMethod('show_cookies', file=show_cookies)
self.globs['foo'] = 1
def test_suite():
return TestSuite((
......
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# 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.
#
##############################################################################
"""Tests for installPackage
$Id$
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
from unittest import TestSuite
from Testing import ZopeTestCase
from Testing.ZopeTestCase import ZopeLite
from Testing.ZopeTestCase import ZopeDocTestSuite
from Products.Five import zcml
from zope.testing import cleanup
import Products
def testInstallPackage():
"""
Test if installPackage works.
>>> from Testing import ZopeTestCase
>>> from Products.Five import zcml
Register testpackage
>>> ZopeTestCase.hasPackage('testpackage')
False
>>> config = '''
... <configure
... xmlns:five="http://namespaces.zope.org/five">
... <five:registerPackage
... package="testpackage"
... initialize="testpackage.initialize"
... />
... </configure>'''
>>> zcml.load_string(config)
The package is registered now
>>> ZopeTestCase.hasPackage('testpackage')
True
But not yet installed
>>> app = self._app()
>>> 'testpackage' in app.Control_Panel.Products.objectIds()
False
Install it
>>> ZopeTestCase.installPackage('testpackage', quiet=True)
testpackage.initialize called
Now it shows up in Control_Panel
>>> app = self._app()
>>> 'testpackage' in app.Control_Panel.Products.objectIds()
True
hasPackage still returns True
>>> ZopeTestCase.hasPackage('testpackage')
True
A package is only installed once, subsequent calls to installPackage
are ignored:
>>> ZopeTestCase.installPackage('testpackage', quiet=True)
"""
class TestClass(ZopeTestCase.FunctionalTestCase):
def afterSetUp(self):
cleanup.cleanUp()
zcml._initialized = False
zcml.load_site()
self.saved = sys.path[:]
sys.path.append(ZopeTestCase.__path__[0])
def afterClear(self):
cleanup.cleanUp()
sys.path[:] = self.saved
registered = getattr(Products, '_registered_packages', None)
if registered is not None:
Products._registered_packages = [m for m in registered
if m.__name__ != 'testpackage']
to_initialize = getattr(Products, '_packages_to_initialize', None)
if to_initialize is not None:
Products._packages_to_initialize = [(m, f) for (m, f) in to_initialize
if m.__name__ != 'testpackage']
def test_suite():
if ZopeLite.active:
return TestSuite((
ZopeDocTestSuite(test_class=TestClass),
))
else:
return TestSuite()
if __name__ == '__main__':
framework()
......@@ -12,7 +12,7 @@
##############################################################################
"""Example Zope doctest
$Id: testZopeDocTest.py,v 1.2 2005/03/26 18:07:08 shh42 Exp $
$Id$
"""
import os, sys
......@@ -29,8 +29,12 @@ def setUp(self):
>>> 'object' in folder.objectIds()
True
>>> foo
1
'''
self.folder.manage_addFolder('object', '')
self.globs['foo'] = 1
def test_suite():
......
......@@ -202,8 +202,8 @@ class BaseRequest:
def close(self):
self.other.clear()
self._held=None
notify(EndRequestEvent(None, self))
self._held=None
def processInputs(self):
"""Do any input processing that could raise errors
......@@ -634,7 +634,8 @@ class BaseRequest:
def _hold(self, object):
"""Hold a reference to an object to delay it's destruction until mine
"""
self._held=self._held+(object,)
if self._held is not None:
self._held=self._held+(object,)
def exec_callables(callables):
result = None
......
......@@ -1475,7 +1475,7 @@ def parse_cookie(text,
qparmre=re.compile(
'([\x00- ]*([^\x00- ;,="]+)="([^"]*)"([\x00- ]*[;,])?[\x00- ]*)'),
parmre=re.compile(
'([\x00- ]*([^\x00- ;,="]+)=([^\x00- ;,"]*)([\x00- ]*[;,])?[\x00- ]*)'),
'([\x00- ]*([^\x00- ;,="]+)=([^;,"]*)([\x00- ]*[;,])?[\x00- ]*)'),
paramlessre=re.compile(
'([\x00- ]*([^\x00- ;,="]+)[\x00- ]*[;,][\x00- ]*)'),
......
......@@ -247,6 +247,17 @@ class TestBaseRequest(TestCase):
self.assertRaises(NotFound, r.traverse, 'folder/simpleSet')
self.assertRaises(NotFound, r.traverse, 'folder/simpleFrozenSet')
def test_hold_after_close(self):
# Request should no longer accept holds after it has been closed
r = self.makeBaseRequest()
r._hold(lambda x: None)
self.assertEqual(len(r._held), 1)
r.close()
# No more holding from now on
self.assertEqual(r._held, None)
r._hold(lambda x: None)
self.assertEqual(r._held, None)
from ZPublisher import NotFound
import zope.interface
......
......@@ -658,6 +658,20 @@ class ProcessInputsTests(unittest.TestCase):
self.assertEquals(req.cookies['hmm'], '')
self.assertEquals(req.cookies['baz'], 'gee')
# Unquoted multi-space cookies
env['HTTP_COOKIE'] = 'single=cookie data; ' \
'quoted="cookie data with unquoted spaces"; ' \
'multi=cookie data with unquoted spaces; ' \
'multi2=cookie data with unquoted spaces'
req = self._getHTTPRequest(env)
self.assertEquals(req.cookies['single'], 'cookie data')
self.assertEquals(req.cookies['quoted'],
'cookie data with unquoted spaces')
self.assertEquals(req.cookies['multi'],
'cookie data with unquoted spaces')
self.assertEquals(req.cookies['multi2'],
'cookie data with unquoted spaces')
TEST_ENVIRON = {
'CONTENT_TYPE': 'multipart/form-data; boundary=12345',
'REQUEST_METHOD': 'POST',
......
......@@ -150,6 +150,14 @@ def cgi_maxlen(value):
def http_header_max_length(value):
return value
def enable_ms_author_via(value):
import webdav
webdav.enable_ms_author_via = value
def enable_ms_public_header(value):
import webdav
webdav.enable_ms_public_header = value
def catalog_getObject_raises(value):
if value is not None:
......
......@@ -95,6 +95,50 @@ class StartupTestCase(unittest.TestCase):
items.sort()
self.assertEqual(items, [("FEARFACTORY", "rocks"), ("NSYNC","doesnt")])
def test_ms_author_via(self):
import webdav
from Zope2.Startup.handlers import handleConfig
default_setting = webdav.enable_ms_author_via
try:
conf, handler = self.load_config_text("""\
instancehome <<INSTANCE_HOME>>
enable-ms-author-via true
""")
handleConfig(None, handler)
self.assert_(webdav.enable_ms_author_via == True)
conf, handler = self.load_config_text("""\
instancehome <<INSTANCE_HOME>>
enable-ms-author-via false
""")
handleConfig(None, handler)
self.assert_(webdav.enable_ms_author_via == False)
finally:
webdav.enable_ms_author_via = default_setting
def test_ms_public_header(self):
import webdav
from Zope2.Startup.handlers import handleConfig
default_setting = webdav.enable_ms_public_header
try:
conf, handler = self.load_config_text("""\
instancehome <<INSTANCE_HOME>>
enable-ms-public-header true
""")
handleConfig(None, handler)
self.assert_(webdav.enable_ms_public_header == True)
conf, handler = self.load_config_text("""\
instancehome <<INSTANCE_HOME>>
enable-ms-public-header false
""")
handleConfig(None, handler)
self.assert_(webdav.enable_ms_public_header == False)
finally:
webdav.enable_ms_public_header = default_setting
def test_path(self):
p1 = tempfile.mktemp()
p2 = tempfile.mktemp()
......
......@@ -553,6 +553,63 @@
</description>
</key>
<key name="enable-ms-author-via" datatype="boolean" handler="enable_ms_author_via" default="off">
<description>
Set this directive to 'true' to enable the "MS-Author-Via" header
in response to an OPTIONS WebDAV request. Early versions of
Microsoft Web Folders and Microsoft Office require this header to
be present to be able to connect to Zope via WebDAV.
This is disabled by default since it makes a lot of standards-compliant
things unhappy AND it tricks Microsoft Office into trying to edit Office
files stored in Zope via WebDAV even when the user isn't allowed to edit
them and is only trying to download them.
Check this collector entry for more information:
http://www.zope.org/Collectors/Zope/1441
Recent versions of Microsoft Web Folders, updated after January
2005, do not require this header anymore, and instead require a
"Public" header to be present in reply to the OPTIONS WebDAV
request.
(http://www.redmountainsw.com/wordpress/archives/webfolders-zope)
To get a recent Microsoft Web Folders implementation, refer to
Microsoft KB Article 907306.
(Software Update for Web Folders: May 18, 2007).
</description>
<metadefault>off</metadefault>
</key>
<key name="enable-ms-public-header" datatype="boolean" handler="enable_ms_public_header" default="off">
<description>
Set this directive to 'on' to enable sending the "Public" header
in response to an WebDAV OPTIONS request.
Though recent WebDAV drafts mention this header, the original
WebDAV RFC did not mention it as part of the standard. Very few
web servers out there include this header in their replies, most
notably IIS and Netscape Enterprise 3.6.
Since many best practices documents out in the web mention
turning off this header with the subject of "Mask Your Web Server
For Enhanced Security", this setting is off by
default. Presumably malicious people might take the presence of
this header as indication of an IIS Web Server and try to attack
your site, so be careful when turning it on.
Recent versions of Microsoft Web Folders, updated after January
2005, *do* require this header to be present in reply to the
OPTIONS WebDAV request.
(http://www.redmountainsw.com/wordpress/archives/webfolders-zope)
To get a recent Microsoft Web Folders implementation, refer to
Microsoft KB Article 907306.
(Software Update for Web Folders: May 18, 2007).
</description>
<metadefault>off</metadefault>
</key>
<key name="dns-server" datatype=".dns_resolver" attribute="dns_resolver">
<description>
Specify the IP address of your DNS server in order to cause resolved
......
......@@ -18,8 +18,10 @@ $Id$
import mimetypes
import sys
import warnings
import re
from urllib import unquote
import webdav
import ExtensionClass
from Globals import InitializeClass
from AccessControl import getSecurityManager
......@@ -54,6 +56,7 @@ from OFS.event import ObjectClonedEvent
from OFS.event import ObjectWillBeMovedEvent
import OFS.subscribers
ms_dav_agent = re.compile("Microsoft.*Internet Publishing.*")
class Resource(ExtensionClass.Base, Lockable.LockableItem):
......@@ -213,6 +216,15 @@ class Resource(ExtensionClass.Base, Lockable.LockableItem):
RESPONSE.setHeader('Allow', ', '.join(self.__http_methods__))
RESPONSE.setHeader('Content-Length', 0)
RESPONSE.setHeader('DAV', '1,2', 1)
# Microsoft Web Folders compatibility, only enabled if
# User-Agent matches.
if ms_dav_agent.match(REQUEST.get_header('User-Agent', '')):
if webdav.enable_ms_public_header:
RESPONSE.setHeader('Public', ', '.join(self.__http_methods__))
if webdav.enable_ms_author_via:
RESPONSE.setHeader('MS-Author-Via', 'DAV')
RESPONSE.setStatus(200)
return RESPONSE
......
......@@ -36,3 +36,6 @@
Microsoft, U.C. Irvine, Netscape, Novell. February, 1999."""
__version__='$Revision: 1.7 $'[11:-2]
enable_ms_author_via = False
enable_ms_public_header = False
import unittest
MS_DAV_AGENT = "Microsoft Data Access Internet Publishing Provider DAV"
def make_request_response(environ=None):
from StringIO import StringIO
from ZPublisher.HTTPRequest import HTTPRequest
from ZPublisher.HTTPResponse import HTTPResponse
if environ is None:
environ = {}
stdout = StringIO()
stdin = StringIO()
resp = HTTPResponse(stdout=stdout)
environ.setdefault('SERVER_NAME', 'foo')
environ.setdefault('SERVER_PORT', '80')
environ.setdefault('REQUEST_METHOD', 'GET')
req = HTTPRequest(stdin, environ, resp)
return req, resp
class TestResource(unittest.TestCase):
......@@ -12,6 +30,60 @@ class TestResource(unittest.TestCase):
verifyClass(IDAVResource, Resource)
verifyClass(IWriteLock, Resource)
def test_ms_author_via(self):
import webdav
from webdav.Resource import Resource
default_settings = webdav.enable_ms_author_via
try:
req, resp = make_request_response()
resource = Resource()
resource.OPTIONS(req, resp)
self.assert_(not resp.headers.has_key('ms-author-via'))
webdav.enable_ms_author_via = True
req, resp = make_request_response()
resource = Resource()
resource.OPTIONS(req, resp)
self.assert_(not resp.headers.has_key('ms-author-via'))
req, resp = make_request_response(
environ={'USER_AGENT': MS_DAV_AGENT})
resource = Resource()
resource.OPTIONS(req, resp)
self.assert_(resp.headers.has_key('ms-author-via'))
self.assert_(resp.headers['ms-author-via'] == 'DAV')
finally:
webdav.enable_ms_author_via = default_settings
def test_ms_public_header(self):
import webdav
from webdav.Resource import Resource
default_settings = webdav.enable_ms_public_header
try:
req, resp = make_request_response()
resource = Resource()
resource.OPTIONS(req, resp)
self.assert_(not resp.headers.has_key('public'))
webdav.enable_ms_public_header = True
req, resp = make_request_response()
resource = Resource()
resource.OPTIONS(req, resp)
self.assert_(not resp.headers.has_key('public'))
self.assert_(resp.headers.has_key('allow'))
req, resp = make_request_response(
environ={'USER_AGENT': MS_DAV_AGENT})
resource = Resource()
resource.OPTIONS(req, resp)
self.assert_(resp.headers.has_key('public'))
self.assert_(resp.headers.has_key('allow'))
self.assert_(resp.headers['public'] == resp.headers['allow'])
finally:
webdav.enable_ms_public_header = default_settings
def test_suite():
return unittest.TestSuite((
......
This diff is collapsed.
......@@ -443,6 +443,70 @@ instancehome $INSTANCE
#
# http-header-max-length 16384
# Directive: enable-ms-author-via
#
# Description:
# Set this directive to 'true' to enable the "MS-Author-Via" header
# in response to an OPTIONS WebDAV request. Early versions of
# Microsoft Web Folders and Microsoft Office require this header to
# be present to be able to connect to Zope via WebDAV.
#
# This is disabled by default since it makes a lot of standards-compliant
# things unhappy AND it tricks Microsoft Office into trying to edit Office
# files stored in Zope via WebDAV even when the user isn't allowed to edit
# them and is only trying to download them.
#
# Check this collector entry for more information:
# http://www.zope.org/Collectors/Zope/1441
#
# Recent versions of Microsoft Web Folders, updated after January
# 2005, do not require this header anymore, and instead require a
# "Public" header to be present in reply to the OPTIONS WebDAV
# request.
# (http://www.redmountainsw.com/wordpress/archives/webfolders-zope)
#
# To get a recent Microsoft Web Folders implementation, refer to
# Microsoft KB Article 907306.
# (Software Update for Web Folders: May 18, 2007).
#
# Default: off
#
# Example:
#
# enable-ms-author-via on
# Directive: enable-ms-public-header
#
# Description:
# Set this directive to 'on' to enable sending the "Public" header
# in response to an WebDAV OPTIONS request.
#
# Though recent WebDAV drafts mention this header, the original
# WebDAV RFC did not mention it as part of the standard. Very few
# web servers out there include this header in their replies, most
# notably IIS and Netscape Enterprise 3.6.
#
# Since many best practices documents out in the web mention
# turning off this header with the subject of "Mask Your Web Server
# For Enhanced Security", this setting is off by
# default. Presumably malicious people might take the presence of
# this header as indication of an IIS Web Server and try to attack
# your site, so be careful when turning it on.
#
# Recent versions of Microsoft Web Folders, updated after January
# 2005, *do* require this header to be present in reply to the
# OPTIONS WebDAV request.
# (http://www.redmountainsw.com/wordpress/archives/webfolders-zope)
#
# To get a recent Microsoft Web Folders implementation, refer to
# Microsoft KB Article 907306.
# (Software Update for Web Folders: May 18, 2007).
#
# Default: off
#
# Example:
#
# enable-ms-public-header on
# Directive: automatically-quote-dtml-request-data
#
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment