Skip to content
Commits on Source (7)
repo: 3959d33612ccaadc0d4d707227fbed09ac35e5fe
node: 218854b02a71c1ba79b446bf4273179b0098ae48
branch: Orthanc-1.4.1
node: afb581263e1d79e67ebf2fc1ca85780f9f3783e5
branch: Orthanc-1.4.2
latesttag: dcmtk-3.6.1
latesttagdistance: 366
changessincelatesttag: 408
latesttagdistance: 423
changessincelatesttag: 465
......@@ -15,6 +15,6 @@ Authors of Orthanc
Belgium
* Osimis S.A. <info@osimis.io>
Rue des Chasseurs Ardennais 3
4031 Liege
Rue du Bois Saint-Jean 15/1
4102 Seraing
Belgium
......@@ -125,6 +125,7 @@ if (ENABLE_PLUGINS)
Plugins/Engine/OrthancPlugins.cpp
Plugins/Engine/PluginsEnumerations.cpp
Plugins/Engine/PluginsErrorDictionary.cpp
Plugins/Engine/PluginsJob.cpp
Plugins/Engine/PluginsManager.cpp
)
......
......@@ -109,7 +109,16 @@ namespace Orthanc
DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER,
DICOM_TAG_SOP_INSTANCE_UID,
DICOM_TAG_IMAGE_POSITION_PATIENT, // New in db v6
DICOM_TAG_IMAGE_COMMENTS // New in db v6
DICOM_TAG_IMAGE_COMMENTS, // New in db v6
/**
* Main DICOM tags that are not part of any release of the
* database schema yet, and that will be part of future db v7. In
* the meantime, the user must call "/tools/reconstruct" once to
* access these tags if the corresponding DICOM files where
* indexed in the database by an older version of Orthanc.
**/
DICOM_TAG_IMAGE_ORIENTATION_PATIENT // New in Orthanc 1.4.2
};
......
......@@ -527,8 +527,6 @@ namespace Orthanc
transferSyntaxes.push_back(UID_JPEGProcess28TransferSyntax);
transferSyntaxes.push_back(UID_JPEGProcess29TransferSyntax);
transferSyntaxes.push_back(UID_JPEGProcess14SV1TransferSyntax);
transferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax);
transferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax);
}
if (!server.HasApplicationEntityFilter() ||
......@@ -536,15 +534,17 @@ namespace Orthanc
{
transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax);
transferSyntaxes.push_back(UID_JPEG2000TransferSyntax);
transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax);
transferSyntaxes.push_back(UID_JPEG2000TransferSyntax);
transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax);
transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax);
}
if (!server.HasApplicationEntityFilter() ||
server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_JpegLossless))
{
transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax);
transferSyntaxes.push_back(UID_JPEG2000TransferSyntax);
transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax);
transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax);
transferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax);
transferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax);
}
if (!server.HasApplicationEntityFilter() ||
......
......@@ -1714,11 +1714,32 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
{
std::auto_ptr<DcmItem> item(new DcmItem);
switch (value[i].type())
{
case Json::objectValue:
{
Json::Value::Members members = value[i].getMemberNames();
for (Json::Value::ArrayIndex j = 0; j < members.size(); j++)
{
item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeDataUriScheme, dicomEncoding));
}
break;
}
case Json::arrayValue:
{
// Lua cannot disambiguate between an empty dictionary
// and an empty array
if (value[i].size() != 0)
{
throw OrthancException(ErrorCode_BadParameterType);
}
break;
}
default:
throw OrthancException(ErrorCode_BadParameterType);
}
sequence->append(item.release());
}
......
......@@ -525,6 +525,13 @@ namespace Orthanc
void ParsedDicomFile::Clear(const DicomTag& tag,
bool onlyIfExists)
{
if (tag.GetElement() == 0x0000)
{
// Prevent manually modifying generic group length tags: This is
// handled by DCMTK serialization
return;
}
InvalidateCache();
DcmItem* dicom = pimpl_->file_->getDataset();
......@@ -612,6 +619,13 @@ namespace Orthanc
const Json::Value& value,
bool decodeDataUriScheme)
{
if (tag.GetElement() == 0x0000)
{
// Prevent manually modifying generic group length tags: This is
// handled by DCMTK serialization
return;
}
if (pimpl_->file_->getDataset()->tagExists(ToDcmtkBridge::Convert(tag)))
{
throw OrthancException(ErrorCode_AlreadyExistingTag);
......@@ -724,6 +738,13 @@ namespace Orthanc
bool decodeDataUriScheme,
DicomReplaceMode mode)
{
if (tag.GetElement() == 0x0000)
{
// Prevent manually modifying generic group length tags: This is
// handled by DCMTK serialization
return;
}
InvalidateCache();
DcmDataset& dicom = *pimpl_->file_->getDataset();
......@@ -756,6 +777,13 @@ namespace Orthanc
bool decodeDataUriScheme,
DicomReplaceMode mode)
{
if (tag.GetElement() == 0x0000)
{
// Prevent manually modifying generic group length tags: This is
// handled by DCMTK serialization
return;
}
InvalidateCache();
DcmDataset& dicom = *pimpl_->file_->getDataset();
......
......@@ -145,13 +145,18 @@
static inline uint16_t __orthanc_bswap16(uint16_t a)
{
const uint8_t* p = reinterpret_cast<const uint8_t*>(&a);
return (static_cast<uint16_t>(p[0]) << 8 |
static_cast<uint16_t>(p[1]));
// WARNING: The implementation below makes LSB (Linux Standard
// Base) segfault in release builds. Don't use it!!!
// return (a << 8) | (a >> 8);
/**
* Note that an alternative implementation was included in Orthanc
* 1.4.0 and 1.4.1:
*
* # hg log -p -r 2706
*
* This alternative implementation only hid an underlying problem
* with pointer alignment on some architectures, and was thus
* reverted. Check out issue #99:
* https://bitbucket.org/sjodogne/orthanc/issues/99
**/
return (a << 8) | (a >> 8);
}
static inline uint32_t __orthanc_bswap32(uint32_t a)
......
......@@ -509,7 +509,10 @@ namespace Orthanc
enum DicomFromJsonFlags
{
DicomFromJsonFlags_DecodeDataUriScheme = (1 << 0),
DicomFromJsonFlags_GenerateIdentifiers = (1 << 1)
DicomFromJsonFlags_GenerateIdentifiers = (1 << 1),
// Some predefined combinations
DicomFromJsonFlags_None = 0
};
enum DicomVersion
......@@ -567,6 +570,15 @@ namespace Orthanc
JobStepCode_Retry
};
enum JobStopReason
{
JobStopReason_Paused,
JobStopReason_Canceled,
JobStopReason_Success,
JobStopReason_Failure,
JobStopReason_Retry
};
/**
* WARNING: Do not change the explicit values in the enumerations
......
......@@ -35,6 +35,7 @@
#include "MemoryStorageArea.h"
#include "../OrthancException.h"
#include "../Logging.h"
namespace Orthanc
{
......@@ -54,6 +55,9 @@ namespace Orthanc
size_t size,
FileContentType type)
{
LOG(INFO) << "Creating attachment \"" << uuid << "\" of \"" << static_cast<int>(type)
<< "\" type (size: " << (size / (1024 * 1024) + 1) << "MB)";
boost::mutex::scoped_lock lock(mutex_);
if (size != 0 &&
......@@ -76,6 +80,9 @@ namespace Orthanc
const std::string& uuid,
FileContentType type)
{
LOG(INFO) << "Reading attachment \"" << uuid << "\" of \""
<< static_cast<int>(type) << "\" content type";
boost::mutex::scoped_lock lock(mutex_);
Content::const_iterator found = content_.find(uuid);
......@@ -98,6 +105,8 @@ namespace Orthanc
void MemoryStorageArea::Remove(const std::string& uuid,
FileContentType type)
{
LOG(INFO) << "Deleting attachment \"" << uuid << "\" of type " << static_cast<int>(type);
boost::mutex::scoped_lock lock(mutex_);
Content::iterator found = content_.find(uuid);
......
......@@ -95,10 +95,12 @@ namespace Orthanc
std::string httpsCACertificates_;
std::string proxy_;
long timeout_;
bool verbose_;
GlobalParameters() :
httpsVerifyPeers_(true),
timeout_(0)
timeout_(0),
verbose_(false)
{
}
......@@ -173,6 +175,16 @@ namespace Orthanc
Pkcs11::Initialize(module, pin, verbose);
}
#endif
bool IsDefaultVerbose() const
{
return verbose_;
}
void SetDefaultVerbose(bool verbose)
{
verbose_ = verbose;
}
};
......@@ -184,7 +196,7 @@ namespace Orthanc
};
static void ThrowException(HttpStatus status)
void HttpClient::ThrowException(HttpStatus status)
{
switch (status)
{
......@@ -240,6 +252,36 @@ namespace Orthanc
}
/*static int CurlDebugCallback(CURL *handle,
curl_infotype type,
char *data,
size_t size,
void *userptr)
{
switch (type)
{
case CURLINFO_TEXT:
case CURLINFO_HEADER_IN:
case CURLINFO_HEADER_OUT:
case CURLINFO_SSL_DATA_IN:
case CURLINFO_SSL_DATA_OUT:
case CURLINFO_END:
case CURLINFO_DATA_IN:
case CURLINFO_DATA_OUT:
{
std::string s(data, size);
LOG(INFO) << "libcurl: " << s;
break;
}
default:
break;
}
return 0;
}*/
struct CurlHeaderParameters
{
bool lowerCase_;
......@@ -314,7 +356,7 @@ namespace Orthanc
url_ = "";
method_ = HttpMethod_Get;
lastStatus_ = HttpStatus_200_Ok;
SetVerbose(false);
SetVerbose(GlobalParameters::GetInstance().IsDefaultVerbose());
timeout_ = GlobalParameters::GetInstance().GetDefaultTimeout();
GlobalParameters::GetInstance().GetDefaultProxy(proxy_);
GlobalParameters::GetInstance().GetSslConfiguration(verifyPeers_, caCertificates_);
......@@ -358,6 +400,13 @@ namespace Orthanc
SetPkcs11Enabled(service.IsPkcs11Enabled());
SetUrl(service.GetUrl() + uri);
for (WebServiceParameters::Dictionary::const_iterator
it = service.GetHttpHeaders().begin();
it != service.GetHttpHeaders().end(); ++it)
{
AddHeader(it->first, it->second);
}
}
......@@ -376,6 +425,7 @@ namespace Orthanc
if (isVerbose_)
{
CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 1));
//CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_DEBUGFUNCTION, &CurlDebugCallback));
}
else
{
......@@ -613,6 +663,14 @@ namespace Orthanc
code = GetHttpStatus(curl_easy_perform(pimpl_->curl_), pimpl_->curl_, &status);
}
LOG(INFO) << "HTTP status code " << status << " after "
<< EnumerationToString(method_) << " request on: " << url_;
if (isVerbose_)
{
LOG(INFO) << "cURL status code: " << code;
}
CheckCode(code);
if (status == 0)
......@@ -711,6 +769,12 @@ namespace Orthanc
}
void HttpClient::SetDefaultVerbose(bool verbose)
{
GlobalParameters::GetInstance().SetDefaultVerbose(verbose);
}
void HttpClient::SetDefaultProxy(const std::string& proxy)
{
GlobalParameters::GetInstance().SetDefaultProxy(proxy);
......
......@@ -275,6 +275,8 @@ namespace Orthanc
static void ConfigureSsl(bool httpsVerifyPeers,
const std::string& httpsCACertificates);
static void SetDefaultVerbose(bool verbose);
static void SetDefaultProxy(const std::string& proxy);
static void SetDefaultTimeout(long timeout);
......@@ -288,5 +290,7 @@ namespace Orthanc
void ApplyAndThrowException(Json::Value& answerBody,
HttpHeaders& answerHeaders);
static void ThrowException(HttpStatus status);
};
}
......@@ -432,16 +432,44 @@ namespace Orthanc
{
if (!Toolbox::StartsWith(*it, "Set-Cookie: "))
{
LOG(ERROR) << "The only headers that can be set in multipart answers are Set-Cookie (here: " << *it << " is set)";
LOG(ERROR) << "The only headers that can be set in multipart answers "
<< "are Set-Cookie (here: " << *it << " is set)";
throw OrthancException(ErrorCode_BadSequenceOfCalls);
}
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();
multipartContentType_ = contentType;
header += "Content-Type: multipart/" + subType + "; type=" + contentType + "; boundary=" + multipartBoundary_ + "\r\n\r\n";
header += ("Content-Type: multipart/" + subType + "; type=" +
tmp + "; boundary=" + multipartBoundary_ + "\r\n\r\n");
stream_.Send(true, header.c_str(), header.size());
state_ = State_WritingMultipart;
......
......@@ -611,7 +611,7 @@ namespace Orthanc
if (!server.IsRemoteAccessAllowed() &&
!localhost)
{
output.SendUnauthorized(ORTHANC_REALM);
output.SendUnauthorized(server.GetRealm());
return;
}
......@@ -655,7 +655,7 @@ namespace Orthanc
if (server.IsAuthenticationEnabled() &&
!IsAccessGranted(server, headers))
{
output.SendUnauthorized(ORTHANC_REALM);
output.SendUnauthorized(server.GetRealm());
return;
}
......@@ -682,7 +682,7 @@ namespace Orthanc
if (!filter->IsAllowed(method, request->uri, remoteIp,
username.c_str(), headers, argumentsGET))
{
//output.SendUnauthorized(ORTHANC_REALM);
//output.SendUnauthorized(server.GetRealm());
output.SendStatus(HttpStatus_403_Forbidden);
return;
}
......@@ -917,6 +917,7 @@ namespace Orthanc
keepAlive_ = false;
httpCompression_ = true;
exceptionFormatter_ = NULL;
realm_ = ORTHANC_REALM;
#if ORTHANC_ENABLE_SSL == 1
// Check for the Heartbleed exploit
......
......@@ -96,6 +96,7 @@ namespace Orthanc
bool keepAlive_;
bool httpCompression_;
IHttpExceptionFormatter* exceptionFormatter_;
std::string realm_;
bool IsRunning() const;
......@@ -188,5 +189,15 @@ namespace Orthanc
{
return exceptionFormatter_;
}
const std::string& GetRealm() const
{
return realm_;
}
void SetRealm(const std::string& realm)
{
realm_ = realm;
}
};
}
......@@ -1219,6 +1219,19 @@ namespace Orthanc
break;
}
case Orthanc::PixelFormat_RGBA32:
{
PixelTraits<Orthanc::PixelFormat_RGBA32>::PixelType pixel;
pixel.red_ = red;
pixel.green_ = green;
pixel.blue_ = blue;
pixel.alpha_ = alpha;
BresenhamPixelWriter<Orthanc::PixelFormat_RGBA32> writer(image, pixel);
writer.DrawSegment(x0, y0, x1, y1);
break;
}
case Orthanc::PixelFormat_RGB24:
{
PixelTraits<Orthanc::PixelFormat_RGB24>::PixelType pixel;
......
......@@ -209,7 +209,8 @@ namespace Orthanc
throw OrthancException(ErrorCode_NotImplemented);
}
if (Toolbox::DetectEndianness() == Endianness_Little && bytesPerChannel == 2)
if (Toolbox::DetectEndianness() == Endianness_Little &&
bytesPerChannel == 2)
{
for (unsigned int h = 0; h < height; ++h)
{
......@@ -217,7 +218,12 @@ namespace Orthanc
for (unsigned int w = 0; w < GetWidth(); ++w, ++pixel)
{
*pixel = htobe16(*pixel);
// memcpy() is necessary to avoid segmentation fault if the
// "pixel" pointer is not 16-bit aligned (which is the case
// if "offset" is an odd number). Check out issue #99:
// https://bitbucket.org/sjodogne/orthanc/issues/99
uint16_t v = htobe16(*pixel);
memcpy(pixel, &v, sizeof(v));
}
}
}
......
......@@ -130,7 +130,13 @@ namespace Orthanc
for (unsigned int w = 0; w < width * channelCount; ++w)
{
*q = htobe16(*p);
// memcpy() is necessary to avoid segmentation fault if the
// "pixel" pointer is not 16-bit aligned (which is the case
// if "offset" is an odd number). Check out issue #99:
// https://bitbucket.org/sjodogne/orthanc/issues/99
uint16_t v = htobe16(*p);
memcpy(q, &v, sizeof(uint16_t));
p++;
q++;
}
......
......@@ -280,6 +280,67 @@ namespace Orthanc
};
template <>
struct PixelTraits<PixelFormat_RGBA32>
{
struct PixelType
{
uint8_t red_;
uint8_t green_;
uint8_t blue_;
uint8_t alpha_;
};
ORTHANC_FORCE_INLINE
static PixelFormat GetPixelFormat()
{
return PixelFormat_RGBA32;
}
ORTHANC_FORCE_INLINE
static void SetZero(PixelType& target)
{
target.red_ = 0;
target.green_ = 0;
target.blue_ = 0;
target.alpha_ = 0;
}
ORTHANC_FORCE_INLINE
static void Copy(PixelType& target,
const PixelType& source)
{
target.red_ = source.red_;
target.green_ = source.green_;
target.blue_ = source.blue_;
target.alpha_ = source.alpha_;
}
ORTHANC_FORCE_INLINE
static bool IsEqual(const PixelType& a,
const PixelType& b)
{
return (a.red_ == b.red_ &&
a.green_ == b.green_ &&
a.blue_ == b.blue_ &&
a.alpha_ == b.alpha_);
}
ORTHANC_FORCE_INLINE
static void FloatToPixel(PixelType& target,
float value)
{
uint8_t v;
PixelTraits<PixelFormat_Grayscale8>::FloatToPixel(v, value);
target.red_ = v;
target.green_ = v;
target.blue_ = v;
target.alpha_ = 255;
}
};
template <>
struct PixelTraits<PixelFormat_Float32>
{
......
......@@ -50,12 +50,13 @@ namespace Orthanc
// Method called once the job enters the jobs engine
virtual void Start() = 0;
virtual JobStepResult ExecuteStep() = 0;
virtual JobStepResult Step() = 0;
// Method called once the job is resubmitted after a failure
virtual void SignalResubmit() = 0;
virtual void Reset() = 0;
virtual void ReleaseResources() = 0; // For pausing/canceling jobs
// For pausing/canceling/ending jobs: This method must release allocated resources
virtual void Stop(JobStopReason reason) = 0;
virtual float GetProgress() = 0;
......