diff Core/HttpServer/HttpOutput.cpp @ 3528:f6fe095f7130

don't open a multipart stream if plugin only sends one part
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 26 Sep 2019 13:40:49 +0200
parents 2f6dcb9c8cc1
children b6a569e6e85b
line wrap: on
line diff
--- a/Core/HttpServer/HttpOutput.cpp	Thu Sep 26 10:50:58 2019 +0200
+++ b/Core/HttpServer/HttpOutput.cpp	Thu Sep 26 13:40:49 2019 +0200
@@ -410,8 +410,25 @@
   }
 
 
-  void HttpOutput::StateMachine::StartMultipart(const std::string& subType,
-                                                const std::string& contentType)
+  void HttpOutput::StateMachine::CheckHeadersCompatibilityWithMultipart() const
+  {
+    for (std::list<std::string>::const_iterator
+           it = headers_.begin(); it != headers_.end(); ++it)
+    {
+      if (!Toolbox::StartsWith(*it, "Set-Cookie: "))
+      {
+        throw OrthancException(ErrorCode_BadSequenceOfCalls,
+                               "The only headers that can be set in multipart answers "
+                               "are Set-Cookie (here: " + *it + " is set)");
+      }
+    }
+  }
+
+
+  static void PrepareMultipartMainHeader(std::string& boundary,
+                                         std::string& contentTypeHeader,
+                                         const std::string& subType,
+                                         const std::string& contentType)
   {
     if (subType != "mixed" &&
         subType != "related")
@@ -419,6 +436,40 @@
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
+    /**
+     * Fix for issue 54 ("Decide what to do wrt. quoting of multipart
+     * answers"). The "type" parameter in the "Content-Type" HTTP
+     * header must be quoted if it contains a forward slash "/". This
+     * is necessary for DICOMweb compatibility with OsiriX, but breaks
+     * compatibility with old releases of the client in the Orthanc
+     * DICOMweb plugin <= 0.3 (releases >= 0.4 work fine).
+     *
+     * Full history is available at the following locations:
+     * - In changeset 2248:69b0f4e8a49b:
+     *   # hg history -v -r 2248
+     * - https://bitbucket.org/sjodogne/orthanc/issues/54/
+     * - https://groups.google.com/d/msg/orthanc-users/65zhIM5xbKI/TU5Q1_LhAwAJ
+     **/
+    std::string tmp;
+    if (contentType.find('/') == std::string::npos)
+    {
+      // No forward slash in the content type
+      tmp = contentType;
+    }
+    else
+    {
+      // Quote the content type because of the forward slash
+      tmp = "\"" + contentType + "\"";
+    }
+
+    boundary = Toolbox::GenerateUuid() + "-" + Toolbox::GenerateUuid();
+    contentTypeHeader = ("multipart/" + subType + "; type=" + tmp + "; boundary=" + boundary);
+  }
+
+
+  void HttpOutput::StateMachine::StartMultipart(const std::string& subType,
+                                                const std::string& contentType)
+  {
     if (state_ != State_WritingHeader)
     {
       throw OrthancException(ErrorCode_BadSequenceOfCalls);
@@ -464,65 +515,31 @@
     }
 
     // Possibly add the cookies
+    CheckHeadersCompatibilityWithMultipart();
+
     for (std::list<std::string>::const_iterator
            it = headers_.begin(); it != headers_.end(); ++it)
     {
-      if (!Toolbox::StartsWith(*it, "Set-Cookie: "))
-      {
-        throw OrthancException(ErrorCode_BadSequenceOfCalls,
-                               "The only headers that can be set in multipart answers "
-                               "are Set-Cookie (here: " + *it + " is set)");
-      }
-
       header += *it;
     }
 
-    /**
-     * Fix for issue 54 ("Decide what to do wrt. quoting of multipart
-     * answers"). The "type" parameter in the "Content-Type" HTTP
-     * header must be quoted if it contains a forward slash "/". This
-     * is necessary for DICOMweb compatibility with OsiriX, but breaks
-     * compatibility with old releases of the client in the Orthanc
-     * DICOMweb plugin <= 0.3 (releases >= 0.4 work fine).
-     *
-     * Full history is available at the following locations:
-     * - In changeset 2248:69b0f4e8a49b:
-     *   # hg history -v -r 2248
-     * - https://bitbucket.org/sjodogne/orthanc/issues/54/
-     * - https://groups.google.com/d/msg/orthanc-users/65zhIM5xbKI/TU5Q1_LhAwAJ
-     **/
-    std::string tmp;
-    if (contentType.find('/') == std::string::npos)
-    {
-      // No forward slash in the content type
-      tmp = contentType;
-    }
-    else
-    {
-      // Quote the content type because of the forward slash
-      tmp = "\"" + contentType + "\"";
-    }
-
-    multipartBoundary_ = Toolbox::GenerateUuid() + "-" + Toolbox::GenerateUuid();
+    std::string contentTypeHeader;
+    PrepareMultipartMainHeader(multipartBoundary_, contentTypeHeader, subType, contentType);
     multipartContentType_ = contentType;
-    header += ("Content-Type: multipart/" + subType + "; type=" +
-               tmp + "; boundary=" + multipartBoundary_ + "\r\n\r\n");
+    header += ("Content-Type: " + contentTypeHeader + "\r\n\r\n");
 
     stream_.Send(true, header.c_str(), header.size());
     state_ = State_WritingMultipart;
   }
 
 
-  void HttpOutput::StateMachine::SendMultipartItem(const void* item, 
-                                                   size_t length,
-                                                   const std::map<std::string, std::string>& headers)
+  static void PrepareMultipartItemHeader(std::string& target,
+                                         size_t length,
+                                         const std::map<std::string, std::string>& headers,
+                                         const std::string& boundary,
+                                         const std::string& contentType)
   {
-    if (state_ != State_WritingMultipart)
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-
-    std::string header = "--" + multipartBoundary_ + "\r\n";
+    target = "--" + boundary + "\r\n";
 
     bool hasContentType = false;
     bool hasContentLength = false;
@@ -531,7 +548,7 @@
     for (std::map<std::string, std::string>::const_iterator
            it = headers.begin(); it != headers.end(); ++it)
     {
-      header += it->first + ": " + it->second + "\r\n";
+      target += it->first + ": " + it->second + "\r\n";
 
       std::string tmp;
       Toolbox::ToLowerCase(tmp, it->first);
@@ -554,19 +571,32 @@
 
     if (!hasContentType)
     {
-      header += "Content-Type: " + multipartContentType_ + "\r\n";
+      target += "Content-Type: " + contentType + "\r\n";
     }
 
     if (!hasContentLength)
     {
-      header += "Content-Length: " + boost::lexical_cast<std::string>(length) + "\r\n";
+      target += "Content-Length: " + boost::lexical_cast<std::string>(length) + "\r\n";
     }
 
     if (!hasMimeVersion)
     {
-      header += "MIME-Version: 1.0\r\n\r\n";
+      target += "MIME-Version: 1.0\r\n\r\n";
+    }
+  }
+
+
+  void HttpOutput::StateMachine::SendMultipartItem(const void* item,
+                                                   size_t length,
+                                                   const std::map<std::string, std::string>& headers)
+  {
+    if (state_ != State_WritingMultipart)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
     }
 
+    std::string header;
+    PrepareMultipartItemHeader(header, length, headers, multipartBoundary_, multipartContentType_);
     stream_.Send(false, header.c_str(), header.size());
 
     if (length > 0)
@@ -685,4 +715,43 @@
     stateMachine_.CloseBody();
   }
 
+
+  void HttpOutput::AnswerMultipartWithoutChunkedTransfer(
+    const std::string& subType,
+    const std::string& contentType,
+    const std::vector<const void*>& parts,
+    const std::vector<size_t>& sizes,
+    const std::vector<const std::map<std::string, std::string>*>& headers)
+  {
+    if (parts.size() != sizes.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    stateMachine_.CheckHeadersCompatibilityWithMultipart();
+
+    std::string boundary, contentTypeHeader;
+    PrepareMultipartMainHeader(boundary, contentTypeHeader, subType, contentType);
+    SetContentType(contentTypeHeader);
+
+    std::map<std::string, std::string> empty;
+
+    ChunkedBuffer chunked;
+    for (size_t i = 0; i < parts.size(); i++)
+    {
+      std::string partHeader;
+      PrepareMultipartItemHeader(partHeader, sizes[i], headers[i] == NULL ? empty : *headers[i], 
+                                 boundary, contentType);
+
+      chunked.AddChunk(partHeader);
+      chunked.AddChunk(parts[i], sizes[i]);
+      chunked.AddChunk("\r\n");    
+    }
+
+    chunked.AddChunk("--" + boundary + "--\r\n");
+
+    std::string body;
+    chunked.Flatten(body);
+    Answer(body);
+  }
 }