changeset 4252:f047e2734655

fix webdav
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 15 Oct 2020 17:13:35 +0200
parents cbf9afa17415
children 2221051b42df
files NEWS OrthancFramework/Sources/HttpServer/HttpServer.cpp OrthancFramework/Sources/HttpServer/IWebDavBucket.cpp OrthancFramework/Sources/HttpServer/IWebDavBucket.h OrthancServer/Sources/OrthancWebDav.cpp
diffstat 5 files changed, 102 insertions(+), 61 deletions(-) [+]
line wrap: on
line diff
--- 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)
 ==========================
--- 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);
             }
--- 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<std::string>& target)
+  {
+    for (std::list<Resource*>::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<std::string> 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);
--- 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 <pugixml.hpp>
 
 #include <list>
+#include <set>
 
 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<std::string>& target);
+
       void AddResource(Resource* resource);  // Takes ownership
 
       void Format(std::string& target,
--- 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<std::string> resources;
+        try
+        {
+          context_.GetIndex().GetChildren(resources, parentSeries_);
+        }
+        catch (OrthancException&)
+        {
+          // Unknown (or deleted) parent series
+          return true;
+        }
+
+        for (std::list<std::string>::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<std::string> content;
+          collection.ListDisplayNames(content);
+
+          for (std::set<std::string>::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