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..67ee42bf
--- /dev/null
+++ b/.travis/install.sh
@@ -0,0 +1,12 @@
+#!/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 install cmake zlib1g-dev libssh-dev gettext expat libcurl4-openssl-dev libxml2-utils
+    sudo pip install virtualenv
+else
+    brew update
+    brew install gettext libssh 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..2e179bf5 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,8 +72,8 @@ 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" )
+if( MINGW OR UNIX )
+    if (${CMAKE_CXX_COMPILER_ID} STREQUAL GNU)
         ADD_DEFINITIONS(-Wall
                     -Wcast-align
                     -Wpointer-arith
@@ -76,18 +84,8 @@ IF( MINGW OR UNIX )
                    )
     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 ))
+    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))
         ADD_DEFINITIONS( -std=gnu++98 ) # to support snprintf
     ELSE()
         ADD_DEFINITIONS( -std=c++98 )
@@ -120,7 +118,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/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/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/src/CMakeLists.txt b/src/CMakeLists.txt
index aecd6215..d4dc6375 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
@@ -246,6 +242,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 +266,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 +308,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 +320,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 +337,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/actions.cpp b/src/actions.cpp
index 0ebe8505..9f850097 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)
     {
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/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/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..a308bfd9 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,16 @@ 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
+                            DataBuf data(Safe::add(subBox.length, static_cast<uint32_t>(8)));
                             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..4ad74e1e 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);
         }
 
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/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/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/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/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/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_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..134b1471
--- /dev/null
+++ b/tests/suite.conf
@@ -0,0 +1,21 @@
+[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
diff --git a/tests/system_tests.py b/tests/system_tests.py
new file mode 100644
index 00000000..e22c78e5
--- /dev/null
+++ b/tests/system_tests.py
@@ -0,0 +1,331 @@
+# -*- coding: utf-8 -*-
+
+import configparser
+import os
+import inspect
+import subprocess
+import threading
+import shlex
+import sys
+import shutil
+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
+
+
+_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
+
+
+class Case(unittest.TestCase):
+    """
+    System test case base class, provides the functionality to interpret static
+    class members as system tests and runs them.
+
+    This class reads in the members commands, retval, stdout, stderr and runs
+    the format function on each, where format is called with the kwargs being a
+    merged dictionary of all variables that were extracted from the suite's
+    configuration file and all static members of the current class.
+
+    The resulting commands are then run using the subprocess module and compared
+    against the expected values that were provided in the static
+    members. Furthermore a threading.Timer is used to abort the execution if a
+    configured timeout is reached.
+
+    The class itself must be inherited from, otherwise it is not useful at all,
+    as it does not provide any static members that could be used to run system
+    tests. However, a class that inherits from this class needn't provide any
+    member functions at all, the inherited test_run() function performs all
+    required functionality in child classes.
+    """
+
+    """ maxDiff set so that arbitrarily large diffs will be shown """
+    maxDiff = None
+
+    @classmethod
+    def setUpClass(cls):
+        """
+        This function adds the variables variable_dict & work_dir to the class.
+
+        work_dir - set to the file where the current class is defined
+        variable_dict - a merged dictionary of all static members of the current
+                        class and all variables extracted from the suite's
+                        configuration file
+        """
+        cls.variable_dict = _disjoint_dict_merge(cls.__dict__, _parameters)
+        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, string):
+        """
+        Expands all variables in curly braces in the given string using the
+        dictionary variable_dict.
+
+        The expansion itself is performed by the builtin string method format().
+        A KeyError indicates that the supplied string contains a variable
+        in curly braces that is missing from self.variable_dict
+        """
+        return str(string).format(**self.variable_dict)
+
+    def test_run(self):
+        """
+        Actual system test function which runs the provided commands,
+        pre-processes all variables and post processes the output before passing
+        it on to compare_stderr() & compare_stdout().
+        """
+
+        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()
+
+            self.assertFalse(timeout["flag"] and "Timeout reached")
+            self.compare_stdout(
+                i, command,
+                _process_output_post(got_stdout.decode('utf-8')), stdout
+            )
+            self.compare_stderr(
+                i, command,
+                _process_output_post(got_stderr.decode('utf-8')), stderr
+            )
+            self.assertEqual(retval, proc.returncode)
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!
