# HG changeset patch # User Sebastien Jodogne # Date 1602774815 -7200 # Node ID f047e27346557643efab3d23d603e99d10ff7df4 # Parent cbf9afa174152a6e88db3f89e77e7cf994b844cd fix webdav diff -r cbf9afa17415 -r f047e2734655 NEWS --- a/NEWS Tue Oct 13 15:42:10 2020 +0200 +++ b/NEWS Thu Oct 15 17:13:35 2020 +0200 @@ -1,6 +1,12 @@ Pending changes in the mainline =============================== +General +------- + +* Serving the content of Orthanc as a WebDAV network share +* New configuration options: "WebDavEnabled", "WebDavDeleteAllowed" and "WebDavUploadAllowed" + Version 1.7.4 (2020-09-18) ========================== diff -r cbf9afa17415 -r f047e2734655 OrthancFramework/Sources/HttpServer/HttpServer.cpp --- a/OrthancFramework/Sources/HttpServer/HttpServer.cpp Tue Oct 13 15:42:10 2020 +0200 +++ b/OrthancFramework/Sources/HttpServer/HttpServer.cpp Thu Oct 15 17:13:35 2020 +0200 @@ -963,8 +963,7 @@ else if (method == "DELETE") { - if (!path.empty() && // Cannot delete the root - bucket->second->DeleteItem(path)) + if (bucket->second->DeleteItem(path)) { output.SendStatus(HttpStatus_204_NoContent); } diff -r cbf9afa17415 -r f047e2734655 OrthancFramework/Sources/HttpServer/IWebDavBucket.cpp --- a/OrthancFramework/Sources/HttpServer/IWebDavBucket.cpp Tue Oct 13 15:42:10 2020 +0200 +++ b/OrthancFramework/Sources/HttpServer/IWebDavBucket.cpp Thu Oct 15 17:13:35 2020 +0200 @@ -50,25 +50,20 @@ namespace Orthanc { - void IWebDavBucket::Resource::SetNameInternal(const std::string& name) - { - if (name.find('/') != std::string::npos || - name.find('\\') != std::string::npos || - name.find('\0') != std::string::npos) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Bad resource name for WebDAV: " + name); - } - - name_ = name; - } - - - IWebDavBucket::Resource::Resource() : + IWebDavBucket::Resource::Resource(const std::string& displayName) : + displayName_(displayName), hasModificationTime_(false), creationTime_(GetNow()), modificationTime_(GetNow()) { + if (displayName.empty() || + displayName.find('/') != std::string::npos || + displayName.find('\\') != std::string::npos || + displayName.find('\0') != std::string::npos) + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Bad resource name for WebDAV: " + displayName); + } } @@ -154,17 +149,11 @@ } - IWebDavBucket::File::File(const std::string& name) : + IWebDavBucket::File::File(const std::string& displayName) : + Resource(displayName), contentLength_(0), mime_(MimeType_Binary) { - if (name.empty()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Cannot use an empty filename in WebDAV"); - } - - SetNameInternal(name); } @@ -172,8 +161,8 @@ const std::string& parentPath) const { std::string href; - Toolbox::UriEncode(href, AddTrailingSlash(parentPath) + GetName()); - FormatInternal(node, href, GetName(), GetCreationTime(), GetModificationTime()); + Toolbox::UriEncode(href, AddTrailingSlash(parentPath) + GetDisplayName()); + FormatInternal(node, href, GetDisplayName(), GetCreationTime(), GetModificationTime()); pugi::xml_node prop = node.first_element_by_path("D:propstat/D:prop"); prop.append_child("D:resourcetype"); @@ -190,8 +179,8 @@ const std::string& parentPath) const { std::string href; - Toolbox::UriEncode(href, AddTrailingSlash(parentPath) + GetName()); - FormatInternal(node, href, GetName(), GetCreationTime(), GetModificationTime()); + Toolbox::UriEncode(href, AddTrailingSlash(parentPath) + GetDisplayName()); + FormatInternal(node, href, GetDisplayName(), GetCreationTime(), GetModificationTime()); pugi::xml_node prop = node.first_element_by_path("D:propstat/D:prop"); prop.append_child("D:resourcetype").append_child("D:collection"); @@ -223,6 +212,16 @@ } + void IWebDavBucket::Collection::ListDisplayNames(std::set& target) + { + for (std::list::iterator it = resources_.begin(); it != resources_.end(); ++it) + { + assert(*it != NULL); + target.insert((*it)->GetDisplayName()); + } + } + + void IWebDavBucket::Collection::Format(std::string& target, const std::string& parentPath) const { @@ -234,19 +233,17 @@ { pugi::xml_node self = root.append_child(); + std::vector tokens; + Toolbox::SplitUriComponents(tokens, parentPath); + std::string folder; - size_t lastSlash = parentPath.rfind('/'); - if (lastSlash == std::string::npos) + if (!tokens.empty()) { - folder = parentPath; + folder = tokens.back(); } - else - { - folder = parentPath.substr(lastSlash + 1); - } - + std::string href; - Toolbox::UriEncode(href, AddTrailingSlash(parentPath)); + Toolbox::UriEncode(href, Toolbox::FlattenUri(tokens) + "/"); boost::posix_time::ptime now = GetNow(); FormatInternal(self, href, folder, now, now); diff -r cbf9afa17415 -r f047e2734655 OrthancFramework/Sources/HttpServer/IWebDavBucket.h --- a/OrthancFramework/Sources/HttpServer/IWebDavBucket.h Tue Oct 13 15:42:10 2020 +0200 +++ b/OrthancFramework/Sources/HttpServer/IWebDavBucket.h Thu Oct 15 17:13:35 2020 +0200 @@ -38,6 +38,7 @@ #include #include +#include namespace Orthanc { @@ -49,16 +50,13 @@ class Resource : public boost::noncopyable { private: - std::string name_; + std::string displayName_; bool hasModificationTime_; boost::posix_time::ptime creationTime_; boost::posix_time::ptime modificationTime_; - protected: - void SetNameInternal(const std::string& name); - public: - Resource(); + Resource(const std::string& displayName); virtual ~Resource() { @@ -68,9 +66,9 @@ void SetModificationTime(const boost::posix_time::ptime& t); - const std::string& GetName() const + const std::string& GetDisplayName() const { - return name_; + return displayName_; } const boost::posix_time::ptime& GetCreationTime() const @@ -95,7 +93,7 @@ MimeType mime_; public: - File(const std::string& name); + File(const std::string& displayName); void SetContentLength(uint64_t contentLength) { @@ -127,9 +125,9 @@ class Folder : public Resource { public: - Folder(const std::string& name) + Folder(const std::string& displayName) : + Resource(displayName) { - SetNameInternal(name); } virtual void Format(pugi::xml_node& node, @@ -150,6 +148,8 @@ return resources_.size(); } + void ListDisplayNames(std::set& target); + void AddResource(Resource* resource); // Takes ownership void Format(std::string& target, diff -r cbf9afa17415 -r f047e2734655 OrthancServer/Sources/OrthancWebDav.cpp --- a/OrthancServer/Sources/OrthancWebDav.cpp Tue Oct 13 15:42:10 2020 +0200 +++ b/OrthancServer/Sources/OrthancWebDav.cpp Thu Oct 15 17:13:35 2020 +0200 @@ -552,15 +552,41 @@ virtual bool DeleteItem(const UriComponents& path) ORTHANC_OVERRIDE { - std::string instanceId; - if (LookupInstanceId(instanceId, path)) + if (path.empty()) { - Json::Value info; - return context_.DeleteResource(info, instanceId, ResourceType_Instance); + // Delete all + std::list resources; + try + { + context_.GetIndex().GetChildren(resources, parentSeries_); + } + catch (OrthancException&) + { + // Unknown (or deleted) parent series + return true; + } + + for (std::list::const_iterator it = resources.begin(); + it != resources.end(); ++it) + { + Json::Value info; + context_.DeleteResource(info, *it, ResourceType_Instance); + } + + return true; } else { - return false; + std::string instanceId; + if (LookupInstanceId(instanceId, path)) + { + Json::Value info; + return context_.DeleteResource(info, instanceId, ResourceType_Instance); + } + else + { + return false; + } } } }; @@ -696,10 +722,23 @@ if (path.empty()) { - IWebDavBucket::Collection tmp; - if (ListSubfolders(tmp)) + IWebDavBucket::Collection collection; + if (ListSubfolders(collection)) { - return (tmp.GetSize() == 0); + std::set content; + collection.ListDisplayNames(content); + + for (std::set::const_iterator + it = content.begin(); it != content.end(); ++it) + { + INode* node = GetChild(*it); + if (node) + { + node->DeleteItem(path); + } + } + + return true; } else { @@ -1316,8 +1355,8 @@ path[0] == BY_STUDIES || path[0] == BY_DATES) { - IWebDavBucket::Collection tmp; - return GetRootNode(path[0]).ListCollection(tmp, UriComponents(path.begin() + 1, path.end())); + IWebDavBucket::Collection collection; + return GetRootNode(path[0]).ListCollection(collection, UriComponents(path.begin() + 1, path.end())); } else if (allowUpload_ && path[0] == UPLOADS) @@ -1553,7 +1592,7 @@ { if (path[2] == STUDY_INFO) { - return true; // Allow deletion of virtual files + return true; // Allow deletion of virtual files (to avoid blocking recursive DELETE) } query.AddRestConstraint(DICOM_TAG_SERIES_INSTANCE_UID, path[2], @@ -1565,7 +1604,7 @@ { if (path[3] == SERIES_INFO) { - return true; // Allow deletion of virtual files + return true; // Allow deletion of virtual files (to avoid blocking recursive DELETE) } else if (boost::ends_with(path[3], ".dcm")) { @@ -1582,7 +1621,7 @@ } DicomDeleteVisitor visitor(context_, level); - context_.Apply(visitor, query, ResourceType_Instance, 0 /* since */, 0 /* no limit */); + context_.Apply(visitor, query, level, 0 /* since */, 0 /* no limit */); return true; } else