Skip to content

Commits on Source 7

repo: 3959d33612ccaadc0d4d707227fbed09ac35e5fe
node: 90140b7796a1738a525f3a9d1ef404cb35e5e2c7
branch: Orthanc-1.5.7
node: 4beabcea3a5cb79506ca1f9cd28d6a6b8c7b7f03
branch: Orthanc-1.5.8
latesttag: dcmtk-3.6.1
latesttagdistance: 938
changessincelatesttag: 1066
latesttagdistance: 1010
changessincelatesttag: 1158
......@@ -35,6 +35,7 @@
#include <list>
#include <map>
#include <vector>
#include <boost/noncopyable.hpp>
#include <cassert>
......@@ -151,6 +152,17 @@ namespace Orthanc
const T& GetOldest() const;
const Payload& GetOldestPayload() const;
void GetAllKeys(std::vector<T>& keys) const
{
keys.clear();
keys.reserve(GetSize());
for (typename Index::const_iterator it = index_.begin(); it != index_.end(); it++)
{
keys.push_back(it->first);
}
}
};
......
......@@ -62,6 +62,6 @@ namespace Orthanc
return *elements_[i];
}
void Print(FILE* fp) const;
void Print(FILE* fp) const; // For debugging only
};
}
......@@ -116,8 +116,9 @@ namespace Orthanc
photometric_ = PhotometricInterpretation_Unknown;
}
width_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_COLUMNS).GetContent());
height_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_ROWS).GetContent());
values.GetValue(DICOM_TAG_COLUMNS).ParseFirstUnsignedInteger(width_); // in some US images, we've seen tag values of "800\0"; that's why we parse the 'first' value
values.GetValue(DICOM_TAG_ROWS).ParseFirstUnsignedInteger(height_);
bitsAllocated_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_BITS_ALLOCATED).GetContent());
try
......
......@@ -40,6 +40,8 @@
#include "../Endianness.h"
#include "../Logging.h"
#include "../OrthancException.h"
#include "../Toolbox.h"
#include "DicomArray.h"
namespace Orthanc
......@@ -875,7 +877,7 @@ namespace Orthanc
}
bool DicomMap::CopyToString(std::string& result,
bool DicomMap::LookupStringValue(std::string& result,
const DicomTag& tag,
bool allowBinary) const
{
......@@ -984,6 +986,11 @@ namespace Orthanc
void DicomMap::FromDicomAsJson(const Json::Value& dicomAsJson)
{
if (dicomAsJson.type() != Json::objectValue)
{
throw OrthancException(ErrorCode_BadFileFormat);
}
Clear();
Json::Value::Members tags = dicomAsJson.getMemberNames();
......@@ -1133,4 +1140,163 @@ namespace Orthanc
map_[tag] = value.release();
}
}
void DicomMap::FromDicomWeb(const Json::Value& source)
{
static const char* const ALPHABETIC = "Alphabetic";
static const char* const IDEOGRAPHIC = "Ideographic";
static const char* const INLINE_BINARY = "InlineBinary";
static const char* const PHONETIC = "Phonetic";
static const char* const VALUE = "Value";
static const char* const VR = "vr";
Clear();
if (source.type() != Json::objectValue)
{
throw OrthancException(ErrorCode_BadFileFormat);
}
Json::Value::Members tags = source.getMemberNames();
for (size_t i = 0; i < tags.size(); i++)
{
const Json::Value& item = source[tags[i]];
DicomTag tag(0, 0);
if (item.type() != Json::objectValue ||
!item.isMember(VR) ||
item[VR].type() != Json::stringValue ||
!DicomTag::ParseHexadecimal(tag, tags[i].c_str()))
{
throw OrthancException(ErrorCode_BadFileFormat);
}
ValueRepresentation vr = StringToValueRepresentation(item[VR].asString(), false);
if (item.isMember(INLINE_BINARY))
{
const Json::Value& value = item[INLINE_BINARY];
if (value.type() == Json::stringValue)
{
std::string decoded;
Toolbox::DecodeBase64(decoded, value.asString());
SetValue(tag, decoded, true /* binary data */);
}
}
else if (!item.isMember(VALUE))
{
// Tag is present, but it has a null value
SetValue(tag, "", false /* not binary */);
}
else
{
const Json::Value& value = item[VALUE];
if (value.type() == Json::arrayValue)
{
bool supported = true;
std::string s;
for (Json::Value::ArrayIndex i = 0; i < value.size() && supported; i++)
{
if (!s.empty())
{
s += '\\';
}
switch (value[i].type())
{
case Json::objectValue:
if (vr == ValueRepresentation_PersonName &&
value[i].type() == Json::objectValue)
{
if (value[i].isMember(ALPHABETIC) &&
value[i][ALPHABETIC].type() == Json::stringValue)
{
s += value[i][ALPHABETIC].asString();
}
bool hasIdeographic = false;
if (value[i].isMember(IDEOGRAPHIC) &&
value[i][IDEOGRAPHIC].type() == Json::stringValue)
{
s += '=' + value[i][IDEOGRAPHIC].asString();
hasIdeographic = true;
}
if (value[i].isMember(PHONETIC) &&
value[i][PHONETIC].type() == Json::stringValue)
{
if (!hasIdeographic)
{
s += '=';
}
s += '=' + value[i][PHONETIC].asString();
}
}
else
{
// This is the case of sequences
supported = false;
}
break;
case Json::stringValue:
s += value[i].asString();
break;
case Json::intValue:
s += boost::lexical_cast<std::string>(value[i].asInt());
break;
case Json::uintValue:
s += boost::lexical_cast<std::string>(value[i].asUInt());
break;
case Json::realValue:
s += boost::lexical_cast<std::string>(value[i].asDouble());
break;
default:
break;
}
}
if (supported)
{
SetValue(tag, s, false /* not binary */);
}
}
}
}
}
std::string DicomMap::GetStringValue(const DicomTag& tag,
const std::string& defaultValue,
bool allowBinary) const
{
std::string s;
if (LookupStringValue(s, tag, allowBinary))
{
return s;
}
else
{
return defaultValue;
}
}
void DicomMap::Print(FILE* fp) const
{
DicomArray a(*this);
a.Print(fp);
}
}
......@@ -198,7 +198,7 @@ namespace Orthanc
void LogMissingTagsForStore() const;
bool CopyToString(std::string& result,
bool LookupStringValue(std::string& result,
const DicomTag& tag,
bool allowBinary) const;
......@@ -231,5 +231,13 @@ namespace Orthanc
void Serialize(Json::Value& target) const;
void Unserialize(const Json::Value& source);
void FromDicomWeb(const Json::Value& source);
std::string GetStringValue(const DicomTag& tag,
const std::string& defaultValue,
bool allowBinary) const;
void Print(FILE* fp) const; // For debugging only
};
}
......@@ -200,6 +200,12 @@ namespace Orthanc
static const DicomTag DICOM_TAG_WINDOW_CENTER(0x0028, 0x1050);
static const DicomTag DICOM_TAG_WINDOW_WIDTH(0x0028, 0x1051);
static const DicomTag DICOM_TAG_DOSE_GRID_SCALING(0x3004, 0x000e);
static const DicomTag DICOM_TAG_RED_PALETTE_COLOR_LOOKUP_TABLE_DATA(0x0028, 0x1201);
static const DicomTag DICOM_TAG_GREEN_PALETTE_COLOR_LOOKUP_TABLE_DATA(0x0028, 0x1202);
static const DicomTag DICOM_TAG_BLUE_PALETTE_COLOR_LOOKUP_TABLE_DATA(0x0028, 0x1203);
static const DicomTag DICOM_TAG_RED_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR(0x0028, 0x1101);
static const DicomTag DICOM_TAG_GREEN_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR(0x0028, 0x1102);
static const DicomTag DICOM_TAG_BLUE_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR(0x0028, 0x1103);
// Counting patients, studies and series
// https://www.medicalconnections.co.uk/kb/Counting_Studies_Series_and_Instances
......@@ -220,4 +226,7 @@ namespace Orthanc
static const DicomTag DICOM_TAG_RELATED_FRAME_OF_REFERENCE_UID(0x3006, 0x00c2);
static const DicomTag DICOM_TAG_CURRENT_REQUESTED_PROCEDURE_EVIDENCE_SEQUENCE(0x0040, 0xa375);
static const DicomTag DICOM_TAG_REFERENCED_SERIES_SEQUENCE(0x0008, 0x1115);
static const DicomTag DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE(0x3006, 0x0010);
static const DicomTag DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE(0x3006, 0x0012);
static const DicomTag DICOM_TAG_RT_REFERENCED_SERIES_SEQUENCE(0x3006, 0x0014);
}
......@@ -94,6 +94,60 @@ namespace Orthanc
}
#endif
// same as ParseValue but in case the value actually contains a sequence,
// it will return the first value
// this has been introduced to support invalid "width/height" DICOM tags in some US
// images where the width is stored as "800\0" !
template <typename T,
bool allowSigned>
static bool ParseFirstValue(T& result,
const DicomValue& source)
{
if (source.IsBinary() ||
source.IsNull())
{
return false;
}
try
{
std::string value = Toolbox::StripSpaces(source.GetContent());
if (value.empty())
{
return false;
}
if (!allowSigned &&
value[0] == '-')
{
return false;
}
if (value.find("\\") == std::string::npos)
{
result = boost::lexical_cast<T>(value);
return true;
}
else
{
std::vector<std::string> tokens;
Toolbox::TokenizeString(tokens, value, '\\');
if (tokens.size() >= 1)
{
result = boost::lexical_cast<T>(tokens[0]);
return true;
}
return false;
}
}
catch (boost::bad_lexical_cast&)
{
return false;
}
}
template <typename T,
bool allowSigned>
......@@ -177,6 +231,11 @@ namespace Orthanc
return ParseValue<double, true>(result, *this);
}
bool DicomValue::ParseFirstUnsignedInteger(unsigned int& result) const
{
return ParseFirstValue<unsigned int, true>(result, *this);
}
bool DicomValue::CopyToString(std::string& result,
bool allowBinary) const
{
......
......@@ -112,6 +112,8 @@ namespace Orthanc
bool ParseDouble(double& result) const;
bool ParseFirstUnsignedInteger(unsigned int& result) const;
void Serialize(Json::Value& target) const;
void Unserialize(const Json::Value& source);
......
......@@ -459,7 +459,7 @@ namespace Orthanc
// The global variable "numberOfDcmAllStorageSOPClassUIDs" is
// only published if DCMTK >= 3.6.2:
// https://bitbucket.org/sjodogne/orthanc/issues/137
assert(count == numberOfDcmAllStorageSOPClassUIDs);
assert(static_cast<int>(count) == numberOfDcmAllStorageSOPClassUIDs);
#endif
cond = ASC_acceptContextsWithPreferredTransferSyntaxes(
......
......@@ -83,6 +83,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "../../PrecompiledHeaders.h"
#include "FindScp.h"
#include "../../DicomFormat/DicomArray.h"
#include "../../DicomParsing/FromDcmtkBridge.h"
#include "../../DicomParsing/ToDcmtkBridge.h"
#include "../../Logging.h"
......
......@@ -135,6 +135,17 @@ namespace Orthanc
{
return Action_None;
}
else if (parentTags.size() == 2 &&
parentTags[0] == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE &&
parentTags[1] == DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE &&
tag == DICOM_TAG_REFERENCED_SOP_INSTANCE_UID)
{
// in RT-STRUCT, this ReferencedSOPInstanceUID is actually referencing a StudyInstanceUID !!
// (observed in many data sets including: https://wiki.cancerimagingarchive.net/display/Public/Lung+CT+Segmentation+Challenge+2017)
// tested in test_anonymize_relationships_5
newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Study);
return Action_Replace;
}
else if (tag == DICOM_TAG_FRAME_OF_REFERENCE_UID ||
tag == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_UID ||
tag == DICOM_TAG_REFERENCED_SOP_INSTANCE_UID ||
......@@ -158,6 +169,15 @@ namespace Orthanc
newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series);
return Action_Replace;
}
else if (parentTags.size() == 3 &&
parentTags[0] == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE &&
parentTags[1] == DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE &&
parentTags[2] == DICOM_TAG_RT_REFERENCED_SERIES_SEQUENCE &&
tag == DICOM_TAG_SERIES_INSTANCE_UID)
{
newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series);
return Action_Replace;
}
else if (parentTags.size() == 1 &&
parentTags[0] == DICOM_TAG_REFERENCED_SERIES_SEQUENCE &&
tag == DICOM_TAG_SERIES_INSTANCE_UID)
......@@ -249,7 +269,17 @@ namespace Orthanc
}
}
void DicomModification::RegisterMappedDicomIdentifier(const std::string& original,
const std::string& mapped,
ResourceType level)
{
UidMap::const_iterator previous = uidMap_.find(std::make_pair(level, original));
if (previous == uidMap_.end())
{
uidMap_.insert(std::make_pair(std::make_pair(level, original), mapped));
}
}
std::string DicomModification::MapDicomIdentifier(const std::string& original,
ResourceType level)
......@@ -976,7 +1006,6 @@ namespace Orthanc
"When modifying an instance, the parent SeriesInstanceUID cannot be manually modified");
}
// (0) Create a summary of the source file, if a custom generator
// is provided
if (identifierGenerator_ != NULL)
......@@ -984,35 +1013,64 @@ namespace Orthanc
toModify.ExtractDicomSummary(currentSource_);
}
// (1) Make sure the relationships are updated with the ids that we force too
// i.e: an RT-STRUCT is referencing its own StudyInstanceUID
if (isAnonymization_ && updateReferencedRelationships_)
{
if (IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
{
std::string original;
std::string replacement = GetReplacementAsString(DICOM_TAG_STUDY_INSTANCE_UID);
toModify.GetTagValue(original, DICOM_TAG_STUDY_INSTANCE_UID);
RegisterMappedDicomIdentifier(original, replacement, ResourceType_Study);
}
if (IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
{
std::string original;
std::string replacement = GetReplacementAsString(DICOM_TAG_SERIES_INSTANCE_UID);
toModify.GetTagValue(original, DICOM_TAG_SERIES_INSTANCE_UID);
RegisterMappedDicomIdentifier(original, replacement, ResourceType_Series);
}
if (IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
{
std::string original;
std::string replacement = GetReplacementAsString(DICOM_TAG_SOP_INSTANCE_UID);
toModify.GetTagValue(original, DICOM_TAG_SOP_INSTANCE_UID);
RegisterMappedDicomIdentifier(original, replacement, ResourceType_Instance);
}
}
// (1) Remove the private tags, if need be
// (2) Remove the private tags, if need be
if (removePrivateTags_)
{
toModify.RemovePrivateTags(privateTagsToKeep_);
}
// (2) Clear the tags specified by the user
// (3) Clear the tags specified by the user
for (SetOfTags::const_iterator it = clearings_.begin();
it != clearings_.end(); ++it)
{
toModify.Clear(*it, true /* only clear if the tag exists in the original file */);
}
// (3) Remove the tags specified by the user
// (4) Remove the tags specified by the user
for (SetOfTags::const_iterator it = removals_.begin();
it != removals_.end(); ++it)
{
toModify.Remove(*it);
}
// (4) Replace the tags
// (5) Replace the tags
for (Replacements::const_iterator it = replacements_.begin();
it != replacements_.end(); ++it)
{
toModify.Replace(it->first, *it->second, true /* decode data URI scheme */, DicomReplaceMode_InsertIfAbsent);
}
// (5) Update the DICOM identifiers
// (6) Update the DICOM identifiers
if (level_ <= ResourceType_Study &&
!IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
{
......@@ -1045,7 +1103,7 @@ namespace Orthanc
MapDicomTags(toModify, ResourceType_Instance);
}
// (6) Update the "referenced" relationships in the case of an anonymization
// (7) Update the "referenced" relationships in the case of an anonymization
if (isAnonymization_)
{
RelationshipsVisitor visitor(*this);
......
......@@ -92,6 +92,10 @@ namespace Orthanc
std::string MapDicomIdentifier(const std::string& original,
ResourceType level);
void RegisterMappedDicomIdentifier(const std::string& original,
const std::string& mapped,
ResourceType level);
void MapDicomTags(ParsedDicomFile& dicom,
ResourceType level);
......
......@@ -653,7 +653,15 @@ namespace Orthanc
}
catch (boost::bad_lexical_cast&)
{
throw OrthancException(ErrorCode_BadFileFormat);
std::string tmp;
if (value.size() < 64 &&
Toolbox::IsAsciiString(value))
{
tmp = ": " + value;
}
LOG(WARNING) << "Ignoring DICOM tag (" << tag.Format()
<< ") with invalid content for VR " << EnumerationToString(vr) << tmp;
}
}
}
......
......@@ -357,6 +357,8 @@ namespace Orthanc
static void CopyPixels(ImageAccessor& target,
const DicomIntegerPixelAccessor& source)
{
// WARNING - "::min()" should be replaced by "::lowest()" if
// dealing with float or double (which is not the case so far)
const PixelType minValue = std::numeric_limits<PixelType>::min();
const PixelType maxValue = std::numeric_limits<PixelType>::max();
......
......@@ -1141,6 +1141,25 @@ namespace Orthanc
}
const char* EnumerationToString(Endianness endianness)
{
switch (endianness)
{
case Endianness_Little:
return "Little-endian";
case Endianness_Big:
return "Big-endian";
case Endianness_Unknown:
return "Unknown endianness";
default:
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
}
Encoding StringToEncoding(const char* encoding)
{
std::string s(encoding);
......
......@@ -749,6 +749,8 @@ namespace Orthanc
const char* EnumerationToString(MimeType mime);
const char* EnumerationToString(Endianness endianness);
Encoding StringToEncoding(const char* encoding);
ResourceType StringToResourceType(const char* type);
......
......@@ -933,6 +933,8 @@ namespace Orthanc
CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &answer));
const boost::posix_time::ptime start = boost::posix_time::microsec_clock::universal_time();
if (boost::starts_with(url_, "https://"))
{
code = OrthancHttpClientPerformSSL(pimpl_->curl_, &status);
......@@ -942,7 +944,10 @@ namespace Orthanc
code = GetHttpStatus(curl_easy_perform(pimpl_->curl_), pimpl_->curl_, &status);
}
LOG(INFO) << "HTTP status code " << status << " after "
const boost::posix_time::ptime end = boost::posix_time::microsec_clock::universal_time();
LOG(INFO) << "HTTP status code " << status << " in "
<< ((end - start).total_milliseconds()) << " ms after "
<< EnumerationToString(method_) << " request on: " << url_;
if (isVerbose_)
......
......@@ -410,7 +410,24 @@ namespace Orthanc
}
void HttpOutput::StateMachine::StartMultipart(const std::string& subType,
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" &&
......@@ -419,6 +436,40 @@ namespace Orthanc
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 @@ namespace Orthanc
}
// 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,
static void PrepareMultipartItemHeader(std::string& target,
size_t length,
const std::map<std::string, std::string>& headers)
{
if (state_ != State_WritingMultipart)
const std::map<std::string, std::string>& headers,
const std::string& boundary,
const std::string& contentType)
{
throw OrthancException(ErrorCode_BadSequenceOfCalls);
}
std::string header = "--" + multipartBoundary_ + "\r\n";
target = "--" + boundary + "\r\n";
bool hasContentType = false;
bool hasContentLength = false;
......@@ -531,7 +548,7 @@ namespace Orthanc
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 @@ namespace Orthanc
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 @@ namespace Orthanc
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);
}
}
......@@ -41,6 +41,7 @@
#include <string>
#include <stdint.h>
#include <map>
#include <vector>
namespace Orthanc
{
......@@ -113,6 +114,8 @@ namespace Orthanc
{
return state_;
}
void CheckHeadersCompatibilityWithMultipart() const;
};
StateMachine stateMachine_;
......@@ -229,5 +232,19 @@ namespace Orthanc
}
void Answer(IHttpStreamAnswer& stream);
/**
* This method is a replacement to the combination
* "StartMultipart()" + "SendMultipartItem()". It generates the
* same answer, but it gives a chance to compress the body if
* "Accept-Encoding: gzip" is provided by the client, which is not
* possible in chunked transfers.
**/
void 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);
};
}