# HG changeset patch # User Sebastien Jodogne # Date 1559909671 -7200 # Node ID 4e8205871967ac9fecdf021d48d2bc10f31df1bb # Parent 4acd1431e603caed6139fd03f85419adee8b85e4 OrthancPluginRegisterMultipartRestCallback() is working diff -r 4acd1431e603 -r 4e8205871967 Core/HttpServer/MultipartStreamReader.cpp --- 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 tokens; - Orthanc::Toolbox::TokenizeString(tokens, tmp, ';'); + Orthanc::Toolbox::TokenizeString(tokens, contentTypeHeader, ';'); if (tokens.empty()) { diff -r 4acd1431e603 -r 4e8205871967 Core/HttpServer/MultipartStreamReader.h --- 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); }; } diff -r 4acd1431e603 -r 4e8205871967 Plugins/Engine/OrthancPlugins.cpp --- 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 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(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(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 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()); } } diff -r 4acd1431e603 -r 4e8205871967 Plugins/Include/orthanc/OrthancCPlugin.h --- 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, diff -r 4acd1431e603 -r 4e8205871967 Plugins/Samples/Common/OrthancPluginCppWrapper.cpp --- 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( - 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(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; } } diff -r 4acd1431e603 -r 4e8205871967 Plugins/Samples/Common/OrthancPluginCppWrapper.h --- 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& groups, const std::map& headers) = 0; diff -r 4acd1431e603 -r 4e8205871967 UnitTestsSources/RestApiTests.cpp --- 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 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");