def http_PROPFIND(self, request): """ Respond to a PROPFIND request. (RFC 2518, section 8.1) """ if not self.exists(): # Return 403 if parent does not allow Bind parentURL = parentForURL(request.uri) parent = (yield request.locateResource(parentURL)) yield parent.authorize(request, (davxml.Bind(),)) log.error("Resource not found: %s" % (self,)) raise HTTPError(responsecode.NOT_FOUND) # # Check authentication and access controls # yield self.authorize(request, (davxml.Read(),)) # # Read request body # try: doc = (yield davXMLFromStream(request.stream)) except ValueError, e: log.error("Error while handling PROPFIND body: %s" % (e,)) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
def http_MKCOL(self, request): """ Respond to a MKCOL request. (RFC 2518, section 8.3) """ parent = waitForDeferred(request.locateResource(parentForURL(request.uri))) yield parent parent = parent.getResult() x = waitForDeferred(parent.authorize(request, (davxml.Bind(), ))) yield x x.getResult() if self.exists(): log.error("Attempt to create collection where file exists: %s" % (self, )) raise HTTPError(responsecode.NOT_ALLOWED) if not parent.isCollection(): log.error( "Attempt to create collection with non-collection parent: %s" % (self, )) raise HTTPError( StatusResponse(responsecode.CONFLICT, "Parent resource is not a collection.")) # # Read request body # x = waitForDeferred(noDataFromStream(request.stream)) yield x try: x.getResult() except ValueError, e: log.error("Error while handling MKCOL body: %s" % (e, )) raise HTTPError(responsecode.UNSUPPORTED_MEDIA_TYPE)
def authorize(self, request): if self.exists(): d = self.authorize(request, (davxml.Read(),)) else: d = request.locateResource(parentForURL(request.uri)) d.addCallback(lambda parent: parent.authorize(request, (davxml.Bind(),))) return d
def http_PROPFIND(self, request): """ Respond to a PROPFIND request. (RFC 2518, section 8.1) """ if not self.exists(): # Return 403 if parent does not allow Bind parentURL = parentForURL(request.uri) parent = (yield request.locateResource(parentURL)) yield parent.authorize(request, (davxml.Bind(), )) log.error("Resource not found: %s" % (self, )) raise HTTPError(responsecode.NOT_FOUND) # # Check authentication and access controls # yield self.authorize(request, (davxml.Read(), )) # # Read request body # try: doc = (yield davXMLFromStream(request.stream)) except ValueError, e: log.error("Error while handling PROPFIND body: %s" % (e, )) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
def http_MKCOL(self, request): """ Respond to a MKCOL request. (RFC 2518, section 8.3) """ parent = waitForDeferred(request.locateResource(parentForURL(request.uri))) yield parent parent = parent.getResult() x = waitForDeferred(parent.authorize(request, (davxml.Bind(),))) yield x x.getResult() if self.exists(): log.error("Attempt to create collection where file exists: %s" % (self,)) raise HTTPError(responsecode.NOT_ALLOWED) if not parent.isCollection(): log.error("Attempt to create collection with non-collection parent: %s" % (self,)) raise HTTPError(StatusResponse( responsecode.CONFLICT, "Parent resource is not a collection." )) # # Read request body # x = waitForDeferred(noDataFromStream(request.stream)) yield x try: x.getResult() except ValueError, e: log.error("Error while handling MKCOL body: %s" % (e,)) raise HTTPError(responsecode.UNSUPPORTED_MEDIA_TYPE)
def authorize(self, request): if self.exists(): d = self.authorize(request, (davxml.Read(), )) else: d = request.locateResource(parentForURL(request.uri)) d.addCallback(lambda parent: parent.authorize(request, (davxml.Bind(), ))) return d
def http_COPY(self, request): """ Respond to a COPY request. (RFC 2518, section 8.8) """ r = waitForDeferred(prepareForCopy(self, request)) yield r r = r.getResult() destination, destination_uri, depth = r # # Check authentication and access controls # x = waitForDeferred( self.authorize(request, (davxml.Read(), ), recurse=True)) yield x x.getResult() if destination.exists(): x = waitForDeferred( destination.authorize( request, (davxml.WriteContent(), davxml.WriteProperties()), recurse=True)) yield x x.getResult() else: destparent = waitForDeferred( request.locateResource(parentForURL(destination_uri))) yield destparent destparent = destparent.getResult() x = waitForDeferred(destparent.authorize(request, (davxml.Bind(), ))) yield x x.getResult() # May need to add a location header addLocation(request, destination_uri) # x = waitForDeferred(copy(self.fp, destination.fp, destination_uri, depth)) x = waitForDeferred( put_common.storeResource(request, source=self, source_uri=request.uri, destination=destination, destination_uri=destination_uri, deletesource=False, depth=depth)) yield x yield x.getResult()
def http_COPY(self, request): """ Respond to a COPY request. (RFC 2518, section 8.8) """ r = waitForDeferred(prepareForCopy(self, request)) yield r r = r.getResult() destination, destination_uri, depth = r # # Check authentication and access controls # x = waitForDeferred(self.authorize(request, (davxml.Read(),), recurse=True)) yield x x.getResult() if destination.exists(): x = waitForDeferred(destination.authorize( request, (davxml.WriteContent(), davxml.WriteProperties()), recurse=True )) yield x x.getResult() else: destparent = waitForDeferred(request.locateResource(parentForURL(destination_uri))) yield destparent destparent = destparent.getResult() x = waitForDeferred(destparent.authorize(request, (davxml.Bind(),))) yield x x.getResult() # May need to add a location header addLocation(request, destination_uri) #x = waitForDeferred(copy(self.fp, destination.fp, destination_uri, depth)) x = waitForDeferred(put_common.storeResource(request, source=self, source_uri=request.uri, destination=destination, destination_uri=destination_uri, deletesource=False, depth=depth )) yield x yield x.getResult()
def preconditions_PUT(self, request): # # Check authentication and access controls # if self.exists(): x = waitForDeferred(self.authorize(request, (davxml.WriteContent(),))) yield x x.getResult() else: parent = waitForDeferred(request.locateResource(parentForURL(request.uri))) yield parent parent = parent.getResult() if not parent.exists(): raise HTTPError( StatusResponse( responsecode.CONFLICT, "cannot PUT to non-existent parent")) x = waitForDeferred(parent.authorize(request, (davxml.Bind(),))) yield x x.getResult() # # HTTP/1.1 (RFC 2068, section 9.6) requires that we respond with a Not # Implemented error if we get a Content-* header which we don't # recognize and handle properly. # for header, _ignore_value in request.headers.getAllRawHeaders(): if header.startswith("Content-") and header not in ( # "Content-Base", # Doesn't make sense in PUT? # "Content-Encoding", # Requires that we decode it? "Content-Language", "Content-Length", # "Content-Location", # Doesn't make sense in PUT? "Content-MD5", # "Content-Range", # FIXME: Need to implement this "Content-Type", ): log.error("Client sent unrecognized content header in PUT request: %s" % (header,)) raise HTTPError(StatusResponse( responsecode.NOT_IMPLEMENTED, "Unrecognized content header %r in request." % (header,) ))
def preconditions_PUT(self, request): # # Check authentication and access controls # if self.exists(): x = waitForDeferred(self.authorize(request, (davxml.WriteContent(), ))) yield x x.getResult() else: parent = waitForDeferred( request.locateResource(parentForURL(request.uri))) yield parent parent = parent.getResult() if not parent.exists(): raise HTTPError( StatusResponse(responsecode.CONFLICT, "cannot PUT to non-existent parent")) x = waitForDeferred(parent.authorize(request, (davxml.Bind(), ))) yield x x.getResult() # # HTTP/1.1 (RFC 2068, section 9.6) requires that we respond with a Not # Implemented error if we get a Content-* header which we don't # recognize and handle properly. # for header, _ignore_value in request.headers.getAllRawHeaders(): if header.startswith("Content-") and header not in ( # "Content-Base", # Doesn't make sense in PUT? # "Content-Encoding", # Requires that we decode it? "Content-Language", "Content-Length", # "Content-Location", # Doesn't make sense in PUT? "Content-MD5", # "Content-Range", # FIXME: Need to implement this "Content-Type", ): log.error( "Client sent unrecognized content header in PUT request: %s" % (header, )) raise HTTPError( StatusResponse( responsecode.NOT_IMPLEMENTED, "Unrecognized content header %r in request." % (header, )))
def http_ACL(self, request): # # Override base ACL request handling to ensure that the calendar/address book # homes cannot have ACL's set, and calendar/address object resources too. # if self.exists(): if isinstance(self, CalendarHomeResource) or isinstance(self, AddressBookHomeResource): raise HTTPError(responsecode.NOT_ALLOWED) parentURL = parentForURL(request.uri) parent = (yield request.locateResource(parentURL)) if isPseudoCalendarCollectionResource(parent) or isAddressBookCollectionResource(parent): raise HTTPError(responsecode.NOT_ALLOWED) # Do normal ACL behavior response = (yield super(CalDAVResource, self).http_ACL(request)) returnValue(response)
def http_MKCALENDAR(self, request): """ Respond to a MKCALENDAR request. (CalDAV-access-09, section 5.3.1) """ # # Check authentication and access controls # parent = (yield request.locateResource(parentForURL(request.uri))) yield parent.authorize(request, (davxml.Bind(), )) if self.exists(): log.error("Attempt to create collection where resource exists: %s" % (self, )) raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, (davxml.dav_namespace, "resource-must-be-null"), "Resource already exists", )) if not parent.isCollection(): log.error( "Attempt to create collection with non-collection parent: %s" % (self, )) raise HTTPError( ErrorResponse( responsecode.CONFLICT, (caldavxml.caldav_namespace, "calendar-collection-location-ok"), "Cannot create calendar inside another calendar", )) # # Read request body # try: doc = (yield davXMLFromStream(request.stream)) yield self.createCalendar(request) except ValueError, e: log.error("Error while handling MKCALENDAR: %s" % (e, )) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
def http_ACL(self, request): # # Override base ACL request handling to ensure that the calendar/address book # homes cannot have ACL's set, and calendar/address object resources too. # if self.exists(): if isinstance(self, CalendarHomeResource) or isinstance( self, AddressBookHomeResource): raise HTTPError(responsecode.NOT_ALLOWED) parentURL = parentForURL(request.uri) parent = (yield request.locateResource(parentURL)) if isPseudoCalendarCollectionResource( parent) or isAddressBookCollectionResource(parent): raise HTTPError(responsecode.NOT_ALLOWED) # Do normal ACL behavior response = (yield super(CalDAVResource, self).http_ACL(request)) returnValue(response)
def http_MKCALENDAR(self, request): """ Respond to a MKCALENDAR request. (CalDAV-access-09, section 5.3.1) """ # # Check authentication and access controls # parent = (yield request.locateResource(parentForURL(request.uri))) yield parent.authorize(request, (davxml.Bind(),)) if self.exists(): log.error("Attempt to create collection where resource exists: %s" % (self,)) raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, (davxml.dav_namespace, "resource-must-be-null"), "Resource already exists" ) ) if not parent.isCollection(): log.error("Attempt to create collection with non-collection parent: %s" % (self,)) raise HTTPError( ErrorResponse( responsecode.CONFLICT, (caldavxml.caldav_namespace, "calendar-collection-location-ok"), "Cannot create calendar inside another calendar", ) ) # # Read request body # try: doc = (yield davXMLFromStream(request.stream)) yield self.createCalendar(request) except ValueError, e: log.error("Error while handling MKCALENDAR: %s" % (e,)) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
def http_DELETE(self, request): """ Respond to a DELETE request. (RFC 2518, section 8.6) """ if not self.exists(): log.error("File not found: %s" % (self,)) raise HTTPError(responsecode.NOT_FOUND) depth = request.headers.getHeader("depth", "infinity") # # Check authentication and access controls # parent = waitForDeferred(request.locateResource(parentForURL(request.uri))) yield parent parent = parent.getResult() x = waitForDeferred(parent.authorize(request, (davxml.Unbind(),))) yield x x.getResult() x = waitForDeferred(deleteResource(request, self, request.uri, depth)) yield x yield x.getResult()
def http_DELETE(self, request): """ Respond to a DELETE request. (RFC 2518, section 8.6) """ if not self.exists(): log.error("File not found: %s" % (self, )) raise HTTPError(responsecode.NOT_FOUND) depth = request.headers.getHeader("depth", "infinity") # # Check authentication and access controls # parent = waitForDeferred(request.locateResource(parentForURL(request.uri))) yield parent parent = parent.getResult() x = waitForDeferred(parent.authorize(request, (davxml.Unbind(), ))) yield x x.getResult() x = waitForDeferred(deleteResource(request, self, request.uri, depth)) yield x yield x.getResult()
def http_GET(self, request): if self.exists(): # Special sharing request on a calendar or address book if self.isCalendarCollection() or self.isAddressBookCollection(): # Check for action=share if request.args: action = request.args.get("action", ("",)) if len(action) != 1: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-action"), "Invalid action parameter: %s" % (action,), )) action = action[0] dispatch = { "share" : self.directShare, }.get(action, None) if dispatch is None: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "supported-action"), "Action not supported: %s" % (action,), )) response = (yield dispatch(request)) returnValue(response) else: # FIXME: this should be implemented in storebridge.CalendarObject.render # Look for calendar access restriction on existing resource. parentURL = parentForURL(request.uri) parent = (yield request.locateResource(parentURL)) if isPseudoCalendarCollectionResource(parent): # Check authorization first yield self.authorize(request, (davxml.Read(),)) # Accept header handling accepted_type = bestAcceptType(request.headers.getHeader("accept"), Component.allowedTypes()) if accepted_type is None: raise HTTPError(StatusResponse(responsecode.NOT_ACCEPTABLE, "Cannot generate requested data type")) caldata = (yield self.iCalendarForUser(request)) # Filter any attendee hidden instances caldata = HiddenInstanceFilter().filter(caldata) if self.accessMode: # Non DAV:owner's have limited access to the data isowner = (yield self.isOwner(request)) # Now "filter" the resource calendar data caldata = PrivateEventFilter(self.accessMode, isowner).filter(caldata) response = Response() response.stream = MemoryStream(caldata.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference, format=accepted_type)) response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (accepted_type,))) # Add Schedule-Tag header if property is present if self.scheduleTag: response.headers.setHeader("Schedule-Tag", self.scheduleTag) returnValue(response) # Do normal GET behavior response = (yield super(CalDAVResource, self).http_GET(request)) returnValue(response)
def http_MOVE(self, request): """ Respond to a MOVE request. (RFC 2518, section 8.9) """ r = waitForDeferred(prepareForCopy(self, request)) yield r r = r.getResult() destination, destination_uri, depth = r # # Check authentication and access controls # parentURL = parentForURL(request.uri) parent = waitForDeferred(request.locateResource(parentURL)) yield parent parent = parent.getResult() x = waitForDeferred(parent.authorize(request, (davxml.Unbind(),))) yield x x.getResult() if destination.exists(): x = waitForDeferred(destination.authorize( request, (davxml.Bind(), davxml.Unbind()), recurse=True )) yield x x.getResult() else: destparentURL = parentForURL(destination_uri) destparent = waitForDeferred(request.locateResource(destparentURL)) yield destparent destparent = destparent.getResult() x = waitForDeferred(destparent.authorize(request, (davxml.Bind(),))) yield x x.getResult() # May need to add a location header addLocation(request, destination_uri) # # RFC 2518, section 8.9 says that we must act as if the Depth header is set # to infinity, and that the client must omit the Depth header or set it to # infinity. # # This seems somewhat at odds with the notion that a bad request should be # rejected outright; if the client sends a bad depth header, the client is # broken, and section 8 suggests that a bad request should be rejected... # # Let's play it safe for now and ignore broken clients. # if self.isCollection() and depth != "infinity": msg = "Client sent illegal depth header value for MOVE: %s" % (depth,) log.error(msg) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg)) # Lets optimise a move within the same directory to a new resource as a simple move # rather than using the full transaction based storeResource api. This allows simple # "rename" operations to work quickly. if (not destination.exists()) and destparent == parent: x = waitForDeferred(move(self.fp, request.uri, destination.fp, destination_uri, depth)) else: x = waitForDeferred(put_common.storeResource(request, source=self, source_uri=request.uri, destination=destination, destination_uri=destination_uri, deletesource=True, depth=depth)) yield x yield x.getResult()
def test_parentForURL(self): """ parentForURL() """ self.assertEquals(util.parentForURL("http://server/"), None) self.assertEquals(util.parentForURL("http://server//"), None) self.assertEquals(util.parentForURL("http://server/foo/.."), None) self.assertEquals(util.parentForURL("http://server/foo/../"), None) self.assertEquals(util.parentForURL("http://server/foo/."), "http://server/") self.assertEquals(util.parentForURL("http://server/foo/./"), "http://server/") self.assertEquals(util.parentForURL("http://server/foo"), "http://server/") self.assertEquals(util.parentForURL("http://server//foo"), "http://server/") self.assertEquals(util.parentForURL("http://server/foo/bar/.."), "http://server/") self.assertEquals(util.parentForURL("http://server/foo/bar/."), "http://server/foo/") self.assertEquals(util.parentForURL("http://server/foo/bar"), "http://server/foo/") self.assertEquals(util.parentForURL("http://server/foo/bar/"), "http://server/foo/") self.assertEquals(util.parentForURL("http://server/foo/bar?x=1&y=2"), "http://server/foo/") self.assertEquals(util.parentForURL("http://server/foo/bar/?x=1&y=2"), "http://server/foo/") self.assertEquals(util.parentForURL("/"), None) self.assertEquals(util.parentForURL("/foo/.."), None) self.assertEquals(util.parentForURL("/foo/../"), None) self.assertEquals(util.parentForURL("/foo/."), "/") self.assertEquals(util.parentForURL("/foo/./"), "/") self.assertEquals(util.parentForURL("/foo"), "/") self.assertEquals(util.parentForURL("/foo"), "/") self.assertEquals(util.parentForURL("/foo/bar/.."), "/") self.assertEquals(util.parentForURL("/foo/bar/."), "/foo/") self.assertEquals(util.parentForURL("/foo/bar"), "/foo/") self.assertEquals(util.parentForURL("/foo/bar/"), "/foo/") self.assertEquals(util.parentForURL("/foo/bar?x=1&y=2"), "/foo/") self.assertEquals(util.parentForURL("/foo/bar/?x=1&y=2"), "/foo/")
def http_MKCOL(self, request): # # Check authentication and access controls # parent = (yield request.locateResource(parentForURL(request.uri))) yield parent.authorize(request, (davxml.Bind(), )) if self.exists(): log.error("Attempt to create collection where resource exists: {s!r}", s=self) raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, (davxml.dav_namespace, "resource-must-be-null"), "Resource already exists", )) if not parent.isCollection(): log.error( "Attempt to create collection with non-collection parent: {s!r}", s=self) raise HTTPError( ErrorResponse( responsecode.CONFLICT, (davxml.dav_namespace, "collection-location-ok"), "Cannot create calendar inside another calendar", )) # # Don't allow DAV collections in a calendar or address book collection # if config.EnableCalDAV: parent = (yield self._checkParents(request, isPseudoCalendarCollectionResource)) if parent is not None: raise HTTPError( StatusResponse( responsecode.FORBIDDEN, "Cannot create collection within calendar collection %s" % (parent, ))) if config.EnableCardDAV: parent = (yield self._checkParents(request, isAddressBookCollectionResource)) if parent is not None: raise HTTPError( StatusResponse( responsecode.FORBIDDEN, "Cannot create collection within address book collection %s" % (parent, ))) # # Read request body # try: doc = (yield davXMLFromStream(request.stream)) except ValueError, e: log.error("Error while handling MKCOL: {ex}", ex=e) # TODO: txweb2.dav 'MKCOL' tests demand this particular response # code, but should we really be looking at the XML content or the # content-type header? It seems to me like this ought to be considered # a BAD_REQUEST if it claims to be XML but isn't, but an # UNSUPPORTED_MEDIA_TYPE if it claims to be something else. -glyph raise HTTPError( StatusResponse(responsecode.UNSUPPORTED_MEDIA_TYPE, str(e)))
def report_DAV__expand_property(self, request, expand_property): """ Generate an expand-property REPORT. (RFC 3253, section 3.8) TODO: for simplicity we will only support one level of expansion. """ # Verify root element if not isinstance(expand_property, element.ExpandProperty): raise ValueError( "%s expected as root element, not %s." % (element.ExpandProperty.sname(), expand_property.sname())) # Only handle Depth: 0 depth = request.headers.getHeader("depth", "0") if depth != "0": log.error("Non-zero depth is not allowed: %s" % (depth, )) raise HTTPError( StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth, ))) # # Get top level properties to expand and make sure we only have one level # properties = {} for property in expand_property.children: namespace = property.attributes.get("namespace", dav_namespace) name = property.attributes.get("name", "") # Make sure children have no children props_to_find = [] for child in property.children: if child.children: log.error( "expand-property REPORT only supports single level expansion" ) raise HTTPError( StatusResponse( responsecode.NOT_IMPLEMENTED, "expand-property REPORT only supports single level expansion" )) child_namespace = child.attributes.get("namespace", dav_namespace) child_name = child.attributes.get("name", "") props_to_find.append((child_namespace, child_name)) properties[(namespace, name)] = props_to_find # # Generate the expanded responses status for each top-level property # properties_by_status = { responsecode.OK: [], responsecode.NOT_FOUND: [], } filteredaces = None lastParent = None for qname in properties.iterkeys(): try: prop = (yield self.readProperty(qname, request)) # Form the PROPFIND-style DAV:prop element we need later props_to_return = element.PropertyContainer(*properties[qname]) # Now dereference any HRefs responses = [] for href in prop.children: if isinstance(href, element.HRef): # Locate the Href resource and its parent resource_uri = str(href) child = (yield request.locateResource(resource_uri)) if not child or not child.exists(): responses.append( element.StatusResponse( href, element.Status.fromResponseCode( responsecode.NOT_FOUND))) continue parent = (yield request.locateResource( parentForURL(resource_uri))) # Check privileges on parent - must have at least DAV:read try: yield parent.checkPrivileges(request, (element.Read(), )) except AccessDeniedError: responses.append( element.StatusResponse( href, element.Status.fromResponseCode( responsecode.FORBIDDEN))) continue # Cache the last parent's inherited aces for checkPrivileges optimization if lastParent != parent: lastParent = parent # Do some optimisation of access control calculation by determining any inherited ACLs outside of # the child resource loop and supply those to the checkPrivileges on each child. filteredaces = ( yield parent.inheritedACEsforChildren(request)) # Check privileges - must have at least DAV:read try: yield child.checkPrivileges( request, (element.Read(), ), inherited_aces=filteredaces) except AccessDeniedError: responses.append( element.StatusResponse( href, element.Status.fromResponseCode( responsecode.FORBIDDEN))) continue # Now retrieve all the requested properties on the HRef resource yield prop_common.responseForHref( request, responses, href, child, prop_common.propertyListForResource, props_to_return, ) prop.children = responses properties_by_status[responsecode.OK].append(prop) except: f = Failure() log.error( "Error reading property {qname} for resource {req}: {failure}", qname=qname, req=request.uri, failure=f.value) status = statusForFailure(f, "getting property: %s" % (qname, )) if status not in properties_by_status: properties_by_status[status] = [] properties_by_status[status].append(propertyName(qname)) # Build the overall response propstats = [ element.PropertyStatus( element.PropertyContainer(*properties_by_status[pstatus]), element.Status.fromResponseCode(pstatus)) for pstatus in properties_by_status if properties_by_status[pstatus] ] returnValue( MultiStatusResponse( (element.PropertyStatusResponse(element.HRef(request.uri), *propstats), )))
def http_MOVE(self, request): """ Respond to a MOVE request. (RFC 2518, section 8.9) """ r = waitForDeferred(prepareForCopy(self, request)) yield r r = r.getResult() destination, destination_uri, depth = r # # Check authentication and access controls # parentURL = parentForURL(request.uri) parent = waitForDeferred(request.locateResource(parentURL)) yield parent parent = parent.getResult() x = waitForDeferred(parent.authorize(request, (davxml.Unbind(), ))) yield x x.getResult() if destination.exists(): x = waitForDeferred( destination.authorize(request, (davxml.Bind(), davxml.Unbind()), recurse=True)) yield x x.getResult() else: destparentURL = parentForURL(destination_uri) destparent = waitForDeferred(request.locateResource(destparentURL)) yield destparent destparent = destparent.getResult() x = waitForDeferred(destparent.authorize(request, (davxml.Bind(), ))) yield x x.getResult() # May need to add a location header addLocation(request, destination_uri) # # RFC 2518, section 8.9 says that we must act as if the Depth header is set # to infinity, and that the client must omit the Depth header or set it to # infinity. # # This seems somewhat at odds with the notion that a bad request should be # rejected outright; if the client sends a bad depth header, the client is # broken, and section 8 suggests that a bad request should be rejected... # # Let's play it safe for now and ignore broken clients. # if self.isCollection() and depth != "infinity": msg = "Client sent illegal depth header value for MOVE: %s" % (depth, ) log.error(msg) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg)) # Lets optimise a move within the same directory to a new resource as a simple move # rather than using the full transaction based storeResource api. This allows simple # "rename" operations to work quickly. if (not destination.exists()) and destparent == parent: x = waitForDeferred( move(self.fp, request.uri, destination.fp, destination_uri, depth)) else: x = waitForDeferred( put_common.storeResource(request, source=self, source_uri=request.uri, destination=destination, destination_uri=destination_uri, deletesource=True, depth=depth)) yield x yield x.getResult()
def report_DAV__expand_property(self, request, expand_property): """ Generate an expand-property REPORT. (RFC 3253, section 3.8) TODO: for simplicity we will only support one level of expansion. """ # Verify root element if not isinstance(expand_property, element.ExpandProperty): raise ValueError("%s expected as root element, not %s." % (element.ExpandProperty.sname(), expand_property.sname())) # Only handle Depth: 0 depth = request.headers.getHeader("depth", "0") if depth != "0": log.error("Non-zero depth is not allowed: %s" % (depth,)) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,))) # # Get top level properties to expand and make sure we only have one level # properties = {} for property in expand_property.children: namespace = property.attributes.get("namespace", dav_namespace) name = property.attributes.get("name", "") # Make sure children have no children props_to_find = [] for child in property.children: if child.children: log.error("expand-property REPORT only supports single level expansion") raise HTTPError(StatusResponse( responsecode.NOT_IMPLEMENTED, "expand-property REPORT only supports single level expansion" )) child_namespace = child.attributes.get("namespace", dav_namespace) child_name = child.attributes.get("name", "") props_to_find.append((child_namespace, child_name)) properties[(namespace, name)] = props_to_find # # Generate the expanded responses status for each top-level property # properties_by_status = { responsecode.OK : [], responsecode.NOT_FOUND : [], } filteredaces = None lastParent = None for qname in properties.iterkeys(): try: prop = (yield self.readProperty(qname, request)) # Form the PROPFIND-style DAV:prop element we need later props_to_return = element.PropertyContainer(*properties[qname]) # Now dereference any HRefs responses = [] for href in prop.children: if isinstance(href, element.HRef): # Locate the Href resource and its parent resource_uri = str(href) child = (yield request.locateResource(resource_uri)) if not child or not child.exists(): responses.append(element.StatusResponse(href, element.Status.fromResponseCode(responsecode.NOT_FOUND))) continue parent = (yield request.locateResource(parentForURL(resource_uri))) # Check privileges on parent - must have at least DAV:read try: yield parent.checkPrivileges(request, (element.Read(),)) except AccessDeniedError: responses.append(element.StatusResponse(href, element.Status.fromResponseCode(responsecode.FORBIDDEN))) continue # Cache the last parent's inherited aces for checkPrivileges optimization if lastParent != parent: lastParent = parent # Do some optimisation of access control calculation by determining any inherited ACLs outside of # the child resource loop and supply those to the checkPrivileges on each child. filteredaces = (yield parent.inheritedACEsforChildren(request)) # Check privileges - must have at least DAV:read try: yield child.checkPrivileges(request, (element.Read(),), inherited_aces=filteredaces) except AccessDeniedError: responses.append(element.StatusResponse(href, element.Status.fromResponseCode(responsecode.FORBIDDEN))) continue # Now retrieve all the requested properties on the HRef resource yield prop_common.responseForHref( request, responses, href, child, prop_common.propertyListForResource, props_to_return, ) prop.children = responses properties_by_status[responsecode.OK].append(prop) except: f = Failure() log.error( "Error reading property {qname} for resource {req}: {failure}", qname=qname, req=request.uri, failure=f.value ) status = statusForFailure(f, "getting property: %s" % (qname,)) if status not in properties_by_status: properties_by_status[status] = [] properties_by_status[status].append(propertyName(qname)) # Build the overall response propstats = [ element.PropertyStatus( element.PropertyContainer(*properties_by_status[pstatus]), element.Status.fromResponseCode(pstatus) ) for pstatus in properties_by_status if properties_by_status[pstatus] ] returnValue(MultiStatusResponse((element.PropertyStatusResponse(element.HRef(request.uri), *propstats),)))
def http_MKCOL(self, request): # # Check authentication and access controls # parent = (yield request.locateResource(parentForURL(request.uri))) yield parent.authorize(request, (davxml.Bind(),)) if self.exists(): log.error("Attempt to create collection where resource exists: %s" % (self,)) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (davxml.dav_namespace, "resource-must-be-null"), "Resource already exists", )) if not parent.isCollection(): log.error("Attempt to create collection with non-collection parent: %s" % (self,)) raise HTTPError(ErrorResponse( responsecode.CONFLICT, (davxml.dav_namespace, "collection-location-ok"), "Cannot create calendar inside another calendar", )) # # Don't allow DAV collections in a calendar or address book collection # if config.EnableCalDAV: parent = (yield self._checkParents(request, isPseudoCalendarCollectionResource)) if parent is not None: raise HTTPError(StatusResponse( responsecode.FORBIDDEN, "Cannot create collection within calendar collection %s" % (parent,) )) if config.EnableCardDAV: parent = (yield self._checkParents(request, isAddressBookCollectionResource)) if parent is not None: raise HTTPError(StatusResponse( responsecode.FORBIDDEN, "Cannot create collection within address book collection %s" % (parent,) )) # # Read request body # try: doc = (yield davXMLFromStream(request.stream)) except ValueError, e: log.error("Error while handling MKCOL: %s" % (e,)) # TODO: txweb2.dav 'MKCOL' tests demand this particular response # code, but should we really be looking at the XML content or the # content-type header? It seems to me like this ought to be considered # a BAD_REQUEST if it claims to be XML but isn't, but an # UNSUPPORTED_MEDIA_TYPE if it claims to be something else. -glyph raise HTTPError( StatusResponse(responsecode.UNSUPPORTED_MEDIA_TYPE, str(e)) )
def http_GET(self, request): if self.exists(): # Special sharing request on a calendar or address book if self.isCalendarCollection() or self.isAddressBookCollection(): # Check for action=share if request.args: action = request.args.get("action", ("",)) if len(action) != 1: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-action"), "Invalid action parameter: %s" % (action,), )) action = action[0] dispatch = { "share": self.directShare, }.get(action, None) if dispatch is None: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "supported-action"), "Action not supported: %s" % (action,), )) response = (yield dispatch(request)) returnValue(response) else: # FIXME: this should be implemented in storebridge.CalendarObject.render # Look for calendar access restriction on existing resource. parentURL = parentForURL(request.uri) parent = (yield request.locateResource(parentURL)) if isPseudoCalendarCollectionResource(parent): # Check authorization first yield self.authorize(request, (davxml.Read(),)) # Accept header handling accepted_type = bestAcceptType(request.headers.getHeader("accept"), Component.allowedTypes()) if accepted_type is None: raise HTTPError(StatusResponse(responsecode.NOT_ACCEPTABLE, "Cannot generate requested data type")) caldata = (yield self.componentForUser()) # Filter any attendee hidden instances caldata = HiddenInstanceFilter().filter(caldata) if self.accessMode: # Non DAV:owner's have limited access to the data isowner = (yield self.isOwner(request)) # Now "filter" the resource calendar data caldata = PrivateEventFilter(self.accessMode, isowner).filter(caldata) response = Response() response.stream = MemoryStream(caldata.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference, format=accepted_type)) response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (accepted_type,))) # Add Schedule-Tag header if property is present if self.scheduleTag: response.headers.setHeader("Schedule-Tag", self.scheduleTag) returnValue(response) # Do normal GET behavior response = (yield super(CalDAVResource, self).http_GET(request)) returnValue(response)