[CalendarServer-changes] [6472] CalendarServer/trunk/twistedcaldav
source_changes at macosforge.org
source_changes at macosforge.org
Wed Oct 27 11:53:53 PDT 2010
Revision: 6472
http://trac.macosforge.org/projects/calendarserver/changeset/6472
Author: cdaboo at apple.com
Date: 2010-10-27 11:53:51 -0700 (Wed, 27 Oct 2010)
Log Message:
-----------
Handle link resources with missing target resources by returning a 404.
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/linkresource.py
CalendarServer/trunk/twistedcaldav/sharedcollection.py
Added Paths:
-----------
CalendarServer/trunk/twistedcaldav/test/test_link.py
Modified: CalendarServer/trunk/twistedcaldav/linkresource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/linkresource.py 2010-10-27 17:36:01 UTC (rev 6471)
+++ CalendarServer/trunk/twistedcaldav/linkresource.py 2010-10-27 18:53:51 UTC (rev 6472)
@@ -14,15 +14,16 @@
# limitations under the License.
##
+
from twext.python.log import LoggingMixIn
+from twext.web2 import responsecode, server, http
+from twext.web2.dav import davxml
+from twext.web2.http import HTTPError, StatusResponse
+from twext.web2.resource import WrapperResource
from twisted.internet.defer import inlineCallbacks, returnValue, maybeDeferred
-from twext.web2.http import HTTPError
-from twext.web2 import responsecode
-from twext.web2.resource import WrapperResource
from twistedcaldav.config import config
-from twext.web2.dav import davxml
__all__ = [
"LinkResource",
@@ -42,27 +43,34 @@
class LinkResource(CalDAVComplianceMixIn, WrapperResource, LoggingMixIn):
"""
- This is similar to a WrapperResource except that we locate our resource dynamically.
+ This is similar to a WrapperResource except that we locate our resource dynamically. We need to deal with the
+ case of a missing underlying resource (broken link) as indicated by self._linkedResource being None.
"""
def __init__(self, parent, link_url):
self.parent = parent
self.linkURL = link_url
+ self.loopDetect = set()
super(LinkResource, self).__init__(self.parent.principalCollections())
@inlineCallbacks
def linkedResource(self, request):
if not hasattr(self, "_linkedResource"):
+ if self.linkURL in self.loopDetect:
+ raise HTTPError(StatusResponse(responsecode.LOOP_DETECTED, "Recursive link target: %s" % (self.linkURL,)))
+ else:
+ self.loopDetect.add(self.linkURL)
self._linkedResource = (yield request.locateResource(self.linkURL))
+ self.loopDetect.remove(self.linkURL)
if self._linkedResource is None:
- raise HTTPError(responsecode.NOT_FOUND)
+ raise HTTPError(StatusResponse(responsecode.NOT_FOUND, "Missing link target: %s" % (self.linkURL,)))
returnValue(self._linkedResource)
def isCollection(self):
- return True
+ return True if hasattr(self, "_linkedResource") else False
def resourceType(self):
return self._linkedResource.resourceType() if hasattr(self, "_linkedResource") else davxml.ResourceType.link
@@ -70,33 +78,41 @@
def locateChild(self, request, segments):
def _defer(result):
- return (result, segments)
+ if result is None:
+ return (self, server.StopTraversal)
+ else:
+ return (result, segments)
d = self.linkedResource(request)
d.addCallback(_defer)
return d
+ @inlineCallbacks
def renderHTTP(self, request):
- return self.linkedResource(request)
+ linked_to = (yield self.linkedResource(request))
+ if linked_to:
+ returnValue(linked_to)
+ else:
+ returnValue(http.StatusResponse(responsecode.OK, "Link resource with missing target: %s" % (self.linkURL,)))
def getChild(self, name):
- return self._linkedResource.getChild(name)
+ return self._linkedResource.getChild(name) if hasattr(self, "_linkedResource") else None
@inlineCallbacks
def hasProperty(self, property, request):
hosted = (yield self.linkedResource(request))
- result = (yield hosted.hasProperty(property, request))
+ result = (yield hosted.hasProperty(property, request)) if hosted else False
returnValue(result)
@inlineCallbacks
def readProperty(self, property, request):
hosted = (yield self.linkedResource(request))
- result = (yield hosted.readProperty(property, request))
+ result = (yield hosted.readProperty(property, request)) if hosted else None
returnValue(result)
@inlineCallbacks
def writeProperty(self, property, request):
hosted = (yield self.linkedResource(request))
- result = (yield hosted.writeProperty(property, request))
+ result = (yield hosted.writeProperty(property, request)) if hosted else None
returnValue(result)
class LinkFollowerMixIn(object):
@@ -104,17 +120,13 @@
@inlineCallbacks
def locateChild(self, req, segments):
+ self._inside_locateChild = True
resource, path = (yield maybeDeferred(super(LinkFollowerMixIn, self).locateChild, req, segments))
- MAX_LINK_DEPTH = 10
- ctr = 0
- seenResource = set()
while isinstance(resource, LinkResource):
- seenResource.add(resource)
- ctr += 1
- resource = (yield resource.linkedResource(req))
-
- if ctr > MAX_LINK_DEPTH or resource in seenResource:
- raise HTTPError(responsecode.LOOP_DETECTED)
+ linked_to = (yield resource.linkedResource(req))
+ if linked_to is None:
+ break
+ resource = linked_to
returnValue((resource, path))
Modified: CalendarServer/trunk/twistedcaldav/sharedcollection.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharedcollection.py 2010-10-27 17:36:01 UTC (rev 6471)
+++ CalendarServer/trunk/twistedcaldav/sharedcollection.py 2010-10-27 18:53:51 UTC (rev 6472)
@@ -18,6 +18,9 @@
"SharedCollectionResource",
]
+from twext.web2.http import HTTPError, StatusResponse
+from twext.web2 import responsecode
+
from twisted.internet.defer import inlineCallbacks, returnValue
from twistedcaldav.linkresource import LinkResource
@@ -29,24 +32,31 @@
class SharedCollectionResource(LinkResource):
"""
- This is similar to a WrapperResource except that we locate our shared collection resource dynamically.
+ This is similar to a LinkResource except that we locate our shared collection resource dynamically.
"""
def __init__(self, parent, share):
self.share = share
- super(SharedCollectionResource, self).__init__(parent, None)
+ super(SharedCollectionResource, self).__init__(parent, self.share.hosturl)
@inlineCallbacks
def linkedResource(self, request):
+ """
+ Resolve the share host url to the underlying resource (set to be a virtual share).
+ """
if not hasattr(self, "_linkedResource"):
self._linkedResource = (yield request.locateResource(self.share.hosturl))
- # FIXME: this is awkward - because we are "mutation" this object into a virtual share
- # we must not cache the resource at this URL, otherwise an access of the owner's resource
- # will return the same virtually shared one which would be wrong.
- request._forgetResource(self._linkedResource, self.share.hosturl)
+ if self._linkedResource is not None:
+ # FIXME: this is awkward - because we are "mutating" this object into a virtual share
+ # we must not cache the resource at this URL, otherwise an access of the owner's resource
+ # will return the same virtually shared one which would be wrong.
+ request._forgetResource(self._linkedResource, self.share.hosturl)
+
+ ownerPrincipal = (yield self.parent.ownerPrincipal(request))
+ self._linkedResource.setVirtualShare(ownerPrincipal, self.share)
+ else:
+ raise HTTPError(StatusResponse(responsecode.NOT_FOUND, "Missing link target: %s" % (self.linkURL,)))
- ownerPrincipal = (yield self.parent.ownerPrincipal(request))
- self._linkedResource.setVirtualShare(ownerPrincipal, self.share)
returnValue(self._linkedResource)
Added: CalendarServer/trunk/twistedcaldav/test/test_link.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_link.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/test/test_link.py 2010-10-27 18:53:51 UTC (rev 6472)
@@ -0,0 +1,119 @@
+##
+# Copyright (c) 2008 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError
+from twext.web2.test.test_server import SimpleRequest
+
+from twisted.internet.defer import inlineCallbacks, succeed
+
+from twistedcaldav.linkresource import LinkResource
+from twistedcaldav.resource import CalendarHomeResource
+from twistedcaldav.sharedcollection import SharedCollectionResource
+from twistedcaldav.test.util import TestCase
+
+
+class StubProperty(object):
+ def qname(self):
+ return "StubQnamespace", "StubQname"
+
+class StubHome(object):
+ def properties(self):
+ return []
+
+ def calendarWithName(self, name):
+ return succeed(None)
+
+class StubCalendarHomeResource(CalendarHomeResource):
+ def principalForRecord(self):
+ return None
+
+class StubShare(object):
+ def __init__(self, link):
+ self.hosturl = link
+
+class LinkResourceTests(TestCase):
+
+ @inlineCallbacks
+ def test_okLink(self):
+ resource = CalendarHomeResource(self.site.resource, "home", object(), StubHome())
+ self.site.resource.putChild("home", resource)
+ link = LinkResource(resource, "/home/outbox/")
+ resource.putChild("link", link)
+
+ request = SimpleRequest(self.site, "GET", "/home/link/")
+ linked_to, _ignore = (yield resource.locateChild(request, ["link",]))
+ self.assertTrue(linked_to is resource.getChild("outbox"))
+
+ @inlineCallbacks
+ def test_badLink(self):
+ resource = CalendarHomeResource(self.site.resource, "home", object(), StubHome())
+ self.site.resource.putChild("home", resource)
+ link = LinkResource(resource, "/home/outbox/abc")
+ resource.putChild("link", link)
+
+ request = SimpleRequest(self.site, "GET", "/home/link/")
+ try:
+ yield resource.locateChild(request, ["link",])
+ except HTTPError, e:
+ self.assertEqual(e.response.code, responsecode.NOT_FOUND)
+ else:
+ self.fail("HTTPError exception not raised")
+
+ @inlineCallbacks
+ def test_recursiveLink(self):
+ resource = CalendarHomeResource(self.site.resource, "home", object(), StubHome())
+ self.site.resource.putChild("home", resource)
+ link1 = LinkResource(resource, "/home/link2/")
+ resource.putChild("link1", link1)
+ link2 = LinkResource(resource, "/home/link1/")
+ resource.putChild("link2", link2)
+
+ request = SimpleRequest(self.site, "GET", "/home/link1/")
+ try:
+ yield resource.locateChild(request, ["link1",])
+ except HTTPError, e:
+ self.assertEqual(e.response.code, responsecode.LOOP_DETECTED)
+ else:
+ self.fail("HTTPError exception not raised")
+
+class SharedCollectionResourceTests(TestCase):
+
+ @inlineCallbacks
+ def test_okLink(self):
+ resource = StubCalendarHomeResource(self.site.resource, "home", object(), StubHome())
+ self.site.resource.putChild("home", resource)
+ link = SharedCollectionResource(resource, StubShare("/home/outbox/"))
+ resource.putChild("link", link)
+
+ request = SimpleRequest(self.site, "GET", "/home/link/")
+ linked_to, _ignore = (yield resource.locateChild(request, ["link",]))
+ self.assertTrue(linked_to is resource.getChild("outbox"))
+
+ @inlineCallbacks
+ def test_badLink(self):
+ resource = CalendarHomeResource(self.site.resource, "home", object(), StubHome())
+ self.site.resource.putChild("home", resource)
+ link = SharedCollectionResource(resource, StubShare("/home/outbox/abc"))
+ resource.putChild("link", link)
+
+ request = SimpleRequest(self.site, "GET", "/home/link/")
+ try:
+ yield resource.locateChild(request, ["link",])
+ except HTTPError, e:
+ self.assertEqual(e.response.code, responsecode.NOT_FOUND)
+ else:
+ self.fail("HTTPError exception not raised")
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20101027/9e98b60d/attachment-0001.html>
More information about the calendarserver-changes
mailing list