File exiv2-update-to-0.26-branch.patch of Package exiv2.26842

diff --git a/.travis.yml b/.travis.yml
index 8af0ea6d..66877a6a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,40 +1,26 @@
 language: cpp
 
-sudo: false
+matrix:
+  include:
+    - os: linux
+      dist: trusty
+      sudo: required
+      compiler: gcc
+    - os: linux
+      dist: trusty
+      sudo: required
+      compiler: clang
+    - os: osx
+      osx_image: xcode9
+      compiler: clang
+      env: PYTHON=3.6.2 CMAKE_OPTIONS="-DCMAKE_BUILD_TYPE=Release -DEXIV2_ENABLE_VIDEO=ON -DEXIV2_ENABLE_WEBREADY=ON -DEXIV2_BUILD_UNIT_TESTS=ON" # All enabled
 
-addons:
-    apt:
-      packages:
-        - cmake
-        - zlib1g-dev
-        - libssh-dev
-        - libssh
-        - libcurl4-openssl-dev
-        - gettext
-      sources:
-        - kalakris-cmake
-        
-compiler:
-  - gcc
-  - clang
+env:
+    #- CMAKE_OPTIONS="-DCMAKE_BUILD_TYPE=Release" # Default
+    #- CMAKE_OPTIONS="-DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=OFF"   # Default (Debug mode + static libs)
+    - CMAKE_OPTIONS="-DCMAKE_BUILD_TYPE=Release -DEXIV2_ENABLE_VIDEO=ON -DEXIV2_ENABLE_WEBREADY=ON" # All enabled
+    #- CMAKE_OPTIONS="-DCMAKE_BUILD_TYPE=Release -DEXIV2_ENABLE_XMP=OFF -DEXIV2_ENABLE_NLS=OFF -DEXIV2_ENABLE_LENSDATA=OFF" # All disabled
+    #- CMAKE_OPTIONS="-DCMAKE_BUILD_TYPE=Release -DEXIV2_ENABLE_WEBREADY=ON -DEXIV2_ENABLE_CURL=OFF -DEXIV2_ENABLE_SSH=OFF" # WebReady without SSH nor CURL
 
-before_install:
-  - echo $LANG
-  - echo $LC_ALL
-  - if [ $TRAVIS_OS_NAME == osx ]; then brew update && brew install libssh curl; fi
-  - rvm use $RVM --install --binary --fuzzy
-  - gem update --system
-#  - gem --version
-
-script:
-  - cmake -DCMAKE_INSTALL_PREFIX=..\dist -EXIV2_ENABLE_NLS=ON -DEXIV2_ENABLE_CURL=OFF -DEXIV2_ENABLE_SSH=OFF .
-  - cmake --build . && cmake --build . --target install 
-  - cmake -DCMAKE_INSTALL_PREFIX=..\dist2 -EXIV2_ENABLE_NLS=ON -DEXIV2_ENABLE_CURL=ON -DEXIV2_ENABLE_SSH=ON -DEXIV2_ENABLE_WEBREADY=ON .
-  - cmake --build . && cmake --build . --target install 
-
-notifications:
-  email: false
-  
-os:
-  - linux
-  - osx
\ No newline at end of file
+install: ./.travis/install.sh
+script:  ./.travis/run.sh
diff --git a/.travis/install.sh b/.travis/install.sh
new file mode 100755
index 00000000..852c51f3
--- /dev/null
+++ b/.travis/install.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+set -e # Enables cheking of return values from each command
+set -x # Prints every command
+
+if [[ "$(uname -s)" == 'Linux' ]]; then
+    sudo apt-get update
+    sudo apt-get install cmake zlib1g-dev libssh-dev gettext expat libcurl4-openssl-dev libxml2-utils
+    sudo pip install virtualenv
+else
+    brew update
+    brew install gettext expat zlib curl md5sha1sum
+    brew upgrade python
+fi
diff --git a/.travis/run.sh b/.travis/run.sh
new file mode 100755
index 00000000..1a32a2c2
--- /dev/null
+++ b/.travis/run.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+set -e
+set -x
+
+mkdir test/tmp/
+mkdir build && cd build
+cmake ${CMAKE_OPTIONS} -DCMAKE_INSTALL_PREFIX=install ..
+make -j2
+make tests
+make install
+
+cd ../tests/
+python3 runner.py
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7034bb67..98a172ed 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,12 +20,20 @@ endif()
 CMAKE_MINIMUM_REQUIRED( VERSION 3.1.0 )
 PROJECT( exiv2 )
 
+include( GNUInstallDirs )
+
 if( POLICY CMP0042 )
     cmake_policy(SET CMP0042 NEW) # enable MACOSX_RPATH support
 else()
     SET(CMAKE_MACOSX_RPATH 1)
 endif()
 
+if (NOT MSVC)
+    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
+    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
+    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
+endif()
+
 SET( PACKAGE_COPYRIGHT      "Andreas Huggel" )
 SET( PACKAGE_BUGREPORT      "ahuggel@gmx.net" )
 SET( PACKAGE                "exiv2" )
@@ -64,33 +72,25 @@ ENDIF()
 # set include path for FindXXX.cmake files
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/config/")
 
-IF( MINGW OR UNIX )
-    IF ( CMAKE_CXX_COMPILER STREQUAL "g++" OR CMAKE_C_COMPILER STREQUAL "gcc" )
-        ADD_DEFINITIONS(-Wall
-                    -Wcast-align
-                    -Wpointer-arith
-                    -Wformat-security
-                    -Wmissing-format-attribute
-                    -Woverloaded-virtual
-                    -W
-                   )
+if( MINGW OR UNIX )
+    if (${CMAKE_CXX_COMPILER_ID} STREQUAL GNU)
+        string(CONCAT WARNING_FLAGS " -Wall"
+          " -Wcast-align"
+          " -Wpointer-arith"
+          " -Wformat-security"
+          " -Wmissing-format-attribute"
+          " -Woverloaded-virtual"
+          " -W"
+          )
+        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}")
+        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS}")
     ENDIF()
 
-    execute_process(COMMAND ${CMAKE_CXX_COMPILER} --version OUTPUT_VARIABLE COMPILER_VERSION)
-    string(REGEX MATCHALL "[a-z\+]+" GCC_COMPILER_COMPONENTS ${COMPILER_VERSION})
-    list(GET GCC_COMPILER_COMPONENTS 0 COMPILER)
-
-    execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION)
-    string(REGEX MATCHALL "[0-9]+" GCC_VERSION_COMPONENTS ${GCC_VERSION})
-    list(GET GCC_VERSION_COMPONENTS 0 GCC_MAJOR)
-    list(GET GCC_VERSION_COMPONENTS 1 GCC_MINOR)
-
-    message(STATUS Compiler: ${COMPILER} " Major:" ${GCC_MAJOR} " Minor:" ${GCC_MINOR})
-
-    IF ( CYGWIN OR ( ${GCC_MAJOR} GREATER 5 ))
-        ADD_DEFINITIONS( -std=gnu++98 ) # to support snprintf
+    message(STATUS "Compiler info: ${CMAKE_CXX_COMPILER_ID} (${CMAKE_CXX_COMPILER}) ; version: ${CMAKE_CXX_COMPILER_VERSION}")
+    IF ( CYGWIN OR (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 5.0))
+        set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++98" ) # to support snprintf
     ELSE()
-        ADD_DEFINITIONS( -std=c++98 )
+        set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++98" )
     ENDIF()
 
 ENDIF( MINGW OR UNIX )
@@ -120,7 +120,6 @@ IF( EXIV2_ENABLE_XMP )
     IF (NOT MINGW)
         set(THREADS_PREFER_PTHREAD_FLAG ON)
     ENDIF()
-    find_package(Threads REQUIRED)
 ENDIF( EXIV2_ENABLE_XMP )
 
 INCLUDE( config/CMakeChecks.txt )
diff --git a/README b/README
index 1b28bfd7..2d0743f2 100644
--- a/README
+++ b/README
@@ -1,3 +1,5 @@
+https://travis-ci.org/Exiv2/exiv2.svg?branch=0.26
+
     @@@Marco@@@@@b                   ;mm                       /##Gilles###\
     j@@@#Robin",                     Brad                     /@@@Thomas@@@@Q
      @@@#       \                     ##                     @@@b     |@@@b
diff --git a/config/CMakeChecks.txt b/config/CMakeChecks.txt
index 77922930..0b458695 100644
--- a/config/CMakeChecks.txt
+++ b/config/CMakeChecks.txt
@@ -37,8 +37,6 @@ INCLUDE( CheckSymbolExists )
 INCLUDE( CheckCSourceCompiles )
 INCLUDE( CheckCXXSourceCompiles )
 
-INCLUDE( GNUInstallDirs )
-
 INCLUDE( FindIconv )
 
 SET( STDC_HEADERS ON )
@@ -51,6 +49,8 @@ INCLUDE_DIRECTORIES( ${CMAKE_INCLUDE_PATH} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_C
 LINK_DIRECTORIES( ${CMAKE_LIBRARY_PATH} )
 SET( CMAKE_REQUIRED_INCLUDES ${CMAKE_INCLUDE_PATH} )
 
+find_package(Threads REQUIRED)
+
 IF( EXIV2_ENABLE_PNG )
     FIND_PACKAGE( ZLIB REQUIRED )
     INCLUDE_DIRECTORIES( ${ZLIB_INCLUDE_DIR} )
@@ -95,15 +95,13 @@ ELSE( EXIV2_ENABLE_SHARED )
 ENDIF( EXIV2_ENABLE_SHARED )
 
 IF( EXIV2_ENABLE_NLS )
-    #FIND_PACKAGE(Intl REQUIRED)
-    #INCLUDE_DIRECTORIES(${Intl_INCLUDE_DIRS})
-    IF( NOT LOCALEDIR )
-        SET( LOCALEDIR "${CMAKE_INSTALL_PREFIX}/share/locale" )
-        IF( WIN32 )
-            STRING( REPLACE "/" "\\\\" LOCALEDIR ${LOCALEDIR} )
-        ENDIF( WIN32 )
-    ENDIF( NOT LOCALEDIR )
-    ADD_DEFINITIONS( -DEXV_LOCALEDIR="${LOCALEDIR}" )
+    FIND_PACKAGE(Intl)
+    if(Intl_FOUND)
+        INCLUDE_DIRECTORIES(${Intl_INCLUDE_DIRS})
+        SET(LIBINTL_LIBRARIES ${Intl_LIBRARIES})
+    else()
+        SET(LIBINTL_LIBRARIES)
+    endif()
     SET( ENABLE_NLS 1 )
 ENDIF( EXIV2_ENABLE_NLS )
 
diff --git a/config/FindMSGFMT.cmake b/config/FindMSGFMT.cmake
index 9a73f2bc..393c3a9b 100644
--- a/config/FindMSGFMT.cmake
+++ b/config/FindMSGFMT.cmake
@@ -81,7 +81,7 @@ MACRO(ADD_TRANSLATIONS _baseName)
             COMMAND ${MSGFMT_EXECUTABLE} -o ${_out} ${_in}
             DEPENDS ${_in} )
         INSTALL(FILES ${_out}
-            DESTINATION ${LOCALEDIR}/${_file_we}/LC_MESSAGES/
+            DESTINATION ${CMAKE_INSTALL_LOCALEDIR}/${_file_we}/LC_MESSAGES/
             RENAME ${_baseName}.mo )
         SET(_outputs ${_outputs} ${_out})
     ENDFOREACH(_file)
diff --git a/config/config.mk.in b/config/config.mk.in
index 8d920647..4754c722 100644
--- a/config/config.mk.in
+++ b/config/config.mk.in
@@ -165,7 +165,7 @@ endif
 # **********************************************************************
 # Compilation shortcuts
 COMPILE.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c
-COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) -c
+COMPILE.c  = $(CC) $(CFLAGS) -c
 # LINK.cc does not need $(LIBS), libtool's dark magic takes care of that
 # when linking a binary with a libtool library.
 LINK.cc = $(CXX) $(LDFLAGS)
diff --git a/config/exiv2.pc.cmake b/config/exiv2.pc.cmake
index 256f8ac6..afc16e2b 100644
--- a/config/exiv2.pc.cmake
+++ b/config/exiv2.pc.cmake
@@ -1,7 +1,7 @@
 prefix=@CMAKE_INSTALL_PREFIX@
 exec_prefix=${prefix}
-libdir=${prefix}/lib
-includedir=${prefix}/include
+libdir=@CMAKE_INSTALL_FULL_LIBDIR@
+includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
 
 Name: exiv2
 Description: Exif and IPTC metadata library and tools
diff --git a/include/exiv2/error.hpp b/include/exiv2/error.hpp
index 01d23fc1..ed67c4be 100644
--- a/include/exiv2/error.hpp
+++ b/include/exiv2/error.hpp
@@ -193,6 +193,74 @@ namespace Exiv2 {
         return os << error.what();
     }
 
+    //! Complete list of all Exiv2 error codes
+    enum ErrorCode {
+        kerGeneralError = -1,
+        kerSuccess = 0,
+        kerErrorMessage,
+        kerCallFailed,
+        kerNotAnImage,
+        kerInvalidDataset,
+        kerInvalidRecord,
+        kerInvalidKey,
+        kerInvalidTag,
+        kerValueNotSet,
+        kerDataSourceOpenFailed,
+        kerFileOpenFailed,
+        kerFileContainsUnknownImageType,
+        kerMemoryContainsUnknownImageType,
+        kerUnsupportedImageType,
+        kerFailedToReadImageData,
+        kerNotAJpeg,
+        kerFailedToMapFileForReadWrite,
+        kerFileRenameFailed,
+        kerTransferFailed,
+        kerMemoryTransferFailed,
+        kerInputDataReadFailed,
+        kerImageWriteFailed,
+        kerNoImageInInputData,
+        kerInvalidIfdId,
+        //! Entry::setValue: Value too large
+        kerValueTooLarge,
+        //! Entry::setDataArea: Value too large
+        kerDataAreaValueTooLarge,
+        kerOffsetOutOfRange,
+        kerUnsupportedDataAreaOffsetType,
+        kerInvalidCharset,
+        kerUnsupportedDateFormat,
+        kerUnsupportedTimeFormat,
+        kerWritingImageFormatUnsupported,
+        kerInvalidSettingForImage,
+        kerNotACrwImage,
+        kerFunctionNotSupported,
+        kerNoNamespaceInfoForXmpPrefix,
+        kerNoPrefixForNamespace,
+        kerTooLargeJpegSegment,
+        kerUnhandledXmpdatum,
+        kerUnhandledXmpNode,
+        kerXMPToolkitError,
+        kerDecodeLangAltPropertyFailed,
+        kerDecodeLangAltQualifierFailed,
+        kerEncodeLangAltPropertyFailed,
+        kerPropertyNameIdentificationFailed,
+        kerSchemaNamespaceNotRegistered,
+        kerNoNamespaceForPrefix,
+        kerAliasesNotSupported,
+        kerInvalidXmpText,
+        kerTooManyTiffDirectoryEntries,
+        kerMultipleTiffArrayElementTagsInDirectory,
+        kerWrongTiffArrayElementTagType,
+        kerInvalidKeyXmpValue,
+        kerInvalidIccProfile,
+        kerInvalidXMP,
+        kerTiffDirectoryTooLarge,
+        kerInvalidTypeValue,
+        kerInvalidMalloc,
+        kerCorruptedMetadata,
+        kerArithmeticOverflow,
+        kerMallocFailed,
+    };
+
     /*!
       @brief Simple error class used for exceptions. An output operator is
              provided to print errors to a stream.
diff --git a/include/exiv2/types.hpp b/include/exiv2/types.hpp
index b19a79ab..b84095ab 100644
--- a/include/exiv2/types.hpp
+++ b/include/exiv2/types.hpp
@@ -201,7 +201,7 @@ namespace Exiv2 {
         //! Default constructor
         DataBuf() : pData_(0), size_(0) {}
         //! Constructor with an initial buffer size
-        explicit DataBuf(long size) : pData_(new byte[size]), size_(size) {}
+        explicit DataBuf(long size) : pData_(new byte[size]()), size_(size) {}
         //! Constructor, copies an existing buffer
         DataBuf(const byte* pData, long size);
         /*!
diff --git a/include/exiv2/value.hpp b/include/exiv2/value.hpp
index 64a8ca7f..b7d76fef 100644
--- a/include/exiv2/value.hpp
+++ b/include/exiv2/value.hpp
@@ -44,6 +44,7 @@
 #include <sstream>
 #include <memory>
 #include <cstring>
+#include <climits>
 
 // *****************************************************************************
 // namespace extensions
@@ -1658,11 +1659,13 @@ namespace Exiv2 {
         ok_ = true;
         return static_cast<long>(value_[n]);
     }
+// #55 crash when value_[n].first == LONG_MIN
+#define LARGE_INT 1000000
     // Specialization for rational
     template<>
     inline long ValueType<Rational>::toLong(long n) const
     {
-        ok_ = (value_[n].second != 0);
+        ok_ = (value_[n].second != 0 && -LARGE_INT < value_[n].first && value_[n].first < LARGE_INT);
         if (!ok_) return 0;
         return value_[n].first / value_[n].second;
     }
@@ -1670,7 +1673,7 @@ namespace Exiv2 {
     template<>
     inline long ValueType<URational>::toLong(long n) const
     {
-        ok_ = (value_[n].second != 0);
+        ok_ = (value_[n].second != 0 && value_[n].first < LARGE_INT);
         if (!ok_) return 0;
         return value_[n].first / value_[n].second;
     }
diff --git a/po/CMakeLists.txt b/po/CMakeLists.txt
index 63b37733..0403fc24 100644
--- a/po/CMakeLists.txt
+++ b/po/CMakeLists.txt
@@ -8,10 +8,6 @@
 # automatically include all po files in the directory
 FILE(GLOB PO_FILES *.po)
 
-if ( NOT MSVC )
-	set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
-endif()
-
 UPDATE_TRANSLATIONS(exiv2 ${PO_FILES})
 
 ADD_TRANSLATIONS(exiv2 ${PO_FILES})
diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt
index 9690aa0e..cc30a324 100644
--- a/samples/CMakeLists.txt
+++ b/samples/CMakeLists.txt
@@ -5,10 +5,6 @@
 # Redistribution and use is allowed according to the terms of the BSD license.
 # For details see the accompanying COPYING-CMAKE-SCRIPTS file.
 
-if ( NOT MSVC )
-	set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
-endif()
-
 include_directories("${CMAKE_SOURCE_DIR}/include" "${CMAKE_SOURCE_DIR}/src")
 
 SET( SAMPLES addmoddel.cpp
@@ -49,7 +45,7 @@ FOREACH(entry ${SAMPLES})
     ADD_EXECUTABLE( ${target} ${target}.cpp )
     ADD_TEST( ${target}_test ${target} )
     TARGET_LINK_LIBRARIES( ${target} ${PRIVATE_VAR} exiv2lib Threads::Threads ${EXPAT_LIBRARIES})
-    INSTALL( TARGETS ${target} ${INSTALL_TARGET_STANDARD_ARGS} )
+    INSTALL( TARGETS ${target} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
 ENDFOREACH(entry ${SAMPLES})
 
 ###################################
@@ -62,25 +58,25 @@ ENDIF( MSVC )
 SET( MC_SRC ${MC_SRC} metacopy.cpp ../src/utils.cpp )
 ADD_EXECUTABLE( metacopy ${MC_SRC} )
 TARGET_LINK_LIBRARIES( metacopy ${PRIVATE_VAR} exiv2lib Threads::Threads ${EXPAT_LIBRARIES} ${ZLIB_LIBRARIES})
-INSTALL( TARGETS metacopy ${INSTALL_TARGET_STANDARD_ARGS} )
+INSTALL( TARGETS metacopy RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
 
 SET   ( PATHTEST_SRC             ${PATHTEST_SRC}  path-test.cpp ../src/utils.cpp )
 ADD_EXECUTABLE       ( pathtest  ${PATHTEST_SRC} )
 SET_TARGET_PROPERTIES( pathtest  PROPERTIES OUTPUT_NAME path-test )
 TARGET_LINK_LIBRARIES( pathtest  ${PRIVATE_VAR} exiv2lib Threads::Threads ${EXPAT_LIBRARIES} ${ZLIB_LIBRARIES})
-INSTALL      ( TARGETS pathtest  ${INSTALL_TARGET_STANDARD_ARGS} )
+INSTALL      ( TARGETS pathtest  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
 
 SET( EXIV2JSON_SRC     exiv2json.cpp Jzon.cpp )
 ADD_EXECUTABLE(        exiv2json ${EXIV2JSON_SRC} )
 SET_TARGET_PROPERTIES( exiv2json PROPERTIES OUTPUT_NAME exiv2json )
 TARGET_LINK_LIBRARIES( exiv2json ${PRIVATE_VAR} ${PRIVATE_VAR} exiv2lib Threads::Threads ${EXPAT_LIBRARIES} ${ZLIB_LIBRARIES})
-INSTALL( TARGETS       exiv2json ${INSTALL_TARGET_STANDARD_ARGS} )
+INSTALL( TARGETS       exiv2json RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
 
 SET( GEOTAG_SRC        geotag.cpp )
 ADD_EXECUTABLE(        geotag    ${GEOTAG_SRC} )
 SET_TARGET_PROPERTIES( geotag    PROPERTIES OUTPUT_NAME geotag )
 TARGET_LINK_LIBRARIES( geotag    ${PRIVATE_VAR} exiv2lib Threads::Threads ${EXPAT_LIBRARIES} ${ZLIB_LIBRARIES})
-INSTALL( TARGETS       geotag    ${INSTALL_TARGET_STANDARD_ARGS} )
+INSTALL( TARGETS       geotag    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
 
 # ******************************************************************************
 # Man page
diff --git a/samples/exiv2json.cpp b/samples/exiv2json.cpp
index 505268d9..a81268f0 100644
--- a/samples/exiv2json.cpp
+++ b/samples/exiv2json.cpp
@@ -148,6 +148,11 @@ bool isArray(std::string& value)
 template <class T>
 void push(Jzon::Node& node,const std::string& key,T i)
 {
+#define ABORT_IF_I_EMTPY          \
+    if (i->value().size() == 0) { \
+        return;                   \
+    }
+
     std::string value = i->value().toString();
 
     switch ( i->typeId() ) {
@@ -179,6 +184,7 @@ void push(Jzon::Node& node,const std::string& key,T i)
 
         case Exiv2::unsignedRational:
         case Exiv2::signedRational: {
+             ABORT_IF_I_EMTPY
              Jzon::Array     arr;
              Exiv2::Rational rat = i->value().toRational();
              arr.Add(rat.first );
@@ -187,6 +193,7 @@ void push(Jzon::Node& node,const std::string& key,T i)
         } break;
 
         case Exiv2::langAlt: {
+             ABORT_IF_I_EMTPY
              Jzon::Object l ;
              const Exiv2::LangAltValue& langs = dynamic_cast<const Exiv2::LangAltValue&>(i->value());
              for ( Exiv2::LangAltValue::ValueType::const_iterator lang = langs.value_.begin()
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index aecd6215..ba4c1b81 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -6,10 +6,6 @@
 # Redistribution and use is allowed according to the terms of the BSD license.
 # For details see the accompanying COPYING-CMAKE-SCRIPTS file.
 
-if ( NOT MSVC )
-    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
-endif()
-
 # Private headers which are only needed for the library itself
 SET( LIBEXIV2_PRIVATE_HDR canonmn_int.hpp
                           casiomn_int.hpp
@@ -222,10 +218,7 @@ IF(NOT HAVE_TIMEGM )
     SET( PATHTEST_SRC     ${PATHTEST_SRC} localtime.c    )
 ENDIF( NOT HAVE_TIMEGM )
 
-IF( MSVC )
-    SET( EXIV2_SRC        ${EXIV2_SRC}    getopt_win32.c )
-    SET( LIBEXIV2_SRC     ${LIBEXIV2_SRC} getopt_win32.c )
-ENDIF( MSVC )
+SET( EXIV2_SRC        ${EXIV2_SRC}    getopt_win32.c )
 
 ##
 # msvn tuning
@@ -246,6 +239,8 @@ SET_TARGET_PROPERTIES( exiv2lib PROPERTIES
     OUTPUT_NAME   exiv2
 )
 
+target_compile_definitions(exiv2lib PRIVATE EXV_LOCALEDIR="${CMAKE_INSTALL_LOCALEDIR}" )
+
 IF ( UNIX )
   IF ( NOT CYGWIN AND NOT MINGW )
     SET (LINUX 1)
@@ -268,10 +263,12 @@ else()
         TARGET_LINK_LIBRARIES( exiv2lib ${PRIVATE_VAR} dl ${EXPAT_LIBRARIES} )
         TARGET_LINK_LIBRARIES( exiv2lib ${PRIVATE_VAR} dl ${CURL_LIBRARIES} )
         TARGET_LINK_LIBRARIES( exiv2lib ${PRIVATE_VAR} dl ${SSH_LIBRARIES} )
+        TARGET_LINK_LIBRARIES( exiv2lib ${PRIVATE_VAR} dl ${CMAKE_THREAD_LIBS_INIT} )
     else()
         TARGET_LINK_LIBRARIES( exiv2lib ${PRIVATE_VAR} ${EXPAT_LIBRARIES} )
         TARGET_LINK_LIBRARIES( exiv2lib ${PRIVATE_VAR} ${CURL_LIBRARIES} )
         TARGET_LINK_LIBRARIES( exiv2lib ${PRIVATE_VAR} ${SSH_LIBRARIES} )
+        TARGET_LINK_LIBRARIES( exiv2lib ${PRIVATE_VAR} ${CMAKE_THREAD_LIBS_INIT} )
     endif()
 endif()
 
@@ -308,7 +305,11 @@ IF (CYGWIN OR MINGW)
     TARGET_LINK_LIBRARIES( exiv2lib ${PRIVATE_VAR}  psapi ws2_32 )
 ENDIF(CYGWIN OR MINGW)
 
-INSTALL( TARGETS exiv2lib ${INSTALL_TARGET_STANDARD_ARGS} )
+INSTALL(TARGETS exiv2lib
+    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+)
 
 include(../CMake_msvc.txt)
 msvc_runtime_configure(${EXIV2_ENABLE_SHARED} ${EXIV2_ENABLE_DYNAMIC_RUNTIME})
@@ -316,13 +317,14 @@ msvc_runtime_configure(${EXIV2_ENABLE_SHARED} ${EXIV2_ENABLE_DYNAMIC_RUNTIME})
 # ******************************************************************************
 # exiv2 application
 ADD_EXECUTABLE( exiv2 ${EXIV2_SRC}  ${EXIV2_HDR} )
-TARGET_LINK_LIBRARIES( exiv2 exiv2lib )
-INSTALL( TARGETS exiv2 ${INSTALL_TARGET_STANDARD_ARGS} )
+target_compile_definitions(exiv2 PRIVATE EXV_LOCALEDIR="${CMAKE_INSTALL_LOCALEDIR}" )
+TARGET_LINK_LIBRARIES( exiv2 exiv2lib ${LIBINTL_LIBRARIES} )
+INSTALL( TARGETS exiv2 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
 
 # ******************************************************************************
 # connection test application
 ADD_EXECUTABLE( conntest ${CONNTEST} )
-TARGET_LINK_LIBRARIES( conntest ${PRIVATE_VAR}  exiv2lib ${CURL_LIBRARIES} ${SSH_LIBRARIES})
+TARGET_LINK_LIBRARIES( conntest ${PRIVATE_VAR}  exiv2lib ${CURL_LIBRARIES} ${SSH_LIBRARIES} ${LIBINTL_LIBRARIES} )
 
 # ******************************************************************************
 # exifprint application
@@ -332,7 +334,7 @@ TARGET_LINK_LIBRARIES( conntest ${PRIVATE_VAR}  exiv2lib ${CURL_LIBRARIES} ${SSH
 # ******************************************************************************
 # remotetest application
 ADD_EXECUTABLE( remotetest ${REMOTETEST} )
-TARGET_LINK_LIBRARIES( remotetest exiv2lib )
+TARGET_LINK_LIBRARIES( remotetest exiv2lib ${LIBINTL_LIBRARIES} )
 
 # ******************************************************************************
 # Headers
diff --git a/src/Makefile b/src/Makefile
index 8a8366fe..d046e331 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -131,8 +131,7 @@ CCSRC += asfvideo.cpp      \
 	 utilsvideo.cpp
 endif
 
-# Add library C source files to this list
-EXIVCSRC  =
+# C source files
 ifndef HAVE_TIMEGM
 CSRC =   localtime.c
 endif
@@ -141,9 +140,7 @@ endif
 EXIV2MAIN = exiv2.cpp
 EXIV2SRC  = actions.cpp \
             utils.cpp
-
-# C source files for the Exiv2 application
-EXIVCSRC  =
+EXIVCSRC  = getopt_win32.c
 
 # ******************************************************************************
 # Library
@@ -176,7 +173,7 @@ OBJ    = $(CCOBJ) $(COBJ)
 LOBJ   = $(CCLOBJ) $(CLOBJ)
 
 EXIV2OBJ  = $(EXIV2MAIN:.cpp=.o) $(EXIV2SRC:.cpp=.o)
-EXIV2COBJ = $(EXIVCSRC:.c=.o)
+EXIVCOBJ  = $(EXIVCSRC:.c=.o)
 EXIV2EXE  = $(EXIV2MAIN:.cpp=$(EXEEXT))
 
 ifdef DEP_TRACKING
@@ -251,9 +248,9 @@ lib: $(OBJ)
 $(BINARY): %: %.o lib
 	@$(LIBTOOL) --mode=link $(LINK.cc) -o $@ $(LIBRARY) $@.o -rpath $(libdir)
 
-$(EXIV2EXE): lib $(EXIV2OBJ) $(EXIV2COBJ)
+$(EXIV2EXE): lib $(EXIV2OBJ) $(EXIVCOBJ)
 	mkdir -pv ../bin 2>&1 > /dev/null
-	@$(LIBTOOL) --mode=link $(LINK.cc) -o ../bin/$@ $(LIBRARY) $(EXIV2OBJ) $(EXIV2COBJ) -rpath $(libdir)
+	@$(LIBTOOL) --mode=link $(LINK.cc) -o ../bin/$@ $(LIBRARY) $(EXIV2OBJ) $(EXIVCOBJ) -rpath $(libdir)
 
 install-header:
 	$(INSTALL_DIRS) $(DESTDIR)$(incdir)
diff --git a/src/actions.cpp b/src/actions.cpp
index 0ebe8505..b31d6ec6 100644
--- a/src/actions.cpp
+++ b/src/actions.cpp
@@ -59,6 +59,7 @@ EXIV2_RCSID("@(#) $Id$")
 #include <ctime>
 #include <cmath>
 #include <cassert>
+#include <stdexcept>
 #include <sys/types.h>                  // for stat()
 #include <sys/stat.h>                   // for stat()
 #ifdef EXV_HAVE_UNISTD_H
@@ -236,33 +237,43 @@ namespace Action {
     }
 
     int Print::run(const std::string& path)
-    try {
-        path_ = path;
-        int rc = 0;
-        Exiv2::PrintStructureOption option = Exiv2::kpsNone ;
-        switch (Params::instance().printMode_) {
-            case Params::pmSummary:   rc = printSummary();     break;
-            case Params::pmList:      rc = printList();        break;
-            case Params::pmComment:   rc = printComment();     break;
-            case Params::pmPreview:   rc = printPreviewList(); break;
-            case Params::pmStructure: rc = printStructure(std::cout,Exiv2::kpsBasic)     ; break;
-            case Params::pmRecursive: rc = printStructure(std::cout,Exiv2::kpsRecursive) ; break;
-
-            case Params::pmXMP:
-                 option = option == Exiv2::kpsNone ? Exiv2::kpsXMP        : option;  // drop
-            case Params::pmIccProfile:{
-                 option = option == Exiv2::kpsNone ? Exiv2::kpsIccProfile : option;
-                 _setmode(_fileno(stdout),O_BINARY);
-                 rc = printStructure(std::cout,option);
-            } break;
+    {
+        try {
+            path_ = path;
+            int rc = 0;
+            Exiv2::PrintStructureOption option = Exiv2::kpsNone ;
+            switch (Params::instance().printMode_) {
+                case Params::pmSummary:   rc = printSummary();     break;
+                case Params::pmList:      rc = printList();        break;
+                case Params::pmComment:   rc = printComment();     break;
+                case Params::pmPreview:   rc = printPreviewList(); break;
+                case Params::pmStructure: rc = printStructure(std::cout,Exiv2::kpsBasic)     ; break;
+                case Params::pmRecursive: rc = printStructure(std::cout,Exiv2::kpsRecursive) ; break;
+
+                case Params::pmXMP:
+                    if (option == Exiv2::kpsNone)
+                        option = Exiv2::kpsXMP;
+                    // drop
+                case Params::pmIccProfile:
+                    if (option == Exiv2::kpsNone)
+                        option = Exiv2::kpsIccProfile;
+                    _setmode(_fileno(stdout),O_BINARY);
+                    rc = printStructure(std::cout,option);
+                    break;
+            }
+            return rc;
+        }
+        catch(const Exiv2::AnyError& e) {
+            std::cerr << "Exiv2 exception in print action for file "
+                      << path << ":\n" << e << "\n";
+            return 1;
+        }
+        catch(const std::overflow_error& e) {
+            std::cerr << "std::overflow_error exception in print action for file "
+                      << path << ":\n" << e.what() << "\n";
+            return 1;
         }
-        return rc;
     }
-    catch(const Exiv2::AnyError& e) {
-        std::cerr << "Exiv2 exception in print action for file "
-                  << path << ":\n" << e << "\n";
-        return 1;
-    } // Print::run
 
     int Print::printStructure(std::ostream& out, Exiv2::PrintStructureOption option)
     {
@@ -625,91 +636,90 @@ namespace Action {
 
     bool Print::printMetadatum(const Exiv2::Metadatum& md, const Exiv2::Image* pImage)
     {
-        if (!grepTag(md.key())) return false;
-        if (!keyTag (md.key())) return false;
+        if (!grepTag(md.key()))
+            return false;
+        if (!keyTag(md.key()))
+            return false;
 
-        if (   Params::instance().unknown_
-            && md.tagName().substr(0, 2) == "0x") {
+        if (Params::instance().unknown_ && md.tagName().substr(0, 2) == "0x") {
             return false;
         }
+
         bool const manyFiles = Params::instance().files_.size() > 1;
         if (manyFiles) {
-            std::cout << std::setfill(' ') << std::left << std::setw(20)
-                      << path_ << "  ";
+            std::cout << std::setfill(' ') << std::left << std::setw(20) << path_ << "  ";
         }
+
         bool first = true;
         if (Params::instance().printItems_ & Params::prTag) {
-            if (!first) std::cout << " ";
+            if (!first)
+                std::cout << " ";
             first = false;
-            std::cout << "0x" << std::setw(4) << std::setfill('0')
-                      << std::right << std::hex
-                      << md.tag();
+            std::cout << "0x" << std::setw(4) << std::setfill('0') << std::right << std::hex << md.tag();
         }
         if (Params::instance().printItems_ & Params::prSet) {
-            if (!first) std::cout << " ";
+            if (!first)
+                std::cout << " ";
             first = false;
-            std::cout << "set" ;
+            std::cout << "set";
         }
         if (Params::instance().printItems_ & Params::prGroup) {
-            if (!first) std::cout << " ";
+            if (!first)
+                std::cout << " ";
             first = false;
-            std::cout << std::setw(12) << std::setfill(' ') << std::left
-                      << md.groupName();
+            std::cout << std::setw(12) << std::setfill(' ') << std::left << md.groupName();
         }
         if (Params::instance().printItems_ & Params::prKey) {
-            if (!first) std::cout << " ";
+            if (!first)
+                std::cout << " ";
             first = false;
-            std::cout << std::setfill(' ') << std::left << std::setw(44)
-                      << md.key();
+            std::cout << std::setfill(' ') << std::left << std::setw(44) << md.key();
         }
         if (Params::instance().printItems_ & Params::prName) {
-            if (!first) std::cout << " ";
+            if (!first)
+                std::cout << " ";
             first = false;
-            std::cout << std::setw(27) << std::setfill(' ') << std::left
-                      << md.tagName();
+            std::cout << std::setw(27) << std::setfill(' ') << std::left << md.tagName();
         }
         if (Params::instance().printItems_ & Params::prLabel) {
-            if (!first) std::cout << " ";
+            if (!first)
+                std::cout << " ";
             first = false;
-            std::cout << std::setw(30) << std::setfill(' ') << std::left
-                      << md.tagLabel();
+            std::cout << std::setw(30) << std::setfill(' ') << std::left << md.tagLabel();
         }
         if (Params::instance().printItems_ & Params::prType) {
-            if (!first) std::cout << " ";
+            if (!first)
+                std::cout << " ";
             first = false;
             std::cout << std::setw(9) << std::setfill(' ') << std::left;
             const char* tn = md.typeName();
             if (tn) {
                 std::cout << tn;
-            }
-            else {
+            } else {
                 std::ostringstream os;
                 os << "0x" << std::setw(4) << std::setfill('0') << std::hex << md.typeId();
                 std::cout << os.str();
             }
         }
         if (Params::instance().printItems_ & Params::prCount) {
-            if (!first) std::cout << " ";
+            if (!first)
+                std::cout << " ";
             first = false;
-            std::cout << std::dec << std::setw(3)
-                      << std::setfill(' ') << std::right
-                      << md.count();
+            std::cout << std::dec << std::setw(3) << std::setfill(' ') << std::right << md.count();
         }
         if (Params::instance().printItems_ & Params::prSize) {
-            if (!first) std::cout << " ";
+            if (!first)
+                std::cout << " ";
             first = false;
-            std::cout << std::dec << std::setw(3)
-                      << std::setfill(' ') << std::right
-                      << md.size();
+            std::cout << std::dec << std::setw(3) << std::setfill(' ') << std::right << md.size();
         }
-        if (Params::instance().printItems_ & Params::prValue) {
-            if (!first) std::cout << "  ";
+        if (Params::instance().printItems_ & Params::prValue && md.size() > 0) {
+            if (!first)
+                std::cout << "  ";
             first = false;
-            if (   Params::instance().binary_
-                && (   md.typeId() == Exiv2::undefined
-                    || md.typeId() == Exiv2::unsignedByte
-                    || md.typeId() == Exiv2::signedByte)
-                && md.size() > 128) {
+            if (md.size() > 128 && Params::instance().binary_ &&
+                (md.typeId() == Exiv2::undefined || md.typeId() == Exiv2::unsignedByte ||
+                 md.typeId() == Exiv2::signedByte)) {
                 std::cout << _("(Binary value suppressed)") << std::endl;
                 return true;
             }
@@ -727,22 +737,22 @@ namespace Action {
             }
             if (!done) {
                 // #1114 - show negative values for SByte
-                if (md.typeId() != Exiv2::signedByte){
+                if (md.typeId() != Exiv2::signedByte) {
                     std::cout << std::dec << md.value();
                 } else {
                     int value = md.value().toLong();
-                    std::cout << std::dec << (value<128?value:value-256);
+                    std::cout << std::dec << (value < 128 ? value : value - 256);
                 }
             }
         }
         if (Params::instance().printItems_ & Params::prTrans) {
-            if (!first) std::cout << "  ";
+            if (!first)
+                std::cout << "  ";
             first = false;
-            if (   Params::instance().binary_
-                && (   md.typeId() == Exiv2::undefined
-                    || md.typeId() == Exiv2::unsignedByte
-                    || md.typeId() == Exiv2::signedByte)
-                && md.size() > 128) {
+            if (Params::instance().binary_ &&
+                (md.typeId() == Exiv2::undefined || md.typeId() == Exiv2::unsignedByte ||
+                 md.typeId() == Exiv2::signedByte) &&
+                md.size() > 128) {
                 std::cout << _("(Binary value suppressed)") << std::endl;
                 return true;
             }
@@ -754,16 +764,17 @@ namespace Action {
                     done = true;
                 }
             }
-            if (!done) std::cout << std::dec << md.print(&pImage->exifData());
+            if (!done)
+                std::cout << std::dec << md.print(&pImage->exifData());
         }
         if (Params::instance().printItems_ & Params::prHex) {
-            if (!first) std::cout << std::endl;
+            if (!first)
+                std::cout << std::endl;
             first = false;
-            if (   Params::instance().binary_
-                && (   md.typeId() == Exiv2::undefined
-                    || md.typeId() == Exiv2::unsignedByte
-                    || md.typeId() == Exiv2::signedByte)
-                && md.size() > 128) {
+            if (Params::instance().binary_ &&
+                (md.typeId() == Exiv2::undefined || md.typeId() == Exiv2::unsignedByte ||
+                 md.typeId() == Exiv2::signedByte) &&
+                md.size() > 128) {
                 std::cout << _("(Binary value suppressed)") << std::endl;
                 return true;
             }
@@ -773,7 +784,7 @@ namespace Action {
         }
         std::cout << std::endl;
         return true;
-    } // Print::printMetadatum
+    }  // Print::printMetadatum
 
     int Print::printComment()
     {
diff --git a/src/basicio.cpp b/src/basicio.cpp
index 95589cd2..f2e1518b 100644
--- a/src/basicio.cpp
+++ b/src/basicio.cpp
@@ -990,6 +990,7 @@ namespace Exiv2 {
     DataBuf FileIo::read(long rcount)
     {
         assert(p_->fp_ != 0);
+        if ( (size_t) rcount > size() ) throw Error(57);
         DataBuf buf(rcount);
         long readCount = read(buf.pData_, buf.size_);
         buf.size_ = readCount;
diff --git a/src/crwimage.cpp b/src/crwimage.cpp
index ca79aa74..989c0eb8 100644
--- a/src/crwimage.cpp
+++ b/src/crwimage.cpp
@@ -745,12 +745,11 @@ namespace Exiv2 {
 
     DataLocId CiffComponent::dataLocation(uint16_t tag)
     {
-        DataLocId di = invalidDataLocId;
         switch (tag & 0xc000) {
-        case 0x0000: di = valueData; break;
-        case 0x4000: di = directoryData; break;
+        case 0x0000: return valueData;
+        case 0x4000: return directoryData;
+        default: throw Error(33);
         }
-        return di;
     } // CiffComponent::dataLocation
 
     /*!
diff --git a/src/crwimage_int.hpp b/src/crwimage_int.hpp
index 01a0c572..99189262 100644
--- a/src/crwimage_int.hpp
+++ b/src/crwimage_int.hpp
@@ -78,7 +78,6 @@ namespace Exiv2 {
 
     //! Type to identify where the data is stored in a directory
     enum DataLocId {
-        invalidDataLocId,
         valueData,
         directoryData,
         lastDataLocId
diff --git a/src/enforce.hpp b/src/enforce.hpp
new file mode 100644
index 00000000..b2d77eea
--- /dev/null
+++ b/src/enforce.hpp
@@ -0,0 +1,96 @@
+// ********************************************************* -*- C++ -*-
+/*
+ * Copyright (C) 2004-2018 Exiv2 maintainers
+ *
+ * This program is part of the Exiv2 distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
+ */
+/*!
+  @file    enforce.hpp
+  @brief   Port of D's enforce() to C++ & Exiv2
+  @author  Dan Čermák (D4N)
+           <a href="mailto:dan.cermak@cgc-instruments.com">dan.cermak@cgc-instruments.com</a>
+  @date    11-March-18, D4N: created
+ */
+
+#include <string>
+
+#include "error.hpp"
+
+/*!
+ * @brief Ensure that condition is true, otherwise throw an exception of the
+ * type exception_t
+ *
+ * @tparam exception_t  Exception type that is thrown, must provide a
+ * constructor that accepts a single argument to which arg1 is forwarded.
+ *
+ * @todo once we have C++>=11 use variadic templates and std::forward to remove
+ * all overloads of enforce
+ */
+template <typename exception_t, typename T>
+inline void enforce(bool condition, const T& arg1)
+{
+    if (!condition) {
+        throw exception_t(arg1);
+    }
+}
+
+/*!
+ * @brief Ensure that condition is true, otherwise throw an Exiv2::Error with
+ * the given error_code.
+ */
+inline void enforce(bool condition, Exiv2::ErrorCode err_code)
+{
+    if (!condition) {
+        throw Exiv2::Error(err_code);
+    }
+}
+
+/*!
+ * @brief Ensure that condition is true, otherwise throw an Exiv2::Error with
+ * the given error_code & arg1.
+ */
+template <typename T>
+inline void enforce(bool condition, Exiv2::ErrorCode err_code, const T& arg1)
+{
+    if (!condition) {
+        throw Exiv2::Error(err_code, arg1);
+    }
+}
+
+/*!
+ * @brief Ensure that condition is true, otherwise throw an Exiv2::Error with
+ * the given error_code, arg1 & arg2.
+ */
+template <typename T, typename U>
+inline void enforce(bool condition, Exiv2::ErrorCode err_code, const T& arg1, const U& arg2)
+{
+    if (!condition) {
+        throw Exiv2::Error(err_code, arg1, arg2);
+    }
+}
+
+/*!
+ * @brief Ensure that condition is true, otherwise throw an Exiv2::Error with
+ * the given error_code, arg1, arg2 & arg3.
+ */
+template <typename T, typename U, typename V>
+inline void enforce(bool condition, Exiv2::ErrorCode err_code, const T& arg1, const U& arg2, const V& arg3)
+{
+    if (!condition) {
+        throw Exiv2::Error(err_code, arg1, arg2, arg3);
+    }
+}
diff --git a/src/error.cpp b/src/error.cpp
index 80378c19..5d63957d 100644
--- a/src/error.cpp
+++ b/src/error.cpp
@@ -106,6 +106,11 @@ namespace {
         { 52, N_("%1 has invalid XMP value type `%2'") }, // %1=key, %2=value type
         { 53, N_("Not a valid ICC Profile") },
         { 54, N_("Not valid XMP") },
+        { 55, N_("tiff directory length is too large") },
+        { 56, N_("invalid type value detected in Image::printIFDStructure") },
+        { 57, N_("invalid memory allocation request") },
+        { 58, N_("corrupted image metadata") },
+        { 59, N_("Arithmetic operation overflow") },
     };
 
 }
diff --git a/src/exiv2.cpp b/src/exiv2.cpp
index d6a45e1b..588da1aa 100644
--- a/src/exiv2.cpp
+++ b/src/exiv2.cpp
@@ -150,30 +150,36 @@ int main(int argc, char* const argv[])
         return 0;
     }
 
-    // Create the required action class
-    Action::TaskFactory& taskFactory = Action::TaskFactory::instance();
-    Action::Task::AutoPtr task
-        = taskFactory.create(Action::TaskType(params.action_));
-    assert(task.get());
-
-    // Process all files
     int rc = 0;
-    int n = 1;
-    int s = static_cast<int>(params.files_.size());
-    int w = s > 9 ? s > 99 ? 3 : 2 : 1;
-    for (Params::Files::const_iterator i = params.files_.begin();
-         i != params.files_.end(); ++i) {
-        if (params.verbose_) {
-            std::cout << _("File") << " " << std::setw(w) << std::right << n++ << "/" << s << ": "
-                      << *i << std::endl;
+
+    try {
+        // Create the required action class
+        Action::TaskFactory& taskFactory = Action::TaskFactory::instance();
+        Action::Task::AutoPtr task = taskFactory.create(Action::TaskType(params.action_));
+        assert(task.get());
+
+        // Process all files
+        int n = 1;
+        int s = static_cast<int>(params.files_.size());
+        int w = s > 9 ? s > 99 ? 3 : 2 : 1;
+        for (Params::Files::const_iterator i = params.files_.begin(); i != params.files_.end(); ++i) {
+            if (params.verbose_) {
+                std::cout << _("File") << " " << std::setw(w) << std::right << n++ << "/" << s << ": " << *i
+                          << std::endl;
+            }
+            int ret = task->run(*i);
+            if (rc == 0)
+                rc = ret;
         }
-        int ret = task->run(*i);
-        if (rc == 0) rc = ret;
-    }
 
-    taskFactory.cleanup();
-    params.cleanup();
-    Exiv2::XmpParser::terminate();
+        taskFactory.cleanup();
+        params.cleanup();
+        Exiv2::XmpParser::terminate();
+
+    } catch (const std::exception& exc) {
+        std::cerr << "Uncaught exception: " << exc.what() << std::endl;
+        rc = 1;
+    }
 
     // Return a positive one byte code for better consistency across platforms
     return static_cast<unsigned int>(rc) % 256;
diff --git a/src/getopt_win32.c b/src/getopt_win32.c
index fca29924..18dfcfbf 100644
--- a/src/getopt_win32.c
+++ b/src/getopt_win32.c
@@ -194,6 +194,10 @@ permute_args(panonopt_start, panonopt_end, opt_end, nargv)
 	}
 }
 
+#ifdef __GETOPT_DEFINE_ARGV__
+char * const *__argv;
+#endif
+
 /*
  * getopt_internal --
  *	Parse argc/argv argument vector.  Called by user level routines.
@@ -205,6 +209,11 @@ getopt_internal(nargc, nargv, options)
 	char * const *nargv;
 	const char *options;
 {
+
+#ifdef __GETOPT_DEFINE_ARGV__
+    __argv=nargv;
+#endif
+
 	char *oli;				/* option letter list index */
 	int optchar;
 
diff --git a/src/getopt_win32.h b/src/getopt_win32.h
index 6b6f643b..cd5760a3 100644
--- a/src/getopt_win32.h
+++ b/src/getopt_win32.h
@@ -38,6 +38,13 @@
 extern "C" {
 #endif
 
+#if !defined(_WIN32) &&  !defined(__CYGWIN__) && !defined(__MINGW__) && !defined(_MSC_VER)
+// the symbol __argv (and __argc and __progname and __env) are defined in Windows environments
+// for *ix environments, __argv is declared here, defined: getopt_win32.c, init'd: getopt_internal()
+#define __GETOPT_DEFINE_ARGV__
+extern char * const *__argv;
+#endif
+
 extern int   opterr;      /* if error message should be printed */
 extern int   optind;      /* index into parent argv vector */
 extern int   optopt;      /* character checked for validity */
diff --git a/src/image.cpp b/src/image.cpp
index 0d828045..929d4976 100644
--- a/src/image.cpp
+++ b/src/image.cpp
@@ -399,7 +399,13 @@ namespace Exiv2 {
                                 ;
 
                 // if ( offset > io.size() ) offset = 0; // Denial of service?
-                DataBuf  buf(size*count + pad+20);  // allocate a buffer
+
+                // #55 and #56 memory allocation crash test/data/POC8
+                long long allocate = (long long) size*count + pad+20;
+                if ( allocate > (long long) io.size() ) {
+                    throw Error(57);
+                }
+                DataBuf  buf(allocate);  // allocate a buffer
                 std::memcpy(buf.pData_,dir.pData_+8,4);  // copy dir[8:11] into buffer (short strings)
                 if ( count*size > 4 ) {            // read into buffer
                     size_t   restore = io.tell();  // save
diff --git a/src/jp2image.cpp b/src/jp2image.cpp
index 1892fd43..3cebc2a8 100644
--- a/src/jp2image.cpp
+++ b/src/jp2image.cpp
@@ -41,6 +41,7 @@ EXIV2_RCSID("@(#) $Id$")
 #include "error.hpp"
 #include "futils.hpp"
 #include "types.hpp"
+#include "safe_op.hpp"
 
 // + standard includes
 #include <string>
@@ -269,10 +270,21 @@ namespace Exiv2
                             std::cout << "Exiv2::Jp2Image::readMetadata: "
                                      << "Color data found" << std::endl;
 #endif
-                            long pad = 3 ; // 3 padding bytes 2 0 0
-                            DataBuf data(subBox.length+8);
+
+                            const long pad = 3 ; // 3 padding bytes 2 0 0
+			    const size_t data_length = Safe::add(subBox.length, static_cast<uint32_t>(8));
+			    // data_length makes no sense if it is larger than the rest of the file
+			    if (data_length > io_->size() - io_->tell()) {
+				throw Error(58);
+			    }
+                            DataBuf data(data_length);
                             io_->read(data.pData_,data.size_);
-                            long    iccLength = getULong(data.pData_+pad, bigEndian);
+                            const long    iccLength = getULong(data.pData_+pad, bigEndian);
+                            // subtracting pad from data.size_ is safe:
+                            // size_ is at least 8 and pad = 3
+                            if (iccLength > data.size_ - pad) {
+                                throw Error(58);
+                            }
                             DataBuf icc(iccLength);
                             ::memcpy(icc.pData_,data.pData_+pad,icc.size_);
 #ifdef DEBUG
diff --git a/src/nikonmn.cpp b/src/nikonmn.cpp
index 571ab806..34bf601c 100644
--- a/src/nikonmn.cpp
+++ b/src/nikonmn.cpp
@@ -299,6 +299,8 @@ namespace Exiv2 {
                                                const Value& value,
                                                const ExifData* exifData)
     {
+        if ( ! exifData ) return os << "undefined" ;
+
         if ( value.count() >= 9 ) {
             ByteOrder bo = getKeyString("Exif.MakerNote.ByteOrder",exifData) == "MM" ? bigEndian : littleEndian;
             byte      p[4];
diff --git a/src/pentaxmn.cpp b/src/pentaxmn.cpp
index 4fc38be0..b22cb43b 100644
--- a/src/pentaxmn.cpp
+++ b/src/pentaxmn.cpp
@@ -1167,6 +1167,8 @@ namespace Exiv2 {
 
     std::ostream& PentaxMakerNote::printShutterCount(std::ostream& os, const Value& value, const ExifData* metadata)
     {
+        if ( ! metadata ) return os << "undefined" ;
+
         ExifData::const_iterator dateIt = metadata->findKey(
                 ExifKey("Exif.PentaxDng.Date"));
         if (dateIt == metadata->end()) {
diff --git a/src/pngchunk.cpp b/src/pngchunk.cpp
index da4ccd01..5f1d2087 100644
--- a/src/pngchunk.cpp
+++ b/src/pngchunk.cpp
@@ -68,6 +68,8 @@ namespace Exiv2 {
                                    int*           outWidth,
                                    int*           outHeight)
     {
+        assert(data.size_ >= 8);
+
         // Extract image width and height from IHDR chunk.
 
         *outWidth  = getLong((const byte*)data.pData_,     bigEndian);
@@ -107,15 +109,17 @@ namespace Exiv2 {
     {
         // From a tEXt, zTXt, or iTXt chunk,
         // we get the key, it's a null terminated string at the chunk start
-        if (data.size_ <= (stripHeader ? 8 : 0)) throw Error(14);
-        const byte *key = data.pData_ + (stripHeader ? 8 : 0);
+        const int offset = stripHeader ? 8 : 0;
+        if (data.size_ <= offset) throw Error(14);
+        const byte *key = data.pData_ + offset;
 
         // Find null string at end of key.
         int keysize=0;
-        for ( ; key[keysize] != 0 ; keysize++)
+        while (key[keysize] != 0)
         {
+            keysize++;
             // look if keysize is valid.
-            if (keysize >= data.size_)
+            if (keysize+offset >= data.size_)
                 throw Error(14);
         }
 
@@ -621,6 +625,10 @@ namespace Exiv2 {
             sp++;
 
         length = (long) atol(sp);
+        const char* eot = (char*)text.pData_ + text.size_;
+        if (length < 0 || length > (eot - sp)/2) {
+            throw Error(14);
+        }
 
         while (*sp != ' ' && *sp != '\n')
             sp++;
diff --git a/src/pngimage.cpp b/src/pngimage.cpp
index 11b41982..ed7399a2 100644
--- a/src/pngimage.cpp
+++ b/src/pngimage.cpp
@@ -441,7 +441,9 @@ namespace Exiv2 {
 #ifdef DEBUG
                     std::cout << "Exiv2::PngImage::readMetadata: Found IHDR chunk (length: " << dataOffset << ")\n";
 #endif
-                    PngChunk::decodeIHDRChunk(cdataBuf, &pixelWidth_, &pixelHeight_);
+                    if (cdataBuf.size_ >= 8) {
+                        PngChunk::decodeIHDRChunk(cdataBuf, &pixelWidth_, &pixelHeight_);
+                    }
                 }
                 else if (!memcmp(cheaderBuf.pData_ + 4, "tEXt", 4))
                 {
diff --git a/src/preview.cpp b/src/preview.cpp
index c34c8bd2..0752ade7 100644
--- a/src/preview.cpp
+++ b/src/preview.cpp
@@ -36,6 +36,7 @@ EXIV2_RCSID("@(#) $Id$")
 
 #include "preview.hpp"
 #include "futils.hpp"
+#include "safe_op.hpp"
 
 #include "image.hpp"
 #include "cr2image.hpp"
@@ -547,7 +548,8 @@ namespace {
             }
         }
 
-        if (offset_ + size_ > static_cast<uint32_t>(image_.io().size())) return;
+        if (Safe::add(offset_, size_) > static_cast<uint32_t>(image_.io().size()))
+            return;
 
         valid_ = true;
     }
@@ -801,7 +803,7 @@ namespace {
                     // this saves one copying of the buffer
                     uint32_t offset = dataValue.toLong(0);
                     uint32_t size = sizes.toLong(0);
-                    if (offset + size <= static_cast<uint32_t>(io.size()))
+                    if (Safe::add(offset, size) <= static_cast<uint32_t>(io.size()))
                         dataValue.setDataArea(base + offset, size);
                 }
                 else {
@@ -811,7 +813,7 @@ namespace {
                     for (int i = 0; i < sizes.count(); i++) {
                         uint32_t offset = dataValue.toLong(i);
                         uint32_t size = sizes.toLong(i);
-                        if (offset + size <= static_cast<uint32_t>(io.size()))
+                        if (Safe::add(offset, size) <= static_cast<uint32_t>(io.size()))
                             memcpy(pos, base + offset, size);
                         pos += size;
                     }
diff --git a/src/safe_op.hpp b/src/safe_op.hpp
new file mode 100644
index 00000000..014b7f3a
--- /dev/null
+++ b/src/safe_op.hpp
@@ -0,0 +1,310 @@
+// ********************************************************* -*- C++ -*-
+/*
+ * Copyright (C) 2004-2017 Exiv2 maintainers
+ *
+ * This program is part of the Exiv2 distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
+ */
+/*!
+  @file    safe_op.hpp
+  @brief   Overflow checks for integers
+  @author  Dan Čermák (D4N)
+           <a href="mailto:dan.cermak@cgc-instruments.com">dan.cermak@cgc-instruments.com</a>
+  @date    14-Dec-17, D4N: created
+ */
+
+#ifndef SAFE_OP_HPP_
+#define SAFE_OP_HPP_
+
+#include <limits>
+#include <stdexcept>
+
+#ifdef _MSC_VER
+#include <Intsafe.h>
+#endif
+
+/*!
+ * @brief Arithmetic operations with overflow checks
+ */
+namespace Safe
+{
+    /*!
+     * @brief Helper structs for providing integer overflow checks.
+     *
+     * This namespace contains the internal helper structs fallback_add_overflow
+     * and builtin_add_overflow. Both have a public static member function add
+     * with the following interface:
+     *
+     * bool add(T summand_1, T summand_2, T& result)
+     *
+     * where T is the type over which the struct is templated.
+     *
+     * The function performs a check whether the addition summand_1 + summand_2
+     * can be performed without an overflow. If the operation would overflow,
+     * true is returned and the addition is not performed if it would result in
+     * undefined behavior. If no overflow occurs, the sum is saved in result and
+     * false is returned.
+     *
+     * fallback_add_overflow implements a portable but slower overflow check.
+     * builtin_add_overflow uses compiler builtins (when available) and should
+     * be considerably faster. As builtins are not available for all types,
+     * builtin_add_overflow falls back to fallback_add_overflow when no builtin
+     * is available.
+     */
+    namespace Internal
+    {
+        /*!
+         * @brief Helper struct to determine whether a type is signed or unsigned
+
+         * This struct is a backport of std::is_signed from C++11. It has a public
+         * enum with the property VALUE which is true when the type is signed or
+         * false if it is unsigned.
+         */
+        template <typename T>
+        struct is_signed
+        {
+            enum
+            {
+                VALUE = T(-1) < T(0)
+            };
+        };
+
+        /*!
+         * @brief Helper struct for SFINAE, from C++11
+
+         * This struct has a public typedef called type typedef'd to T if B is
+         * true. Otherwise there is no typedef.
+         */
+        template <bool B, class T = void>
+        struct enable_if
+        {
+        };
+
+        /*!
+         * @brief Specialization of enable_if for the case B == true
+         */
+        template <class T>
+        struct enable_if<true, T>
+        {
+            typedef T type;
+        };
+
+        /*!
+         * @brief Fallback overflow checker, specialized via SFINAE
+         *
+         * This struct implements a 'fallback' addition with an overflow check,
+         * i.e. it does not rely on compiler intrinsics.  It is specialized via
+         * SFINAE for signed and unsigned integer types and provides a public
+         * static member function add.
+         */
+        template <typename T, typename = void>
+        struct fallback_add_overflow;
+
+        /*!
+         * @brief Overload of fallback_add_overflow for signed integers
+         */
+        template <typename T>
+        struct fallback_add_overflow<T, typename enable_if<is_signed<T>::VALUE>::type>
+        {
+            /*!
+             * @brief Adds the two summands only if no overflow occurs
+             *
+             * This function performs a check if summand_1 + summand_2 would
+             * overflow and returns true in that case. If no overflow occurs,
+             * the sum is saved in result and false is returned.
+             *
+             * @return true on overflow, false on no overflow
+             *
+             * The check for an overflow is performed before the addition to
+             * ensure that no undefined behavior occurs. The value in result is
+             * only valid when the function returns false.
+             *
+             * Further information:
+             * https://wiki.sei.cmu.edu/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow
+             */
+            static bool add(T summand_1, T summand_2, T& result)
+            {
+                if (((summand_2 >= 0) && (summand_1 > std::numeric_limits<T>::max() - summand_2)) ||
+                    ((summand_2 < 0) && (summand_1 < std::numeric_limits<T>::min() - summand_2))) {
+                    return true;
+                } else {
+                    result = summand_1 + summand_2;
+                    return false;
+                }
+            }
+        };
+
+        /*!
+         * @brief Overload of fallback_add_overflow for unsigned integers
+         */
+        template <typename T>
+        struct fallback_add_overflow<T, typename enable_if<!is_signed<T>::VALUE>::type>
+        {
+            /*!
+             * @brief Adds the two summands only if no overflow occurs
+             *
+             * This function performs a check if summand_1 + summand_2 would
+             * overflow and returns true in that case. If no overflow occurs,
+             * the sum is saved in result and false is returned.
+             *
+             * @return true on overflow, false on no overflow
+             *
+             * Further information:
+             * https://wiki.sei.cmu.edu/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap
+             */
+            static bool add(T summand_1, T summand_2, T& result)
+            {
+                if (summand_1 > std::numeric_limits<T>::max() - summand_2) {
+                    return true;
+                } else {
+                    result = summand_1 + summand_2;
+                    return false;
+                }
+            }
+        };
+
+        /*!
+         * @brief Overflow checker using compiler intrinsics
+         *
+         * This struct provides an add function with the same interface &
+         * behavior as fallback_add_overload::add but it relies on compiler
+         * intrinsics instead. This version should be considerably faster than
+         * the fallback version as it can fully utilize available CPU
+         * instructions & the compiler's diagnostic.
+         *
+         * However, as some compilers don't provide intrinsics for certain
+         * types, the default implementation of add is the version from falback.
+         *
+         * The struct is explicitly specialized for each type via #ifdefs for
+         * each compiler.
+         */
+        template <typename T>
+        struct builtin_add_overflow
+        {
+            /*!
+             * @brief Add summand_1 and summand_2 and check for overflows.
+             *
+             * This is the default add() function that uses
+             * fallback_add_overflow<T>::add(). All specializations must have
+             * exactly the same interface and behave the same way.
+             */
+            static inline bool add(T summand_1, T summand_2, T& result)
+            {
+                return fallback_add_overflow<T>::add(summand_1, summand_2, result);
+            }
+        };
+
+#if defined(__GNUC__) || defined(__clang__)
+#if __GNUC__ >= 5
+
+/*!
+ * This macro pastes a specialization of builtin_add_overflow using gcc's &
+ * clang's __builtin_(s/u)add(l)(l)_overlow()
+ *
+ * The add function is implemented by forwarding the parameters to the intrinsic
+ * and returning its value.
+ *
+ * The intrinsics are documented here:
+ * https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html#Integer-Overflow-Builtins
+ */
+#define SPECIALIZE_builtin_add_overflow(type, builtin_name)                  \
+    template <>                                                              \
+    struct builtin_add_overflow<type>                                        \
+    {                                                                        \
+        static inline bool add(type summand_1, type summand_2, type& result) \
+        {                                                                    \
+            return builtin_name(summand_1, summand_2, &result);              \
+        }                                                                    \
+    }
+
+        SPECIALIZE_builtin_add_overflow(int, __builtin_sadd_overflow);
+        SPECIALIZE_builtin_add_overflow(long, __builtin_saddl_overflow);
+        SPECIALIZE_builtin_add_overflow(long long, __builtin_saddll_overflow);
+
+        SPECIALIZE_builtin_add_overflow(unsigned int, __builtin_uadd_overflow);
+        SPECIALIZE_builtin_add_overflow(unsigned long, __builtin_uaddl_overflow);
+        SPECIALIZE_builtin_add_overflow(unsigned long long, __builtin_uaddll_overflow);
+
+#undef SPECIALIZE_builtin_add_overflow
+#endif
+
+#elif defined(_MSC_VER)
+
+/*!
+ * This macro pastes a specialization of builtin_add_overflow using MSVC's
+ * U(Int/Long/LongLong)Add.
+ *
+ * The add function is implemented by forwarding the parameters to the
+ * intrinsic. As MSVC's intrinsics return S_OK on success, this specialization
+ * returns whether the intrinsics return value does not equal S_OK. This ensures
+ * a uniform interface of the add function (false is returned when no overflow
+ * occurs, true on overflow).
+ *
+ * The intrinsics are documented here:
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/ff516460(v=vs.85).aspx
+ */
+#define SPECIALIZE_builtin_add_overflow_WIN(type, builtin_name)              \
+    template <>                                                              \
+    struct builtin_add_overflow<type>                                        \
+    {                                                                        \
+        static inline bool add(type summand_1, type summand_2, type& result) \
+        {                                                                    \
+            return builtin_name(summand_1, summand_2, &result) != S_OK;      \
+        }                                                                    \
+    }
+
+        SPECIALIZE_builtin_add_overflow_WIN(unsigned int, UIntAdd);
+        SPECIALIZE_builtin_add_overflow_WIN(unsigned long, ULongAdd);
+        SPECIALIZE_builtin_add_overflow_WIN(unsigned long long, ULongLongAdd);
+
+#undef SPECIALIZE_builtin_add_overflow_WIN
+
+#endif
+
+    }  // namespace Internal
+
+    /*!
+     * @brief Safe addition, throws an exception on overflow.
+     *
+     * This function returns the result of summand_1 and summand_2 only when the
+     * operation would not overflow, otherwise an exception of type
+     * std::overflow_error is thrown.
+     *
+     * @param[in] summand_1, summand_2  summands to be summed up
+     * @return  the sum of summand_1 and summand_2
+     * @throws  std::overflow_error if the addition would overflow
+     *
+     * This function utilizes compiler builtins when available and should have a
+     * very small performance hit then. When builtins are unavailable, a more
+     * extensive check is required.
+     *
+     * Builtins are available for the following configurations:
+     * - GCC/Clang for signed and unsigned int, long and long long (not char & short)
+     * - MSVC for unsigned int, long and long long
+     */
+    template <typename T>
+    T add(T summand_1, T summand_2)
+    {
+        T res = 0;
+        if (Internal::builtin_add_overflow<T>::add(summand_1, summand_2, res)) {
+            throw std::overflow_error("Overflow in addition");
+        }
+        return res;
+    }
+
+}  // namespace Safe
+
+#endif  // SAFE_OP_HPP_
diff --git a/src/tiffcomposite.cpp b/src/tiffcomposite.cpp
index c6b860d8..0c9b9c4a 100644
--- a/src/tiffcomposite.cpp
+++ b/src/tiffcomposite.cpp
@@ -1611,6 +1611,8 @@ namespace Exiv2 {
     uint32_t TiffImageEntry::doWriteImage(IoWrapper& ioWrapper,
                                           ByteOrder  /*byteOrder*/) const
     {
+        if ( !pValue() ) throw Error(21); // #1296
+        
         uint32_t len = pValue()->sizeDataArea();
         if (len > 0) {
 #ifdef DEBUG
diff --git a/src/tiffimage.cpp b/src/tiffimage.cpp
index f20c69eb..415c018f 100644
--- a/src/tiffimage.cpp
+++ b/src/tiffimage.cpp
@@ -200,7 +200,7 @@ namespace Exiv2 {
         Exiv2::ExifKey            key("Exif.Image.InterColorProfile");
         Exiv2::ExifData::iterator pos   = exifData_.findKey(key);
         if ( pos != exifData_.end()  ) {
-            iccProfile_.alloc(pos->count());
+            iccProfile_.alloc(pos->count()*pos->typeSize());
             pos->copy(iccProfile_.pData_,bo);
         }
 
diff --git a/src/tiffvisitor.cpp b/src/tiffvisitor.cpp
index 74f8d078..49fbf961 100644
--- a/src/tiffvisitor.cpp
+++ b/src/tiffvisitor.cpp
@@ -47,6 +47,7 @@ EXIV2_RCSID("@(#) $Id$")
 #include <iostream>
 #include <iomanip>
 #include <cassert>
+#include <limits>
 
 // *****************************************************************************
 namespace {
@@ -1294,11 +1295,12 @@ namespace Exiv2 {
             }
             uint16_t tag = getUShort(p, byteOrder());
             TiffComponent::AutoPtr tc = TiffCreator::create(tag, object->group());
-            // The assertion typically fails if a component is not configured in
-            // the TIFF structure table
-            assert(tc.get());
-            tc->setStart(p);
-            object->addChild(tc);
+            if (tc.get()) {
+                tc->setStart(p);
+                object->addChild(tc);
+            } else {
+               EXV_WARNING << "Unable to handle tag " << tag << ".\n";
+            }
             p += 12;
         }
 
@@ -1493,6 +1495,10 @@ namespace Exiv2 {
         }
         p += 4;
         uint32_t isize= 0; // size of Exif.Sony1.PreviewImage
+
+        if (count > std::numeric_limits<uint32_t>::max() / typeSize) {
+            throw Error(59);
+        }
         uint32_t size = typeSize * count;
         uint32_t offset = getLong(p, byteOrder());
         byte* pData = p;
@@ -1516,7 +1522,19 @@ namespace Exiv2 {
                 size = 0;
         }
         if (size > 4) {
+            // setting pData to pData_ + baseOffset() + offset can result in pData pointing to invalid memory,
+            // as offset can be arbitrarily large
+            if ((static_cast<uintptr_t>(baseOffset()) > std::numeric_limits<uintptr_t>::max() - static_cast<uintptr_t>(offset))
+             || (static_cast<uintptr_t>(baseOffset() + offset) > std::numeric_limits<uintptr_t>::max() - reinterpret_cast<uintptr_t>(pData_)))
+            {
+                throw Error(59);
+            }
+            if (pData_ + static_cast<uintptr_t>(baseOffset()) + static_cast<uintptr_t>(offset) > pLast_) {
+                throw Error(58);
+            }
             pData = const_cast<byte*>(pData_) + baseOffset() + offset;
+
+	    // check for size being invalid
             if (size > static_cast<uint32_t>(pLast_ - pData)) {
 #ifndef SUPPRESS_WARNINGS
                 EXV_ERROR << "Upper boundary of data for "
@@ -1536,7 +1554,9 @@ namespace Exiv2 {
             }
         }
         Value::AutoPtr v = Value::create(typeId);
-        assert(v.get());
+        if (!v.get()) {
+            throw Error(58);
+        }
         if ( !isize ) {
         	v->read(pData, size, byteOrder());
         } else {
diff --git a/src/utils.cpp b/src/utils.cpp
index a3d36497..2a092330 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -32,18 +32,15 @@ EXIV2_RCSID("@(#) $Id$")
 #include "config.h"
 
 #include "utils.hpp"
-
-// + standard includes
-#if defined(_MSC_VER) || defined(__MINGW__)
-# include "getopt_win32.h"
-#endif
+#include "getopt_win32.h"
 
 #if defined(_MSC_VER)
 # define S_ISREG(m)      (((m) & S_IFMT) == S_IFREG)
 #endif
 
+// + standard includes
 #ifdef EXV_HAVE_UNISTD_H
-# include <unistd.h>                     // for getopt(), stat()
+# include <unistd.h>                     // for stat()
 #endif
 
 #include <sys/types.h>
diff --git a/src/webpimage.cpp b/src/webpimage.cpp
index e4057d6c..1fc18fe8 100644
--- a/src/webpimage.cpp
+++ b/src/webpimage.cpp
@@ -36,6 +36,7 @@
 
 #include "webpimage.hpp"
 #include "image_int.hpp"
+#include "enforce.hpp"
 #include "futils.hpp"
 #include "basicio.hpp"
 #include "tags.hpp"
@@ -493,7 +494,9 @@ namespace Exiv2 {
 
         io_->read(data, WEBP_TAG_SIZE * 3);
 
-        WebPImage::decodeChunks(Exiv2::getULong(data + WEBP_TAG_SIZE, littleEndian) + 12);
+        const uint32_t filesize = Exiv2::getULong(data + WEBP_TAG_SIZE, littleEndian) + 8;
+        enforce(filesize <= io_->size(), Exiv2::kerCorruptedMetadata);
+        WebPImage::decodeChunks(filesize);
 
     } // WebPImage::readMetadata
 
@@ -511,7 +514,8 @@ namespace Exiv2 {
         while ( !io_->eof() && (uint64_t) io_->tell() < filesize) {
             io_->read(chunkId.pData_, WEBP_TAG_SIZE);
             io_->read(size_buff, WEBP_TAG_SIZE);
-            long size = Exiv2::getULong(size_buff, littleEndian);
+            const uint32_t size = Exiv2::getULong(size_buff, littleEndian);
+            enforce(size <= (filesize - io_->tell()), Exiv2::kerCorruptedMetadata);
 
             DataBuf payload(size);
 
@@ -791,8 +795,9 @@ namespace Exiv2 {
         }
     }
 
-    long WebPImage::getHeaderOffset(byte *data, long data_size,
-                                    byte *header, long header_size) {
+    long WebPImage::getHeaderOffset(byte* data, long data_size, byte* header, long header_size)
+    {
+        if (data_size < header_size) { return -1; }
         long pos = -1;
         for (long i=0; i < data_size - header_size; i++) {
             if (memcmp(header, &data[i], header_size) == 0) {
diff --git a/test/bugfixes-test.sh b/test/bugfixes-test.sh
index f91c6759..b10b839e 100755
--- a/test/bugfixes-test.sh
+++ b/test/bugfixes-test.sh
@@ -602,6 +602,7 @@ source ./functions.source
     runTest exiv2 -pX                   $filename | xmllint --format -
 
     num=1231
+    printf "$num " >&3
     for X in a b; do
       filename=exiv2-bug$num$X.jpg
       echo '------>' Bug $filename '<-------' >&2
@@ -622,6 +623,7 @@ source ./functions.source
     runTest exiv2 -pa                   $filename
 
     num=1252
+    printf "$num " >&3
     for X in a b; do
       filename=exiv2-bug$num$X.exv
       echo '------>' Bug $filename '<-------' >&2
@@ -629,6 +631,118 @@ source ./functions.source
       runTest exiv2 -pa --grep lens/i   $filename
     done
 
+    num=1305
+    printf "$num " >&3
+    filename=IMGP0006-min.jpg
+    echo '------>' Bug $filename '<-------' >&2
+    copyTestFile                        $filename
+    runTest exiv2                       $filename
+
+    num=g55
+    printf "$num " >&3
+    filename=POC8
+    echo '------>' Bug $filename '<-------' >&2
+    copyTestFile                      $filename
+    runTest exiv2                     $filename
+
+    num=g57
+    printf "$num " >&3
+    filename=POC
+    echo '------>' Bug $filename '<-------' >&2
+    copyTestFile                      $filename
+    runTest exiv2                     $filename
+
+    num=g79
+    printf "$num " >&3
+    filename=POC2
+    echo '------>' Bug $filename '<-------' >&2
+    copyTestFile                      $filename
+    runTest exiv2                     $filename
+
+    num=g52
+    printf "$num " >&3
+    filename=POC5
+    echo '------>' Bug $filename '<-------' >&2
+    copyTestFile                      $filename
+    runTest exiv2                     $filename
+
+    num=g51
+    printf "$num " >&3
+    filename=POC4
+    echo '------>' Bug $filename '<-------' >&2
+    copyTestFile                      $filename
+    runTest exiv2                     $filename
+
+    num=g50
+    printf "$num " >&3
+    filename=POC3
+    echo '------>' Bug $filename '<-------' >&2
+    copyTestFile                      $filename
+    runTest exiv2                     $filename
+
+    num=g53
+    printf "$num " >&3
+    filename=POC6
+    echo '------>' Bug $filename '<-------' >&2
+    copyTestFile                      $filename
+    runTest exiv2                     $filename
+
+    num=g54
+    printf "$num " >&3
+    filename=POC9
+    echo '------>' Bug $filename '<-------' >&2
+    copyTestFile                      $filename
+    runTest exiv2                     $filename
+
+    num=g58
+    printf "$num " >&3
+    filename=POC11
+    echo '------>' Bug $filename '<-------' >&2
+    copyTestFile                      $filename
+    runTest exiv2                     $filename
+
+    num=g59
+    printf "$num " >&3
+    filename=POC12
+    echo '------>' Bug $filename '<-------' >&2
+    copyTestFile                      $filename
+    runTest exiv2                     $filename
+
+    num=g60
+    printf "$num " >&3
+    filename=POC13
+    echo '------>' Bug $filename '<-------' >&2
+    copyTestFile                      $filename
+    runTest exiv2                     $filename
+
+    num=g71
+    printf "$num " >&3
+    filename=003-heap-buffer-over
+    echo '------>' Bug $filename '<-------' >&2
+    copyTestFile                      $filename
+    runTest exiv2                     $filename
+
+    num=g73
+    printf "$num " >&3
+    filename=02-Invalid-mem-def
+    echo '------>' Bug $filename '<-------' >&2
+    copyTestFile                      $filename
+    runTest exiv2                     $filename
+
+    num=g74
+    printf "$num " >&3
+    filename=005-invalid-mem
+    echo '------>' Bug $filename '<-------' >&2
+    copyTestFile                      $filename
+    runTest exiv2                     $filename
+
+    num=g75
+    printf "$num " >&3
+    filename=008-invalid-mem
+    echo '------>' Bug $filename '<-------' >&2
+    copyTestFile                      $filename
+    runTest exiv2                     $filename
+
 ) 3>&1 > $results 2>&1
 
 printf "\n"
diff --git a/test/data/003-heap-buffer-over b/test/data/003-heap-buffer-over
new file mode 100644
index 00000000..2c490f60
Binary files /dev/null and b/test/data/003-heap-buffer-over differ
diff --git a/test/data/005-invalid-mem b/test/data/005-invalid-mem
new file mode 100644
index 00000000..40f0a393
Binary files /dev/null and b/test/data/005-invalid-mem differ
diff --git a/test/data/008-invalid-mem b/test/data/008-invalid-mem
new file mode 100644
index 00000000..8397f174
Binary files /dev/null and b/test/data/008-invalid-mem differ
diff --git a/test/data/02-Invalid-mem-def b/test/data/02-Invalid-mem-def
new file mode 100644
index 00000000..e506eea4
Binary files /dev/null and b/test/data/02-Invalid-mem-def differ
diff --git a/test/data/1-out-of-read-Poc b/test/data/1-out-of-read-Poc
new file mode 100644
index 00000000..813e4ffb
Binary files /dev/null and b/test/data/1-out-of-read-Poc differ
diff --git a/test/data/2-out-of-read-Poc b/test/data/2-out-of-read-Poc
new file mode 100644
index 00000000..6421044a
Binary files /dev/null and b/test/data/2-out-of-read-Poc differ
diff --git a/test/data/IMGP0006-min.jpg b/test/data/IMGP0006-min.jpg
new file mode 100644
index 00000000..e2cd107b
Binary files /dev/null and b/test/data/IMGP0006-min.jpg differ
diff --git a/test/data/POC b/test/data/POC
new file mode 100755
index 00000000..c697bf39
Binary files /dev/null and b/test/data/POC differ
diff --git a/test/data/POC11 b/test/data/POC11
new file mode 100644
index 00000000..b7b8a24b
Binary files /dev/null and b/test/data/POC11 differ
diff --git a/test/data/POC12 b/test/data/POC12
new file mode 100644
index 00000000..1355fccf
Binary files /dev/null and b/test/data/POC12 differ
diff --git a/test/data/POC13 b/test/data/POC13
new file mode 100644
index 00000000..fed558f6
Binary files /dev/null and b/test/data/POC13 differ
diff --git a/test/data/POC2 b/test/data/POC2
new file mode 100755
index 00000000..a49d49c1
Binary files /dev/null and b/test/data/POC2 differ
diff --git a/test/data/POC3 b/test/data/POC3
new file mode 100644
index 00000000..70eb1960
Binary files /dev/null and b/test/data/POC3 differ
diff --git a/test/data/POC4 b/test/data/POC4
new file mode 100644
index 00000000..7cb73902
Binary files /dev/null and b/test/data/POC4 differ
diff --git a/test/data/POC5 b/test/data/POC5
new file mode 100644
index 00000000..1eabee74
Binary files /dev/null and b/test/data/POC5 differ
diff --git a/test/data/POC6 b/test/data/POC6
new file mode 100644
index 00000000..04a43ede
Binary files /dev/null and b/test/data/POC6 differ
diff --git a/test/data/POC8 b/test/data/POC8
new file mode 100755
index 00000000..8a1c03b9
Binary files /dev/null and b/test/data/POC8 differ
diff --git a/test/data/POC9 b/test/data/POC9
new file mode 100644
index 00000000..e45c270f
Binary files /dev/null and b/test/data/POC9 differ
diff --git a/test/data/bugfixes-test.out b/test/data/bugfixes-test.out
index d8754025..2192fa1f 100644
Binary files a/test/data/bugfixes-test.out and b/test/data/bugfixes-test.out differ
diff --git a/test/data/cve_2017_18005_reproducer.tiff b/test/data/cve_2017_18005_reproducer.tiff
new file mode 100644
index 00000000..b9dd23a7
Binary files /dev/null and b/test/data/cve_2017_18005_reproducer.tiff differ
diff --git a/test/data/exiv2-memorymmap-error b/test/data/exiv2-memorymmap-error
new file mode 100644
index 00000000..0bec4359
Binary files /dev/null and b/test/data/exiv2-memorymmap-error differ
diff --git a/test/data/issue_170_poc b/test/data/issue_170_poc
new file mode 100644
index 00000000..439b7687
Binary files /dev/null and b/test/data/issue_170_poc differ
diff --git a/test/data/issue_187 b/test/data/issue_187
new file mode 100644
index 00000000..3e05cc9e
Binary files /dev/null and b/test/data/issue_187 differ
diff --git a/test/data/poc_2017-12-12_issue188 b/test/data/poc_2017-12-12_issue188
new file mode 100644
index 00000000..6f91c3e9
Binary files /dev/null and b/test/data/poc_2017-12-12_issue188 differ
diff --git a/test/functions.source b/test/functions.source
index 38c9a8d0..cc8d67ff 100644
--- a/test/functions.source
+++ b/test/functions.source
@@ -109,6 +109,7 @@ reportTest()
         echo "all testcases passed."
     else
         diff $diffargs $lhs $rhs
+	exit 3
     fi
 }
 
diff --git a/test/tiff-test.sh b/test/tiff-test.sh
old mode 100644
new mode 100755
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 00000000..40a96afc
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/tests/bugfixes/__init__.py b/tests/bugfixes/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/bugfixes/github/__init__.py b/tests/bugfixes/github/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/bugfixes/github/test_CVE_2017_17669.py b/tests/bugfixes/github/test_CVE_2017_17669.py
new file mode 100644
index 00000000..803bb92a
--- /dev/null
+++ b/tests/bugfixes/github/test_CVE_2017_17669.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+import system_tests
+
+
+class RunPocFile(system_tests.Case):
+
+    filename = "{data_path}/issue_187"
+    commands = ["{exiv2} " + filename]
+    retval = [1]
+    stdout = [""]
+    stderr = [
+	"""{exiv2_exception_msg} """ + filename + """:
+{error_14_message}
+"""
+    ]
diff --git a/tests/bugfixes/github/test_CVE_2017_17725.py b/tests/bugfixes/github/test_CVE_2017_17725.py
new file mode 100644
index 00000000..8273a49a
--- /dev/null
+++ b/tests/bugfixes/github/test_CVE_2017_17725.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+import system_tests
+
+
+class TestCvePoC(system_tests.Case):
+
+    url = "https://github.com/Exiv2/exiv2/issues/188"
+    found_by = ["Wei You", "@youwei1988"]
+
+    filename = "{data_path}/poc_2017-12-12_issue188"
+    commands = ["{exiv2} " + filename]
+    stdout = [""]
+    stderr = ["""std::overflow_error exception in print action for file """ + filename + """:
+Overflow in addition
+"""]
+    retval = [1]
diff --git a/tests/bugfixes/github/test_CVE_2017_18005.py b/tests/bugfixes/github/test_CVE_2017_18005.py
new file mode 100644
index 00000000..e7ae3f7a
--- /dev/null
+++ b/tests/bugfixes/github/test_CVE_2017_18005.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+import system_tests
+
+
+class TestPoC(system_tests.Case):
+
+    url = "https://github.com/Exiv2/exiv2/issues/168"
+
+    stderr_common = """Error: Directory Image: IFD exceeds data buffer, cannot read next pointer.
+Error: Offset of directory Image, entry 0x0117 is out of bounds: Offset = 0x30303030; truncating the entry
+""" + 12 * """Error: Offset of directory Image, entry 0x3030 is out of bounds: Offset = 0x30303030; truncating the entry
+"""
+
+    filename = "{data_path}/cve_2017_18005_reproducer.tiff"
+
+    commands = [
+        "{exiv2} -v pr -P EIXxgklnycsvth " + filename,
+        "{exiv2json} " + filename
+    ]
+
+    stdout = ["""File 1/1: """ + filename + """
+0x0117 Image        Exif.Image.StripByteCounts                   StripByteCounts             Strip Byte Count               SByte       0   0  
+
+""",
+    """{{
+	"Exif": {{
+		"Image": {{
+			"StripByteCounts": 0,
+			"0x3030": 0,
+			"0x3030": "",
+			"0x3030": 0,
+			"0x3030": 0,
+			"0x3030": 0,
+			"0x3030": 0,
+			"0x3030": 0,
+			"0x3030": 0,
+			"0x3030": 0,
+			"0x3030": 0,
+			"0x3030": 0,
+			"0x3030": 0,
+			"0x3030": 0
+		}}
+	}}
+}}
+"""
+    ]
+    stderr = [
+        stderr_common + filename + """: No IPTC data found in the file
+""" + filename + """: No XMP data found in the file
+""",
+        stderr_common
+    ]
+    retval = [0, 0]
diff --git a/tests/bugfixes/github/test_CVE_2018_12264.py b/tests/bugfixes/github/test_CVE_2018_12264.py
new file mode 100644
index 00000000..f30d1936
--- /dev/null
+++ b/tests/bugfixes/github/test_CVE_2018_12264.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+import system_tests
+
+
+class AdditionOverflowInLoaderTiffGetData(metaclass=system_tests.CaseMeta):
+    """
+    Regression test for bug #366:
+    https://github.com/Exiv2/exiv2/issues/366
+    aka CVE-2018-12264
+    https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-12264
+    """
+    filename = "$data_path/2-out-of-read-Poc"
+    commands = ["$exiv2 -ep $filename"]
+    stdout = [""]
+    stderr = [
+        """Warning: Directory Image, entry 0x0111: Strip 0 is outside of the data area; ignored.
+$uncaught_exception Overflow in addition
+"""
+    ]
+    retval = [1]
diff --git a/tests/bugfixes/github/test_CVE_2018_12265.py b/tests/bugfixes/github/test_CVE_2018_12265.py
new file mode 100644
index 00000000..66c51d52
--- /dev/null
+++ b/tests/bugfixes/github/test_CVE_2018_12265.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+import system_tests
+
+
+class AdditionOverflowInLoaderExifJpeg(metaclass=system_tests.CaseMeta):
+    """
+    Regression test for bug #365:
+    https://github.com/Exiv2/exiv2/issues/365
+    aka CVE 2018-12265:
+    https://cve.mitre.org/cgi-bin/cvename.cgi?name=2018-12265
+    """
+    filename = "$data_path/1-out-of-read-Poc"
+    commands = ["$exiv2 -ep $filename"]
+    stdout = [""]
+    stderr = [
+        """Error: Upper boundary of data for directory Image, entry 0x00fe is out of bounds: Offset = 0x0000002a, size = 64, exceeds buffer size by 22 Bytes; truncating the entry
+Warning: Directory Image, entry 0x0201: Strip 0 is outside of the data area; ignored.
+Warning: Directory Image, entry 0x0201: Strip 7 is outside of the data area; ignored.
+Error: Offset of directory Thumbnail, entry 0x0201 is out of bounds: Offset = 0x00000000; truncating the entry
+$uncaught_exception Overflow in addition
+"""
+    ]
+    retval = [1]
diff --git a/tests/bugfixes/github/test_CVE_2018_4868.py b/tests/bugfixes/github/test_CVE_2018_4868.py
new file mode 100644
index 00000000..434eec6b
--- /dev/null
+++ b/tests/bugfixes/github/test_CVE_2018_4868.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+import system_tests
+
+
+class TestCvePoC(system_tests.Case):
+
+    url = "https://github.com/Exiv2/exiv2/issues/202"
+    cve_url = "http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-4868"
+    found_by = ["afl", "topsecLab", "xcainiao"]
+
+    filename = "{data_path}/exiv2-memorymmap-error"
+    commands = ["{exiv2} " + filename]
+    stdout = [""]
+    stderr = ["""{exiv2_exception_msg} """ + filename + """:
+{error_58_message}
+"""]
+    retval = [1]
diff --git a/tests/bugfixes/github/test_issue_170.py b/tests/bugfixes/github/test_issue_170.py
new file mode 100644
index 00000000..77bc7b1b
--- /dev/null
+++ b/tests/bugfixes/github/test_issue_170.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+import system_tests
+
+
+class decodeIHDRChunkOutOfBoundsRead(system_tests.Case):
+
+    url = "https://github.com/Exiv2/exiv2/issues/170"
+
+    filename = "{data_path}/issue_170_poc"
+
+    commands = ["{exiv2} " + filename]
+    stdout = [""]
+    stderr = ["""{exiv2_exception_msg} """ + filename + """:
+{error_14_message}
+"""]
+    retval = [1]
diff --git a/tests/doc.md b/tests/doc.md
new file mode 100644
index 00000000..ad31bd57
--- /dev/null
+++ b/tests/doc.md
@@ -0,0 +1,426 @@
+# TL;DR
+
+If you just want to write a simple test case, check out the file
+`writing_tests.md`.
+
+# Introduction
+
+This test suite is intended for system tests, i.e. for running a binary with
+certain parameters and comparing the output against an expected value. This is
+especially useful for a regression test suite, but can be also used for testing
+of new features where unit testing is not feasible, e.g. to test new command
+line parameters.
+
+The test suite is configured via `INI` style files using Python's builtin
+[ConfigParser](https://docs.python.org/3/library/configparser.html)
+module. Such a configuration file looks roughly like this:
+``` ini
+[DEFAULT]
+some_var: some_val
+
+[section 1]
+empty_var:
+multiline_var: this is a multiline string
+    as long as the indentation
+    is present
+# comments can be inserted
+# some_var is implicitly present in this section by the DEFAULT section
+
+[section 2]
+# set some_var for this section to something else than the default
+some_var: some_other_val
+# values from other sections can be inserted
+vars can have whitespaces: ${some_var} ${section 1: multiline var}
+multiline var: multiline variables can have
+
+    empty lines too
+```
+
+For further details concerning the syntax, please consult the official
+documentation. The `ConfigParser` module is used with the following defaults:
+- Comments are started by `#` only
+- The separator between a variable and the value is `:`
+- Multiline comments can have empty lines
+- Extended Interpolation is used (this allows to refer to other sections when
+  inserting values using the `${section:variable}` syntax)
+
+Please keep in mind that leading and trailing whitespaces are **stripped** from
+strings when extracting variable values. So this:
+
+``` ini
+some_var:     some value with whitespaces before and after    
+```
+is equivalent to this:
+``` ini
+some_var:some value with whitespaces before and after
+```
+
+The test suite itself uses the builtin `unittest` module of Python to discover
+and run the individual test cases. The test cases themselves are implemented in
+Python source files, but the required Python knowledge is minimal.
+
+## Test suite
+
+The test suite is configured via one configuration file whose location
+automatically sets the root directory of the test suite. The `unittest` module
+then recursively searches all sub-directories with a `__init__.py` file for
+files of the form `test_*.py`, which it automatically interprets as test cases
+(more about these in the next section). Python will automatically interpret each
+directory as a module and use this to format the output, e.g. the test case
+`regression/crashes/test_bug_15.py` will be interpreted as the module
+`regression.crashes.test_bug_15`. Thus one can use the directory structure to
+group test cases.
+
+The test suite's configuration file should have the following form:
+
+``` ini
+[General]
+timeout: 0.1
+
+[paths]
+binary: ../build/bin/binary
+important_file: ../conf/main.cfg
+
+[variables]
+abort_error: ERROR
+abort_exit value: 1
+```
+
+The General section only contains the `timeout` parameter, which is actually
+optional (when left out 1.0 is assumed). The timeout sets the maximum time in
+seconds for each command that is run before it is aborted. This allows for test
+driven development with tests that cause infinite loops or similar hangs in the
+test suite.
+
+The paths and variables sections define global variables for the system test
+suite, which every test case can read. Following the DRY principle, one can put
+common outputs of the tested binary in a variable, so that changing an error
+message does not result in an hour long update of the test suite. Both sections
+are merged together before being passed on to the test cases, thus they must not
+contain variables with the same name (doing so results in an error).
+
+While the values in the variables section are simply passed on to the test cases
+the paths section is special as its contents are interpreted as relative paths
+(with respect to the test suite's root) and are expanded to absolute paths
+before being passed to the test cases. This can be used to inform each test case
+about the location of a built binary or a configuration file without having to
+rely on environment variables.
+
+However, sometimes environment variables are very handy to implement variable
+paths or platform differences (like different build directories or file
+extensions). For this, the test suite supports the `ENV` and `ENV fallback`
+sections. In conjunction with the extended interpolation of the `ConfigParser`
+module, these can be quite useful. Consider the following example:
+
+``` ini
+[General]
+timeout: 0.1
+
+[ENV]
+variable_prefix: PREFIX
+file_extension: FILE_EXT
+
+[ENV fallback]
+variable_prefix: ../build
+
+[paths]
+binary: ${ENV:variable_prefix}/bin/binary${ENV:file_extension}
+important_file: ../conf/main.cfg
+
+[variables]
+abort_error: ERROR
+abort_exit value: 1
+```
+
+The `ENV` section is, similarly to the `paths` section, special insofar as the
+variables are extracted from the environment with the given name. E.g. the
+variable `file_extension` would be set to the value of the environment variable
+`FILE_EXT`. If the environment variable is not defined, then the test suite will
+look in the `ENV fallback` section for a fallback. E.g. in the above example
+`variable_prefix` has the fallback or default value of `../build` which will be
+used if the environment variable `PREFIX` is not set. If no fallback is provided
+then an empty string is used instead, which would happen to `file_extension` if
+`FILE_EXT` would be unset.
+
+This can be combined with the extended interpolation of Python's `ConfigParser`,
+which allows to include variables from arbitrary sections into other variables
+using the `${sect:var_name}` syntax. This would be expanded to the value of
+`var_name` from the section `sect`. The above example only utilizes this in the
+`paths` section, but it can also be used in the `variables` section, if that
+makes sense for the use case.
+
+Returning to the example config file, the path `binary` would be inferred in the
+following steps:
+1. extract `PREFIX` & `FILE_EXT` from the environment, if they don't exist use
+   the default values from `ENV fallback` or ""
+2. substitute the strings `${ENV:variable_prefix}` and `${ENV:file_extension}`
+3. expand the relative path to an absolute path
+
+Please note that while the `INI` file allows for variables with whitespaces or
+`-` in their names, such variables will cause errors as they are invalid
+variable names in Python.
+
+
+## Test cases
+
+The test cases are defined in Python source files utilizing the unittest module,
+thus every file must also be a valid Python file. Each file defining a test case
+must start with `test_` and have the file extension `py`. To be discovered by
+the unittest module it must reside in a directory with a (empty) `__init__.py`
+file.
+
+A test case should test one logical unit, e.g. test for regressions of a certain
+bug or check if a command line option works. Each test case can run multiple
+commands which results are compared to an expected standard output, standard
+error and return value. Should differences arise or should one of the commands
+take too long, then an error message with the exact differences is shown to the
+user.
+
+An example test case file would look like this:
+
+``` python
+# -*- coding: utf-8 -*-
+
+import system_tests
+
+
+class AnInformativeName(system_tests.Case):
+
+    filename = "invalid_input_file"
+    commands = [
+	    "{binary} -c {import_file} -i {filename}"
+	]
+    retval = ["{abort_exit_value}"]
+    stdout = ["Reading {filename}"]
+    stderr = [
+        """{abort_error}
+error in {filename}
+"""
+    ]
+```
+
+The first 6 lines are necessary boilerplate to pull in the necessary routines to
+run the actual tests (these are implemented in the module `system_tests` with
+the class `system_tests.Case` extending `unittest.TestCase`). When adding new
+tests one should choose a new class name that briefly summarizes the test. Note
+that the file name (without the extension) with the directory structure is
+interpreted as the module by Python and pre-pended to the class name when
+reporting about the tests. E.g. the file `regression/crashes/test_bug_15.py`
+with the class `OutOfBoundsRead` gets reported as
+`regression.crashes.test_bug_15.OutOfBoundsRead** already including a brief
+summary of this test.
+
+**Caution:** Always import `system_tests` in the aforementioned syntax and don't
+use `from system_tests import Case`. This will not work, as the `system_tests`
+module stores the suite's config internally which will not be available if you
+perform a `from system_tests import Case` (this causes Python to create a copy
+of the class `system_tests.Case` for your module, without reading the
+configuration file).
+
+In the following lines the lists `commands`, `retval`, `stdout` and `stderr`
+should be defined. These are lists of strings and must all have the same amount
+of elements.
+
+The test suite at first takes all these strings and substitutes all values in
+curly braces with variables either defined in this class alongside (like
+`filename` in the above example) or with the values defined in the test suite's
+configuration file. Please note that defining a variable with the same name as a
+variable in the suite's configuration file will result in an error (otherwise
+one of the variables would take precedence leading to unexpected results). The
+substitution of values in performed using Python's string `format()` method and
+more elaborate format strings can be used when necessary.
+
+In the above example the command would thus expand to:
+``` shell
+/path/to/the/dir/build/bin/binary -c /path/to/the/dir/conf/main.cfg -i invalid_input_file
+```
+and similarly for `stdout` and `stderr`.
+
+Once the substitution is performed, each command is run using Python's
+`subprocess` module, its output is compared to the values in `stdout` and
+`stderr` and its return value to `retval`. Please note that for portability
+reasons the subprocess module is run with `shell=False`, thus shell expansions
+or pipes will not work.
+
+As the test cases are implemented in Python, one can take full advantage of
+Python for the construction of the necessary lists. For example when 10 commands
+should be run and all return 0, one can write `retval = 10 * [0]` instead of
+writing 0 ten times. The same is of course possible for strings.
+
+There are however some peculiarities with multiline strings in Python. Normal
+strings start and end with a single `"` but multiline strings start with three
+`"`. Also, while the variable names must be indented, new lines in multiline
+strings must not or additional whitespaces will be added. E.g.:
+
+``` python
+    stderr = [
+        """something
+        else"""
+    ]
+```
+will actually result in the string:
+
+```
+something
+        else
+```
+and not:
+```
+something
+else
+```
+as the indentation might have suggested.
+
+Also note that in this example the string will not be terminated with a newline
+character. To achieve that put the `"""` on the following line.
+
+
+## Advanced test cases
+
+This section describes more advanced features that are probably not necessary
+the "standard" usage of the test suite.
+
+
+### Creating file copies
+
+For tests that modify their input file it is useful to run these with a
+disposable copy of the input file and not with the original. For this purpose
+the test suite features a decorator which creates a copy of the supplied files
+and deletes the copies after the test ran.
+
+Example:
+``` python
+# -*- coding: utf-8 -*-
+
+import system_tests
+
+
+@system_tests.CopyFiles("{filename}", "{some_path}/another_file.txt")
+class AnInformativeName(system_tests.Case):
+
+    filename = "invalid_input_file"
+    commands = [
+	    "{binary} -c {import_file} -i {filename}"
+	]
+    retval = ["{abort_exit_value}"]
+    stdout = ["Reading {filename}"]
+    stderr = [
+        """{abort_error}
+error in {filename}
+"""
+    ]
+```
+
+In this example, the test suite would automatically create a copy of the files
+`invalid_input_file` and `{some_path}/another_file.txt` (`some_path` would be of
+course expanded too) named `invalid_input_file_copy` and
+`{some_path}/another_file_copy.txt`. After the test ran, the copies are
+deleted. Please note that variable expansion in the filenames is possible.
+
+
+### Customizing the output check
+
+Some tests do not require a "brute-force" comparison of the whole output of a
+program but only a very simple check (e.g. that a string is present). For these
+cases, one can customize how stdout and stderr checked for errors.
+
+The `system_tests.Case` class has two public functions for the check of stdout &
+stderr: `compare_stdout` & `compare_stderr`. They have the following interface:
+``` python
+compare_stdout(self, i, command, got_stdout, expected_stdout)
+compare_stderr(self, i, command, got_stderr, expected_stderr)
+```
+with the parameters:
+- i: index of the command in the `commands` list
+- command: a string of the actually invoked command
+- got_stdout/stderr: the obtained stdout, post-processed depending on the
+  platform so that lines always end with `\n`
+- expected_stdout/stderr: the expected output extracted from
+  `self.stdout`/`self.stderr`
+
+These functions can be overridden in child classes to perform custom checks (or
+to omit them completely, too). Please however note, that it is not possible to
+customize how the return value is checked. This is indented, as the return value
+is often used by the OS to indicate segfaults and ignoring it (in combination
+with flawed checks of the output) could lead to crashes not being noticed.
+
+
+### Manually expanding variables in strings
+
+In case completely custom checks have to be run but one still wants to access
+the variables from the test suite, the class `system_test.Case` provides the
+function `expand_variables(self, string)`. It performs the previously described
+variable substitution using the test suite's configuration file.
+
+Unfortunately, it has to run in a class member function. The `setUp()` function
+can be used for this, as it is run before each test. For example like this:
+``` python
+class SomeName(system_tests.Case):
+
+	def setUp(self):
+		self.commands = [self.expand_variables("{some_var}/foo.txt")]
+		self.stderr = [""]
+		self.stdout = [self.expand_variables("{success_message}")]
+		self.retval = [0]
+```
+
+This example will work, as the test runner reads the data for `commands`,
+`stderr`, `stdout` and `retval` from the class instance. What however will not
+work is creating a new member in `setUp()` and trying to use it as a variable
+for expansion, like this:
+``` python
+class SomeName(system_tests.Case):
+
+	def setUp(self):
+		self.new_var = "foo"
+		self.another_string = self.expand_variables("{new_var}")
+```
+
+This example fails in `self.expand_variables` because the expansion uses only
+static class members (which `new_var` is not). Also, if you modify a static
+class member in `setUp()` the changed version will **not** be used for variable
+expansion, as the variables are saved in a new dictionary **before** `setUp()`
+runs. Thus this:
+``` python
+class SomeName(system_tests.Case):
+
+	new_var = "foo"
+
+	def setUp(self):
+		self.new_var = "bar"
+		self.another_string = self.expand_variables("{new_var}")
+```
+
+will result in `another_string` being "foo" and not "bar".
+
+
+### Possible pitfalls
+
+- Do not provide a custom `setUpClass()` function for the test
+  cases. `setUpClass()` is used by `system_tests.Case` to store the variables
+  for expansion.
+
+- Keep in mind that the variable expansion uses Python's `format()`
+  function. This can make it more cumbersome to include formatted strings into
+  variables like `commands` which will likely contain other variables from the
+  test suite. E.g.: `commands = ["{binary} {:s}".format(f) for f in files]` will
+  not work as `format()` will expect a value for binary. This can be worked
+  around using either the old Python formatting via `%` or by formatting first
+  and then concatenating the problematic parts.
+
+
+## Running the test suite
+
+The test suite is written for Python 3 but is in principle also compatible with
+Python 2, albeit it is not regularly tested, so its functionality is not
+guaranteed with Python 2.
+
+Then navigate to the `tests/` subdirectory and run:
+``` shell
+python3 runner.py
+```
+
+The runner script also supports the optional arguments `--config_file` which
+allows to provide a different test suite configuration file than the default
+`suite.conf`. It also forwards the verbosity setting via the `-v`/`--verbose`
+flags to Python's unittest module.
diff --git a/tests/runner.py b/tests/runner.py
new file mode 100644
index 00000000..745dcebd
--- /dev/null
+++ b/tests/runner.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+if __name__ == '__main__':
+
+    import argparse
+    import os
+    import unittest
+    import sys
+
+    import system_tests
+
+    parser = argparse.ArgumentParser(description="The system test suite")
+
+    parser.add_argument(
+        "--config_file",
+        type=str,
+        nargs=1,
+        default=['suite.conf']
+    )
+    parser.add_argument(
+        "--verbose", "-v",
+        action='count',
+        default=1
+    )
+    args = parser.parse_args()
+    conf_file = args.config_file[0]
+    discovery_root = os.path.dirname(conf_file)
+
+    system_tests.configure_suite(conf_file)
+
+    discovered_tests = unittest.TestLoader().discover(discovery_root)
+    test_res = unittest.runner.TextTestRunner(verbosity=args.verbose)\
+                              .run(discovered_tests)
+
+    sys.exit(0 if len(test_res.failures) + len(test_res.errors) == 0 else 1)
diff --git a/tests/suite.conf b/tests/suite.conf
new file mode 100644
index 00000000..197b7427
--- /dev/null
+++ b/tests/suite.conf
@@ -0,0 +1,22 @@
+[General]
+timeout: 1
+
+[ENV]
+exiv2_path: EXIV2_PATH
+binary_extension: EXIV2_EXT
+
+[ENV fallback]
+exiv2_path: ../build/bin
+
+[paths]
+exiv2: ${ENV:exiv2_path}/exiv2${ENV:binary_extension}
+exiv2json: ${ENV:exiv2_path}/exiv2json${ENV:binary_extension}
+data_path: ../test/data
+tiff-test: ${ENV:exiv2_path}/tiff-test${ENV:binary_extension}
+
+[variables]
+error_14_message: Failed to read image data
+error_58_message: corrupted image metadata
+error_57_message: invalid memory allocation request
+exiv2_exception_msg: Exiv2 exception in print action for file
+uncaught_exception: Uncaught exception:
diff --git a/tests/system_tests.py b/tests/system_tests.py
new file mode 100644
index 00000000..ee6a0fac
--- /dev/null
+++ b/tests/system_tests.py
@@ -0,0 +1,380 @@
+# -*- coding: utf-8 -*-
+
+import configparser
+import os
+import inspect
+import subprocess
+import threading
+import shlex
+import sys
+import shutil
+import string
+import unittest
+
+
+if sys.platform == 'win32':
+    def _cmd_splitter(cmd):
+        return cmd
+
+    def _process_output_post(output):
+        return output.replace('\r\n', '\n')
+
+else:
+    def _cmd_splitter(cmd):
+        return shlex.split(cmd)
+
+    def _process_output_post(output):
+        return output
+
+
+def _disjoint_dict_merge(d1, d2):
+    """
+    Merges two dictionaries with no common keys together and returns the result.
+
+    >>> d1 = {"a": 1}
+    >>> d2 = {"b": 2, "c": 3}
+    >>> _disjoint_dict_merge(d1, d2) == {"a": 1, "b": 2, "c": 3}
+    True
+
+    Calling this function with dictionaries that share keys raises a ValueError:
+    >>> _disjoint_dict_merge({"a": 1, "b": 6}, {"b": 2, "a": 3})
+    Traceback (most recent call last):
+     ..
+    ValueError: Dictionaries have common keys.
+
+    """
+    inter = set(d1.keys()).intersection(set(d2.keys()))
+    if len(inter) > 0:
+        raise ValueError("Dictionaries have common keys.")
+    res = d1.copy()
+    res.update(d2)
+    return res
+
+
+#: global parameters extracted from the test suite's configuration file
+_parameters = {}
+
+
+def configure_suite(config_file):
+    """
+    Populates a global datastructure with the parameters from the suite's
+    configuration file.
+
+    This function performs the following steps:
+    1. read in the file ``config_file`` via the ConfigParser module using
+       extended interpolation
+    2. check that the sections ``variables`` and ``paths`` are disjoint
+    3. extract the environment variables given in the ``ENV`` section
+    4. save all entries from the ``variables`` section in the global
+       datastructure
+    5. interpret all entries in the ``paths`` section as relative paths from the
+       configuration file, expand them to absolute paths and save them in the
+       global datastructure
+
+    For further information concerning the rationale behind this, please consult
+    the documentation in ``doc.md``.
+    """
+
+    if not os.path.exists(config_file):
+        raise ValueError(
+            "Test suite config file {:s} does not exist"
+            .format(os.path.abspath(config_file))
+        )
+
+    config = configparser.ConfigParser(
+        interpolation=configparser.ExtendedInterpolation(),
+        delimiters=(':'),
+        comment_prefixes=('#')
+    )
+    config.read(config_file)
+
+    _parameters["suite_root"] = os.path.split(os.path.abspath(config_file))[0]
+    _parameters["timeout"] = config.getfloat("General", "timeout", fallback=1.0)
+
+    if 'variables' in config and 'paths' in config:
+        intersecting_keys = set(config["paths"].keys())\
+                            .intersection(set(config["variables"].keys()))
+        if len(intersecting_keys) > 0:
+            raise ValueError(
+                "The sections 'paths' and 'variables' must not share keys, "
+                "but they have the following common key{:s}: {:s}"
+                .format(
+                    's' if len(intersecting_keys) > 1 else '',
+                    ', '.join(k for k in intersecting_keys)
+                )
+            )
+
+    # extract variables from the environment
+    for key in config['ENV']:
+        if key in config['ENV fallback']:
+            fallback = config['ENV fallback'][key]
+        else:
+            fallback = ""
+        config['ENV'][key] = os.getenv(config['ENV'][key]) or fallback
+
+    if 'variables' in config:
+        for key in config['variables']:
+            _parameters[key] = config['variables'][key]
+
+    if 'paths' in config:
+        for key in config['paths']:
+            rel_path = config['paths'][key]
+            abs_path = os.path.abspath(
+                os.path.join(_parameters["suite_root"], rel_path)
+            )
+            if not os.path.exists(abs_path):
+                raise ValueError(
+                    "Path replacement for {short}: {abspath} does not exist"
+                    " (was expanded from {rel})".format(
+                        short=key,
+                        abspath=abs_path,
+                        rel=rel_path)
+                )
+            _parameters[key] = abs_path
+
+
+def _setUp_factory(old_setUp, *files):
+    """
+    Factory function that returns a setUp function suitable to replace the
+    existing setUp of a unittest.TestCase. The returned setUp calls at first
+    old_setUp(self) and then creates a copy of all files in *files with the
+    name: fname.ext -> fname_copy.ext
+
+    All file names in *files are at first expanded using self.expand_variables()
+    and the path to the copy is saved in self._file_copies
+    """
+    def setUp(self):
+        old_setUp(self)
+        self._file_copies = []
+        for f in files:
+            expanded_fname = self.expand_variables(f)
+            fname, ext = os.path.splitext(expanded_fname)
+            new_name = fname + '_copy' + ext
+            self._file_copies.append(
+                shutil.copyfile(expanded_fname, new_name)
+            )
+    return setUp
+
+
+def _tearDown_factory(old_tearDown):
+    """
+    Factory function that returns a new tearDown method to replace an existing
+    tearDown method. It at first deletes all files in self._file_copies and then
+    calls old_tearDown(self).
+    This factory is intended to be used in conjunction with _setUp_factory
+    """
+    def tearDown(self):
+        for f in self._file_copies:
+            os.remove(f)
+        old_tearDown(self)
+    return tearDown
+
+
+def CopyFiles(*files):
+    """
+    Decorator for subclasses of system_test.Case that automatically creates a
+    copy of the files specified as the parameters to the decorator.
+
+    Example:
+    >>> @CopyFiles("{some_var}/file.txt", "{another_var}/other_file.png")
+        class Foo(Case):
+            pass
+
+    The decorator will inject new setUp method that at first calls the already
+    defined setUp(), then expands all supplied file names using
+    Case.expand_variables and then creates copies by appending '_copy' before
+    the file extension. The paths to the copies are stored in self._file_copies.
+
+    The decorator also injects a new tearDown method that deletes all files in
+    self._file_copies and then calls the original tearDown method.
+
+    This function will also complain if it is called without arguments or
+    without paranthesis, which is valid decorator syntax but is obviously a bug
+    in this case.
+    """
+    if len(files) == 0:
+        raise ValueError("No files to copy supplied.")
+    elif len(files) == 1:
+        if isinstance(files[0], type):
+            raise UserWarning(
+                "Decorator used wrongly, must be called with filenames in paranthesis"
+            )
+
+    def wrapper(cls):
+        old_setUp = cls.setUp
+        cls.setUp = _setUp_factory(old_setUp, *files)
+
+        old_tearDown = cls.tearDown
+        cls.tearDown = _tearDown_factory(old_tearDown)
+
+        return cls
+
+    return wrapper
+
+
+def test_run(self):
+    """
+    This function reads in the members commands, retval, stdout, stderr and runs
+    the `expand_variables` function on each. The resulting commands are then run
+    using the subprocess module and compared against the expected values that
+    were provided in the static members via `compare_stdout` and
+    `compare_stderr`. Furthermore a threading.Timer is used to abort the
+    execution if a configured timeout is reached.
+
+    It is automatically added as a member function to each system test by the
+    CaseMeta metaclass. This ensures that it is run by each system test
+    **after** setUp() and setUpClass() were run.
+    """
+    for i, command, retval, stdout, stderr in zip(range(len(self.commands)),
+                                                  self.commands,
+                                                  self.retval,
+                                                  self.stdout,
+                                                  self.stderr):
+        command, retval, stdout, stderr = map(
+            self.expand_variables, [command, retval, stdout, stderr]
+        )
+        retval = int(retval)
+        timeout = {"flag": False}
+
+        proc = subprocess.Popen(
+            _cmd_splitter(command),
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+            cwd=self.work_dir
+        )
+
+        def timeout_reached(timeout):
+            timeout["flag"] = True
+            proc.kill()
+
+        t = threading.Timer(
+            _parameters["timeout"], timeout_reached, args=[timeout]
+        )
+        t.start()
+        got_stdout, got_stderr = proc.communicate()
+        t.cancel()
+
+        processed_stdout = _process_output_post(got_stdout.decode('utf-8'))
+        processed_stderr = _process_output_post(got_stderr.decode('utf-8'))
+
+        self.assertFalse(timeout["flag"] and "Timeout reached")
+        self.compare_stdout(i, command, processed_stdout, stdout)
+        self.compare_stderr(i, command, processed_stderr, stderr)
+        self.assertEqual(retval, proc.returncode)
+
+
+class Case(unittest.TestCase):
+    """
+    System test case base class, provides the functionality to interpret static
+    class members as system tests.
+
+    The class itself only provides utility functions and system tests need not
+    inherit from it, as it is automatically added via the CaseMeta metaclass.
+    """
+
+    #: maxDiff set so that arbitrarily large diffs will be shown
+    maxDiff = None
+
+    @classmethod
+    def setUpClass(cls):
+        """
+        This function adds the variable work_dir to the class, which is the path
+        to the directory where the python source file is located.
+        """
+        cls.work_dir = os.path.dirname(inspect.getfile(cls))
+
+    def compare_stdout(self, i, command, got_stdout, expected_stdout):
+        """
+        Function to compare whether the expected & obtained stdout match.
+
+        This function is automatically invoked by test_run with the following
+        parameters:
+        i - the index of the current command that is run in self.commands
+        command - the command that was run
+        got_stdout - the obtained stdout, post-processed depending on the
+                     platform so that lines always end with \n
+        expected_stdout - the expected stdout extracted from self.stdout
+
+        The default implementation simply uses assertMultiLineEqual from
+        unittest.TestCase. This function can be overridden in a child class to
+        implement a custom check.
+        """
+        self.assertMultiLineEqual(expected_stdout, got_stdout)
+
+    def compare_stderr(self, i, command, got_stderr, expected_stderr):
+        """ Same as compare_stdout only for standard-error. """
+        self.assertMultiLineEqual(expected_stderr, got_stderr)
+
+    def expand_variables(self, unexpanded_string):
+        """
+        Expands all variables of the form ``$var`` in the given string using the
+        dictionary `variable_dict`.
+
+        The expansion itself is performed by the string's template module using
+        via `safe_substitute`.
+        """
+        return string.Template(str(unexpanded_string))\
+            .safe_substitute(**self.variable_dict)
+
+
+class CaseMeta(type):
+    """ System tests generation metaclass.
+
+    This metaclass is performs the following tasks:
+
+    1. Add the `test_run` function as a member of the test class
+    2. Add the `Case` class as the parent class
+    3. Expand all variables already defined in the class, so that any additional
+       code does not have to perform this task
+
+    Using a metaclass instead of inheriting from case has the advantage, that we
+    can expand all variables in the strings before any test case or even the
+    class constructor is run! Thus users will immediately see the expanded
+    result. Also adding the `test_run` function as a direct member and not via
+    inheritance enforces that it is being run **after** the test cases setUp &
+    setUpClass (which oddly enough seems not to be the case in the unittest
+    module where test functions of the parent class run before setUpClass of the
+    child class).
+    """
+
+    def __new__(mcs, clsname, bases, dct):
+
+        changed = False
+
+        # expand all non-private variables by brute force
+        # => try all expanding all elements defined in the current class until
+        # there is no change in them any more
+        keys = [key for key in list(dct.keys()) if not key.startswith('_')]
+        while not changed:
+            for key in keys:
+
+                old_value = dct[key]
+
+                # only try expanding strings and lists
+                if isinstance(old_value, str):
+                    new_value = string.Template(old_value).safe_substitute(
+                        **_disjoint_dict_merge(dct, _parameters)
+                    )
+                elif isinstance(old_value, list):
+                    # do not try to expand anything but strings in the list
+                    new_value = [
+                        string.Template(elem).safe_substitute(
+                            **_disjoint_dict_merge(dct, _parameters)
+                        )
+                        if isinstance(elem, str) else elem
+                        for elem in old_value
+                    ]
+                else:
+                    continue
+
+                if old_value != new_value:
+                    changed = True
+                    dct[key] = new_value
+
+        dct['variable_dict'] = _disjoint_dict_merge(dct, _parameters)
+        dct['test_run'] = test_run
+
+        if Case not in bases:
+            bases += (Case,)
+
+        return super(CaseMeta, mcs).__new__(mcs, clsname, bases, dct)
diff --git a/tests/writing_tests.md b/tests/writing_tests.md
new file mode 100644
index 00000000..e3e9cfe9
--- /dev/null
+++ b/tests/writing_tests.md
@@ -0,0 +1,42 @@
+## Writing new tests
+
+The test suite is intended to run a binary and compare its standard output,
+standard error and return value against provided values. This is implemented
+using Python's `unittest` module and thus all test files are Python files.
+
+The simplest test has the following structure:
+``` python
+# -*- coding: utf-8 -*-
+
+import system_tests
+
+
+class GoodTestName(system_tests.Case):
+
+    filename = "{data_path}/test_file"
+    commands = ["{exiv2} " + filename, "{exiv2} " + filename + '_2']
+    stdout = [""] * 2
+    stderr = ["""{exiv2_exception_msg} """ + filename + """:
+{error_58_message}
+"""] * 2
+    retval = [1] * 2
+```
+
+The test suite will run the provided commands in `commands` and compare them to
+the output in `stdout` and `stderr` and it will compare the return values.
+
+The strings in curly braces are variables either defined in this test's class or
+are taken from the suite's configuration file (see `doc.md` for a complete
+explanation).
+
+When creating new tests, follow roughly these steps:
+
+1. Choose an appropriate subdirectory where the test belongs. If none fits
+   create a new one and put an empty `__init__.py` file there.
+
+2. Create a new file with a name matching `test_*.py`. Copy the class definition
+   from the above example and choose an appropriate class name.
+
+3. Run the test suite via `python3 runner.py` and ensure that your test case is
+   actually run! Either run the suite with the `-v` option which will output all
+   test cases that were run or simply add an error and check if errors occur.
diff --git a/xmpsdk/CMakeLists.txt b/xmpsdk/CMakeLists.txt
index f081d46e..0059a255 100644
--- a/xmpsdk/CMakeLists.txt
+++ b/xmpsdk/CMakeLists.txt
@@ -37,7 +37,10 @@ IF( EXIV2_ENABLE_XMP AND EXIV2_ENABLE_LIBXMP )
         TARGET_LINK_LIBRARIES(xmp Threads::Threads ${EXPAT_LIBRARIES})
     endif()
     # 1119  Install libxmp.a for use by third party applications (Thanks, Emmanuel)
-    INSTALL(TARGETS xmp ${INSTALL_TARGET_STANDARD_ARGS} )
+    INSTALL(TARGETS xmp
+        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    )
 ENDIF()
 
 # That's all Folks!
openSUSE Build Service is sponsored by