示例#1
0
 def test_statusForFailure_HTTPError(self):
     """
     statusForFailure() for HTTPErrors
     """
     for code in responsecode.RESPONSES:
         self._check_exception(HTTPError(code), code)
         self._check_exception(HTTPError(ErrorResponse(code, ("http://twistedmatrix.com/", "bar"))), code)
示例#2
0
def _prepareForCopy(destination, destination_uri, request, depth):
    #
    # Destination must be a DAV resource
    #

    try:
        destination = IDAVResource(destination)
    except TypeError:
        log.err("Attempt to %s to a non-DAV resource: (%s) %s" %
                (request.method, destination.__class__, destination_uri))
        raise HTTPError(
            StatusResponse(
                responsecode.FORBIDDEN,
                "Destination %s is not a WebDAV resource." %
                (destination_uri, )))

    #
    # FIXME: Right now we don't know how to copy to a non-DAVFile resource.
    # We may need some more API in IDAVResource.
    # So far, we need: .exists(), .fp.parent()
    #
    import OPSI.web2.dav.static  # Lazy import to avoid circular dependency

    if not isinstance(destination, OPSI.web2.dav.static.DAVFile):
        log.err("DAV copy between non-DAVFile DAV resources isn't implemented")
        raise HTTPError(
            StatusResponse(
                responsecode.NOT_IMPLEMENTED,
                "Destination %s is not a DAVFile resource." %
                (destination_uri, )))

    #
    # Check for existing destination resource
    #

    overwrite = request.headers.getHeader("overwrite", True)

    if destination.exists() and not overwrite:
        log.err(
            "Attempt to %s onto existing file without overwrite flag enabled: %s"
            % (request.method, destination.fp.path))
        raise HTTPError(
            StatusResponse(
                responsecode.PRECONDITION_FAILED,
                "Destination %s already exists." % (destination_uri, )))

    #
    # Make sure destination's parent exists
    #

    if not destination.fp.parent().isdir():
        log.err("Attempt to %s to a resource with no parent: %s" %
                (request.method, destination.fp.path))
        raise HTTPError(
            StatusResponse(responsecode.CONFLICT, "No parent collection."))

    return destination, destination_uri, depth
示例#3
0
        def defer():
            if property.protected:
                raise HTTPError(StatusResponse(
                    responsecode.FORBIDDEN,
                    "Protected property %s may not be set." % (property.sname(),)
                ))

            if property.namespace == twisted_private_namespace:
                raise HTTPError(StatusResponse(
                    responsecode.FORBIDDEN,
                    "Properties in the %s namespace are private to the server." % (property.sname(),)
                ))

            return self.deadProperties().set(property)
示例#4
0
    def doMove(r):
        destination, destination_uri, depth = r

        #
        # 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.fp.isdir() and depth != "infinity":
            msg = "Client sent illegal depth header value for MOVE: %s" % (
                depth, )
            log.err(msg)
            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))

        # May need to add a location header
        addLocation(request, destination_uri)

        return move(self.fp, request.uri, destination.fp, destination_uri,
                    depth)
示例#5
0
            def do(action, property):
                """
                Perform action(property, request) while maintaining an
                undo queue.
                """
                has = waitForDeferred(self.hasProperty(property, request))
                yield has
                has = has.getResult()

                if has:
                    oldProperty = waitForDeferred(
                        self.readProperty(property, request))
                    yield oldProperty
                    oldProperty.getResult()

                    def undo():
                        return self.writeProperty(oldProperty, request)
                else:

                    def undo():
                        return self.removeProperty(property, request)

                try:
                    x = waitForDeferred(action(property, request))
                    yield x
                    x.getResult()
                except ValueError, e:
                    # Convert ValueError exception into HTTPError
                    responses.add(
                        Failure(exc_value=HTTPError(
                            StatusResponse(responsecode.FORBIDDEN, str(e)))),
                        property)
                    yield False
                    return
示例#6
0
def http_REPORT(self, request):
    """
    Respond to a REPORT request. (RFC 3253, section 3.6)
    """
    if not self.fp.exists():
        log.err("File not found: %s" % (self.fp.path,))
        raise HTTPError(responsecode.NOT_FOUND)

    #
    # Read request body
    #
    try:
        doc = waitForDeferred(davXMLFromStream(request.stream))
        yield doc
        doc = doc.getResult()
    except ValueError, e:
        log.err("Error while handling REPORT body: %s" % (e,))
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
示例#7
0
def http_DELETE(self, request):
    """
    Respond to a DELETE request. (RFC 2518, section 8.6)
    """
    if not self.fp.exists():
        log.err("File not found: %s" % (self.fp.path, ))
        raise HTTPError(responsecode.NOT_FOUND)

    depth = request.headers.getHeader("depth", "infinity")

    return delete(request.uri, self.fp, depth)
示例#8
0
def preconditions_PUT(self, request):
    if self.fp.exists():
        if not self.fp.isfile():
            log.err("Unable to PUT to non-file: %s" % (self.fp.path, ))
            raise HTTPError(
                StatusResponse(
                    responsecode.FORBIDDEN,
                    "The requested resource exists but is not backed by a regular file."
                ))
        resource_is_new = False
    else:
        if not self.fp.parent().isdir():
            log.err("No such directory: %s" % (self.fp.path, ))
            raise HTTPError(
                StatusResponse(responsecode.CONFLICT,
                               "Parent collection resource does not exist."))
        resource_is_new = True

    #
    # 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, 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.err(
                "Client sent unrecognized content header in PUT request: %s" %
                (header, ))
            raise HTTPError(
                StatusResponse(
                    responsecode.NOT_IMPLEMENTED,
                    "Unrecognized content header %r in request." % (header, )))
示例#9
0
    def get(self, qname):
        try:
            value = self.attrs[self._encode(qname)]
        except KeyError:
            raise HTTPError(StatusResponse(
                responsecode.NOT_FOUND,
                "No such property: {%s}%s" % qname
            ))

        doc = davxml.WebDAVDocument.fromString(value)

        return doc.root_element
示例#10
0
        def defer():
            if type(property) is tuple:
                qname = property
                sname = "{%s}%s" % property
            else:
                qname = property.qname()
                sname = property.sname()

            if qname in self.liveProperties:
                raise HTTPError(StatusResponse(
                    responsecode.FORBIDDEN,
                    "Live property %s cannot be deleted." % (sname,)
                ))

            if qname[0] == twisted_private_namespace:
                raise HTTPError(StatusResponse(
                    responsecode.FORBIDDEN,
                    "Properties in the %s namespace are private to the server." % (sname,)
                ))

            return self.deadProperties().delete(qname)
示例#11
0
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()

    if self.fp.exists():
        log.err("Attempt to create collection where file exists: %s"
                % (self.fp.path,))
        raise HTTPError(responsecode.NOT_ALLOWED)

    if not parent.isCollection():
        log.err("Attempt to create collection with non-collection parent: %s"
                % (self.fp.path,))
        raise HTTPError(StatusResponse(
            responsecode.CONFLICT,
            "Parent resource is not a collection."
        ))

    if not self.fp.parent().isdir():
        log.err("Attempt to create collection with no parent directory: %s"
                % (self.fp.path,))
        raise HTTPError(StatusResponse(
            responsecode.INTERNAL_SERVER_ERROR,
            "The requested resource is not backed by a parent directory."
        ))

    #
    # Read request body
    #
    x = waitForDeferred(noDataFromStream(request.stream))
    yield x
    try:
        x.getResult()
    except ValueError, e:
        log.err("Error while handling MKCOL body: %s" % (e,))
        raise HTTPError(responsecode.UNSUPPORTED_MEDIA_TYPE)
示例#12
0
def prepareForCopy(self, request):
    #
    # Get the depth
    #

    depth = request.headers.getHeader("depth", "infinity")

    if depth not in ("0", "infinity"):
        msg = ("Client sent illegal depth header value: %s" % (depth, ))
        log.err(msg)
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))

    #
    # Verify this resource exists
    #

    if not self.exists():
        log.err("File not found: %s" % (self.fp.path, ))
        raise HTTPError(
            StatusResponse(responsecode.NOT_FOUND,
                           "Source resource %s not found." % (request.uri, )))

    #
    # Get the destination
    #

    destination_uri = request.headers.getHeader("destination")

    if not destination_uri:
        msg = "No destination header in %s request." % (request.method, )
        log.err(msg)
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))

    d = request.locateResource(destination_uri)
    d.addCallback(_prepareForCopy, destination_uri, request, depth)

    return d
示例#13
0
def mkcollection(filepath):
    """
    Perform a X{MKCOL} on the given filepath.
    @param filepath: the L{FilePath} of the collection resource to create.
    @raise HTTPError: (containing an appropriate response) if the operation
        fails.
    @return: a deferred response with a status code of L{responsecode.CREATED}
        if the destination already exists, or L{responsecode.NO_CONTENT} if the
        destination was created by the X{MKCOL} operation.
    """
    try:
        os.mkdir(filepath.path)
        # Restat filepath because we modified it
        filepath.restat(False)
    except:
        raise HTTPError(
            statusForFailure(
                Failure(),
                "creating directory in MKCOL: %s" % (filepath.path, )))

    return succeed(responsecode.CREATED)
示例#14
0
        def defer():
            if type(property) is tuple:
                qname = property
                sname = "{%s}%s" % property
            else:
                qname = property.qname()
                sname = property.sname()

            namespace, name = qname

            if namespace == dav_namespace:
                if name == "resourcetype":
                    # Allow live property to be overriden by dead property
                    if self.deadProperties().contains(qname):
                        return self.deadProperties().get(qname)
                    if self.isCollection():
                        return davxml.ResourceType.collection
                    return davxml.ResourceType.empty

                if name == "getetag":
                    return davxml.GETETag(self.etag().generate())

                if name == "getcontenttype":
                    mimeType = self.contentType()
                    mimeType.params = None # WebDAV getcontenttype property does not include parameters
                    return davxml.GETContentType(generateContentType(mimeType))

                if name == "getcontentlength":
                    return davxml.GETContentLength(str(self.contentLength()))

                if name == "getlastmodified":
                    return davxml.GETLastModified.fromDate(self.lastModified())

                if name == "creationdate":
                    return davxml.CreationDate.fromDate(self.creationDate())

                if name == "displayname":
                    return davxml.DisplayName(self.displayName())

                if name == "supportedlock":
                    return davxml.SupportedLock(
                        davxml.LockEntry(davxml.LockScope.exclusive, davxml.LockType.write),
                        davxml.LockEntry(davxml.LockScope.shared   , davxml.LockType.write),
                    )

                if name == "acl-restrictions":
                    return davxml.ACLRestrictions()

            if namespace == twisted_dav_namespace:
                if name == "resource-class":
                    class ResourceClass (davxml.WebDAVTextElement):
                        namespace = twisted_dav_namespace
                        name = "resource-class"
                        hidden = False
                    return ResourceClass(self.__class__.__name__)

            if namespace == twisted_private_namespace:
                raise HTTPError(StatusResponse(
                    responsecode.FORBIDDEN,
                    "Properties in the %s namespace are private to the server." % (sname,)
                ))

            return self.deadProperties().get(qname)
示例#15
0
def copy(source_filepath, destination_filepath, destination_uri, depth):
    """
    Perform a X{COPY} from the given source and destination filepaths.
    This will perform a X{DELETE} on the destination if necessary; the caller
    should check and handle the X{overwrite} header before calling L{copy} (as
    in L{COPYMOVE.prepareForCopy}).
    @param source_filepath: a L{FilePath} for the file to copy from.
    @param destination_filepath: a L{FilePath} for the file to copy to.
    @param destination_uri: the URI of the destination resource.
    @param depth: the recursion X{Depth} for the X{COPY} operation, which must
        be one of "0", "1", or "infinity".
    @raise HTTPError: (containing a response with a status code of
        L{responsecode.BAD_REQUEST}) if C{depth} is not "0", "1" or "infinity".
    @raise HTTPError: (containing an appropriate response) if the operation
        fails.  If C{source_filepath} is a directory, the response will be a
        L{MultiStatusResponse}.
    @return: a deferred response with a status code of L{responsecode.CREATED}
        if the destination already exists, or L{responsecode.NO_CONTENT} if the
        destination was created by the X{COPY} operation.
    """
    if source_filepath.isfile():
        #
        # Copy the file
        #
        log.msg("Copying file %s to %s" %
                (source_filepath.path, destination_filepath.path))

        try:
            source_file = source_filepath.open()
        except:
            raise HTTPError(
                statusForFailure(
                    Failure(),
                    "opening file for reading: %s" % (source_filepath.path, )))

        source_stream = FileStream(source_file)
        response = waitForDeferred(
            put(source_stream, destination_filepath, destination_uri))
        yield response
        try:
            response = response.getResult()
        finally:
            source_stream.close()
            source_file.close()
        checkResponse(response, "put", responsecode.NO_CONTENT,
                      responsecode.CREATED)
        yield response
        return

    elif source_filepath.isdir():
        if destination_filepath.exists():
            #
            # Delete the destination
            #
            response = waitForDeferred(
                delete(destination_uri, destination_filepath))
            yield response
            response = response.getResult()
            checkResponse(response, "delete", responsecode.NO_CONTENT)
            success_code = responsecode.NO_CONTENT
        else:
            success_code = responsecode.CREATED

        #
        # Copy the directory
        #
        log.msg("Copying directory %s to %s" %
                (source_filepath.path, destination_filepath.path))

        source_basename = source_filepath.path
        destination_basename = destination_filepath.path

        errors = ResponseQueue(source_basename, "COPY", success_code)

        if destination_filepath.parent().isdir():
            if os.path.islink(source_basename):
                link_destination = os.readlink(source_basename)
                if link_destination[0] != os.path.sep:
                    link_destination = os.path.join(source_basename,
                                                    link_destination)
                try:
                    os.symlink(destination_basename, link_destination)
                except:
                    errors.add(source_basename, Failure())
            else:
                try:
                    os.mkdir(destination_basename)
                except:
                    raise HTTPError(
                        statusForFailure(
                            Failure(), "creating directory %s" %
                            (destination_basename, )))

                if depth == "0":
                    yield success_code
                    return
        else:
            raise HTTPError(
                StatusResponse(
                    responsecode.CONFLICT,
                    "Parent collection for destination %s does not exist" %
                    (destination_uri, )))

        #
        # Recursive copy
        #
        # FIXME: When we report errors, do we report them on the source URI
        # or on the destination URI?  We're using the source URI here.
        #
        # FIXME: defer the walk?

        source_basename_len = len(source_basename)

        def paths(basepath, subpath):
            source_path = os.path.join(basepath, subpath)
            assert source_path.startswith(source_basename)
            destination_path = os.path.join(
                destination_basename, source_path[source_basename_len + 1:])
            return source_path, destination_path

        for dir, subdirs, files in os.walk(source_filepath.path, topdown=True):
            for filename in files:
                source_path, destination_path = paths(dir, filename)
                if not os.path.isdir(os.path.dirname(destination_path)):
                    errors.add(source_path, responsecode.NOT_FOUND)
                else:
                    response = waitForDeferred(
                        copy(FilePath(source_path), FilePath(destination_path),
                             destination_uri, depth))
                    yield response
                    response = response.getResult()
                    checkResponse(response, "copy", responsecode.CREATED,
                                  responsecode.NO_CONTENT)

            for subdir in subdirs:
                source_path, destination_path = paths(dir, subdir)
                log.msg("Copying directory %s to %s" %
                        (source_path, destination_path))

                if not os.path.isdir(os.path.dirname(destination_path)):
                    errors.add(source_path, responsecode.CONFLICT)
                else:
                    if os.path.islink(source_path):
                        link_destination = os.readlink(source_path)
                        if link_destination[0] != os.path.sep:
                            link_destination = os.path.join(
                                source_path, link_destination)
                        try:
                            os.symlink(destination_path, link_destination)
                        except:
                            errors.add(source_path, Failure())
                    else:
                        try:
                            os.mkdir(destination_path)
                        except:
                            errors.add(source_path, Failure())

        yield errors.response()
        return
    else:
        log.err("Unable to COPY to non-file: %s" % (source_filepath.path, ))
        raise HTTPError(
            StatusResponse(
                responsecode.FORBIDDEN,
                "The requested resource exists but is not backed by a regular file."
            ))
示例#16
0
def put(stream, filepath, uri=None):
    """
    Perform a PUT of the given data stream into the given filepath.
    @param stream: the stream to write to the destination.
    @param filepath: the L{FilePath} of the destination file.
    @param uri: the URI of the destination resource.
        If the destination exists, if C{uri} is not C{None}, perform a
        X{DELETE} operation on the destination, but if C{uri} is C{None},
        delete the destination directly.
        Note that whether a L{put} deletes the destination directly vs.
        performing a X{DELETE} on the destination affects the response returned
        in the event of an error during deletion.  Specifically, X{DELETE}
        on collections must return a L{MultiStatusResponse} under certain
        circumstances, whereas X{PUT} isn't required to do so.  Therefore,
        if the caller expects X{DELETE} semantics, it must provide a valid
        C{uri}.
    @raise HTTPError: (containing an appropriate response) if the operation
        fails.
    @return: a deferred response with a status code of L{responsecode.CREATED}
        if the destination already exists, or L{responsecode.NO_CONTENT} if the
        destination was created by the X{PUT} operation.
    """
    log.msg("Writing to file %s" % (filepath.path, ))

    if filepath.exists():
        if uri is None:
            try:
                if filepath.isdir():
                    rmdir(filepath.path)
                else:
                    os.remove(filepath.path)
            except:
                raise HTTPError(
                    statusForFailure(Failure(), "writing to file: %s" %
                                     (filepath.path, )))
        else:
            response = waitForDeferred(delete(uri, filepath))
            yield response
            response = response.getResult()
            checkResponse(response, "delete", responsecode.NO_CONTENT)

        success_code = responsecode.NO_CONTENT
    else:
        success_code = responsecode.CREATED

    #
    # Write the contents of the request stream to resource's file
    #

    try:
        resource_file = filepath.open("w")
    except:
        raise HTTPError(
            statusForFailure(
                Failure(), "opening file for writing: %s" % (filepath.path, )))

    try:
        x = waitForDeferred(readIntoFile(stream, resource_file))
        yield x
        x.getResult()
    except:
        raise HTTPError(
            statusForFailure(Failure(),
                             "writing to file: %s" % (filepath.path, )))

    # Restat filepath since we modified the backing file
    filepath.restat(False)
    yield success_code
示例#17
0
def delete(uri, filepath, depth="infinity"):
    """
    Perform a X{DELETE} operation on the given URI, which is backed by the given
    filepath.
    @param filepath: the L{FilePath} to delete.
    @param depth: the recursion X{Depth} for the X{DELETE} operation, which must
        be "infinity".
    @raise HTTPError: (containing a response with a status code of
        L{responsecode.BAD_REQUEST}) if C{depth} is not "infinity".
    @raise HTTPError: (containing an appropriate response) if the
        delete operation fails.  If C{filepath} is a directory, the response
        will be a L{MultiStatusResponse}.
    @return: a deferred response with a status code of L{responsecode.NO_CONTENT}
        if the X{DELETE} operation succeeds.
    """
    #
    # Remove the file(s)
    #
    # FIXME: defer
    if filepath.isdir():
        #
        # RFC 2518, section 8.6 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, meaning that for collections, we will delete all
        # members.
        #
        # 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 RFC 2518, section 8 suggests that a bad request
        # should be rejected...
        #
        # Let's play it safe for now and ignore broken clients.
        #

        if depth != "infinity":
            msg = ("Client sent illegal depth header value for DELETE: %s" %
                   (depth, ))
            log.err(msg)
            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))

        #
        # Recursive delete
        #
        # RFC 2518, section 8.6 says that if we get an error deleting a resource
        # other than the collection in the request-URI, that we must respond
        # with a multi-status response containing error statuses for each
        # resource that we fail to delete.  It also says we should not return
        # no-content (success) status, which means that we should continue after
        # errors, rather than aborting right away.  This is interesting in that
        # it's different from how most operating system tools act (eg. rm) when
        # recursive filsystem deletes fail.
        #

        uri_path = urllib.unquote(urlsplit(uri)[2])
        if uri_path[-1] == "/":
            uri_path = uri_path[:-1]

        log.msg("Deleting directory %s" % (filepath.path, ))

        # NOTE: len(uri_path) is wrong if os.sep is not one byte long... meh.
        request_basename = filepath.path[:-len(uri_path)]
        request_basename_len = len(request_basename)

        errors = ResponseQueue(request_basename, "DELETE",
                               responsecode.NO_CONTENT)

        # FIXME: defer this
        for dir, subdirs, files in os.walk(filepath.path, topdown=False):
            for filename in files:
                path = os.path.join(dir, filename)
                try:
                    os.remove(path)
                except:
                    errors.add(path, Failure())

            for subdir in subdirs:
                path = os.path.join(dir, subdir)
                if os.path.islink(path):
                    try:
                        os.remove(path)
                    except:
                        errors.add(path, Failure())
                else:
                    try:
                        os.rmdir(path)
                    except:
                        errors.add(path, Failure())

        try:
            os.rmdir(filepath.path)
        except:
            raise HTTPError(
                statusForFailure(Failure(),
                                 "deleting directory: %s" % (filepath.path, )))

        response = errors.response()

    else:
        #
        # Delete a file; much simpler, eh?
        #
        log.msg("Deleting file %s" % (filepath.path, ))
        try:
            os.remove(filepath.path)
        except:
            raise HTTPError(
                statusForFailure(Failure(),
                                 "deleting file: %s" % (filepath.path, )))

        response = responsecode.NO_CONTENT

    # Restat filepath since we deleted the backing file
    filepath.restat(False)

    return succeed(response)
示例#18
0
        raise HTTPError(responsecode.NOT_FOUND)

    #
    # Read request body
    #
    try:
        doc = waitForDeferred(davXMLFromStream(request.stream))
        yield doc
        doc = doc.getResult()
    except ValueError, e:
        log.err("Error while handling REPORT body: %s" % (e,))
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))

    if doc is None:
        raise HTTPError(StatusResponse(
            responsecode.BAD_REQUEST,
            "REPORT request body may not be empty"
        ))

    #
    # Parse request
    #
    namespace = doc.root_element.namespace
    name = doc.root_element.name

    def to_method(s):
        ok = string.ascii_letters + string.digits + "_"
        out = []
        for c in s:
            if c in ok:
                out.append(c)
            else:
示例#19
0
        log.err("Error while handling PROPFIND body: %s" % (e,))
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))

    if doc is None:
        # No request body means get all properties.
        search_properties = "all"
    else:
        #
        # Parse request
        #
        find = doc.root_element
        if not isinstance(find, davxml.PropertyFind):
            error = ("Non-%s element in PROPFIND request body: %s"
                     % (davxml.PropertyFind.sname(), find))
            log.err(error)
            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))

        container = find.children[0]

        if isinstance(container, davxml.AllProperties):
            # Get all properties
            search_properties = "all"
        elif isinstance(container, davxml.PropertyName):
            # Get names only
            search_properties = "names"
        elif isinstance(container, davxml.PropertyContainer):
            properties = container.children
            search_properties = [(p.namespace, p.name) for p in properties]
        else:
            raise AssertionError("Unexpected element type in %s: %s"
                                 % (davxml.PropertyFind.sname(), container))