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)
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)
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
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.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." )) raise AssertionError("We shouldn't be here.")
properties_to_enumerate = properties_to_enumerate.getResult() else: properties_to_enumerate = search_properties for property in properties_to_enumerate: if property in resource_properties: try: resource_property = waitForDeferred(resource.readProperty(property, request)) yield resource_property resource_property = resource_property.getResult() except: f = Failure() log.err("Error reading property %r for resource %s: %s" % (property, uri, f.value)) status = statusForFailure(f, "getting property: %s" % (property,)) if status not in properties_by_status: properties_by_status[status] = [] properties_by_status[status].append(propertyName(property)) else: properties_by_status[responsecode.OK].append(resource_property) else: log.err("Can't find property %r for resource %s" % (property, uri)) properties_by_status[responsecode.NOT_FOUND].append(propertyName(property)) propstats = [] for status in properties_by_status: properties = properties_by_status[status] if not properties: continue
def report_DAV__expand_property(self, request, expand_property): """ Generate an expand-property REPORT. (RFC 3253, section 3.8) """ # FIXME: Handle depth header if not isinstance(expand_property, davxml.ExpandProperty): raise ValueError("%s expected as root element, not %s." % (davxml.ExpandProperty.sname(), expand_property.sname())) # # Expand DAV:allprop # properties = {} for property in expand_property.children: namespace = property.getAttribute("namespace") name = property.getAttribute("name") if not namespace: namespace = dav_namespace if (namespace, name) == (dav_namespace, "allprop"): all_properties = waitForDeferred(self.listAllProp(request)) yield all_properties all_properties = all_properties.getResult() for all_property in all_properties: properties[all_property.qname()] = property else: properties[(namespace, name)] = property # # Look up the requested properties # properties_by_status = { responsecode.OK : [], responsecode.NOT_FOUND : [], } for property in properties: my_properties = waitForDeferred(self.listProperties(request)) yield my_properties my_properties = my_properties.getResult() if property in my_properties: try: value = waitForDeferred(self.readProperty(property, request)) yield value value = value.getResult() if isinstance(value, davxml.HRef): raise NotImplementedError() else: raise NotImplementedError() except: f = Failure() log.err("Error reading property %r for resource %s: %s" % (property, self, f.value)) status = statusForFailure(f, "getting property: %s" % (property,)) if status not in properties_by_status: properties_by_status[status] = [] raise NotImplementedError() #properties_by_status[status].append( # ____propertyName(property) #) else: log.err("Can't find property %r for resource %s" % (property, self)) properties_by_status[responsecode.NOT_FOUND].append(property) raise NotImplementedError()