changeset 3399:4e8205871967

OrthancPluginRegisterMultipartRestCallback() is working
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 07 Jun 2019 14:14:31 +0200
parents 4acd1431e603
children 0faae6f6e3c5
files Core/HttpServer/MultipartStreamReader.cpp Core/HttpServer/MultipartStreamReader.h Plugins/Engine/OrthancPlugins.cpp Plugins/Include/orthanc/OrthancCPlugin.h Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Plugins/Samples/Common/OrthancPluginCppWrapper.h UnitTestsSources/RestApiTests.cpp
diffstat 7 files changed, 96 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- a/Core/HttpServer/MultipartStreamReader.cpp	Fri Jun 07 13:36:43 2019 +0200
+++ b/Core/HttpServer/MultipartStreamReader.cpp	Fri Jun 07 14:14:31 2019 +0200
@@ -205,7 +205,7 @@
                                "No endline at the end of a part");
       }
           
-      handler_->Apply(headers, headersMatcher_.GetPointerEnd(), contentLength);
+      handler_->HandlePart(headers, headersMatcher_.GetPointerEnd(), contentLength);
       current = headersMatcher_.GetMatchEnd() + contentLength + 2;
     }
 
@@ -293,19 +293,13 @@
   }
 
 
-  bool MultipartStreamReader::ParseMultipartHeaders(std::string& contentType,
-                                                    std::string& subType,
-                                                    std::string& boundary,
-                                                    const HttpHeaders& headers)
+  bool MultipartStreamReader::ParseMultipartContentType(std::string& contentType,
+                                                        std::string& subType,
+                                                        std::string& boundary,
+                                                        const std::string& contentTypeHeader)
   {
-    std::string tmp;
-    if (!GetMainContentType(tmp, headers))
-    {
-      return false;
-    }
-
     std::vector<std::string> tokens;
-    Orthanc::Toolbox::TokenizeString(tokens, tmp, ';');
+    Orthanc::Toolbox::TokenizeString(tokens, contentTypeHeader, ';');
 
     if (tokens.empty())
     {
--- a/Core/HttpServer/MultipartStreamReader.h	Fri Jun 07 13:36:43 2019 +0200
+++ b/Core/HttpServer/MultipartStreamReader.h	Fri Jun 07 14:14:31 2019 +0200
@@ -52,9 +52,9 @@
       {
       }
       
-      virtual void Apply(const HttpHeaders& headers,
-                         const void* part,
-                         size_t size) = 0;
+      virtual void HandlePart(const HttpHeaders& headers,
+                              const void* part,
+                              size_t size) = 0;
     };
     
   private:
@@ -99,9 +99,9 @@
     static bool GetMainContentType(std::string& contentType,
                                    const HttpHeaders& headers);
 
-    static bool ParseMultipartHeaders(std::string& contentType,
-                                      std::string& subType,  // Possibly empty
-                                      std::string& boundary,
-                                      const HttpHeaders& headers);
+    static bool ParseMultipartContentType(std::string& contentType,
+                                          std::string& subType,  // Possibly empty
+                                          std::string& boundary,
+                                          const std::string& contentTypeHeader);
   };
 }
--- a/Plugins/Engine/OrthancPlugins.cpp	Fri Jun 07 13:36:43 2019 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Fri Jun 07 14:14:31 2019 +0200
@@ -51,6 +51,7 @@
 #include "../../Core/DicomParsing/Internals/DicomImageDecoder.h"
 #include "../../Core/DicomParsing/ToDcmtkBridge.h"
 #include "../../Core/HttpServer/HttpToolbox.h"
+#include "../../Core/HttpServer/MultipartStreamReader.h"
 #include "../../Core/Images/Image.h"
 #include "../../Core/Images/ImageProcessing.h"
 #include "../../Core/Images/JpegReader.h"
@@ -1322,12 +1323,14 @@
     memset(&request, 0, sizeof(OrthancPluginHttpRequest));
 
     ArgumentsToPlugin(headersKeys, headersValues, headers);
+    assert(headersKeys.size() == headersValues.size());
 
     switch (method)
     {
       case HttpMethod_Get:
         request.method = OrthancPluginHttpMethod_Get;
         ArgumentsToPlugin(getKeys, getValues, getArguments);
+        assert(getKeys.size() == getValues.size());
         break;
 
       case HttpMethod_Post:
@@ -4107,25 +4110,59 @@
   }
 
 
-  class OrthancPlugins::MultipartStream : public IHttpHandler::IStream
+  class OrthancPlugins::MultipartStream : 
+    public IHttpHandler::IStream,
+    private MultipartStreamReader::IHandler
   {
   private:
     OrthancPluginMultipartRestHandler*   handler_;
     _OrthancPluginMultipartRestCallback  parameters_;
+    MultipartStreamReader                reader_;
     PluginsErrorDictionary&              errorDictionary_;
 
+    virtual void HandlePart(const MultipartStreamReader::HttpHeaders& headers,
+                            const void* part,
+                            size_t size)
+    {
+      assert(handler_ != NULL);
+
+      std::string contentType;
+      MultipartStreamReader::GetMainContentType(contentType, headers);
+      Orthanc::Toolbox::ToLowerCase(contentType);
+
+      std::vector<const char*>  headersKeys, headersValues;
+      ArgumentsToPlugin(headersKeys, headersValues, headers);
+      assert(headersKeys.size() == headersValues.size());
+
+      OrthancPluginErrorCode error = parameters_.addPart(
+        handler_, contentType.c_str(), headersKeys.size(),
+        headersKeys.empty() ? NULL : &headersKeys[0],
+        headersValues.empty() ? NULL : &headersValues[0],
+        part, size);
+
+      if (error != OrthancPluginErrorCode_Success)
+      {
+        errorDictionary_.LogError(error, true);
+        throw OrthancException(static_cast<ErrorCode>(error));
+      }
+    }
+
   public:
     MultipartStream(OrthancPluginMultipartRestHandler* handler,
                     const _OrthancPluginMultipartRestCallback& parameters,
+                    const std::string& boundary,
                     PluginsErrorDictionary& errorDictionary) :
       handler_(handler),
       parameters_(parameters),
+      reader_(boundary),
       errorDictionary_(errorDictionary)
     {
       if (handler_ == NULL)
       {
         throw OrthancException(ErrorCode_Plugin, "The plugin has not created a multipart stream handler");
       }
+
+      reader_.SetHandler(*this);
     }
 
     virtual ~MultipartStream()
@@ -4139,25 +4176,15 @@
     virtual void AddBodyChunk(const void* data,
                               size_t size)
     {
-      assert(handler_ != NULL);
-
-      // TODO => multipart parsing
-
-      OrthancPluginErrorCode error = 
-        parameters_.addPart(handler_, "content-type", 0 /* headers */, NULL, NULL,
-                            data, size);
-
-      if (error != OrthancPluginErrorCode_Success)
-      {
-        errorDictionary_.LogError(error, true);
-        throw OrthancException(static_cast<ErrorCode>(error));
-      }
+      reader_.AddChunk(data, size);
     }
 
     virtual void Execute(HttpOutput& output)
     {
       assert(handler_ != NULL);
 
+      reader_.CloseStream();
+
       PImpl::PluginHttpOutput pluginOutput(output);
 
       OrthancPluginErrorCode error = parameters_.execute(
@@ -4187,6 +4214,7 @@
 
         std::vector<const char*> headersKeys, headersValues;
         ArgumentsToPlugin(headersKeys, headersValues, headers);
+        assert(headersKeys.size() == headersValues.size());
 
         OrthancPluginHttpMethod convertedMethod;
         switch (method)
@@ -4203,16 +4231,30 @@
             throw OrthancException(ErrorCode_ParameterOutOfRange);
         }
 
-        std::string contentType = "TODO";   // TODO
+        std::string multipartContentType;
+        if (!MultipartStreamReader::GetMainContentType(multipartContentType, headers))
+        {
+          LOG(INFO) << "Missing Content-Type HTTP header, prevents streaming the body";
+          continue;
+        }
+
+        std::string contentType, subType, boundary;
+        if (!MultipartStreamReader::ParseMultipartContentType
+            (contentType, subType, boundary, multipartContentType))
+        {
+          LOG(INFO) << "Invalid Content-Type HTTP header, "
+                    << "prevents streaming the body: \"" << multipartContentType << "\"";
+          continue;
+        }
 
         OrthancPluginMultipartRestHandler* handler = (*it)->GetParameters().createHandler(
           (*it)->GetParameters().factory,
-          convertedMethod, matcher.GetFlatUri().c_str(), contentType.c_str(),
+          convertedMethod, matcher.GetFlatUri().c_str(), contentType.c_str(), subType.c_str(),
           matcher.GetGroupsCount(), matcher.GetGroups(), headers.size(),
           headers.empty() ? NULL : &headersKeys[0],
           headers.empty() ? NULL : &headersValues[0]);
 
-        return new MultipartStream(handler, (*it)->GetParameters(), GetErrorDictionary());
+        return new MultipartStream(handler, (*it)->GetParameters(), boundary, GetErrorDictionary());
       }
     }
 
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Fri Jun 07 13:36:43 2019 +0200
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Fri Jun 07 14:14:31 2019 +0200
@@ -6918,6 +6918,7 @@
     OrthancPluginHttpMethod            method,
     const char*                        url,
     const char*                        contentType,
+    const char*                        subType,
     uint32_t                           groupsCount,
     const char* const*                 groups,
     uint32_t                           headersCount,
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Fri Jun 07 13:36:43 2019 +0200
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Fri Jun 07 14:14:31 2019 +0200
@@ -2613,6 +2613,7 @@
     OrthancPluginHttpMethod            method,
     const char*                        url,
     const char*                        contentType,
+    const char*                        subType,
     uint32_t                           groupsCount,
     const char* const*                 groups,
     uint32_t                           headersCount,
@@ -2639,7 +2640,8 @@
       }
 
       return reinterpret_cast<OrthancPluginMultipartRestHandler*>(
-        that.CreateHandler(method, url, contentType, g, headers));
+        that.CreateHandler(method, url, contentType, 
+                           subType == NULL ? "" : subType, g, headers));
     }
     catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
     {
@@ -2676,12 +2678,12 @@
     }
     catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
     {
-      LogError("Exception while add a part to a multipart handler");
+      LogError("Exception while adding a part to a multipart handler");
       return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
     }
     catch (...)
     {
-      LogError("Native exception while add a part to a multipart handler");
+      LogError("Native exception while adding a part to a multipart handler");
       return OrthancPluginErrorCode_Plugin;
     }
   }
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Fri Jun 07 13:36:43 2019 +0200
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Fri Jun 07 14:14:31 2019 +0200
@@ -948,6 +948,7 @@
     virtual IHandler* CreateHandler(OrthancPluginHttpMethod method,
                                     const std::string& url,
                                     const std::string& contentType,
+                                    const std::string& subType,  // Possibly empty
                                     const std::vector<std::string>& groups,
                                     const std::map<std::string, std::string>& headers) = 0;
 
--- a/UnitTestsSources/RestApiTests.cpp	Fri Jun 07 13:36:43 2019 +0200
+++ b/UnitTestsSources/RestApiTests.cpp	Fri Jun 07 14:14:31 2019 +0200
@@ -724,9 +724,9 @@
   std::vector<Part> parts_;
 
 public:
-  virtual void Apply(const MultipartStreamReader::HttpHeaders& headers,
-                     const void* part,
-                     size_t size)
+  virtual void HandlePart(const MultipartStreamReader::HttpHeaders& headers,
+                          const void* part,
+                          size_t size)
   {
     parts_.push_back(Part(headers, part, size));
   }
@@ -750,57 +750,51 @@
 
 TEST(MultipartStreamReader, ParseHeaders)
 {
-  std::string ct, b, st;
+  std::string ct, b, st, header;
 
   {
     MultipartStreamReader::HttpHeaders h;
     h["hello"] = "world";
     h["Content-Type"] = "world";  // Should be in lower-case
     h["CONTENT-type"] = "world";  // Should be in lower-case
-    ASSERT_FALSE(MultipartStreamReader::GetMainContentType(ct, h));
-    ASSERT_FALSE(MultipartStreamReader::ParseMultipartHeaders(ct, st, b, h));
+    ASSERT_FALSE(MultipartStreamReader::GetMainContentType(header, h));
   }
 
   {
     MultipartStreamReader::HttpHeaders h;
     h["content-type"] = "world";
-    ASSERT_TRUE(MultipartStreamReader::GetMainContentType(ct, h)); 
-    ASSERT_EQ(ct, "world");
-    ASSERT_FALSE(MultipartStreamReader::ParseMultipartHeaders(ct, st, b, h));
+    ASSERT_TRUE(MultipartStreamReader::GetMainContentType(header, h)); 
+    ASSERT_EQ(header, "world");
+    ASSERT_FALSE(MultipartStreamReader::ParseMultipartContentType(ct, st, b, header));
   }
 
   {
     MultipartStreamReader::HttpHeaders h;
     h["content-type"] = "multipart/related; dummy=value; boundary=1234; hello=world";
-    ASSERT_TRUE(MultipartStreamReader::GetMainContentType(ct, h)); 
-    ASSERT_EQ(ct, h["content-type"]);
-    ASSERT_TRUE(MultipartStreamReader::ParseMultipartHeaders(ct, st, b, h));
+    ASSERT_TRUE(MultipartStreamReader::GetMainContentType(header, h)); 
+    ASSERT_EQ(header, h["content-type"]);
+    ASSERT_TRUE(MultipartStreamReader::ParseMultipartContentType(ct, st, b, header));
     ASSERT_EQ(ct, "multipart/related");
     ASSERT_EQ(b, "1234");
     ASSERT_TRUE(st.empty());
   }
 
   {
-    MultipartStreamReader::HttpHeaders h;
-    h["content-type"] = "multipart/related; boundary=";
-    ASSERT_TRUE(MultipartStreamReader::GetMainContentType(ct, h)); 
-    ASSERT_EQ(ct, h["content-type"]);
-    ASSERT_FALSE(MultipartStreamReader::ParseMultipartHeaders(ct, st, b, h));  // Empty boundary
+    ASSERT_FALSE(MultipartStreamReader::ParseMultipartContentType
+                 (ct, st, b, "multipart/related; boundary="));  // Empty boundary
   }
 
   {
-    MultipartStreamReader::HttpHeaders h;
-    h["content-type"] = "Multipart/Related; TYPE=Application/Dicom; Boundary=heLLO";
-    ASSERT_TRUE(MultipartStreamReader::ParseMultipartHeaders(ct, st, b, h));
+    ASSERT_TRUE(MultipartStreamReader::ParseMultipartContentType
+                (ct, st, b, "Multipart/Related; TYPE=Application/Dicom; Boundary=heLLO"));
     ASSERT_EQ(ct, "multipart/related");
     ASSERT_EQ(b, "heLLO");
     ASSERT_EQ(st, "application/dicom");
   }
 
   {
-    MultipartStreamReader::HttpHeaders h;
-    h["content-type"] = "Multipart/Related; type=\"application/DICOM\"; Boundary=a";
-    ASSERT_TRUE(MultipartStreamReader::ParseMultipartHeaders(ct, st, b, h));
+    ASSERT_TRUE(MultipartStreamReader::ParseMultipartContentType
+                (ct, st, b, "Multipart/Related; type=\"application/DICOM\"; Boundary=a"));
     ASSERT_EQ(ct, "multipart/related");
     ASSERT_EQ(b, "a");
     ASSERT_EQ(st, "application/dicom");