# HG changeset patch # User Sebastien Jodogne # Date 1522252927 -7200 # Node ID 97a74f0eac7a8feb4131d7d7706722e3b172fb12 # Parent 4dcafa8d6633c3b92304931f45c89e353fd1f36b loading DICOM dictionaries in sandboxed environments diff -r 4dcafa8d6633 -r 97a74f0eac7a CMakeLists.txt --- a/CMakeLists.txt Wed Mar 28 15:20:50 2018 +0200 +++ b/CMakeLists.txt Wed Mar 28 18:02:07 2018 +0200 @@ -47,8 +47,6 @@ include(${CMAKE_SOURCE_DIR}/Resources/CMake/VisualStudioPrecompiledHeaders.cmake) include(${CMAKE_SOURCE_DIR}/Resources/CMake/OrthancFrameworkConfiguration.cmake) -include_directories(${ORTHANC_ROOT}) - ##################################################################### ## List of source files diff -r 4dcafa8d6633 -r 97a74f0eac7a Core/DicomParsing/FromDcmtkBridge.cpp --- a/Core/DicomParsing/FromDcmtkBridge.cpp Wed Mar 28 15:20:50 2018 +0200 +++ b/Core/DicomParsing/FromDcmtkBridge.cpp Wed Mar 28 18:02:07 2018 +0200 @@ -48,7 +48,6 @@ #include "../OrthancException.h" #if ORTHANC_SANDBOXED == 0 -# include "../SystemToolbox.h" # include "../TemporaryFile.h" #endif @@ -134,6 +133,7 @@ std::string content; EmbeddedResources::GetFileResource(content, resource); +#if ORTHANC_SANDBOXED == 0 TemporaryFile tmp; tmp.Write(content); @@ -143,6 +143,14 @@ << "your TEMP directory does not contain special characters."; throw OrthancException(ErrorCode_InternalError); } +#else + if (!dictionary.loadFromMemory(content)) + { + LOG(ERROR) << "Cannot read embedded dictionary. Under Windows, make sure that " + << "your TEMP directory does not contain special characters."; + throw OrthancException(ErrorCode_InternalError); + } +#endif } #else diff -r 4dcafa8d6633 -r 97a74f0eac7a Core/Toolbox.cpp --- a/Core/Toolbox.cpp Wed Mar 28 15:20:50 2018 +0200 +++ b/Core/Toolbox.cpp Wed Mar 28 18:02:07 2018 +0200 @@ -103,6 +103,78 @@ namespace Orthanc { + void Toolbox::LinesIterator::FindEndOfLine() + { + lineEnd_ = lineStart_; + + while (lineEnd_ < content_.size() && + content_[lineEnd_] != '\n' && + content_[lineEnd_] != '\r') + { + lineEnd_ += 1; + } + } + + + Toolbox::LinesIterator::LinesIterator(const std::string& content) : + content_(content), + lineStart_(0) + { + FindEndOfLine(); + } + + + bool Toolbox::LinesIterator::GetLine(std::string& target) const + { + assert(lineStart_ <= content_.size() && + lineEnd_ <= content_.size() && + lineStart_ <= lineEnd_); + + if (lineStart_ == content_.size()) + { + return false; + } + else + { + target = content_.substr(lineStart_, lineEnd_ - lineStart_); + return true; + } + } + + + void Toolbox::LinesIterator::Next() + { + lineStart_ = lineEnd_; + + if (lineStart_ != content_.size()) + { + assert(content_[lineStart_] == '\r' || + content_[lineStart_] == '\n'); + + char second; + + if (content_[lineStart_] == '\r') + { + second = '\n'; + } + else + { + second = '\r'; + } + + lineStart_ += 1; + + if (lineStart_ < content_.size() && + content_[lineStart_] == second) + { + lineStart_ += 1; + } + + FindEndOfLine(); + } + } + + void Toolbox::ToUpperCase(std::string& s) { std::transform(s.begin(), s.end(), s.begin(), toupper); @@ -1413,3 +1485,42 @@ return s; } } + + + +OrthancLinesIterator* OrthancLinesIterator_Create(const std::string& content) +{ + return reinterpret_cast(new Orthanc::Toolbox::LinesIterator(content)); +} + + +bool OrthancLinesIterator_GetLine(std::string& target, + const OrthancLinesIterator* iterator) +{ + if (iterator != NULL) + { + return reinterpret_cast(iterator)->GetLine(target); + } + else + { + return false; + } +} + + +void OrthancLinesIterator_Next(OrthancLinesIterator* iterator) +{ + if (iterator != NULL) + { + reinterpret_cast(iterator)->Next(); + } +} + + +void OrthancLinesIterator_Free(OrthancLinesIterator* iterator) +{ + if (iterator != NULL) + { + delete reinterpret_cast(iterator); + } +} diff -r 4dcafa8d6633 -r 97a74f0eac7a Core/Toolbox.h --- a/Core/Toolbox.h Wed Mar 28 15:20:50 2018 +0200 +++ b/Core/Toolbox.h Wed Mar 28 18:02:07 2018 +0200 @@ -79,6 +79,24 @@ namespace Toolbox { + class LinesIterator + { + private: + const std::string& content_; + size_t lineStart_; + size_t lineEnd_; + + void FindEndOfLine(); + + public: + LinesIterator(const std::string& content); + + bool GetLine(std::string& target) const; + + void Next(); + }; + + void ToUpperCase(std::string& s); // Inplace version void ToLowerCase(std::string& s); // Inplace version @@ -218,3 +236,24 @@ std::string GenerateUuid(); } } + + + + +/** + * The plain C, opaque data structure "OrthancLinesIterator" is a thin + * wrapper around Orthanc::Toolbox::LinesIterator, and is only used by + * "../Resources/WebAssembly/dcdict.cc", in order to avoid code + * duplication + **/ + +struct OrthancLinesIterator; + +OrthancLinesIterator* OrthancLinesIterator_Create(const std::string& content); + +bool OrthancLinesIterator_GetLine(std::string& target, + const OrthancLinesIterator* iterator); + +void OrthancLinesIterator_Next(OrthancLinesIterator* iterator); + +void OrthancLinesIterator_Free(OrthancLinesIterator* iterator); diff -r 4dcafa8d6633 -r 97a74f0eac7a Resources/CMake/BoostConfiguration.cmake --- a/Resources/CMake/BoostConfiguration.cmake Wed Mar 28 15:20:50 2018 +0200 +++ b/Resources/CMake/BoostConfiguration.cmake Wed Mar 28 18:02:07 2018 +0200 @@ -233,7 +233,6 @@ ) if (CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR - CMAKE_SYSTEM_NAME STREQUAL "Emscripten" OR # For WebAssembly or asm.js CMAKE_SYSTEM_VERSION STREQUAL "LinuxStandardBase") list(APPEND BOOST_SOURCES ${BOOST_SOURCES_DIR}/libs/locale/src/std/codecvt.cpp @@ -255,7 +254,8 @@ CMAKE_SYSTEM_NAME STREQUAL "kFreeBSD" OR CMAKE_SYSTEM_NAME STREQUAL "PNaCl" OR CMAKE_SYSTEM_NAME STREQUAL "NaCl32" OR - CMAKE_SYSTEM_NAME STREQUAL "NaCl64") + CMAKE_SYSTEM_NAME STREQUAL "NaCl64" OR + CMAKE_SYSTEM_NAME STREQUAL "Emscripten") # For WebAssembly or asm.js list(APPEND BOOST_SOURCES ${BOOST_SOURCES_DIR}/libs/locale/src/posix/codecvt.cpp ${BOOST_SOURCES_DIR}/libs/locale/src/posix/collate.cpp diff -r 4dcafa8d6633 -r 97a74f0eac7a Resources/CMake/DcmtkConfiguration.cmake --- a/Resources/CMake/DcmtkConfiguration.cmake Wed Mar 28 15:20:50 2018 +0200 +++ b/Resources/CMake/DcmtkConfiguration.cmake Wed Mar 28 18:02:07 2018 +0200 @@ -107,6 +107,16 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") # WebAssembly or asm.js # Check out "../WebAssembly/arith.h" + UNSET(SIZEOF_VOID_P CACHE) + UNSET(SIZEOF_CHAR CACHE) + UNSET(SIZEOF_DOUBLE CACHE) + UNSET(SIZEOF_FLOAT CACHE) + UNSET(SIZEOF_INT CACHE) + UNSET(SIZEOF_LONG CACHE) + UNSET(SIZEOF_SHORT CACHE) + UNSET(SIZEOF_VOID_P CACHE) + UNSET(C_CHAR_UNSIGNED CACHE) + SET(SIZEOF_VOID_P 4 CACHE INTERNAL "") SET(SIZEOF_CHAR 1 CACHE INTERNAL "") SET(SIZEOF_DOUBLE 8 CACHE INTERNAL "") @@ -116,6 +126,7 @@ SET(SIZEOF_SHORT 2 CACHE INTERNAL "") SET(SIZEOF_VOID_P 4 CACHE INTERNAL "") SET(C_CHAR_UNSIGNED 0 CACHE INTERNAL "") + configure_file( ${ORTHANC_ROOT}/Resources/WebAssembly/arith.h ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/arith.h @@ -125,6 +136,7 @@ message(FATAL_ERROR "Support your platform here") endif() ENDIF() + if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") SET(DCMTK_ENABLE_CHARSET_CONVERSION "iconv" CACHE STRING "") @@ -268,18 +280,32 @@ endif() endif() + + if (USE_DCMTK_360) + # Removing this file is required with DCMTK 3.6.0 + list(REMOVE_ITEM DCMTK_SOURCES + + ) + else() + if (ORTHANC_SANDBOXED) + configure_file( + ${ORTHANC_ROOT}/Resources/WebAssembly/dcdict.h + ${DCMTK_SOURCES_DIR}/dcmdata/include/dcmtk/dcmdata/dcdict.h + COPYONLY) + + configure_file( + ${ORTHANC_ROOT}/Resources/WebAssembly/dcdict.cc + ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdict.cc + COPYONLY) + endif() + endif() + + list(REMOVE_ITEM DCMTK_SOURCES ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdictbi.cc ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdeftag.cc ) - if (USE_DCMTK_360) - # Removing this file is required with DCMTK 3.6.0 - list(REMOVE_ITEM DCMTK_SOURCES - ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdictbi.cc - ) - endif() - #set_source_files_properties(${DCMTK_SOURCES} # PROPERTIES COMPILE_DEFINITIONS # "PACKAGE_VERSION=\"${DCMTK_PACKAGE_VERSION}\";PACKAGE_VERSION_NUMBER=\"${DCMTK_VERSION_NUMBER}\"") diff -r 4dcafa8d6633 -r 97a74f0eac7a Resources/CMake/OrthancFrameworkConfiguration.cmake --- a/Resources/CMake/OrthancFrameworkConfiguration.cmake Wed Mar 28 15:20:50 2018 +0200 +++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake Wed Mar 28 18:02:07 2018 +0200 @@ -339,7 +339,14 @@ ## if (ENABLE_LOCALE) - include(${CMAKE_CURRENT_LIST_DIR}/LibIconvConfiguration.cmake) + if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + # In WebAssembly or asm.js, we rely on the version of iconv that + # is shipped with the stdlib + unset(USE_BOOST_ICONV CACHE) + else() + include(${CMAKE_CURRENT_LIST_DIR}/LibIconvConfiguration.cmake) + endif() + add_definitions(-DORTHANC_ENABLE_LOCALE=1) endif() diff -r 4dcafa8d6633 -r 97a74f0eac7a Resources/WebAssembly/dcdict.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/WebAssembly/dcdict.cc Wed Mar 28 18:02:07 2018 +0200 @@ -0,0 +1,1087 @@ +/* + * + * Copyright (C) 1994-2016, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by + * + * OFFIS e.V. + * R&D Division Health + * Escherweg 2 + * D-26121 Oldenburg, Germany + * + * + * Module: dcmdata + * + * Author: Andrew Hewett + * + * Purpose: loadable DICOM data dictionary + * + */ + + +#include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ + +#include "dcmtk/ofstd/ofstd.h" +#include "dcmtk/dcmdata/dcdict.h" +#include "dcmtk/ofstd/ofdefine.h" +#include "dcmtk/dcmdata/dcdicent.h" +#include "dcmtk/dcmdata/dctypes.h" + +#define INCLUDE_CSTDLIB +#define INCLUDE_CSTDIO +#define INCLUDE_CSTRING +#define INCLUDE_CCTYPE +#include "dcmtk/ofstd/ofstdinc.h" + +/* +** The separator character between fields in the data dictionary file(s) +*/ +#define DCM_DICT_FIELD_SEPARATOR_CHAR '\t' + +/* +** Comment character for the data dictionary file(s) +*/ +#define DCM_DICT_COMMENT_CHAR '#' + +/* +** THE Global DICOM Data Dictionary +*/ + +GlobalDcmDataDictionary dcmDataDict; + + +/* +** Member Functions +*/ + +static DcmDictEntry* +makeSkelEntry(Uint16 group, Uint16 element, + Uint16 upperGroup, Uint16 upperElement, + DcmEVR evr, const char* tagName, int vmMin, int vmMax, + const char* standardVersion, + DcmDictRangeRestriction groupRestriction, + DcmDictRangeRestriction elementRestriction, + const char* privCreator) +{ + DcmDictEntry* e = NULL; + e = new DcmDictEntry(group, element, upperGroup, upperElement, evr, + tagName, vmMin, vmMax, standardVersion, OFFalse, privCreator); + if (e != NULL) { + e->setGroupRangeRestriction(groupRestriction); + e->setElementRangeRestriction(elementRestriction); + } + return e; +} + + +OFBool DcmDataDictionary::loadSkeletonDictionary() +{ + /* + ** We need to know about Group Lengths to compute them + */ + DcmDictEntry* e = NULL; + e = makeSkelEntry(0x0000, 0x0000, 0xffff, 0x0000, + EVR_UL, "GenericGroupLength", 1, 1, "GENERIC", + DcmDictRange_Unspecified, DcmDictRange_Unspecified, NULL); + addEntry(e); + + /* + ** We need to know about Items and Delimitation Items to parse + ** (and construct) sequences. + */ + e = makeSkelEntry(0xfffe, 0xe000, 0xfffe, 0xe000, + EVR_na, "Item", 1, 1, "DICOM", + DcmDictRange_Unspecified, DcmDictRange_Unspecified, NULL); + addEntry(e); + e = makeSkelEntry(0xfffe, 0xe00d, 0xfffe, 0xe00d, + EVR_na, "ItemDelimitationItem", 1, 1, "DICOM", + DcmDictRange_Unspecified, DcmDictRange_Unspecified, NULL); + addEntry(e); + e = makeSkelEntry(0xfffe, 0xe0dd, 0xfffe, 0xe0dd, + EVR_na, "SequenceDelimitationItem", 1, 1, "DICOM", + DcmDictRange_Unspecified, DcmDictRange_Unspecified, NULL); + addEntry(e); + + skeletonCount = numberOfEntries(); + return OFTrue; +} + + +DcmDataDictionary::DcmDataDictionary(OFBool loadBuiltin, OFBool loadExternal) + : hashDict(), + repDict(), + skeletonCount(0), + dictionaryLoaded(OFFalse) +{ + reloadDictionaries(loadBuiltin, loadExternal); +} + +DcmDataDictionary::~DcmDataDictionary() +{ + clear(); +} + + +void DcmDataDictionary::clear() +{ + hashDict.clear(); + repDict.clear(); + skeletonCount = 0; + dictionaryLoaded = OFFalse; +} + + +static void +stripWhitespace(char* s) +{ + if (s) + { + unsigned char c; + unsigned char *t; + unsigned char *p; + t=p=OFreinterpret_cast(unsigned char *, s); + while ((c = *t++)) if (!isspace(c)) *p++ = c; + *p = '\0'; + } +} + +static char* +stripTrailingWhitespace(char* s) +{ + if (s == NULL) return s; + for + ( + char* it = s + strlen(s) - 1; + it >= s && isspace(OFstatic_cast(unsigned char, *it)); + *it-- = '\0' + ); + return s; +} + +static void +stripLeadingWhitespace(char* s) +{ + if (s) + { + unsigned char c; + unsigned char *t; + unsigned char *p; + t=p=OFreinterpret_cast(unsigned char *, s); + while (isspace(*t)) t++; + while ((c = *t++)) *p++ = c; + *p = '\0'; + } +} + +static OFBool +parseVMField(char* vmField, int& vmMin, int& vmMax) +{ + OFBool ok = OFTrue; + char c = 0; + int dummy = 0; + + /* strip any whitespace */ + stripWhitespace(vmField); + + if (sscanf(vmField, "%d-%d%c", &vmMin, &dummy, &c) == 3) { + /* treat "2-2n" like "2-n" for the moment */ + if ((c == 'n') || (c == 'N')) { + vmMax = DcmVariableVM; + } else { + ok = OFFalse; + } + } else if (sscanf(vmField, "%d-%d", &vmMin, &vmMax) == 2) { + /* range VM (e.g. "2-6") */ + } else if (sscanf(vmField, "%d-%c", &vmMin, &c) == 2) { + if ((c == 'n') || (c == 'N')) { + vmMax = DcmVariableVM; + } else { + ok = OFFalse; + } + } else if (sscanf(vmField, "%d%c", &vmMin, &c) == 2) { + /* treat "2n" like "2-n" for the moment */ + if ((c == 'n') || (c == 'N')) { + vmMax = DcmVariableVM; + } else { + ok = OFFalse; + } + } else if (sscanf(vmField, "%d", &vmMin) == 1) { + /* fixed VM */ + vmMax = vmMin; + } else if (sscanf(vmField, "%c", &c) == 1) { + /* treat "n" like "1-n" */ + if ((c == 'n') || (c == 'N')) { + vmMin = 1; + vmMax = DcmVariableVM; + } else { + ok = OFFalse; + } + } else { + ok = OFFalse; + } + return ok; +} + +static int +splitFields(const char* line, char* fields[], int maxFields, char splitChar) +{ + const char *p; + int foundFields = 0; + size_t len; + + do { +#ifdef __BORLANDC__ + // Borland Builder expects a non-const argument + p = strchr(OFconst_cast(char *, line), splitChar); +#else + p = strchr(line, splitChar); +#endif + if (p == NULL) { + len = strlen(line); + } else { + len = p - line; + } + fields[foundFields] = OFstatic_cast(char *, malloc(len + 1)); + strncpy(fields[foundFields], line, len); + fields[foundFields][len] = '\0'; + foundFields++; + line = p + 1; + } while ((foundFields < maxFields) && (p != NULL)); + + return foundFields; +} + +static OFBool +parseTagPart(char *s, unsigned int& l, unsigned int& h, + DcmDictRangeRestriction& r) +{ + OFBool ok = OFTrue; + char restrictor = ' '; + + r = DcmDictRange_Unspecified; /* by default */ + + if (sscanf(s, "%x-%c-%x", &l, &restrictor, &h) == 3) { + switch (restrictor) { + case 'o': + case 'O': + r = DcmDictRange_Odd; + break; + case 'e': + case 'E': + r = DcmDictRange_Even; + break; + case 'u': + case 'U': + r = DcmDictRange_Unspecified; + break; + default: + DCMDATA_ERROR("DcmDataDictionary: Unknown range restrictor: " << restrictor); + ok = OFFalse; + break; + } + } else if (sscanf(s, "%x-%x", &l, &h) == 2) { + r = DcmDictRange_Even; /* by default */ + } else if (sscanf(s, "%x", &l) == 1) { + h = l; + } else { + ok = OFFalse; + } + return ok; +} + +static OFBool +parseWholeTagField(char* s, DcmTagKey& key, + DcmTagKey& upperKey, + DcmDictRangeRestriction& groupRestriction, + DcmDictRangeRestriction& elementRestriction, + char *&privCreator) +{ + unsigned int gl, gh, el, eh; + groupRestriction = DcmDictRange_Unspecified; + elementRestriction = DcmDictRange_Unspecified; + + stripLeadingWhitespace(s); + stripTrailingWhitespace(s); + + char gs[64]; + char es[64]; + char pc[64]; + size_t slen = strlen(s); + + if (s[0] != '(') return OFFalse; + if (s[slen - 1] != ')') return OFFalse; + if (strchr(s, ',') == NULL) return OFFalse; + + /* separate the group and element parts */ + int i = 1; /* after the '(' */ + int gi = 0; + for (; s[i] != ',' && s[i] != '\0'; i++) + { + gs[gi] = s[i]; + gi++; + } + gs[gi] = '\0'; + + if (s[i] == '\0') return OFFalse; /* element part missing */ + i++; /* after the ',' */ + + stripLeadingWhitespace(s + i); + + int pi = 0; + if (s[i] == '\"') /* private creator */ + { + i++; // skip opening quotation mark + for (; s[i] != '\"' && s[i] != '\0'; i++) pc[pi++] = s[i]; + pc[pi] = '\0'; + if (s[i] == '\0') return OFFalse; /* closing quotation mark missing */ + i++; + stripLeadingWhitespace(s + i); + if (s[i] != ',') return OFFalse; /* element part missing */ + i++; /* after the ',' */ + } + + int ei = 0; + for (; s[i] != ')' && s[i] != '\0'; i++) { + es[ei] = s[i]; + ei++; + } + es[ei] = '\0'; + + /* parse the tag parts into their components */ + stripWhitespace(gs); + if (parseTagPart(gs, gl, gh, groupRestriction) == OFFalse) + return OFFalse; + + stripWhitespace(es); + if (parseTagPart(es, el, eh, elementRestriction) == OFFalse) + return OFFalse; + + if (pi > 0) + { + // copy private creator name + privCreator = new char[strlen(pc) + 1]; // deleted by caller + if (privCreator) strcpy(privCreator,pc); + } + + key.set(OFstatic_cast(unsigned short, gl), OFstatic_cast(unsigned short, el)); + upperKey.set(OFstatic_cast(unsigned short, gh), OFstatic_cast(unsigned short, eh)); + + return OFTrue; +} + +static OFBool +onlyWhitespace(const char* s) +{ + size_t len = strlen(s); + int charsFound = OFFalse; + + for (size_t i = 0; (!charsFound) && (i < len); ++i) { + charsFound = !isspace(OFstatic_cast(unsigned char, s[i])); + } + return (!charsFound)? (OFTrue) : (OFFalse); +} + +static char* +getLine(char* line, int maxLineLen, FILE* f) +{ + char* s; + + s = fgets(line, maxLineLen, f); + + /* strip any trailing white space */ + stripTrailingWhitespace(line); + + return s; +} + +static OFBool +isaCommentLine(const char* s) +{ + OFBool isComment = OFFalse; /* assumption */ + size_t len = strlen(s); + size_t i = 0; + for (i = 0; i < len && isspace(OFstatic_cast(unsigned char, s[i])); ++i) /*loop*/; + isComment = (s[i] == DCM_DICT_COMMENT_CHAR); + return isComment; +} + +OFBool +DcmDataDictionary::reloadDictionaries(OFBool loadBuiltin, OFBool loadExternal) +{ + OFBool result = OFTrue; + clear(); + loadSkeletonDictionary(); + if (loadBuiltin) { + loadBuiltinDictionary(); + dictionaryLoaded = (numberOfEntries() > skeletonCount); + if (!dictionaryLoaded) result = OFFalse; + } + if (loadExternal) { + if (loadExternalDictionaries()) + dictionaryLoaded = OFTrue; + else + result = OFFalse; + } + return result; +} + +OFBool +DcmDataDictionary::loadDictionary(const char* fileName, OFBool errorIfAbsent) +{ + + char lineBuf[DCM_MAXDICTLINESIZE + 1]; + FILE* f = NULL; + int lineNumber = 0; + char* lineFields[DCM_MAXDICTFIELDS + 1]; + int fieldsPresent; + DcmDictEntry* e; + int errorsEncountered = 0; + OFBool errorOnThisLine = OFFalse; + int i; + + DcmTagKey key, upperKey; + DcmDictRangeRestriction groupRestriction = DcmDictRange_Unspecified; + DcmDictRangeRestriction elementRestriction = DcmDictRange_Unspecified; + DcmVR vr; + char* vrName; + char* tagName; + char* privCreator; + int vmMin, vmMax = 1; + const char* standardVersion; + + /* first, check whether 'fileName' really points to a file (and not to a directory or the like) */ + if (!OFStandard::fileExists(fileName) || (f = fopen(fileName, "r")) == NULL) { + if (errorIfAbsent) { + DCMDATA_ERROR("DcmDataDictionary: Cannot open file: " << fileName); + } + return OFFalse; + } + + DCMDATA_DEBUG("DcmDataDictionary: Loading file: " << fileName); + + while (getLine(lineBuf, DCM_MAXDICTLINESIZE, f)) { + lineNumber++; + + if (onlyWhitespace(lineBuf)) { + continue; /* ignore this line */ + } + if (isaCommentLine(lineBuf)) { + continue; /* ignore this line */ + } + + errorOnThisLine = OFFalse; + + /* fields are tab separated */ + fieldsPresent = splitFields(lineBuf, lineFields, + DCM_MAXDICTFIELDS, + DCM_DICT_FIELD_SEPARATOR_CHAR); + + /* initialize dict entry fields */ + vrName = NULL; + tagName = NULL; + privCreator = NULL; + vmMin = vmMax = 1; + standardVersion = "DICOM"; + + switch (fieldsPresent) { + case 0: + case 1: + case 2: + DCMDATA_ERROR("DcmDataDictionary: "<< fileName << ": " + << "too few fields (line " << lineNumber << ")"); + errorOnThisLine = OFTrue; + break; + default: + DCMDATA_ERROR("DcmDataDictionary: " << fileName << ": " + << "too many fields (line " << lineNumber << "): "); + errorOnThisLine = OFTrue; + break; + case 5: + stripWhitespace(lineFields[4]); + standardVersion = lineFields[4]; + /* drop through to next case label */ + case 4: + /* the VM field is present */ + if (!parseVMField(lineFields[3], vmMin, vmMax)) { + DCMDATA_ERROR("DcmDataDictionary: " << fileName << ": " + << "bad VM field (line " << lineNumber << "): " << lineFields[3]); + errorOnThisLine = OFTrue; + } + /* drop through to next case label */ + case 3: + if (!parseWholeTagField(lineFields[0], key, upperKey, + groupRestriction, elementRestriction, privCreator)) + { + DCMDATA_ERROR("DcmDataDictionary: " << fileName << ": " + << "bad Tag field (line " << lineNumber << "): " << lineFields[0]); + errorOnThisLine = OFTrue; + } else { + /* all is OK */ + vrName = lineFields[1]; + stripWhitespace(vrName); + + tagName = lineFields[2]; + stripWhitespace(tagName); + } + } + + if (!errorOnThisLine) { + /* check the VR Field */ + vr.setVR(vrName); + if (vr.getEVR() == EVR_UNKNOWN) { + DCMDATA_ERROR("DcmDataDictionary: " << fileName << ": " + << "bad VR field (line " << lineNumber << "): " << vrName); + errorOnThisLine = OFTrue; + } + } + + if (!errorOnThisLine) { + e = new DcmDictEntry( + key.getGroup(), key.getElement(), + upperKey.getGroup(), upperKey.getElement(), + vr, tagName, vmMin, vmMax, standardVersion, OFTrue, + privCreator); + + e->setGroupRangeRestriction(groupRestriction); + e->setElementRangeRestriction(elementRestriction); + addEntry(e); + } + + for (i = 0; i < fieldsPresent; i++) { + free(lineFields[i]); + lineFields[i] = NULL; + } + + delete[] privCreator; + + if (errorOnThisLine) { + errorsEncountered++; + } + } + + fclose(f); + + /* return OFFalse in case of errors and set internal state accordingly */ + if (errorsEncountered == 0) { + dictionaryLoaded = OFTrue; + return OFTrue; + } + else { + dictionaryLoaded = OFFalse; + return OFFalse; + } +} + +#ifndef HAVE_GETENV + +static +char* getenv() { + return NULL; +} + +#endif /* !HAVE_GETENV */ + + + +OFBool +DcmDataDictionary::loadExternalDictionaries() +{ + const char* env = NULL; + size_t len; + int sepCnt = 0; + OFBool msgIfDictAbsent = OFTrue; + OFBool loadFailed = OFFalse; + + env = getenv(DCM_DICT_ENVIRONMENT_VARIABLE); + if ((env == NULL) || (strlen(env) == 0)) { + env = DCM_DICT_DEFAULT_PATH; + msgIfDictAbsent = OFFalse; + } + + if ((env != NULL) && (strlen(env) != 0)) { + len = strlen(env); + for (size_t i = 0; i < len; ++i) { + if (env[i] == ENVIRONMENT_PATH_SEPARATOR) { + sepCnt++; + } + } + + if (sepCnt == 0) { + if (!loadDictionary(env, msgIfDictAbsent)) { + return OFFalse; + } + } else { + char** dictArray; + + dictArray = OFstatic_cast(char **, malloc((sepCnt + 1) * sizeof(char*))); + + int ndicts = splitFields(env, dictArray, sepCnt + 1, + ENVIRONMENT_PATH_SEPARATOR); + + for (int ii = 0; ii < ndicts; ii++) { + if ((dictArray[ii] != NULL) && (strlen(dictArray[ii]) > 0)) { + if (!loadDictionary(dictArray[ii], msgIfDictAbsent)) { + loadFailed = OFTrue; + } + } + free(dictArray[ii]); + } + free(dictArray); + } + } + + return (loadFailed) ? (OFFalse) : (OFTrue); +} + + +void +DcmDataDictionary::addEntry(DcmDictEntry* e) +{ + if (e->isRepeating()) { + /* + * Find the best position in repeating tag list + * Existing entries are replaced if the ranges and repetition + * constraints are the same. + * If a range represents a subset of an existing range then it + * will be placed before it in the list. This ensures that a + * search will find the subset rather than the superset. + * Otherwise entries are appended to the end of the list. + */ + OFBool inserted = OFFalse; + + DcmDictEntryListIterator iter(repDict.begin()); + DcmDictEntryListIterator last(repDict.end()); + for (; !inserted && iter != last; ++iter) { + if (e->setEQ(**iter)) { + /* replace the old entry with the new */ + DcmDictEntry *old = *iter; + *iter = e; +#ifdef PRINT_REPLACED_DICTIONARY_ENTRIES + DCMDATA_WARN("replacing " << *old); +#endif + delete old; + inserted = OFTrue; + } else if (e->subset(**iter)) { + /* e is a subset of the current list position, insert before */ + repDict.insert(iter, e); + inserted = OFTrue; + } + } + if (!inserted) { + /* insert at end */ + repDict.push_back(e); + inserted = OFTrue; + } + } else { + hashDict.put(e); + } +} + +void +DcmDataDictionary::deleteEntry(const DcmDictEntry& entry) +{ + DcmDictEntry* e = NULL; + e = OFconst_cast(DcmDictEntry *, findEntry(entry)); + if (e != NULL) { + if (e->isRepeating()) { + repDict.remove(e); + delete e; + } else { + hashDict.del(entry.getKey(), entry.getPrivateCreator()); + } + } +} + +const DcmDictEntry* +DcmDataDictionary::findEntry(const DcmDictEntry& entry) const +{ + const DcmDictEntry* e = NULL; + + if (entry.isRepeating()) { + OFBool found = OFFalse; + DcmDictEntryListConstIterator iter(repDict.begin()); + DcmDictEntryListConstIterator last(repDict.end()); + for (; !found && iter != last; ++iter) { + if (entry.setEQ(**iter)) { + found = OFTrue; + e = *iter; + } + } + } else { + e = hashDict.get(entry, entry.getPrivateCreator()); + } + return e; +} + +const DcmDictEntry* +DcmDataDictionary::findEntry(const DcmTagKey& key, const char *privCreator) const +{ + /* search first in the normal tags dictionary and if not found + * then search in the repeating tags list. + */ + const DcmDictEntry* e = NULL; + + e = hashDict.get(key, privCreator); + if (e == NULL) { + /* search in the repeating tags dictionary */ + OFBool found = OFFalse; + DcmDictEntryListConstIterator iter(repDict.begin()); + DcmDictEntryListConstIterator last(repDict.end()); + for (; !found && iter != last; ++iter) { + if ((*iter)->contains(key, privCreator)) { + found = OFTrue; + e = *iter; + } + } + } + return e; +} + +const DcmDictEntry* +DcmDataDictionary::findEntry(const char *name) const +{ + const DcmDictEntry* e = NULL; + const DcmDictEntry* ePrivate = NULL; + + /* search first in the normal tags dictionary and if not found + * then search in the repeating tags list. + */ + DcmHashDictIterator iter; + for (iter = hashDict.begin(); (e == NULL) && (iter != hashDict.end()); ++iter) { + if ((*iter)->contains(name)) { + e = *iter; + if (e->getGroup() % 2) + { + /* tag is a private tag - continue search to be sure to find non-private keys first */ + if (!ePrivate) ePrivate = e; + e = NULL; + } + } + } + + if (e == NULL) { + /* search in the repeating tags dictionary */ + OFBool found = OFFalse; + DcmDictEntryListConstIterator iter2(repDict.begin()); + DcmDictEntryListConstIterator last(repDict.end()); + for (; !found && iter2 != last; ++iter2) { + if ((*iter2)->contains(name)) { + found = OFTrue; + e = *iter2; + } + } + } + + if (e == NULL && ePrivate != NULL) { + /* no standard key found - use the first private key found */ + e = ePrivate; + } + + return e; +} + + +/* ================================================================== */ + + +GlobalDcmDataDictionary::GlobalDcmDataDictionary() + : dataDict(NULL) +#ifdef WITH_THREADS + , dataDictLock() +#endif +{ +} + +GlobalDcmDataDictionary::~GlobalDcmDataDictionary() +{ + /* No threads may be active any more, so no locking needed */ + delete dataDict; +} + +void GlobalDcmDataDictionary::createDataDict() +{ + /* Make sure only one thread tries to initialize the dictionary */ +#ifdef WITH_THREADS + dataDictLock.wrlock(); +#endif +#ifdef DONT_LOAD_EXTERNAL_DICTIONARIES + const OFBool loadExternal = OFFalse; +#else + const OFBool loadExternal = OFTrue; +#endif + /* Make sure no other thread managed to create the dictionary + * before we got our write lock. */ + if (!dataDict) + dataDict = new DcmDataDictionary(OFTrue /*loadBuiltin*/, loadExternal); +#ifdef WITH_THREADS + dataDictLock.unlock(); +#endif +} + +const DcmDataDictionary& GlobalDcmDataDictionary::rdlock() +{ +#ifdef WITH_THREADS + dataDictLock.rdlock(); +#endif + if (!dataDict) + { + /* dataDictLock must not be locked during createDataDict() */ +#ifdef WITH_THREADS + dataDictLock.unlock(); +#endif + createDataDict(); +#ifdef WITH_THREADS + dataDictLock.rdlock(); +#endif + } + return *dataDict; +} + +DcmDataDictionary& GlobalDcmDataDictionary::wrlock() +{ +#ifdef WITH_THREADS + dataDictLock.wrlock(); +#endif + if (!dataDict) + { + /* dataDictLock must not be locked during createDataDict() */ +#ifdef WITH_THREADS + dataDictLock.unlock(); +#endif + createDataDict(); +#ifdef WITH_THREADS + dataDictLock.wrlock(); +#endif + } + return *dataDict; +} + +void GlobalDcmDataDictionary::unlock() +{ +#ifdef WITH_THREADS + dataDictLock.unlock(); +#endif +} + +OFBool GlobalDcmDataDictionary::isDictionaryLoaded() +{ + OFBool result = rdlock().isDictionaryLoaded(); + unlock(); + return result; +} + +void GlobalDcmDataDictionary::clear() +{ + wrlock().clear(); + unlock(); +} + + + + +// Function by the Orthanc project to load a dictionary from a memory +// buffer, which is necessary in sandboxed environments. This is an +// adapted version of DcmDataDictionary::loadDictionary(). + + +#include + +struct OrthancLinesIterator; + +// This plain old C class is implemented in "../../Core/Toolbox.h" +OrthancLinesIterator* OrthancLinesIterator_Create(const std::string& content); + +bool OrthancLinesIterator_GetLine(std::string& target, + const OrthancLinesIterator* iterator); + +void OrthancLinesIterator_Next(OrthancLinesIterator* iterator); + +void OrthancLinesIterator_Free(OrthancLinesIterator* iterator); + + +class LinesIterator : public boost::noncopyable +{ +private: + OrthancLinesIterator* iterator_; + +public: + LinesIterator(const std::string& content) : + iterator_(NULL) + { + iterator_ = OrthancLinesIterator_Create(content); + } + + ~LinesIterator() + { + if (iterator_ != NULL) + { + OrthancLinesIterator_Free(iterator_); + iterator_ = NULL; + } + } + + bool GetLine(std::string& target) const + { + if (iterator_ != NULL) + { + return OrthancLinesIterator_GetLine(target, iterator_); + } + else + { + return false; + } + } + + void Next() + { + if (iterator_ != NULL) + { + OrthancLinesIterator_Next(iterator_); + } + } +}; + + + +OFBool +DcmDataDictionary::loadFromMemory(const std::string& content, OFBool errorIfAbsent) +{ + int lineNumber = 0; + char* lineFields[DCM_MAXDICTFIELDS + 1]; + int fieldsPresent; + DcmDictEntry* e; + int errorsEncountered = 0; + OFBool errorOnThisLine = OFFalse; + int i; + + DcmTagKey key, upperKey; + DcmDictRangeRestriction groupRestriction = DcmDictRange_Unspecified; + DcmDictRangeRestriction elementRestriction = DcmDictRange_Unspecified; + DcmVR vr; + char* vrName; + char* tagName; + char* privCreator; + int vmMin, vmMax = 1; + const char* standardVersion; + + LinesIterator iterator(content); + + std::string line; + while (iterator.GetLine(line)) { + iterator.Next(); + + if (line.size() >= DCM_MAXDICTLINESIZE) { + DCMDATA_ERROR("DcmDataDictionary: Too long line: " << line); + continue; + } + + lineNumber++; + + if (onlyWhitespace(line.c_str())) { + continue; /* ignore this line */ + } + if (isaCommentLine(line.c_str())) { + continue; /* ignore this line */ + } + + errorOnThisLine = OFFalse; + + /* fields are tab separated */ + fieldsPresent = splitFields(line.c_str(), lineFields, + DCM_MAXDICTFIELDS, + DCM_DICT_FIELD_SEPARATOR_CHAR); + + /* initialize dict entry fields */ + vrName = NULL; + tagName = NULL; + privCreator = NULL; + vmMin = vmMax = 1; + standardVersion = "DICOM"; + + switch (fieldsPresent) { + case 0: + case 1: + case 2: + DCMDATA_ERROR("DcmDataDictionary: " + << "too few fields (line " << lineNumber << ")"); + errorOnThisLine = OFTrue; + break; + default: + DCMDATA_ERROR("DcmDataDictionary: " + << "too many fields (line " << lineNumber << "): "); + errorOnThisLine = OFTrue; + break; + case 5: + stripWhitespace(lineFields[4]); + standardVersion = lineFields[4]; + /* drop through to next case label */ + case 4: + /* the VM field is present */ + if (!parseVMField(lineFields[3], vmMin, vmMax)) { + DCMDATA_ERROR("DcmDataDictionary: " + << "bad VM field (line " << lineNumber << "): " << lineFields[3]); + errorOnThisLine = OFTrue; + } + /* drop through to next case label */ + case 3: + if (!parseWholeTagField(lineFields[0], key, upperKey, + groupRestriction, elementRestriction, privCreator)) + { + DCMDATA_ERROR("DcmDataDictionary: " + << "bad Tag field (line " << lineNumber << "): " << lineFields[0]); + errorOnThisLine = OFTrue; + } else { + /* all is OK */ + vrName = lineFields[1]; + stripWhitespace(vrName); + + tagName = lineFields[2]; + stripWhitespace(tagName); + } + } + + if (!errorOnThisLine) { + /* check the VR Field */ + vr.setVR(vrName); + if (vr.getEVR() == EVR_UNKNOWN) { + DCMDATA_ERROR("DcmDataDictionary: " + << "bad VR field (line " << lineNumber << "): " << vrName); + errorOnThisLine = OFTrue; + } + } + + if (!errorOnThisLine) { + e = new DcmDictEntry( + key.getGroup(), key.getElement(), + upperKey.getGroup(), upperKey.getElement(), + vr, tagName, vmMin, vmMax, standardVersion, OFTrue, + privCreator); + + e->setGroupRangeRestriction(groupRestriction); + e->setElementRangeRestriction(elementRestriction); + addEntry(e); + } + + for (i = 0; i < fieldsPresent; i++) { + free(lineFields[i]); + lineFields[i] = NULL; + } + + delete[] privCreator; + + if (errorOnThisLine) { + errorsEncountered++; + } + } + + /* return OFFalse in case of errors and set internal state accordingly */ + if (errorsEncountered == 0) { + dictionaryLoaded = OFTrue; + return OFTrue; + } + else { + dictionaryLoaded = OFFalse; + return OFFalse; + } +} diff -r 4dcafa8d6633 -r 97a74f0eac7a Resources/WebAssembly/dcdict.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/WebAssembly/dcdict.h Wed Mar 28 18:02:07 2018 +0200 @@ -0,0 +1,302 @@ +/* + * + * Copyright (C) 1994-2015, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by + * + * OFFIS e.V. + * R&D Division Health + * Escherweg 2 + * D-26121 Oldenburg, Germany + * + * + * Module: dcmdata + * + * Author: Andrew Hewett + * + * Purpose: Interface for loadable DICOM data dictionary + * + */ + + +#ifndef DCMDICT_H +#define DCMDICT_H + +#include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ + +#include "dcmtk/ofstd/ofthread.h" +#include "dcmtk/dcmdata/dchashdi.h" + +/// maximum length of a line in the loadable DICOM dictionary +#define DCM_MAXDICTLINESIZE 2048 + +/// maximum number of fields per entry in the loadable DICOM dictionary +#define DCM_MAXDICTFIELDS 6 + +/// environment variable pointing to the data dictionary file +#define DCM_DICT_ENVIRONMENT_VARIABLE "DCMDICTPATH" + +#ifndef DCM_DICT_DEFAULT_PATH +/* +** The default dictionary path is system dependent. It should +** be defined in a configuration file included from "osconfig.h" +*/ +#error "DCM_DICT_DEFAULT_PATH is not defined via osconfig.h" +#endif /* !DCM_DICT_DEFAULT_PATH */ + +#ifndef ENVIRONMENT_PATH_SEPARATOR +#define ENVIRONMENT_PATH_SEPARATOR '\n' /* at least define something unlikely */ +#endif + + +/** this class implements a loadable DICOM Data Dictionary + */ +class DCMTK_DCMDATA_EXPORT DcmDataDictionary +{ +public: + + /** constructor + * @param loadBuiltin flag indicating if a built-in data dictionary + * (if any) should be loaded. + * @param loadExternal flag indicating if an external data dictionary + * should be read from file. + */ + DcmDataDictionary(OFBool loadBuiltin, OFBool loadExternal); + + /// destructor + ~DcmDataDictionary(); + + /** checks if a data dictionary is loaded (excluding the skeleton dictionary) + * @return true if loaded, false if no dictionary is present + */ + OFBool isDictionaryLoaded() const { return dictionaryLoaded; } + + /// returns the number of normal (non-repeating) tag entries + int numberOfNormalTagEntries() const { return hashDict.size(); } + + /// returns the number of repeating tag entries + int numberOfRepeatingTagEntries() const { return OFstatic_cast(int, repDict.size()); } + + /** returns the number of dictionary entries that were loaded + * either from file or from a built-in dictionary or both. + */ + int numberOfEntries() const + { return numberOfNormalTagEntries() + + numberOfRepeatingTagEntries() - skeletonCount; } + + /** returns the number of skeleton entries. The skeleton is a collection + * of dictionary entries which are always present, even if neither internal + * nor external dictionary have been loaded. It contains very basic + * things like item delimitation and sequence delimitation. + */ + int numberOfSkeletonEntries() const { return skeletonCount; } + + /** reload data dictionaries. First, all dictionary entries are deleted. + * @param loadBuiltin flag indicating if a built-in data dictionary + * (if any) should be loaded. + * @param loadExternal flag indicating if an external data dictionary + * should be read from file. + * @return true if reload was successful, false if an error occurred + */ + OFBool reloadDictionaries(OFBool loadBuiltin, OFBool loadExternal); + + /** load a particular dictionary from file. + * @param fileName filename + * @param errorIfAbsent causes the method to return false + * if the file cannot be opened + * @return false if the file contains a parse error or if the file could + * not be opened and errorIfAbsent was set, true otherwise. + */ + OFBool loadDictionary(const char* fileName, OFBool errorIfAbsent = OFTrue); + + /** dictionary lookup for the given tag key and private creator name. + * First the normal tag dictionary is searched. If not found + * then the repeating tag dictionary is searched. + * @param key tag key + * @param privCreator private creator name, may be NULL + */ + const DcmDictEntry* findEntry(const DcmTagKey& key, const char *privCreator) const; + + /** dictionary lookup for the given attribute name. + * First the normal tag dictionary is searched. If not found + * then the repeating tag dictionary is searched. + * Only considers standard attributes (i. e. without private creator) + * @param name attribute name + */ + const DcmDictEntry* findEntry(const char *name) const; + + /// deletes all dictionary entries + void clear(); + + /** adds an entry to the dictionary. Must be allocated via new. + * The entry becomes the property of the dictionary and will be + * deallocated (via delete) upon clear() or dictionary destruction. + * If an equivalent entry already exists it will be replaced by + * the new entry and the old entry deallocated (via delete). + * @param entry pointer to new entry + */ + void addEntry(DcmDictEntry* entry); + + /* Iterators to access the normal and the repeating entries */ + + /// returns an iterator to the start of the normal (non-repeating) dictionary + DcmHashDictIterator normalBegin() { return hashDict.begin(); } + + /// returns an iterator to the end of the normal (non-repeating) dictionary + DcmHashDictIterator normalEnd() { return hashDict.end(); } + + /// returns an iterator to the start of the repeating tag dictionary + DcmDictEntryListIterator repeatingBegin() { return repDict.begin(); } + + /// returns an iterator to the end of the repeating tag dictionary + DcmDictEntryListIterator repeatingEnd() { return repDict.end(); } + + // Function by the Orthanc project to load a dictionary from a + // memory buffer, which is necessary in sandboxed + // environments. This is an adapted version of + // DcmDataDictionary::loadDictionary(). + OFBool loadFromMemory(const std::string& content, OFBool errorIfAbsent = OFTrue); + +private: + + /** private undefined assignment operator + */ + DcmDataDictionary &operator=(const DcmDataDictionary &); + + /** private undefined copy constructor + */ + DcmDataDictionary(const DcmDataDictionary &); + + /** loads external dictionaries defined via environment variables + * @return true if successful + */ + OFBool loadExternalDictionaries(); + + /** loads a builtin (compiled) data dictionary. + * Depending on which code is in use, this function may not + * do anything. + */ + void loadBuiltinDictionary(); + + /** loads the skeleton dictionary (the bare minimum needed to run) + * @return true if successful + */ + OFBool loadSkeletonDictionary(); + + /** looks up the given directory entry in the two dictionaries. + * @return pointer to entry if found, NULL otherwise + */ + const DcmDictEntry* findEntry(const DcmDictEntry& entry) const; + + /** deletes the given entry from either dictionary + */ + void deleteEntry(const DcmDictEntry& entry); + + + /** dictionary of normal tags + */ + DcmHashDict hashDict; + + /** dictionary of repeating tags + */ + DcmDictEntryList repDict; + + /** the number of skeleton entries + */ + int skeletonCount; + + /** is a dictionary loaded (more than skeleton) + */ + OFBool dictionaryLoaded; + +}; + + +/** global singleton dicom dictionary that is used by DCMTK in order to lookup + * attribute VR, tag names and so on. The dictionary is internally populated + * on first use, if the user accesses it via rdlock() or wrlock(). The + * dictionary allows safe read (shared) and write (exclusive) access from + * multiple threads in parallel. + */ +class DCMTK_DCMDATA_EXPORT GlobalDcmDataDictionary +{ +public: + /** constructor. + */ + GlobalDcmDataDictionary(); + + /** destructor + */ + ~GlobalDcmDataDictionary(); + + /** acquires a read lock and returns a const reference to + * the dictionary. + * @return const reference to dictionary + */ + const DcmDataDictionary& rdlock(); + + /** acquires a write lock and returns a non-const reference + * to the dictionary. + * @return non-const reference to dictionary. + */ + DcmDataDictionary& wrlock(); + + /** unlocks the read or write lock which must have been acquired previously. + */ + void unlock(); + + /** checks if a data dictionary has been loaded. This method acquires and + * releases a read lock. It must not be called with another lock on the + * dictionary being held by the calling thread. + * @return OFTrue if dictionary has been loaded, OFFalse otherwise. + */ + OFBool isDictionaryLoaded(); + + /** erases the contents of the dictionary. This method acquires and + * releases a write lock. It must not be called with another lock on the + * dictionary being held by the calling thread. This method is intended + * as a help for debugging memory leaks. + */ + void clear(); + +private: + /** private undefined assignment operator + */ + GlobalDcmDataDictionary &operator=(const GlobalDcmDataDictionary &); + + /** private undefined copy constructor + */ + GlobalDcmDataDictionary(const GlobalDcmDataDictionary &); + + /** create the data dictionary instance for this class. Used for first + * intialization. The caller must not have dataDictLock locked. + */ + void createDataDict(); + + /** the data dictionary managed by this class + */ + DcmDataDictionary *dataDict; + +#ifdef WITH_THREADS + /** the read/write lock used to protect access from multiple threads + */ + OFReadWriteLock dataDictLock; +#endif +}; + + +/** The Global DICOM Data Dictionary. + * Will be created before main() starts and gets populated on its first use. + * Tries to load a builtin data dictionary (if compiled in). + * Tries to load data dictionaries from files specified by + * the DCMDICTPATH environment variable. If this environment + * variable does not exist then a default file is loaded (if + * it exists). + * It is possible that no data dictionary gets loaded. This + * is likely to cause unexpected behaviour in the dcmdata + * toolkit classes. + */ +extern DCMTK_DCMDATA_EXPORT GlobalDcmDataDictionary dcmDataDict; + +#endif diff -r 4dcafa8d6633 -r 97a74f0eac7a UnitTestsSources/UnitTestsMain.cpp --- a/UnitTestsSources/UnitTestsMain.cpp Wed Mar 28 15:20:50 2018 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Wed Mar 28 18:02:07 2018 +0200 @@ -1043,6 +1043,73 @@ } +TEST(Toolbox, LinesIterator) +{ + std::string s; + + { + std::string content; + Toolbox::LinesIterator it(content); + ASSERT_FALSE(it.GetLine(s)); + } + + { + std::string content = "\n\r"; + Toolbox::LinesIterator it(content); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_FALSE(it.GetLine(s)); + } + + { + std::string content = "\n Hello \n\nWorld\n\n"; + Toolbox::LinesIterator it(content); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ(" Hello ", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("World", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_FALSE(it.GetLine(s)); it.Next(); + ASSERT_FALSE(it.GetLine(s)); + } + + { + std::string content = "\r Hello \r\rWorld\r\r"; + Toolbox::LinesIterator it(content); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ(" Hello ", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("World", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_FALSE(it.GetLine(s)); it.Next(); + ASSERT_FALSE(it.GetLine(s)); + } + + { + std::string content = "\n\r Hello \n\r\n\rWorld\n\r\n\r"; + Toolbox::LinesIterator it(content); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ(" Hello ", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("World", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_FALSE(it.GetLine(s)); it.Next(); + ASSERT_FALSE(it.GetLine(s)); + } + + { + std::string content = "\r\n Hello \r\n\r\nWorld\r\n\r\n"; + Toolbox::LinesIterator it(content); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ(" Hello ", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("World", s); + ASSERT_TRUE(it.GetLine(s)); it.Next(); ASSERT_EQ("", s); + ASSERT_FALSE(it.GetLine(s)); it.Next(); + ASSERT_FALSE(it.GetLine(s)); + } +} + + int main(int argc, char **argv) { Logging::Initialize();