changeset 4226:7bd5eab3ba25

prototyping IWebDavBucket
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sun, 04 Oct 2020 13:23:53 +0200
parents 81f2d1484886
children 7fff7e683d65
files OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake OrthancFramework/Sources/HttpServer/HttpServer.cpp OrthancFramework/Sources/HttpServer/HttpServer.h OrthancFramework/Sources/HttpServer/IWebDavBucket.cpp OrthancFramework/Sources/HttpServer/IWebDavBucket.h OrthancServer/Sources/main.cpp
diffstat 6 files changed, 515 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake	Wed Sep 30 19:10:56 2020 +0200
+++ b/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake	Sun Oct 04 13:23:53 2020 +0200
@@ -309,6 +309,12 @@
     ${CMAKE_CURRENT_LIST_DIR}/../../Sources/RestApi/RestApiOutput.cpp
     ${CMAKE_CURRENT_LIST_DIR}/../../Sources/RestApi/RestApiPath.cpp
     )
+
+  if (ENABLE_PUGIXML)
+    list(APPEND ORTHANC_CORE_SOURCES_INTERNAL
+      ${CMAKE_CURRENT_LIST_DIR}/../../Sources/HttpServer/IWebDavBucket.cpp
+      )
+  endif()
 endif()
 
 if (ORTHANC_ENABLE_CIVETWEB)
--- a/OrthancFramework/Sources/HttpServer/HttpServer.cpp	Wed Sep 30 19:10:56 2020 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpServer.cpp	Sun Oct 04 13:23:53 2020 +0200
@@ -32,6 +32,10 @@
 #include "../TemporaryFile.h"
 #include "HttpToolbox.h"
 
+#if ORTHANC_ENABLE_PUGIXML == 1
+#  include "IWebDavBucket.h"
+#endif
+
 #if ORTHANC_ENABLE_MONGOOSE == 1
 #  include <mongoose.h>
 
@@ -1097,6 +1101,14 @@
   HttpServer::~HttpServer()
   {
     Stop();
+
+#if ORTHANC_ENABLE_PUGIXML == 1    
+    for (WebDavBuckets::iterator it = webDavBuckets_.begin(); it != webDavBuckets_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      delete it->second;
+    }
+#endif
   }
 
 
@@ -1420,4 +1432,39 @@
     requestTimeout_ = seconds;
     LOG(INFO) << "Request timeout in the HTTP server is set to " << seconds << " seconds";
   }
+
+
+#if ORTHANC_ENABLE_PUGIXML == 1
+  void HttpServer::Register(const std::vector<std::string>& root,
+                            IWebDavBucket* bucket)
+  {
+    std::unique_ptr<IWebDavBucket> protection(bucket);
+    
+    if (bucket == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+
+    std::string s = "/";
+    for (size_t i = 0; i < root.size(); i++)
+    {
+      if (root[i].empty())
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange, "An URI component cannot be empty");
+      }
+      
+      s += root[i];
+    }
+
+    if (webDavBuckets_.find(s) != webDavBuckets_.end())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange,
+                             "Cannot register two WebDAV buckets at the same root: " + s);
+    }
+    else
+    {
+      webDavBuckets_[s] = protection.release();
+    }
+  }
+#endif
 }
--- a/OrthancFramework/Sources/HttpServer/HttpServer.h	Wed Sep 30 19:10:56 2020 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpServer.h	Sun Oct 04 13:23:53 2020 +0200
@@ -38,6 +38,14 @@
 #  error Either ORTHANC_ENABLE_MONGOOSE or ORTHANC_ENABLE_CIVETWEB must be set to 1
 #endif
 
+#if !defined(ORTHANC_ENABLE_PUGIXML)
+#  error The macro ORTHANC_ENABLE_PUGIXML must be defined
+#endif
+
+#if ORTHANC_ENABLE_PUGIXML == 1
+#  include "IWebDavBucket.h"
+#endif
+
 
 #include "IIncomingHttpRequestFilter.h"
 
@@ -93,7 +101,12 @@
     unsigned int threadsCount_;
     bool tcpNoDelay_;
     unsigned int requestTimeout_;  // In seconds
-  
+
+#if ORTHANC_ENABLE_PUGIXML == 1
+    typedef std::map<std::string, IWebDavBucket*>  WebDavBuckets;
+    WebDavBuckets webDavBuckets_;
+#endif
+    
     bool IsRunning() const;
 
   public:
@@ -221,5 +234,10 @@
     {
       return requestTimeout_;
     }
+
+#if ORTHANC_ENABLE_PUGIXML == 1
+    void Register(const std::vector<std::string>& root,
+                  IWebDavBucket* bucket); // Takes ownership
+#endif
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/HttpServer/IWebDavBucket.cpp	Sun Oct 04 13:23:53 2020 +0200
@@ -0,0 +1,220 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "IWebDavBucket.h"
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+
+static boost::posix_time::ptime GetNow()
+{
+  return boost::posix_time::second_clock::universal_time();
+}
+
+
+static std::string AddTrailingSlash(const std::string& s)
+{
+  if (s.empty() ||
+      s[s.size() - 1] != '/')
+  {
+    return s + '/';
+  }
+  else
+  {
+    return s;
+  }
+}
+  
+
+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() :
+    hasModificationTime_(false),
+    creationTime_(GetNow()),
+    modificationTime_(GetNow())
+  {
+  }
+
+
+  void IWebDavBucket::Resource::SetCreationTime(const boost::posix_time::ptime& t)
+  {
+    creationTime_ = t;
+
+    if (!hasModificationTime_)
+    {
+      modificationTime_ = t;
+    }
+  }
+
+
+  void IWebDavBucket::Resource::SetModificationTime(const boost::posix_time::ptime& t)
+  {
+    modificationTime_ = t;
+    hasModificationTime_ = true;
+  }
+
+
+  void IWebDavBucket::Resource::Format(pugi::xml_node& node,
+                                        const std::string& parentPath) const
+  {
+    node.set_name("D:response");
+
+    std::string s = AddTrailingSlash(parentPath) + GetName();
+    node.append_child("D:href").append_child(pugi::node_pcdata).set_value(s.c_str());
+
+    pugi::xml_node propstat = node.append_child("D:propstat");
+    propstat.append_child("D:status").append_child(pugi::node_pcdata).
+      set_value("HTTP/1.1 200 OK");
+
+    pugi::xml_node prop = propstat.append_child("D:prop");
+
+    // IMPORTANT: The "Z" suffix is mandatory on Windows >= 7
+    s = boost::posix_time::to_iso_extended_string(GetCreationTime()) + "Z";
+    prop.append_child("D:creationdate").append_child(pugi::node_pcdata).set_value(s.c_str());
+
+    s = boost::posix_time::to_iso_extended_string(GetModificationTime()) + "Z";
+    prop.append_child("D:getlastmodified").append_child(pugi::node_pcdata).set_value(s.c_str());
+
+#if 0
+    prop.append_child("D:lockdiscovery");
+    pugi::xml_node lock = prop.append_child("D:supportedlock");
+
+    pugi::xml_node lockentry = lock.append_child("D:lockentry");
+    lockentry.append_child("D:lockscope").append_child("D:exclusive");
+    lockentry.append_child("D:locktype").append_child("D:write");
+
+    lockentry = lock.append_child("D:lockentry");
+    lockentry.append_child("D:lockscope").append_child("D:shared");
+    lockentry.append_child("D:locktype").append_child("D:write");
+#endif
+  }
+
+
+  IWebDavBucket::File::File(const std::string& name) :
+    contentLength_(0),
+    mime_(MimeType_Binary)
+  {
+    if (name.empty())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange,
+                             "Cannot use an empty filename in WebDAV");          
+    }
+        
+    SetNameInternal(name);
+  }
+
+
+  void IWebDavBucket::File::Format(pugi::xml_node& node,
+                                   const std::string& parentPath) const
+  {
+    Resource::Format(node, parentPath);
+
+    pugi::xml_node prop = node.first_element_by_path("D:propstat/D:prop");
+    prop.append_child("D:resourcetype");
+
+    std::string s = boost::lexical_cast<std::string>(contentLength_);
+    prop.append_child("D:getcontentlength").append_child(pugi::node_pcdata).set_value(s.c_str());
+
+    s = EnumerationToString(mime_);
+    prop.append_child("D:getcontenttype").append_child(pugi::node_pcdata).set_value(s.c_str());
+
+    prop.append_child("D:displayname").append_child(pugi::node_pcdata).set_value(GetName().c_str());
+  }
+
+
+  void IWebDavBucket::Folder::Format(pugi::xml_node& node,
+                                     const std::string& parentPath) const
+  {
+    Resource::Format(node, parentPath);
+        
+    pugi::xml_node prop = node.first_element_by_path("D:propstat/D:prop");
+    prop.append_child("D:resourcetype").append_child("D:collection");
+
+    //prop.append_child("D:getcontenttype").append_child(pugi::node_pcdata).set_value("httpd/unix-directory");
+
+    std::string s = GetName();
+    prop.append_child("D:displayname").append_child(pugi::node_pcdata).set_value(s.c_str());
+  }
+
+
+  IWebDavBucket::Collection::~Collection()
+  {
+    for (std::list<Resource*>::iterator it = resources_.begin(); it != resources_.end(); ++it)
+    {
+      assert(*it != NULL);
+      delete(*it);
+    }
+  }
+
+
+  void IWebDavBucket::Collection::AddResource(Resource* resource)  // Takes ownership
+  {
+    if (resource == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+    else
+    {
+      resources_.push_back(resource);
+    }
+  }
+
+
+  void IWebDavBucket::Collection::Format(std::string& target,
+                                         const std::string& parentPath) const
+  {
+    pugi::xml_document doc;
+
+    pugi::xml_node root = doc.append_child("D:multistatus");
+    root.append_attribute("xmlns:D").set_value("DAV:");
+
+    for (std::list<Resource*>::const_iterator
+           it = resources_.begin(); it != resources_.end(); ++it)
+    {
+      assert(*it != NULL);
+      pugi::xml_node n = root.append_child();
+      (*it)->Format(n, parentPath);
+    }
+
+    pugi::xml_node decl = doc.prepend_child(pugi::node_declaration);
+    decl.append_attribute("version").set_value("1.0");
+    decl.append_attribute("encoding").set_value("UTF-8");
+
+    Toolbox::XmlToString(target, doc);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/HttpServer/IWebDavBucket.h	Sun Oct 04 13:23:53 2020 +0200
@@ -0,0 +1,161 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if !defined(ORTHANC_ENABLE_PUGIXML)
+#  error The macro ORTHANC_ENABLE_PUGIXML must be defined
+#endif
+
+#if ORTHANC_ENABLE_PUGIXML != 1
+#  error XML support is required to use this file
+#endif
+
+#include "../Compatibility.h"
+#include "../Enumerations.h"
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/noncopyable.hpp>
+#include <pugixml.hpp>
+
+#include <list>
+
+namespace Orthanc
+{
+  class IWebDavBucket : public boost::noncopyable
+  {
+  public:
+    class Resource : public boost::noncopyable
+    {
+    private:
+      std::string               name_;
+      bool                      hasModificationTime_;
+      boost::posix_time::ptime  creationTime_;
+      boost::posix_time::ptime  modificationTime_;
+
+    protected:
+      void SetNameInternal(const std::string& name);
+
+    public:
+      Resource();
+
+      virtual ~Resource()
+      {
+      }
+
+      void SetCreationTime(const boost::posix_time::ptime& t);
+
+      void SetModificationTime(const boost::posix_time::ptime& t);
+
+      const std::string& GetName() const
+      {
+        return name_;
+      }
+
+      const boost::posix_time::ptime& GetCreationTime() const
+      {
+        return creationTime_;
+      }
+
+      const boost::posix_time::ptime& GetModificationTime() const
+      {
+        return modificationTime_;
+      }
+
+      virtual void Format(pugi::xml_node& node,
+                          const std::string& parentPath) const;
+    };
+
+
+    class File : public Resource
+    {
+    private:
+      uint64_t  contentLength_;
+      MimeType  mime_;
+
+    public:
+      File(const std::string& name);
+
+      void SetContentLength(uint64_t contentLength)
+      {
+        contentLength_ = contentLength;
+      }
+
+      void SetMimeType(MimeType mime)
+      {
+        mime_ = mime;
+      }
+
+      uint64_t GetContentLength() const
+      {
+        return contentLength_;
+      }
+
+      MimeType GetMimeType() const
+      {
+        return mime_;
+      }
+
+      virtual void Format(pugi::xml_node& node,
+                          const std::string& parentPath) const ORTHANC_OVERRIDE;
+    };
+
+
+    class Folder : public Resource
+    {
+    public:
+      Folder(const std::string& name)
+      {
+        SetNameInternal(name);
+      }
+      
+      virtual void Format(pugi::xml_node& node,
+                          const std::string& parentPath) const ORTHANC_OVERRIDE;
+    };
+
+
+    class Collection : public boost::noncopyable
+    {
+    private:
+      std::list<Resource*>  resources_;
+
+    public:
+      ~Collection();
+
+      void AddResource(Resource* resource);  // Takes ownership
+
+      void Format(std::string& target,
+                  const std::string& parentPath) const;
+    };
+    
+
+    virtual ~IWebDavBucket()
+    {
+    }
+
+    virtual bool ListCollection(Collection& collection,
+                                const std::vector<std::string>& path) = 0;
+
+    virtual bool GetFileContent(std::string& content,
+                                const std::vector<std::string>& path) = 0;
+  };
+}
--- a/OrthancServer/Sources/main.cpp	Wed Sep 30 19:10:56 2020 +0200
+++ b/OrthancServer/Sources/main.cpp	Sun Oct 04 13:23:53 2020 +0200
@@ -43,6 +43,7 @@
 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
 #include "../../OrthancFramework/Sources/HttpServer/FilesystemHttpHandler.h"
 #include "../../OrthancFramework/Sources/HttpServer/HttpServer.h"
+#include "../../OrthancFramework/Sources/HttpServer/IWebDavBucket.h"  // TODO
 #include "../../OrthancFramework/Sources/Logging.h"
 #include "../../OrthancFramework/Sources/Lua/LuaFunctionCall.h"
 #include "../Plugins/Engine/OrthancPlugins.h"
@@ -611,6 +612,61 @@
 
 
 
+
+class DummyBucket : public IWebDavBucket  // TODO
+{
+private:
+  ServerContext& context_;
+
+public:
+  DummyBucket(ServerContext& context) :
+    context_(context)
+  {
+  }
+  
+  virtual bool ListCollection(Collection& collection,
+                              const UriComponents& path) ORTHANC_OVERRIDE
+  {
+    if (path.size() == 0 ||
+        (path.size() == 1 && path[0] == "Folder1") ||
+        (path.size() == 2 && path[0] == "Folder1" && path[1] == "Folder2"))
+    {
+      for (unsigned int i = 0; i < 5; i++)
+      {
+        std::unique_ptr<File> f(new File("IM" + boost::lexical_cast<std::string>(i) + ".dcm"));
+        f->SetContentLength(1024 * i);
+        collection.AddResource(f.release());
+      }
+        
+      for (unsigned int i = 0; i < 5; i++)
+      {
+        collection.AddResource(new Folder("Folder" + boost::lexical_cast<std::string>(i)));
+      }
+        
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  virtual bool GetFileContent(std::string& content,
+                              const UriComponents& path) ORTHANC_OVERRIDE
+  {
+    std::string s = "/";
+    for (size_t i = 0; i < path.size(); i++)
+    {
+      s += path[i] + "/";
+    }
+      
+    content = "Hello world!\r\n" + s + "\r\n";
+    return true;
+  }
+};
+
+
+
 static void PrintHelp(const char* path)
 {
   std::cout 
@@ -1048,6 +1104,12 @@
     httpServer.SetHttpExceptionFormatter(exceptionFormatter);
     httpServer.Register(context.GetHttpHandler());
 
+    {
+      std::vector<std::string> root;  // TODO
+      root.push_back("davfs");
+      httpServer.Register(root, new DummyBucket(context));
+    }
+
     if (httpServer.GetPortNumber() < 1024)
     {
       LOG(WARNING) << "The HTTP port is privileged ("