# HG changeset patch # User Sebastien Jodogne # Date 1601810633 -7200 # Node ID 7bd5eab3ba256294714839a57b60a6164aa3961d # Parent 81f2d1484886c8a70aa7316e5b3ac03f7ea8c075 prototyping IWebDavBucket diff -r 81f2d1484886 -r 7bd5eab3ba25 OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake --- 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) diff -r 81f2d1484886 -r 7bd5eab3ba25 OrthancFramework/Sources/HttpServer/HttpServer.cpp --- 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 @@ -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& root, + IWebDavBucket* bucket) + { + std::unique_ptr 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 } diff -r 81f2d1484886 -r 7bd5eab3ba25 OrthancFramework/Sources/HttpServer/HttpServer.h --- 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 WebDavBuckets; + WebDavBuckets webDavBuckets_; +#endif + bool IsRunning() const; public: @@ -221,5 +234,10 @@ { return requestTimeout_; } + +#if ORTHANC_ENABLE_PUGIXML == 1 + void Register(const std::vector& root, + IWebDavBucket* bucket); // Takes ownership +#endif }; } diff -r 81f2d1484886 -r 7bd5eab3ba25 OrthancFramework/Sources/HttpServer/IWebDavBucket.cpp --- /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 + * . + **/ + + +#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(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::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::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); + } +} diff -r 81f2d1484886 -r 7bd5eab3ba25 OrthancFramework/Sources/HttpServer/IWebDavBucket.h --- /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 + * . + **/ + + +#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 +#include +#include + +#include + +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 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& path) = 0; + + virtual bool GetFileContent(std::string& content, + const std::vector& path) = 0; + }; +} diff -r 81f2d1484886 -r 7bd5eab3ba25 OrthancServer/Sources/main.cpp --- 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 f(new File("IM" + boost::lexical_cast(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(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 root; // TODO + root.push_back("davfs"); + httpServer.Register(root, new DummyBucket(context)); + } + if (httpServer.GetPortNumber() < 1024) { LOG(WARNING) << "The HTTP port is privileged ("