changeset 2513:97a74f0eac7a

loading DICOM dictionaries in sandboxed environments
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 28 Mar 2018 18:02:07 +0200
parents 4dcafa8d6633
children 38d666a40860
files CMakeLists.txt Core/DicomParsing/FromDcmtkBridge.cpp Core/Toolbox.cpp Core/Toolbox.h Resources/CMake/BoostConfiguration.cmake Resources/CMake/DcmtkConfiguration.cmake Resources/CMake/OrthancFrameworkConfiguration.cmake Resources/WebAssembly/dcdict.cc Resources/WebAssembly/dcdict.h UnitTestsSources/UnitTestsMain.cpp
diffstat 10 files changed, 1658 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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
--- 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<OrthancLinesIterator*>(new Orthanc::Toolbox::LinesIterator(content));
+}
+
+
+bool OrthancLinesIterator_GetLine(std::string& target,
+                                         const OrthancLinesIterator* iterator)
+{
+  if (iterator != NULL)
+  {
+    return reinterpret_cast<const Orthanc::Toolbox::LinesIterator*>(iterator)->GetLine(target);
+  }
+  else
+  {
+    return false;
+  }
+}
+
+
+void OrthancLinesIterator_Next(OrthancLinesIterator* iterator)
+{
+  if (iterator != NULL)
+  {
+    reinterpret_cast<Orthanc::Toolbox::LinesIterator*>(iterator)->Next();
+  }
+}
+
+
+void OrthancLinesIterator_Free(OrthancLinesIterator* iterator)
+{
+  if (iterator != NULL)
+  {
+    delete reinterpret_cast<const Orthanc::Toolbox::LinesIterator*>(iterator);
+  }
+}
--- 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);
--- 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
--- 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}\"")
--- 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()
 
--- /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 <boost/noncopyable.hpp>
+
+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;
+  }
+}
--- /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
--- 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();