Import libappimage_0.1.9+dfsg.orig.tar.xz
authorScarlett Moore <sgmoore@kde.org>
Sun, 10 Feb 2019 13:08:27 +0000 (13:08 +0000)
committerScarlett Moore <sgmoore@kde.org>
Sun, 10 Feb 2019 13:08:27 +0000 (13:08 +0000)
[dgit import orig libappimage_0.1.9+dfsg.orig.tar.xz]

72 files changed:
.gitignore [new file with mode: 0644]
.gitmodules [new file with mode: 0644]
.travis.yml [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
LICENSE [new file with mode: 0644]
README.md [new file with mode: 0644]
cmake/Modules/Findsquashfuse.cmake [new file with mode: 0644]
cmake/dependencies.cmake [new file with mode: 0644]
cmake/imported_dependencies.cmake [new file with mode: 0644]
cmake/libappimageConfig.cmake.in [new file with mode: 0644]
cmake/libappimageConfigVersion.cmake.in [new file with mode: 0644]
cmake/scripts.cmake [new file with mode: 0644]
cmake/tools.cmake [new file with mode: 0644]
include/appimage/appimage.h [new file with mode: 0644]
include/appimage/appimage_shared.h [new file with mode: 0644]
lib/CMakeLists.txt [new file with mode: 0644]
src/CMakeLists.txt [new file with mode: 0644]
src/libappimage/CMakeLists.txt [new file with mode: 0644]
src/libappimage/appimage_handler.c [new file with mode: 0644]
src/libappimage/appimage_handler.h [new file with mode: 0644]
src/libappimage/desktop_file_integration_private.h [new file with mode: 0644]
src/libappimage/desktop_integration.c [new file with mode: 0644]
src/libappimage/desktop_integration.h [new file with mode: 0644]
src/libappimage/libappimage.c [new file with mode: 0644]
src/libappimage/libappimage_private.h [new file with mode: 0644]
src/libappimage/type1.c [new file with mode: 0644]
src/libappimage/type1.h [new file with mode: 0644]
src/libappimage/type2.c [new file with mode: 0644]
src/libappimage/type2.h [new file with mode: 0644]
src/libappimage_hashlib/CMakeLists.txt [new file with mode: 0644]
src/libappimage_hashlib/include/hashlib.h [new file with mode: 0644]
src/libappimage_hashlib/include/md5.h [new file with mode: 0644]
src/libappimage_hashlib/md5.c [new file with mode: 0644]
src/libappimage_shared/CMakeLists.txt [new file with mode: 0644]
src/libappimage_shared/digest.c [new file with mode: 0644]
src/libappimage_shared/elf.c [new file with mode: 0644]
src/libappimage_shared/hexlify.c [new file with mode: 0644]
src/libappimage_shared/light_byteswap.h [new file with mode: 0644]
src/libappimage_shared/light_elf.h [new file with mode: 0644]
src/patches/patch-squashfuse.sh.in [new file with mode: 0755]
src/patches/squashfuse.patch [new file with mode: 0644]
src/patches/squashfuse_dlopen.c [new file with mode: 0644]
src/patches/squashfuse_dlopen.h [new file with mode: 0644]
src/patches/squashfuse_dlopen.patch [new file with mode: 0644]
src/xdg-basedir/CMakeLists.txt [new file with mode: 0644]
src/xdg-basedir/xdg-basedir.c [new file with mode: 0644]
src/xdg-basedir/xdg-basedir.h [new file with mode: 0644]
tests/CMakeLists.txt [new file with mode: 0644]
tests/data/AppImageExtract_6-x86_64.AppImage [new file with mode: 0755]
tests/data/AppImageExtract_6_no_magic_bytes-x86_64.AppImage [new file with mode: 0755]
tests/data/Cura.desktop [new file with mode: 0644]
tests/data/Echo-no-integrate-x86_64.AppImage [new file with mode: 0755]
tests/data/Echo-test1234-x86_64.AppImage [new file with mode: 0755]
tests/data/Echo-x86_64.AppImage [new file with mode: 0755]
tests/data/appimaged-i686.AppImage [new file with mode: 0755]
tests/data/appimagetool-x86_64.AppImage [new file with mode: 0755]
tests/data/elffile [new file with mode: 0755]
tests/data/minimal.iso [new file with mode: 0644]
tests/data/squashfs-root/.DirIcon [new symlink]
tests/data/squashfs-root/AppRun [new file with mode: 0755]
tests/data/squashfs-root/echo.desktop [new file with mode: 0644]
tests/data/squashfs-root/usr/bin/echo [new file with mode: 0755]
tests/data/squashfs-root/utilities-terminal.svg [new file with mode: 0644]
tests/file_management_utils.hpp [new file with mode: 0644]
tests/fixtures.h [new file with mode: 0644]
tests/test-xdg-basedir.cpp [new file with mode: 0644]
tests/test_desktop_integration.cpp [new file with mode: 0644]
tests/test_getsection.cpp [new file with mode: 0644]
tests/test_libappimage.cpp [new file with mode: 0644]
tests/test_shared.cpp [new file with mode: 0644]
travis/build-and-test.sh [new file with mode: 0755]
travis/travis-build.sh [new file with mode: 0755]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..25d2462
--- /dev/null
@@ -0,0 +1,4 @@
+*build*/
+cmake-build-*/
+.idea/
+Testing/
diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..cd6332f
--- /dev/null
@@ -0,0 +1,3 @@
+[submodule "lib/gtest"]
+       path = lib/gtest
+       url = https://github.com/google/googletest.git
diff --git a/.travis.yml b/.travis.yml
new file mode 100644 (file)
index 0000000..6618554
--- /dev/null
@@ -0,0 +1,37 @@
+language: c
+compiler: gcc
+
+services:
+- docker
+
+matrix:
+  include:
+    - env:
+      - ARCH=x86_64
+      - DOCKER_IMAGE=quay.io/appimage/appimagebuild
+    - env:
+      - ARCH=i686
+      - DOCKER_IMAGE=quay.io/appimage/appimagebuild-i386
+    - env:
+      - ARCH=x86_64
+      addons:
+        apt:
+          update: true
+          packages:
+          - libfuse-dev
+          - desktop-file-utils
+
+script:
+- bash travis/travis-build.sh
+
+notifications:
+  irc:
+    channels:
+    - "chat.freenode.net#AppImage"
+    on_success: always  # options: [always|never|change] default: always
+    on_failure: always  # options: [always|never|change] default: always
+    on_start: always    # options: [always|never|change] default: always
+    template:
+    - "%{repository} build %{build_number}: %{result} %{build_url}"
+    use_notice: true
+    # skip_join: true
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b9de6cb
--- /dev/null
@@ -0,0 +1,25 @@
+cmake_minimum_required(VERSION 3.2)
+
+project(libappimage)
+
+set(CMAKE_C_STANDARD 99)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake)
+
+# versioning
+set(V_MAJOR 0)
+set(V_MINOR 1)
+set(V_PATCH 9)
+
+include(cmake/tools.cmake)
+include(cmake/dependencies.cmake)
+
+# used by e.g., Debian packaging infrastructure
+include(GNUInstallDirs)
+
+add_subdirectory(lib)
+add_subdirectory(src)
+
+include(CTest)
+add_subdirectory(tests)
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..f3777cc
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+MIT License
+
+If not stated otherwise within the individual file or subdirectory, the
+original source code in this repository is licensed as below. This does not
+necessarily apply for all dependencies. For the sake of clarity, this license
+does NOT apply to the contents of AppImages that anyone may create.
+Software contained inside an AppImage may be licensed under any license at the
+discretion of the respecive rights holder(s).
+
+Copyright (c) 2004-19 Simon Peter and the AppImage team
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..1643394
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+# libappimage
diff --git a/cmake/Modules/Findsquashfuse.cmake b/cmake/Modules/Findsquashfuse.cmake
new file mode 100644 (file)
index 0000000..24529a4
--- /dev/null
@@ -0,0 +1,18 @@
+cmake_minimum_required(VERSION 3.0)
+
+find_path(squashfuse_H_DIR
+    NAMES squashfuse.h
+    HINTS ${CMAKE_INSTALL_PREFIX}
+    PATH_SUFFIXES include include/linux
+)
+
+if(NOT squashfuse_H_DIR)
+    message(FATAL_ERROR "squashfuse.h not found")
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(squashfuse DEFAULT_MSG SQUASHFUSE_INCLUDE_DIR SQUASHFUSE_LIBRARY_DIR)
+
+add_library(squashfuse IMPORTED SHARED)
+set_property(TARGET squashfuse PROPERTY IMPORTED_LOCATION ${squashfuse_LIBRARY})
+set_property(TARGET squashfuse PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${squashfuse_H_DIR}")
diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake
new file mode 100644 (file)
index 0000000..8d96484
--- /dev/null
@@ -0,0 +1,133 @@
+# >= 3.2 required for ExternalProject_Add_StepDependencies
+cmake_minimum_required(VERSION 3.2)
+
+include(${CMAKE_CURRENT_LIST_DIR}/scripts.cmake)
+
+# imported dependencies
+include(${CMAKE_CURRENT_LIST_DIR}/imported_dependencies.cmake)
+
+if(USE_CCACHE)
+    message(STATUS "Using CCache to build AppImageKit dependencies")
+    # TODO: find way to use find_program with all possible paths
+    # (might differ from distro to distro)
+    # these work on Debian and Ubuntu:
+    set(CC "/usr/lib/ccache/gcc")
+    set(CXX "/usr/lib/ccache/g++")
+else()
+    set(CC "${CMAKE_C_COMPILER}")
+    set(CXX "${CMAKE_CXX_COMPILER}")
+endif()
+
+set(CFLAGS ${DEPENDENCIES_CFLAGS})
+set(CPPFLAGS ${DEPENDENCIES_CPPFLAGS})
+set(LDFLAGS ${DEPENDENCIES_LDFLAGS})
+
+
+set(USE_SYSTEM_XZ OFF CACHE BOOL "Use system xz/liblzma instead of building our own")
+
+if(NOT USE_SYSTEM_XZ)
+    message(STATUS "Downloading and building xz")
+
+    ExternalProject_Add(xz-EXTERNAL
+        URL https://netcologne.dl.sourceforge.net/project/lzmautils/xz-5.2.3.tar.gz
+        URL_HASH SHA512=a5eb4f707cf31579d166a6f95dbac45cf7ea181036d1632b4f123a4072f502f8d57cd6e7d0588f0bf831a07b8fc4065d26589a25c399b95ddcf5f73435163da6
+        CONFIGURE_COMMAND CC=${CC} CXX=${CXX} CFLAGS=${CFLAGS} CPPFLAGS=${CPPFLAGS} LDFLAGS=${LDFLAGS} <SOURCE_DIR>/configure --with-pic --disable-shared --enable-static --prefix=<INSTALL_DIR> --libdir=<INSTALL_DIR>/lib ${EXTRA_CONFIGURE_FLAGS} --disable-xz --disable-xzdec
+        BUILD_COMMAND ${MAKE}
+        INSTALL_COMMAND ${MAKE} install
+    )
+
+    import_external_project(
+        TARGET_NAME xz
+        EXT_PROJECT_NAME xz-EXTERNAL
+        LIBRARY_DIRS <INSTALL_DIR>/lib/
+        LIBRARIES "<INSTALL_DIR>/lib/liblzma.a"
+        INCLUDE_DIRS "<SOURCE_DIR>/src/liblzma/api/"
+    )
+else()
+    message(STATUS "Using system xz")
+
+    import_pkgconfig_target(TARGET_NAME xz PKGCONFIG_TARGET liblzma)
+endif()
+
+
+# as distros don't provide suitable squashfuse and squashfs-tools, those dependencies are bundled in, can, and should
+# be used from this repository for AppImageKit
+# for distro packaging, it can be linked to an existing package just fine
+set(USE_SYSTEM_SQUASHFUSE OFF CACHE BOOL "Use system libsquashfuse instead of building our own")
+
+if(NOT USE_SYSTEM_SQUASHFUSE)
+    message(STATUS "Downloading and building squashfuse")
+
+    # TODO: implement out-of-source builds for squashfuse, as for the other dependencies
+    configure_file(
+        ${CMAKE_CURRENT_SOURCE_DIR}/src/patches/patch-squashfuse.sh.in
+        ${CMAKE_CURRENT_BINARY_DIR}/patch-squashfuse.sh
+        @ONLY
+    )
+
+    ExternalProject_Add(squashfuse-EXTERNAL
+        GIT_REPOSITORY https://github.com/vasi/squashfuse/
+        GIT_TAG 1f98030
+        UPDATE_COMMAND ""  # make sure CMake won't try to fetch updates unnecessarily and hence rebuild the dependency every time
+        PATCH_COMMAND bash -xe ${CMAKE_CURRENT_BINARY_DIR}/patch-squashfuse.sh
+        CONFIGURE_COMMAND ${LIBTOOLIZE} --force
+                  COMMAND env ACLOCAL_FLAGS="-I /usr/share/aclocal" aclocal
+                  COMMAND ${AUTOHEADER}
+                  COMMAND ${AUTOMAKE} --force-missing --add-missing
+                  COMMAND ${AUTORECONF} -fi || true
+                  COMMAND ${SED} -i "/PKG_CHECK_MODULES.*/,/,:./d" configure  # https://github.com/vasi/squashfuse/issues/12
+                  COMMAND ${SED} -i "s/typedef off_t sqfs_off_t/typedef int64_t sqfs_off_t/g" common.h  # off_t's size might differ, see https://stackoverflow.com/a/9073762
+                  COMMAND CC=${CC} CXX=${CXX} CFLAGS=${CFLAGS} LDFLAGS=${LDFLAGS} <SOURCE_DIR>/configure --disable-demo --disable-high-level --without-lzo --without-lz4 --prefix=<INSTALL_DIR> --libdir=<INSTALL_DIR>/lib --with-xz=${xz_PREFIX} ${EXTRA_CONFIGURE_FLAGS}
+                  COMMAND ${SED} -i "s|XZ_LIBS = -llzma |XZ_LIBS = -Bstatic ${xz_LIBRARIES}/|g" Makefile
+        BUILD_COMMAND ${MAKE}
+        BUILD_IN_SOURCE ON
+        INSTALL_COMMAND ${MAKE} install
+    )
+
+    import_external_project(
+        TARGET_NAME libsquashfuse
+        EXT_PROJECT_NAME squashfuse-EXTERNAL
+        LIBRARIES "<SOURCE_DIR>/.libs/libsquashfuse.a;<SOURCE_DIR>/.libs/libsquashfuse_ll.a;<SOURCE_DIR>/.libs/libfuseprivate.a"
+        INCLUDE_DIRS "<SOURCE_DIR>"
+    )
+else()
+    message(STATUS "Using system squashfuse")
+
+    import_pkgconfig_target(TARGET_NAME libsquashfuse PKGCONFIG_TARGET squashfuse)
+endif()
+
+
+set(USE_SYSTEM_LIBARCHIVE OFF CACHE BOOL "Use system libarchive instead of building our own")
+
+if(NOT USE_SYSTEM_LIBARCHIVE)
+    message(STATUS "Downloading and building libarchive")
+
+    ExternalProject_Add(libarchive-EXTERNAL
+        URL https://www.libarchive.org/downloads/libarchive-3.3.1.tar.gz
+        URL_HASH SHA512=90702b393b6f0943f42438e277b257af45eee4fa82420431f6a4f5f48bb846f2a72c8ff084dc3ee9c87bdf8b57f4d8dddf7814870fe2604fe86c55d8d744c164
+        CONFIGURE_COMMAND CC=${CC} CXX=${CXX} CFLAGS=${CFLAGS} CPPFLAGS=${CPPFLAGS} LDFLAGS=${LDFLAGS} <SOURCE_DIR>/configure --with-pic --disable-shared --enable-static --disable-bsdtar --disable-bsdcat --disable-bsdcpio --with-zlib --without-bz2lib --without-iconv --without-lz4 --without-lzma --without-lzo2 --without-nettle --without-openssl --without-xml2 --without-expat --prefix=<INSTALL_DIR> --libdir=<INSTALL_DIR>/lib ${EXTRA_CONFIGURE_FLAGS}
+        BUILD_COMMAND ${MAKE}
+        INSTALL_COMMAND ${MAKE} install
+    )
+
+    import_external_project(
+        TARGET_NAME libarchive
+        EXT_PROJECT_NAME libarchive-EXTERNAL
+        LIBRARIES "<INSTALL_DIR>/lib/libarchive.a"
+        INCLUDE_DIRS "<INSTALL_DIR>/include/"
+    )
+else()
+    message(STATUS "Using system libarchive")
+
+    import_find_pkg_target(libarchive LibArchive LibArchive)
+endif()
+
+
+#### build dependency configuration ####
+
+# only have to build custom xz when not using system libxz
+if(TARGET xz-EXTERNAL)
+    if(TARGET squashfuse-EXTERNAL)
+        ExternalProject_Add_StepDependencies(squashfuse-EXTERNAL configure xz-EXTERNAL)
+    endif()
+endif()
diff --git a/cmake/imported_dependencies.cmake b/cmake/imported_dependencies.cmake
new file mode 100644 (file)
index 0000000..56d7fc0
--- /dev/null
@@ -0,0 +1,10 @@
+include(${CMAKE_CURRENT_LIST_DIR}/scripts.cmake)
+
+# the names of the targets need to differ from the library filenames
+# this is especially an issue with libcairo, where the library is called libcairo
+# therefore, all libs imported this way have been prefixed with lib
+import_pkgconfig_target(TARGET_NAME libglib PKGCONFIG_TARGET glib-2.0>=2.40)
+import_pkgconfig_target(TARGET_NAME libgobject PKGCONFIG_TARGET gobject-2.0>=2.40)
+import_pkgconfig_target(TARGET_NAME libgio PKGCONFIG_TARGET gio-2.0>=2.40)
+import_pkgconfig_target(TARGET_NAME libzlib PKGCONFIG_TARGET zlib)
+import_pkgconfig_target(TARGET_NAME libcairo PKGCONFIG_TARGET cairo)
diff --git a/cmake/libappimageConfig.cmake.in b/cmake/libappimageConfig.cmake.in
new file mode 100644 (file)
index 0000000..29b8238
--- /dev/null
@@ -0,0 +1,22 @@
+# - Config file for the AppImage package
+# Exported Targets
+#  - libappimage
+#  - libappimage_shared
+#
+# Exported Variables (DEPRECATED use the exported targets instead)
+#  LIBAPPIMAGE_INCLUDE_DIRS - include directories for LIBAPPIMAGE
+#  LIBAPPIMAGE_LIBRARIES    - libraries to link against
+
+@PACKAGE_INIT@
+
+# Compute paths
+get_filename_component(LIBAPPIMAGE_CMAKE_DIR ${CMAKE_CURRENT_LIST_FILE} PATH)
+
+# Import dependencies implicitly required by libappimageTargets.cmake
+include(${LIBAPPIMAGE_CMAKE_DIR}/imported_dependencies.cmake)
+
+# Our library dependencies (contains definitions for IMPORTED targets)
+include(${LIBAPPIMAGE_CMAKE_DIR}/libappimageTargets.cmake)
+
+get_target_property(LIBAPPIMAGE_INCLUDE_DIRS libappimage INTERFACE_INCLUDE_DIRECTORIES)
+set(LIBAPPIMAGE_LIBRARIES libappimage)
diff --git a/cmake/libappimageConfigVersion.cmake.in b/cmake/libappimageConfigVersion.cmake.in
new file mode 100644 (file)
index 0000000..a55ea68
--- /dev/null
@@ -0,0 +1,11 @@
+set(PACKAGE_VERSION "@GIT_COMMIT@")
+# Check whether the requested PACKAGE_FIND_VERSION is compatible
+if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}")
+  set(PACKAGE_VERSION_COMPATIBLE FALSE)
+else()
+  set(PACKAGE_VERSION_COMPATIBLE TRUE)
+  if ("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}")
+    set(PACKAGE_VERSION_EXACT TRUE)
+  endif()
+endif()
diff --git a/cmake/scripts.cmake b/cmake/scripts.cmake
new file mode 100644 (file)
index 0000000..6362314
--- /dev/null
@@ -0,0 +1,229 @@
+cmake_minimum_required(VERSION 3.2)
+
+include(ExternalProject)
+
+# searches for absolute paths of libraries, applying LIBRARY_DIRS
+# CMake prefers absolute paths of libraries in non-standard locations, apparently
+# see FindPkgConfig.cmake's _pkg_find_libs for more information
+#
+# positional parameters:
+#  - libraries: name of variable containing list of libraries
+#  - library_dirs
+function(apply_library_dirs libraries library_dirs)
+    foreach(library ${${libraries}})
+        find_library(${library}_path ${library} PATHS ${${library_dirs}} NO_DEFAULT_PATH)
+        if(NOT ${library}_path)
+            list(APPEND new_libraries ${library})
+        else()
+            list(APPEND new_libraries ${${library}_path})
+        endif()
+    endforeach()
+    set(${libraries} ${new_libraries} PARENT_SCOPE)
+    unset(new_libraries)
+endfunction()
+
+# imports a library from the standard set of variables CMake creates when using its pkg-config module or find_package
+# this is code shared by import_pkgconfig_target and import_external_project, hence it's been extracted into a separate
+# CMake function
+#
+# partly inspired by https://github.com/Kitware/CMake/blob/master/Modules/FindPkgConfig.cmake#L187
+#
+# positional parameters:
+#  - target_name: name of the target that should be created
+#  - variable_prefix: prefix of the variable that should be used to create the target from
+function(import_library_from_prefix target_name variable_prefix)
+    if(TARGET ${target_name})
+        message(WARNING "Target exists already, skipping")
+        return()
+    endif()
+
+    add_library(${target_name} INTERFACE IMPORTED GLOBAL)
+
+    if(${variable_prefix}_INCLUDE_DIRS)
+        # need to create directories before setting INTERFACE_INCLUDE_DIRECTORIES, otherwise CMake will complain
+        # possibly related: https://cmake.org/Bug/view.php?id=15052
+        foreach(dir ${${variable_prefix}_INCLUDE_DIRS})
+            if(NOT EXISTS ${dir})
+                if (${dir} MATCHES ${CMAKE_BINARY_DIR})
+                    file(MAKE_DIRECTORY ${dir})
+                    list(APPEND include_dirs ${dir})
+                endif()
+            else()
+                list(APPEND include_dirs ${dir})
+            endif()
+        endforeach()
+        set_property(TARGET ${target_name} PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${include_dirs})
+        unset(include_dirs)
+    endif()
+
+    # if library dirs are set, figure out absolute paths to libraries, like CMake's FindPkgConfig module does
+    if(${variable_prefix}_LIBRARY_DIRS)
+        apply_library_dirs(${variable_prefix}_LIBRARIES ${variable_prefix}_LIBRARY_DIRS)
+    endif()
+
+    set_property(TARGET ${target_name} PROPERTY INTERFACE_LINK_LIBRARIES ${${variable_prefix}_LIBRARIES})
+
+    if(${variable_prefix}_CFLAGS_OTHER)
+        set_property(TARGET ${target_name} PROPERTY INTERFACE_COMPILE_OPTIONS ${${variable_prefix}_CFLAGS_OTHER})
+    endif()
+
+    # export some of the imported properties with the target name as prefix
+    # this is necessary to allow the other external projects, which are not built with CMake or not within the same
+    # CMake context, to link to the libraries built as external projects (or the system ones, depending on the build
+    # configuration)
+    set(${target_name}_INCLUDE_DIRS ${${variable_prefix}_INCLUDE_DIRS} CACHE INTERNAL "")
+    set(${target_name}_LIBRARIES ${${variable_prefix}_LIBRARIES} CACHE INTERNAL "")
+    set(${target_name}_LIBRARY_DIRS ${${variable_prefix}_LIBRARY_DIRS} CACHE INTERNAL "")
+    # TODO: the following might not always apply
+    set(${target_name}_PREFIX ${CMAKE_INSTALL_PREFIX}/lib CACHE INTERNAL "")
+endfunction()
+
+
+# imports a library using pkg-config
+#
+# positional parameters:
+#  - target_name: name of the target that we shall create for you
+#  - pkg_config_target: librar(y name to pass to pkg-config (may include a version)
+function(import_pkgconfig_target)
+    set(keywords STATIC OPTIONAL)
+    set(oneValueArgs TARGET_NAME PKGCONFIG_TARGET)
+    cmake_parse_arguments(IMPORT_PKGCONFIG_TARGET "${keywords}" "${oneValueArgs}" "" "${ARGN}")
+
+    # check whether parameters have been set
+    if(NOT IMPORT_PKGCONFIG_TARGET_TARGET_NAME)
+        message(FATAL_ERROR "TARGET_NAME parameter missing, but is required")
+    endif()
+    if(NOT IMPORT_PKGCONFIG_TARGET_PKGCONFIG_TARGET)
+        message(FATAL_ERROR "PKGCONFIG_TARGET parameter missing, but is required")
+    endif()
+
+    find_package(PkgConfig REQUIRED)
+
+    set(type "shared")
+    if(IMPORT_PKGCONFIG_TARGET_STATIC)
+        set(type "static")
+    endif()
+
+    message(STATUS "Importing target ${IMPORT_PKGCONFIG_TARGET_TARGET_NAME} via pkg-config (${IMPORT_PKGCONFIG_TARGET_PKGCONFIG_TARGET}, ${type})")
+
+    if(NOT IMPORT_PKGCONFIG_TARGET_OPTIONAL)
+        set(extra_args REQUIRED)
+    endif()
+
+    pkg_check_modules(${IMPORT_PKGCONFIG_TARGET_TARGET_NAME}-IMPORTED ${IMPORT_PKGCONFIG_TARGET_PKGCONFIG_TARGET} ${extra_args})
+
+    if(NOT ${IMPORT_PKGCONFIG_TARGET_TARGET_NAME}-IMPORTED_FOUND AND IMPORT_PKGCONFIG_TARGET_OPTIONAL)
+        return()
+    endif()
+
+    if(IMPORT_PKGCONFIG_TARGET_STATIC)
+        set(prefix ${IMPORT_PKGCONFIG_TARGET_TARGET_NAME}-IMPORTED_STATIC)
+    else()
+        set(prefix ${IMPORT_PKGCONFIG_TARGET_TARGET_NAME}-IMPORTED)
+    endif()
+
+    import_library_from_prefix(${IMPORT_PKGCONFIG_TARGET_TARGET_NAME} ${prefix})
+endfunction()
+
+function(import_find_pkg_target target_name pkg_name variable_prefix)
+    message(STATUS "Importing target ${target_name} via find_package (${pkg_name})")
+
+    find_package(${pkg_name})
+    if(NOT ${variable_prefix}_FOUND)
+        message(FATAL_ERROR "${pkg_name} could not be found on the system. You will have to either install it, or use the bundled package.")
+    endif()
+
+    import_library_from_prefix(${target_name} ${variable_prefix})
+endfunction()
+
+
+# imports a library from an existing external project
+#
+# required parameters:
+#  - TARGET_NAME:
+function(import_external_project)
+    set(oneValueArgs TARGET_NAME EXT_PROJECT_NAME)
+    set(multiValueArgs LIBRARIES INCLUDE_DIRS LIBRARY_DIRS)
+    cmake_parse_arguments(IMPORT_EXTERNAL_PROJECT "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}")
+
+    # check whether parameters have been set
+    if(NOT IMPORT_EXTERNAL_PROJECT_TARGET_NAME)
+        message(FATAL_ERROR "TARGET_NAME parameter missing, but is required")
+    endif()
+    if(NOT IMPORT_EXTERNAL_PROJECT_EXT_PROJECT_NAME)
+        message(FATAL_ERROR "EXT_PROJECT_NAME parameter missing, but is required")
+    endif()
+    if(NOT IMPORT_EXTERNAL_PROJECT_LIBRARIES)
+        message(FATAL_ERROR "LIBRARIES parameter missing, but is required")
+    endif()
+    if(NOT IMPORT_EXTERNAL_PROJECT_INCLUDE_DIRS)
+        message(FATAL_ERROR "INCLUDE_DIRS parameter missing, but is required")
+    endif()
+
+    if(TARGET ${target_name})
+        message(WARNING "Target exists already, skipping")
+        return()
+    endif()
+
+    add_library(${IMPORT_EXTERNAL_PROJECT_TARGET_NAME} INTERFACE IMPORTED GLOBAL)
+
+    ExternalProject_Get_Property(${IMPORT_EXTERNAL_PROJECT_EXT_PROJECT_NAME} SOURCE_DIR)
+    ExternalProject_Get_Property(${IMPORT_EXTERNAL_PROJECT_EXT_PROJECT_NAME} INSTALL_DIR)
+
+    # "evaluate" patterns in the passed arguments by using some string replacing magic
+    # this makes it easier to use this function, as some external project properties don't need to be evaluated and
+    # passed beforehand, and should reduce the amount of duplicate code in this file
+    foreach(item ITEMS
+        IMPORT_EXTERNAL_PROJECT_EXT_PROJECT_NAME
+        IMPORT_EXTERNAL_PROJECT_LIBRARIES
+        IMPORT_EXTERNAL_PROJECT_INCLUDE_DIRS
+        IMPORT_EXTERNAL_PROJECT_LIBRARY_DIRS)
+
+        # create new variable with fixed string...
+        string(REPLACE "<SOURCE_DIR>" "${SOURCE_DIR}" ${item}-out "${${item}}")
+        # ... and set the original value to the new value
+        set(${item} "${${item}-out}")
+
+        # create new variable with fixed string...
+        string(REPLACE "<INSTALL_DIR>" "${INSTALL_DIR}" ${item}-out "${${item}}")
+        # ... and set the original value to the new value
+        set(${item} "${${item}-out}")
+    endforeach()
+
+    # if library dirs are set, figure out absolute paths to libraries, like CMake's FindPkgConfig module does
+    if(${IMPORT_EXTERNAL_PROJECT_LIBRARY_DIRS})
+        apply_library_dirs(IMPORT_EXTERNAL_PROJECT_LIBRARIES IMPORT_EXTERNAL_PROJECT_LIBRARY_DIRS)
+    endif()
+
+    set_property(TARGET ${IMPORT_EXTERNAL_PROJECT_TARGET_NAME} PROPERTY INTERFACE_LINK_LIBRARIES "${IMPORT_EXTERNAL_PROJECT_LIBRARIES}")
+
+    if(IMPORT_EXTERNAL_PROJECT_INCLUDE_DIRS)
+        # need to create directories before setting INTERFACE_INCLUDE_DIRECTORIES, otherwise CMake will complain
+        # possibly related: https://cmake.org/Bug/view.php?id=15052
+
+        foreach(dir ${IMPORT_EXTERNAL_PROJECT_INCLUDE_DIRS})
+            if(NOT EXISTS ${dir})
+                if (${dir} MATCHES ${CMAKE_BINARY_DIR})
+                    file(MAKE_DIRECTORY ${dir})
+                    list(APPEND include_dirs ${dir})
+                endif()
+            else()
+                list(APPEND include_dirs ${dir})
+            endif()
+        endforeach()
+        set_property(TARGET ${IMPORT_EXTERNAL_PROJECT_TARGET_NAME} PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${include_dirs})
+        unset(include_dirs)
+    endif()
+
+    # finally, add a depenceny on the external project to make sure it's built
+    add_dependencies(${IMPORT_EXTERNAL_PROJECT_TARGET_NAME} "${IMPORT_EXTERNAL_PROJECT_EXT_PROJECT_NAME}")
+
+    # read external project properties, and export them with the target name as prefix
+    # this is necessary to allow the other external projects, which are not built with CMake or not within the same
+    # CMake context, to link to the libraries built as external projects (or the system ones, depending on the build
+    # configuration)
+    set(${IMPORT_EXTERNAL_PROJECT_TARGET_NAME}_INCLUDE_DIRS "${IMPORT_EXTERNAL_PROJECT_INCLUDE_DIRS}" CACHE INTERNAL "")
+    set(${IMPORT_EXTERNAL_PROJECT_TARGET_NAME}_LIBRARIES "${IMPORT_EXTERNAL_PROJECT_LIBRARIES}" CACHE INTERNAL "")
+    set(${IMPORT_EXTERNAL_PROJECT_TARGET_NAME}_LIBRARY_DIRS "${IMPORT_EXTERNAL_PROJECT_LIBRARY_DIRS}" CACHE INTERNAL "")
+    set(${IMPORT_EXTERNAL_PROJECT_TARGET_NAME}_PREFIX ${INSTALL_DIR} CACHE INTERNAL "")
+endfunction()
diff --git a/cmake/tools.cmake b/cmake/tools.cmake
new file mode 100644 (file)
index 0000000..d0941bb
--- /dev/null
@@ -0,0 +1,61 @@
+if(TOOLS_PREFIX)
+    message(STATUS "TOOLS_PREFIX set, looking for programs with prefix ${TOOLS_PREFIX}")
+endif()
+
+# first of all, make sure required programs are available
+function(check_program)
+    set(keywords FORCE_PREFIX)
+    set(oneValueArgs NAME)
+    cmake_parse_arguments(ARG "${keywords}" "${oneValueArgs}" "" "${ARGN}")
+
+    if(NOT ARG_NAME)
+        message(FATAL_ERROR "NAME argument required for check_program")
+    endif()
+    
+    if(TOOLS_PREFIX)
+        set(prefix ${TOOLS_PREFIX})
+    endif()
+
+    message(STATUS "Checking for program ${ARG_NAME}")
+
+    string(TOUPPER ${ARG_NAME} name_upper)
+
+    if(prefix)
+        # try prefixed version first
+        find_program(${name_upper} ${prefix}${ARG_NAME})
+    endif()
+
+    # try non-prefixed version
+    if(NOT ${name_upper})
+        if(TOOLS_PREFIX AND ARG_FORCE_PREFIX)
+            message(FATAL_ERROR "TOOLS_PREFIX set, but could not find program with prefix in PATH (FORCE_PREFIX is set)")
+        endif()
+
+        find_program(${name_upper} ${ARG_NAME})
+
+        if(NOT ${name_upper})
+            message(FATAL_ERROR "Could not find required program ${ARG_NAME}.")
+        endif()
+    endif()
+
+    message(STATUS "Found program ${ARG_NAME}: ${${name_upper}}")
+
+    mark_as_advanced(${name_upper})
+endfunction()
+
+check_program(NAME aclocal)
+check_program(NAME autoheader)
+check_program(NAME automake)
+check_program(NAME autoreconf)
+check_program(NAME libtoolize)
+check_program(NAME patch)
+check_program(NAME sed)
+check_program(NAME wget)
+check_program(NAME xxd)
+check_program(NAME desktop-file-validate)
+check_program(NAME objcopy FORCE_PREFIX)
+check_program(NAME objdump FORCE_PREFIX)
+check_program(NAME readelf FORCE_PREFIX)
+check_program(NAME strip FORCE_PREFIX)
+check_program(NAME make)
+# TODO: add checks for remaining commands
diff --git a/include/appimage/appimage.h b/include/appimage/appimage.h
new file mode 100644 (file)
index 0000000..7262d53
--- /dev/null
@@ -0,0 +1,137 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <unistd.h>
+#include <stdbool.h>
+
+// include header of shared library, which contains more appimage_ functions
+#include <appimage/appimage_shared.h>
+
+/* Return the md5 hash constructed according to
+* https://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html#THUMBSAVE
+* This can be used to identify files that are related to a given AppImage at a given location */
+char *appimage_get_md5(char const* path);
+
+/* Check if a file is an AppImage. Returns the image type if it is, or -1 if it isn't */
+int appimage_get_type(const char* path, bool verbose);
+
+/*
+ * Finds the desktop file of a registered AppImage and returns the path
+ * Returns NULL if the desktop file can't be found, which should only occur when the AppImage hasn't been registered yet
+ */
+char* appimage_registered_desktop_file_path(const char* path, char* md5, bool verbose);
+
+/*
+ * Check whether an AppImage has been registered in the system
+ */
+bool appimage_is_registered_in_system(const char* path);
+
+/* Register a type 1 AppImage in the system
+ * DEPRECATED don't use in newly written code. Use appimage_is_registered_in_system instead.
+ * */
+bool appimage_type1_register_in_system(const char *path, bool verbose);
+
+/* Register a type 2 AppImage in the system
+ * DEPRECATED don't use in newly written code. Use appimage_is_registered_in_system instead.
+ * */
+bool appimage_type2_register_in_system(const char *path, bool verbose);
+
+/*
+ * Register an AppImage in the system
+ * Returns 0 on success, non-0 otherwise.
+ */
+int appimage_register_in_system(const char *path, bool verbose);
+
+/* Unregister an AppImage in the system */
+int appimage_unregister_in_system(const char *path, bool verbose);
+
+/* Extract a given file from the appimage following the symlinks until a concrete file is found */
+void appimage_extract_file_following_symlinks(const char* appimage_file_path, const char* file_path, const char* target_file_path);
+
+/* Read a given file from the AppImage into a freshly allocated buffer following symlinks
+ * Buffer must be free()d after usage
+ * */
+bool appimage_read_file_into_buffer_following_symlinks(const char* appimage_file_path, const char* file_path, char** buffer, unsigned long* buf_size);
+
+/* Create AppImage thumbnail according to
+ * https://specifications.freedesktop.org/thumbnail-spec/0.8.0/index.html
+ */
+void appimage_create_thumbnail(const char* appimage_file_path, bool verbose);
+
+/* List files contained in the AppImage file.
+ * Returns: a newly allocated char** ended at NULL. If no files ware found also is returned a {NULL}
+ *
+ * You should ALWAYS take care of releasing this using `appimage_string_list_free`.
+ * */
+char** appimage_list_files(const char* path);
+
+/* Releases memory of a string list (a.k.a. list of pointers to char arrays allocated in heap memory). */
+void appimage_string_list_free(char** list);
+
+/*
+ * Checks whether a type 1 AppImage's desktop file has set Terminal=true.
+ *
+ * Returns >0 if set, 0 if not set, <0 on errors.
+ */
+int appimage_type1_is_terminal_app(const char* path);
+
+/*
+ * Checks whether a type 2 AppImage's desktop file has set Terminal=true.
+ *
+ * Returns >0 if set, 0 if not set, <0 on errors.
+ */
+int appimage_type2_is_terminal_app(const char* path);
+
+/*
+ * Checks whether an AppImage's desktop file has set Terminal=true.
+ *
+ * Returns >0 if set, 0 if not set, <0 on errors.
+ */
+int appimage_is_terminal_app(const char* path);
+
+/*
+ * Checks whether a type 1 AppImage's desktop file has set X-AppImage-Version=false.
+ * Useful to check whether the author of an AppImage doesn't want it to be integrated.
+ *
+ * Returns >0 if set, 0 if not set, <0 on errors.
+ */
+int appimage_type1_shall_not_be_integrated(const char* path);
+
+/*
+ * Checks whether a type 2 AppImage's desktop file has set X-AppImage-Version=false.
+ * Useful to check whether the author of an AppImage doesn't want it to be integrated.
+ *
+ * Returns >0 if set, 0 if not set, <0 on errors.
+ */
+int appimage_type2_shall_not_be_integrated(const char* path);
+
+/*
+ * Checks whether an AppImage's desktop file has set X-AppImage-Version=false.
+ * Useful to check whether the author of an AppImage doesn't want it to be integrated.
+ *
+ * Returns >0 if set, 0 if not set, <0 on errors.
+ */
+int appimage_shall_not_be_integrated(const char* path);
+
+/*
+ * Calculate the size of an ELF file on disk based on the information in its header
+ *
+ * Example:
+ *
+ * ls -l   126584
+ *
+ * Calculation using the values also reported by readelf -h:
+ * Start of section headers    e_shoff         124728
+ * Size of section headers             e_shentsize     64
+ * Number of section headers   e_shnum         29
+ *
+ * e_shoff + ( e_shentsize * e_shnum ) =       126584
+ */
+ssize_t appimage_get_elf_size(const char* fname);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/include/appimage/appimage_shared.h b/include/appimage/appimage_shared.h
new file mode 100644 (file)
index 0000000..88db437
--- /dev/null
@@ -0,0 +1,43 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdbool.h>
+#include <stdlib.h>
+
+/*
+ * Return the offset, and the length of an ELF section with a given name in a given ELF file
+ */
+bool appimage_get_elf_section_offset_and_length(const char* fname, const char* section_name, unsigned long* offset, unsigned long* length);
+
+int appimage_print_hex(char* fname, unsigned long offset, unsigned long length);
+int appimage_print_binary(char* fname, unsigned long offset, unsigned long length);
+
+/*
+ * Creates hexadecimal representation of a byte array. Allocates a new char array (string) with the correct size that
+ * needs to be free()d.
+ */
+char* appimage_hexlify(const char* bytes, size_t numBytes);
+
+/*
+ * Calculate MD5 digest of AppImage file, skipping the signature and digest sections.
+ *
+ * The digest section must be skipped as the value calculated by this method is going to be embedded in it by default.
+ *
+ * The signature section must be skipped as the signature will not be available at the time this hash is calculated.
+ *
+ * The hash is _not_ compatible with tools like md5sum.
+ *
+ * You need to allocate a char array of at least 16 bytes (128 bit) and pass a reference to it as digest parameter.
+ * The function will set it to the raw digest, without any kind of termination. Please use appimage_hexlify() if you
+ * need a textual representation.
+ *
+ * Please beware that this calculation is only available for type 2 AppImages.
+ */
+bool appimage_type2_digest_md5(const char* fname, char* digest);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
new file mode 100644 (file)
index 0000000..4daa663
--- /dev/null
@@ -0,0 +1,13 @@
+cmake_minimum_required(VERSION 3.0)
+
+include(CTest)
+
+if(BUILD_TESTING)
+    if(NOT TARGET gtest)
+        if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/gtest)
+            add_subdirectory(gtest EXCLUDE_FROM_ALL)
+        else()
+            message(FATAL_ERROR "gtest submodule not found; please call git submodule update --init or disable the unit tests using -DBUILD_TESTING=OFF")
+        endif()
+    endif()
+endif()
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644 (file)
index 0000000..664bf2a
--- /dev/null
@@ -0,0 +1,50 @@
+# required for pkg-config to create PkgConfig::<prefix> imported library targets
+cmake_minimum_required(VERSION 3.6)
+
+find_package(PkgConfig)
+
+pkg_check_modules(glib glib-2.0>=2.40 IMPORTED_TARGET)
+pkg_check_modules(gobject gobject-2.0>=2.40 IMPORTED_TARGET)
+pkg_check_modules(gio gio-2.0>=2.40 IMPORTED_TARGET)
+pkg_check_modules(zlib zlib IMPORTED_TARGET)
+pkg_check_modules(cairo cairo IMPORTED_TARGET)
+
+add_subdirectory(xdg-basedir)
+add_subdirectory(libappimage_hashlib)
+add_subdirectory(libappimage_shared)
+add_subdirectory(libappimage)
+
+# Export the package for use from the build-tree
+# (this registers the build-tree with a global CMake-registry)
+export(PACKAGE libappimage)
+
+include(CMakePackageConfigHelpers)
+
+# Create the AppImageConfig.cmake and AppImageConfigVersion files
+configure_package_config_file(
+    "${PROJECT_SOURCE_DIR}/cmake/libappimageConfig.cmake.in"
+    "${PROJECT_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/libappimageConfig.cmake"
+    INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libappimage
+)
+# ... for both
+configure_file(
+    "${PROJECT_SOURCE_DIR}/cmake/libappimageConfigVersion.cmake.in"
+    "${PROJECT_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/libappimageConfigVersion.cmake"
+    @ONLY
+)
+
+# Install the AppImageConfig.cmake and AppImageConfigVersion.cmake
+install(FILES
+    "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/libappimageConfig.cmake"
+    "${PROJECT_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/libappimageConfigVersion.cmake"
+    "${PROJECT_SOURCE_DIR}/cmake/scripts.cmake"
+    "${PROJECT_SOURCE_DIR}/cmake/imported_dependencies.cmake"
+    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libappimage
+    COMPONENT libappimage-dev
+)
+
+# Install the export set for use with the install-tree
+install(EXPORT libappimageTargets
+    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libappimage
+    COMPONENT libappimage-dev
+)
diff --git a/src/libappimage/CMakeLists.txt b/src/libappimage/CMakeLists.txt
new file mode 100644 (file)
index 0000000..64bd098
--- /dev/null
@@ -0,0 +1,73 @@
+cmake_minimum_required(VERSION 3.2)
+
+set(libappimage_public_header ${PROJECT_SOURCE_DIR}/include/appimage/appimage.h)
+
+# both libraries use the same set of source files
+set(libappimage_sources
+    ${libappimage_public_header}
+    libappimage.c
+    appimage_handler.h
+    appimage_handler.c
+    type1.c
+    type2.c
+    desktop_integration.h
+    desktop_integration.c
+)
+
+add_library(libappimage SHARED ${libappimage_sources} appimage_handler.h)
+add_library(libappimage_static STATIC ${libappimage_sources})
+
+# set common options
+foreach(target libappimage libappimage_static)
+    # targets are called lib* already, so CMake shouldn't add another lib prefix to the actual files
+    set_target_properties(${target} PROPERTIES PREFIX "")
+
+    target_compile_definitions(${target}
+        # Support Large Files
+        PRIVATE -D_FILE_OFFSET_BITS=64
+        PRIVATE -D_LARGEFILE_SOURCE
+
+        PRIVATE -DGIT_COMMIT="${GIT_COMMIT}"
+        PRIVATE -DENABLE_BINRELOC
+    )
+
+    target_include_directories(${target}
+        PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include/>
+        INTERFACE $<INSTALL_INTERFACE:include/>
+    )
+
+    target_link_libraries(${target}
+        PRIVATE libarchive
+        PRIVATE xdg-basedir
+        # not linking publicly to squashfuse as headers are not needed when using libappimage
+        # unit tests etc., which use squashfuse directly, must link to it explicitly
+        PRIVATE libsquashfuse
+        PRIVATE xz
+        PUBLIC libappimage_shared
+        PUBLIC pthread
+        PUBLIC libglib
+        PUBLIC libgobject
+        PUBLIC libgio
+        PUBLIC libzlib
+        PUBLIC libcairo
+    )
+
+    set_property(TARGET libappimage PROPERTY PUBLIC_HEADER ${libappimage_public_header})
+
+    set_property(TARGET libappimage PROPERTY VERSION ${V_MAJOR}.${V_MINOR}.${V_PATCH})
+    set_property(TARGET libappimage PROPERTY SOVERSION ${V_MAJOR})
+endforeach()
+
+# install libappimage
+install(TARGETS libappimage
+    EXPORT libappimageTargets
+    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libappimage
+    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libappimage
+    PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/appimage COMPONENT libappimage-dev
+)
+
+# Add all targets to the build-tree export set
+export(
+    TARGETS libappimage libappimage_shared libappimage_hashlib
+    FILE "${PROJECT_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/libappimageTargets.cmake"
+)
diff --git a/src/libappimage/appimage_handler.c b/src/libappimage/appimage_handler.c
new file mode 100644 (file)
index 0000000..8ce607d
--- /dev/null
@@ -0,0 +1,83 @@
+// system includes
+#include "stdio.h"
+
+// library includes
+#include <appimage/appimage.h>
+
+// local includes
+#include "appimage_handler.h"
+#include "type1.h"
+#include "type2.h"
+
+/* Factory function for creating the right appimage handler for
+ * a given file. */
+appimage_handler create_appimage_handler(const char *const path) {
+    int appimage_type = appimage_get_type(path, 0);
+
+    appimage_handler handler;
+#ifdef STANDALONE
+    fprintf(stderr,"AppImage type: %d\n", appimage_type);
+#endif
+    switch (appimage_type) {
+        case 1:
+            handler = appimage_type_1_create_handler();
+            break;
+        case 2:
+            handler = appimage_type_2_create_handler();
+            break;
+        default:
+#ifdef STANDALONE
+            fprintf(stderr,"Appimage type %d not supported yet\n", appimage_type);
+#endif
+            handler.traverse = dummy_traverse_func;
+            break;
+    }
+    handler.path = path;
+    handler.is_open = false;
+    return handler;
+}
+
+/*
+ * utility functions
+ */
+bool is_handler_valid(const appimage_handler *handler) {
+    if (handler == NULL) {
+#ifdef STANDALONE
+        fprintf(stderr, "WARNING: Invalid handler found, you should take a look at this now!");
+#endif
+        return false;
+    }
+
+    return true;
+}
+
+void mk_base_dir(const char *target)
+{
+    gchar *dirname = g_path_get_dirname(target);
+    if(g_mkdir_with_parents(dirname, 0755)) {
+#ifdef STANDALONE
+        fprintf(stderr, "Could not create directory: %s\n", dirname);
+#endif
+    }
+
+    g_free(dirname);
+}
+
+/*
+ * Dummy fallback functions
+ */
+void dummy_traverse_func(appimage_handler *handler, traverse_cb command, void *data) {
+    (void) handler;
+    (void) command;
+    (void) data;
+
+    fprintf(stderr, "Called %s\n", __FUNCTION__);
+}
+
+char* dummy_get_file_name (appimage_handler *handler, void *data) {
+    fprintf(stderr, "Called %s\n", __FUNCTION__);
+}
+
+void dummy_extract_file(struct appimage_handler *handler, void *data, char *target) {
+    fprintf(stderr, "Called %s\n", __FUNCTION__);
+}
\ No newline at end of file
diff --git a/src/libappimage/appimage_handler.h b/src/libappimage/appimage_handler.h
new file mode 100644 (file)
index 0000000..ceb3f0d
--- /dev/null
@@ -0,0 +1,53 @@
+#pragma once
+
+// system includes
+#include <glib.h>
+#include <stdbool.h>
+
+/* AppImage generic handler calback to be used in algorithms */
+typedef void (*traverse_cb)(void* handler, void* entry_data, void* user_data);
+
+/* AppImage generic handler to be used in algorithms */
+struct appimage_handler {
+    const gchar* path;
+
+    char* (*get_file_name)(struct appimage_handler* handler, void* entry);
+
+    void (*extract_file)(struct appimage_handler* handler, void* entry, const char* target);
+
+    bool (*read_file_into_new_buffer)(struct appimage_handler* handler, void* entry, char** buffer, unsigned long* buffer_size);
+
+    char* (*get_file_link)(struct appimage_handler* handler, void* entry);
+
+    void (*traverse)(struct appimage_handler* handler, traverse_cb command, void* user_data);
+
+    void* cache;
+    bool is_open;
+
+    // for debugging purposes
+    int type;
+} typedef appimage_handler;
+
+/*
+ * appimage_handler functions
+ */
+
+// constructor
+appimage_handler create_appimage_handler(const char* const path);
+
+/*
+ * utility functions
+ */
+bool is_handler_valid(const appimage_handler* handler);
+
+void mk_base_dir(const char *target);
+
+/*
+ * dummy fallback callbacks
+ */
+void dummy_traverse_func(appimage_handler* handler, traverse_cb command, void* data);
+
+char* dummy_get_file_name(appimage_handler* handler, void* data);
+
+void dummy_extract_file(struct appimage_handler* handler, void* data, char* target);
+
diff --git a/src/libappimage/desktop_file_integration_private.h b/src/libappimage/desktop_file_integration_private.h
new file mode 100644 (file)
index 0000000..91f0d57
--- /dev/null
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+* Finds a file with '.Desktop' or '.desktop' suffix.
+* @param path
+* @return newly allocated const char* with the full path of the file or NULL. Result must be freed.
+*/
+char* find_desktop_file(const char* appdir_path);
+
+GKeyFile* load_desktop_file(const char* file_path);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/libappimage/desktop_integration.c b/src/libappimage/desktop_integration.c
new file mode 100644 (file)
index 0000000..2dbd6c4
--- /dev/null
@@ -0,0 +1,721 @@
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include "desktop_integration.h"
+#include "appimage_handler.h"
+#include "appimage/appimage.h"
+#include "libappimage_private.h"
+
+extern const char* vendorprefix;
+
+char* desktop_integration_create_tempdir() {
+    GError* error = NULL;
+    char* path = g_dir_make_tmp("libappimage-XXXXXX", &error);
+    if (error) {
+        g_warning("%s", error->message);
+        g_error_free(error);
+    }
+
+    return path;
+}
+
+struct {
+    const char* tempdir;
+} typedef traverse_handler_extract_relevant_desktop_integration_files_data;
+
+void create_parent_dir(const char* file_name) {
+    char* parent_dir = g_path_get_dirname(file_name);
+    if (g_mkdir_with_parents(parent_dir, S_IRWXU) == -1)
+        g_warning("Unable to create temporary parent dir: %s", parent_dir);
+
+    g_free(parent_dir);
+}
+
+void traverse_handler_extract_relevant_desktop_integration_files(void* raw_handler, void* entry_data,
+                                                                 void* raw_user_data) {
+    appimage_handler* handler = raw_handler;
+    traverse_handler_extract_relevant_desktop_integration_files_data* data = raw_user_data;
+
+    char* file_name = handler->get_file_name(handler, entry_data);
+    if (g_str_has_suffix(file_name, ".Desktop") ||
+        g_str_has_suffix(file_name, ".desktop") ||
+        g_str_has_prefix(file_name, "usr/share/icons") ||
+        g_str_equal(file_name, ".DirIcon")) {
+
+        char* target = g_strjoin("/", data->tempdir, file_name, NULL);
+
+        create_parent_dir(target);
+
+        handler->extract_file(handler, entry_data, target);
+
+        g_free(target);
+    }
+
+    g_free(file_name);
+}
+
+void desktop_integration_extract_relevant_files(const char* appimage_path, const char* tempdir_path) {
+    appimage_handler handler = create_appimage_handler(appimage_path);
+
+    traverse_handler_extract_relevant_desktop_integration_files_data data;
+    data.tempdir = tempdir_path;
+    handler.traverse(&handler, traverse_handler_extract_relevant_desktop_integration_files, &data);
+}
+
+char* find_desktop_file(const char* path) {
+    GError* error = NULL;
+    GDir* temp_dir = g_dir_open(path, 0, &error);
+    if (error != NULL) {
+        g_warning("%s\n", error->message);
+        return NULL;
+    }
+    const char* entry = NULL;
+    while ((entry = g_dir_read_name(temp_dir)) != NULL) {
+        if (g_str_has_suffix(entry, ".Desktop") ||
+            g_str_has_suffix(entry, ".desktop")) {
+            break;
+        }
+    }
+
+    char* result = NULL;
+    if (entry != NULL)
+        result = g_strjoin("/", path, entry, NULL);
+
+    g_dir_close(temp_dir);
+    return result;
+}
+
+GKeyFile* load_desktop_file(const char* file_path) {
+    GError* error = NULL;
+    GKeyFile* desktop_file = g_key_file_new();
+    g_key_file_load_from_file(desktop_file, file_path,
+                              G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, &error);
+    if (error) {
+        g_warning("%s\n", error->message);
+        g_error_free(error);
+    }
+
+    return desktop_file;
+}
+
+bool save_desktop_file(GKeyFile* desktop_file, const char* file_path) {
+    GError* error = NULL;
+
+    g_key_file_save_to_file(desktop_file, file_path, &error);
+    if (error) {
+        g_warning("%s\n", error->message);
+        g_error_free(error);
+        return false;
+    }
+
+    return true;
+}
+
+bool move_desktop_file(const char* tempdir_path, const char* user_data_dir, const char* md5sum);
+
+/**
+ * Get the path section corresponding to: <theme>/<size>/<category>
+ *  "/usr/share/icons/hicolor/22x22/apps/appicon.png"
+ *                    |_________________|
+ *                            this
+ * */
+char* extract_icon_path_prefix(const char* path) {
+    char** path_parts = g_strsplit(path, "usr/share/icons/", 2);
+    char** itr = path_parts;
+    char* prefix = NULL;
+    if (*itr != NULL) {
+        g_free(*itr);
+        itr++;
+    }
+
+    if (*itr != NULL) {
+        prefix = g_path_get_dirname(*itr);
+        g_free(*itr);
+    }
+
+    g_free(path_parts);
+    return prefix;
+}
+
+bool desktop_integration_modify_desktop_file(const char* appimage_path, const char* tempdir_path, const char* md5) {
+    char* desktop_file_path = find_desktop_file(tempdir_path);
+    char* desktop_filename = g_path_get_basename(desktop_file_path);
+
+    if (desktop_file_path == NULL) {
+        g_critical("Failed to find desktop file path\n");
+        return false;
+    }
+
+    if (desktop_filename == NULL) {
+        g_critical("Failed to query desktop file filename\n");
+        return false;
+    }
+
+    GKeyFile* key_file_structure = load_desktop_file(desktop_file_path);
+    
+    if (!g_key_file_has_key(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, NULL)) {
+        g_critical("Desktop file has no Exec key\n");
+        return false;
+    }
+
+    // parse [Try]Exec= value, replace executable by AppImage path, append parameters
+    // TODO: should respect quoted strings within value
+
+    {
+        char* field_value = g_key_file_get_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP,
+                                                 G_KEY_FILE_DESKTOP_KEY_EXEC, NULL);
+
+        // saving a copy for later free() call
+        char* original_field_value = field_value;
+
+        char* executable = strsep(&field_value, " ");
+
+        // error handling
+        if (executable == NULL) {
+            g_warning("Invalid value for Exec= entry in Desktop file\n");
+            return false;
+        }
+
+        unsigned long new_exec_value_size = strlen(appimage_path) + 1;
+
+        if (field_value != NULL)
+            new_exec_value_size += strlen(field_value) + 1;
+
+        gchar* new_exec_value = calloc(new_exec_value_size, sizeof(gchar));
+
+        // build new value
+        strcpy(new_exec_value, appimage_path);
+
+        if (field_value != NULL && strlen(field_value) > 0) {
+            strcat(new_exec_value, " ");
+            strcat(new_exec_value, field_value);
+        }
+
+        if (original_field_value != NULL)
+            free(original_field_value);
+
+        g_key_file_set_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, new_exec_value);
+
+        g_free(new_exec_value);
+    }
+
+    // force add a TryExec= key
+    g_key_file_set_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, appimage_path);
+
+#ifdef APPIMAGED
+    /* If firejail is on the $PATH, then use it to run AppImages */
+    if(g_find_program_in_path ("firejail")){
+        char *firejail_exec;
+        firejail_exec = g_strdup_printf("firejail --env=DESKTOPINTEGRATION=appimaged --noprofile --appimage '%s'", appimage_path);
+        g_key_file_set_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, firejail_exec);
+
+        gchar *firejail_profile_group = "Desktop Action FirejailProfile";
+        gchar *firejail_profile_exec = g_strdup_printf("firejail --env=DESKTOPINTEGRATION=appimaged --private --appimage '%s'", appimage_path);
+        gchar *firejail_tryexec = "firejail";
+        g_key_file_set_value(key_file_structure, firejail_profile_group, G_KEY_FILE_DESKTOP_KEY_NAME, "Run without sandbox profile");
+        g_key_file_set_value(key_file_structure, firejail_profile_group, G_KEY_FILE_DESKTOP_KEY_EXEC, firejail_profile_exec);
+        g_key_file_set_value(key_file_structure, firejail_profile_group, G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, firejail_tryexec);
+        g_key_file_set_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, "Actions", "FirejailProfile;");
+    }
+#endif
+
+#ifdef APPIMAGED
+    /* Add AppImageUpdate desktop action
+     * https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s10.html
+     * This will only work if AppImageUpdate is on the user's $PATH.
+     * TODO: we could have it call this appimaged instance instead of AppImageUpdate and let it
+     * figure out how to update the AppImage */
+    unsigned long upd_offset = 0;
+    unsigned long upd_length = 0;
+    if (g_find_program_in_path("AppImageUpdate")) {
+        if (appimage_type == 2) {
+            if (!appimage_get_elf_section_offset_and_length(appimage_path, ".upd_info", &upd_offset, &upd_length) ||
+                upd_offset == 0 || upd_length == 0) {
+                fprintf(stderr, "Could not read .upd_info section in AppImage's header\n");
+            }
+            if (upd_length != 1024) {
+#ifdef STANDALONE
+                fprintf(stderr,
+                    "WARNING: .upd_info length is %lu rather than 1024, this might be a bug in the AppImage\n",
+                    upd_length);
+#endif
+            }
+        }
+        if (appimage_type == 1) {
+            /* If we have a type 1 AppImage, then we hardcode the offset and length */
+            upd_offset = 33651; // ISO 9660 Volume Descriptor field
+            upd_length = 512; // Might be wrong
+        }
+#ifdef STANDALONE
+        fprintf(stderr, ".upd_info offset: %lu\n", upd_offset);
+        fprintf(stderr, ".upd_info length: %lu\n", upd_length);
+#endif
+        char buffer[3];
+        FILE* binary = fopen(appimage_path, "rt");
+        if (binary != NULL) {
+            /* Check whether the first three bytes at the offset are not NULL */
+            fseek(binary, upd_offset, SEEK_SET);
+            fread(buffer, 1, 3, binary);
+            fclose(binary);
+            if ((buffer[0] != 0x00) && (buffer[1] != 0x00) && (buffer[2] != 0x00)) {
+                gchar* appimageupdate_group = "Desktop Action AppImageUpdate";
+                gchar* appimageupdate_exec = g_strdup_printf("%s %s", "AppImageUpdate", appimage_path);
+                g_key_file_set_value(key_file_structure, appimageupdate_group, G_KEY_FILE_DESKTOP_KEY_NAME, "Update");
+                g_key_file_set_value(key_file_structure, appimageupdate_group, G_KEY_FILE_DESKTOP_KEY_EXEC,
+                                     appimageupdate_exec);
+                g_key_file_set_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, "Actions", "AppImageUpdate;");
+            }
+        }
+    }
+#endif
+
+    {
+        // parse desktop files and generate a list of locales representing all localized Name/Icon entries
+        // NULL refers to the key without the locale tag
+        // the locales for both entry types need to be tracked separately due to a limitation in the GLib API, see
+        // the comment below for more information
+        gchar* nameLocales[256] = {NULL};
+        gchar* iconLocales[256] = {NULL};
+        gint nameLocalesCount = 1;
+        gint iconLocalesCount = 1;
+
+        {
+            // create temporary in-memory copy of the keyfile
+            gsize bufsize;
+            char* orig_buffer = g_key_file_to_data(key_file_structure, &bufsize, NULL);
+
+            if (orig_buffer == NULL) {
+                fprintf(stderr, "Failed to create in-memory copy of desktop file\n");
+                return false;
+            }
+
+            // need to keep original reference to buffer to be able to free() it later
+            char* buffer = orig_buffer;
+
+            // parse line by line
+            char* line = NULL;
+            while ((line = strsep(&buffer, "\n")) != NULL) {
+                const bool is_name_entry = strncmp(line, "Name[", 5) == 0;
+                const bool is_icon_entry = strncmp(line, "Icon[", 5) == 0;
+
+                // the only keys for which we're interested in localizations is Name and Icon
+                if (!(is_name_entry || is_icon_entry))
+                    continue;
+
+                // python: s = split(line, "[")[1]
+                char* s = strsep(&line, "[");
+                s = strsep(&line, "[");
+
+                // python: locale = split(s, "]")[0]
+                char* locale = strsep(&s, "]");
+
+                // create references to the right variables
+                gchar** locales = NULL;
+                gint* localesCount = NULL;
+
+                if (is_name_entry) {
+                    locales = nameLocales;
+                    localesCount = &nameLocalesCount;
+                } else if (is_icon_entry) {
+                    locales = iconLocales;
+                    localesCount = &iconLocalesCount;
+                }
+
+                // avoid duplicates in list of locales
+                bool is_duplicate = false;
+
+                // unfortunately, the list of locales is not sorted, therefore a linear search is required
+                // however, this won't have a significant impact on the performance
+                // start at index 1, first entry is NULL
+                for (int i = 1; i < *localesCount; i++) {
+                    if (strcmp(locale, locales[i]) == 0) {
+                        is_duplicate = true;
+                        break;
+                    }
+                }
+
+                if (is_duplicate)
+                    continue;
+
+                locales[(*localesCount)++] = strdup(locale);
+            }
+
+            free(orig_buffer);
+        }
+
+        // iterate over locales, check whether name or icon entries exist, and edit accordingly
+        for (int i = 0; i < iconLocalesCount; i++) {
+            const gchar* locale = iconLocales[i];
+
+            // check whether the key is set at all
+            gchar* old_contents = NULL;
+
+            // it's a little annoying that the GLib functions don't simply work with NULL as the locale, that'd
+            // make the following if-else construct unnecessary
+            if (locale == NULL) {
+                old_contents = g_key_file_get_string(
+                    key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, NULL
+                );
+            } else {
+                // please note that the following call will return a value even if there is no localized version
+                // probably to save one call when you're just interested in getting _some_ value while reading
+                // a desktop file
+                old_contents = g_key_file_get_locale_string(
+                    key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, locale, NULL
+                );
+            }
+
+            // continue to next key if not set
+            if (old_contents == NULL) {
+                g_free(old_contents);
+                continue;
+            }
+
+            // copy key's original contents
+            static const gchar old_key[] = "X-AppImage-Old-Icon";
+
+            // append AppImage version
+            gchar* basename = g_path_get_basename(old_contents);
+            gchar* new_contents = g_strdup_printf("%s_%s_%s", vendorprefix, md5, basename);
+            g_free(basename);
+
+            // see comment for above if-else construct
+            if (locale == NULL) {
+                g_key_file_set_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, old_key, old_contents);
+                g_key_file_set_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON,
+                                      new_contents);
+            } else {
+                g_key_file_set_locale_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, old_key, locale,
+                                             old_contents);
+                g_key_file_set_locale_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON,
+                                             locale, new_contents);
+            }
+
+            // cleanup
+            g_free(old_contents);
+            g_free(new_contents);
+        }
+
+        char* appimage_version = g_key_file_get_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP,
+                                                       "X-AppImage-Version", NULL);
+        // check for name entries and append version suffix
+        if (appimage_version != NULL) {
+            for (int i = 0; i < nameLocalesCount; i++) {
+                const gchar* locale = nameLocales[i];
+
+                // check whether the key is set at all
+                gchar* old_contents;
+
+                // it's a little annoying that the GLib functions don't simply work with NULL as the locale, that'd
+                // make the following if-else construct unnecessary
+                if (locale == NULL) {
+                    old_contents = g_key_file_get_string(
+                        key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, NULL
+                    );
+                } else {
+                    // please note that the following call will return a value even if there is no localized version
+                    // probably to save one call when you're just interested in getting _some_ value while reading
+                    // a desktop file
+                    old_contents = g_key_file_get_locale_string(
+                        key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, locale, NULL
+                    );
+                }
+
+                // continue to next key if not set
+                if (old_contents == NULL) {
+                    g_free(old_contents);
+                    continue;
+                }
+
+                gchar* version_suffix = g_strdup_printf("(%s)", appimage_version);
+
+                // check if version suffix has been appended already
+                // this makes sure that the version suffix isn't added more than once
+                if (strlen(version_suffix) > strlen(old_contents) &&
+                    strcmp(old_contents + (strlen(old_contents) - strlen(version_suffix)), version_suffix) != 0) {
+                    // copy key's original contents
+                    static const gchar old_key[] = "X-AppImage-Old-Name";
+
+                    // append AppImage version
+                    gchar* new_contents = g_strdup_printf("%s %s", old_contents, version_suffix);
+
+                    // see comment for above if-else construct
+                    if (locale == NULL) {
+                        g_key_file_set_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, old_key, old_contents);
+                        g_key_file_set_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME,
+                                              new_contents);
+                    } else {
+                        g_key_file_set_locale_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, old_key, locale,
+                                                     old_contents);
+                        g_key_file_set_locale_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP,
+                                                     G_KEY_FILE_DESKTOP_KEY_NAME, locale, new_contents);
+                    }
+
+                    // cleanup
+                    g_free(new_contents);
+                }
+
+                // cleanup
+                g_free(old_contents);
+                g_free(version_suffix);
+            }
+        }
+
+        for (int i = 1; i < iconLocalesCount; i++)
+            free(iconLocales[i]);
+
+        for (int i = 1; i < nameLocalesCount; i++)
+            free(nameLocales[i]);
+
+        // cleanup
+        g_free(appimage_version);
+    }
+
+#ifdef APPIMAGED
+    {
+        gchar *generated_by = g_strdup_printf("Generated by appimaged %s", GIT_COMMIT);
+        g_key_file_set_value(key_file_structure, "Desktop Entry", "X-AppImage-Comment", generated_by);
+        g_free(generated_by);
+    }
+#endif
+    g_key_file_set_value(key_file_structure, "Desktop Entry", "X-AppImage-Identifier", md5);
+
+    bool save_result = save_desktop_file(key_file_structure, desktop_file_path);
+
+    // Clean Up
+    g_key_file_free(key_file_structure);
+    free(desktop_filename);
+    free(desktop_file_path);
+    return save_result;
+}
+
+
+char* read_icon_name_from_desktop_file(const char* tempdir_path, const char* appimage_path_md5);
+
+bool move_app_icons(GSList* path, const char* dir, const char* sum);
+
+GSList* find_app_icons(const char* tempdir_path, char* icon_name);
+
+bool move_diricon_as_app_icon(const char* tempdir_path, const char* user_data_dir, const char* appimage_path_md5,
+                              const char* icon_name);
+
+bool desktop_integration_move_files_to_user_data_dir(const char* tempdir_path, const char* user_data_dir,
+                                                     const char* appimage_path_md5) {
+    // Find application icons (in all sizes)
+    char* icon_name = read_icon_name_from_desktop_file(tempdir_path, appimage_path_md5);
+    GSList* list = find_app_icons(tempdir_path, icon_name);
+
+    bool res = move_desktop_file(tempdir_path, user_data_dir, appimage_path_md5);
+
+    if (res) {
+        if (list != NULL)
+            move_app_icons(list, user_data_dir, appimage_path_md5);
+        else {
+            g_warning("No icons found in AppDir/usr/share/icons. Using .DirIcon as fallback");
+            move_diricon_as_app_icon(tempdir_path, user_data_dir, appimage_path_md5, icon_name);
+        }
+    }
+
+    // TODO: Move MIME-TYPES
+
+    free(icon_name);
+    return res;
+}
+
+/**
+ * Move <tempdir_path>/.DirIcon into <user_data_dir>/icons/hicolor/32x32/apps/<vendorprefix>_<appimage_path_md5>_<icon_name>"
+ * This function provides a fallback workflow for AppImage that don't properly include their icons.
+ *
+ * @param tempdir_path
+ * @param user_data_dir
+ * @param appimage_path_md5
+ * @param icon_name
+ */
+bool move_diricon_as_app_icon(const char* tempdir_path, const char* user_data_dir, const char* appimage_path_md5,
+                              const char* icon_name) {
+    bool success = false;
+    char* target_dir_path = g_build_path("/", user_data_dir, "icons/hicolor/32x32/apps/", NULL);
+    g_mkdir_with_parents(target_dir_path, S_IRWXU);
+
+    char* icon_name_with_extension = g_strjoin("", icon_name, ".png", NULL);
+    char* new_icon_name = g_strjoin("_", vendorprefix, appimage_path_md5, icon_name_with_extension, NULL);
+    char* target_path = g_build_path("/", target_dir_path, new_icon_name, NULL);
+
+    char* source_path = g_build_path("/", tempdir_path, ".DirIcon", NULL);
+
+    success = move_file(source_path, target_path);
+    if (!success)
+        g_warning("Unable to move icon file from %s to %s", source_path, target_path);
+
+    g_free(source_path);
+    g_free(target_path);
+    g_free(new_icon_name);
+    g_free(icon_name_with_extension);
+    g_free(target_dir_path);
+
+    return success;
+}
+
+char* read_icon_name_from_desktop_file(const char* tempdir_path, const char* appimage_path_md5) {
+    char* icon_name = NULL;
+    char* desktop_file_path = find_desktop_file(tempdir_path);
+    GKeyFile* desktop_file = load_desktop_file(desktop_file_path);
+    if (desktop_file) {
+        char* icon_field_value = g_key_file_get_string(desktop_file,
+                                                       G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, NULL);
+
+        char* expected_icon_prefix = g_strjoin("_", vendorprefix, appimage_path_md5, "", NULL);
+        if (g_str_has_prefix(icon_field_value, expected_icon_prefix))
+            icon_name = strdup(icon_field_value + strlen(expected_icon_prefix));
+        else
+            icon_name = strdup(icon_field_value);
+
+        g_free(expected_icon_prefix);
+        g_free(icon_field_value);
+    }
+
+    g_key_file_free(desktop_file);
+    g_free(desktop_file_path);
+    return icon_name;
+}
+
+bool move_icon_file(const char* user_data_dir, const char* appimage_path_md5, const char* path) {
+    bool succeed = true;
+    char* prefix = extract_icon_path_prefix(path);
+    char* file_name = g_path_get_basename(path);
+    char* new_file_name = g_strjoin("_", vendorprefix, appimage_path_md5, file_name, NULL);
+
+    char* target_dir_path = g_build_path("/", user_data_dir, "icons", prefix, NULL);
+    char* target_path = g_build_path("/", target_dir_path, new_file_name, NULL);
+
+    if (g_mkdir_with_parents(target_dir_path, S_IRWXU) == -1) {
+        g_warning("Unable to create dir: %s\n", target_dir_path);
+        succeed = false;
+    }
+
+    if (!move_file(path, target_path)) {
+        g_warning("Unable to move icon to: %s\n", target_path);
+        succeed = false;
+    }
+
+    g_free(target_path);
+    g_free(target_dir_path);
+    g_free(new_file_name);
+    g_free(file_name);
+    g_free(prefix);
+    return succeed;
+}
+
+/**
+ * Look for file icons named <icon_name> in the <tempdir_path>/usr/share/icons.
+ *
+ * @param tempdir_path
+ * @param icon_name
+ * @return GSList of char*. Use `g_slist_free_full(list, &g_free);` to free it.
+ */
+GSList* find_app_icons(const char* tempdir_path, char* icon_name) {
+    char* icons_dir_path = g_build_path("/", tempdir_path, "usr/share/icons", NULL);
+
+    GSList* list = NULL;
+    GQueue* dirs_queue = g_queue_new();
+    g_queue_push_head(dirs_queue, icons_dir_path);
+
+    while (!g_queue_is_empty(dirs_queue)) {
+        char* current_dir_path = g_queue_pop_head(dirs_queue);
+        GDir* current_dir = g_dir_open(current_dir_path, 0, NULL);
+
+        const char* entry = NULL;
+        // Iterate directory entries
+        while ((entry = g_dir_read_name(current_dir)) != NULL) {
+            char* path = g_build_path("/", current_dir_path, entry, NULL);
+
+            if (g_file_test(path, G_FILE_TEST_IS_DIR))
+                g_queue_push_head(dirs_queue, path);
+            else {
+                if (g_str_has_prefix(entry, icon_name))
+                    list = g_slist_append(list, path);
+            }
+
+        }
+
+        g_dir_close(current_dir);
+        g_free(current_dir_path);
+    }
+
+    g_queue_free(dirs_queue);
+    return list;
+}
+
+/**
+ * Move icons files listed in the <icon_files> to the <user_data_dir> keeping the <theme>/<size>/<category>
+ * file structure and appends the prefix "<vendorprefix>_<appimage_path_md5>_" to the file name.
+ *
+ * @param icon_files list of icon files to be moved
+ * @param user_data_dir directory where the desktop integration files will be deployed usually "$HOME/.local/shared"
+ * @param appimage_path_md5
+ * @param icon_name application icon name, extracted from the .desktop file
+ * @return true on success otherwise false
+ * */
+bool move_app_icons(GSList* icon_files, const char* user_data_dir, const char* appimage_path_md5) {
+    bool succeed = true;
+    for (GSList* itr = icon_files; itr != NULL & succeed; itr = itr->next) {
+        const char* path = itr->data;
+        succeed = move_icon_file(user_data_dir, appimage_path_md5, path);
+    }
+
+    return succeed;
+}
+
+/**
+ * Move a the application .desktop file to <user_data_dir>/applications/<vendorprefix>_<md5sum>_<desktopfilename>
+ * @param tempdir_path
+ * @param user_data_dir
+ * @param md5sum
+ */
+bool move_desktop_file(const char* tempdir_path, const char* user_data_dir, const char* md5sum) {
+    bool succeed = false;
+    char* target_dir_path = g_build_path("/", user_data_dir, "applications", NULL);
+    succeed = g_mkdir_with_parents(target_dir_path, S_IRWXU) == 0;
+
+    char* desktop_file_path = find_desktop_file(tempdir_path);
+    char* desktop_filename = g_path_get_basename(desktop_file_path);
+
+    char* target_desktop_filename = g_strdup_printf("%s_%s-%s", vendorprefix, md5sum, desktop_filename);
+    free(desktop_filename);
+
+    char* target_desktop_file_path = g_build_path("/", target_dir_path, target_desktop_filename, NULL);
+    free(target_desktop_filename);
+    free(target_dir_path);
+
+    succeed = move_file(desktop_file_path, target_desktop_file_path);
+
+    free(desktop_file_path);
+    free(target_desktop_file_path);
+
+    return succeed;
+}
+
+void desktop_integration_remove_tempdir(const char* dir_path) {
+    GDir* tempdir = NULL;
+    GError* error;
+    tempdir = g_dir_open(dir_path, 0, &error);
+    if (!tempdir) {
+        g_warning("%s\n", error->message);
+        g_error_free(error);
+        return;
+    }
+
+    const char* entry;
+    while ((entry = g_dir_read_name(tempdir)) != NULL) {
+        char* full_path = g_strjoin("/", dir_path, entry, NULL);
+        if (g_file_test(full_path, G_FILE_TEST_IS_DIR)) {
+            desktop_integration_remove_tempdir(full_path);
+        } else
+            g_remove(full_path);
+
+        free(full_path);
+    }
+
+    g_rmdir(dir_path);
+    g_dir_close(tempdir);
+}
diff --git a/src/libappimage/desktop_integration.h b/src/libappimage/desktop_integration.h
new file mode 100644 (file)
index 0000000..866b9b9
--- /dev/null
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Functions related to the desktop integration of AppImages.
+ */
+
+/**
+ * Create a temporary dir to extract the AppImage Desktop Integration relate files such as:
+ * - .Desktop file
+ * - .AppIcon
+ * - usr/share/icons/...
+ *
+ * @param appimage_path
+ * @return path to the temporary dir
+ */
+char* desktop_integration_create_tempdir();
+
+/**
+ * Extract the AppImage Desktop Integration relate files such as:
+ * - .Desktop file
+ * - .AppIcon
+ * - usr/share/icons/...
+ *
+ * @param appimage_path
+ * @param tempdir_path
+ */
+void desktop_integration_extract_relevant_files(const char* appimage_path, const char* tempdir_path);
+
+
+/**
+ * Modifies the fields on the .Desktop file to make them point to the expected locations of the desktop integration
+ * files.
+ * Fields modified:
+ * - Exec: will point to the AppImage
+ * - TryExcec: will point to the AppImage
+ * - Icon: Will point to the expected path of the application icon in $XDG_DATA_HOME/icons/.../apps/..
+ * @param appimage_path
+ * @param tempdir_path
+ */
+bool desktop_integration_modify_desktop_file(const char* appimage_path, const char* tempdir_path, const char* md5);
+
+
+/**
+ * Move .desktop file and application icons from <tempdir_path>/share to the $XDG_DATA_HOME
+ * @param tempdir_path
+ * @param user_data_dir
+ * @param md5sum
+ */
+bool desktop_integration_move_files_to_user_data_dir(const char* tempdir_path, const char* user_data_dir,
+                                                     const char* md5sum);
+
+/**
+ * Remove recusively remaining files at the temporary dir and the dir itself
+ * @param dir_path
+ */
+void desktop_integration_remove_tempdir(const char* dir_path);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/libappimage/libappimage.c b/src/libappimage/libappimage.c
new file mode 100644 (file)
index 0000000..a289a02
--- /dev/null
@@ -0,0 +1,2069 @@
+/**************************************************************************
+ *
+ * Copyright (c) 2004-18 Simon Peter
+ *
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ **************************************************************************/
+
+#ident "AppImage by Simon Peter, http://appimage.org/"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <errno.h>
+
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+
+#include "squashfuse.h"
+#include <squashfs_fs.h>
+#include "elf.h"
+
+#include "xdg-basedir.h"
+#include "appimage_handler.h"
+
+// own header
+#include "appimage/appimage.h"
+#include "desktop_integration.h"
+#include "type1.h"
+
+#if HAVE_LIBARCHIVE3 == 1 // CentOS
+# include <archive3.h>
+# include <archive_entry3.h>
+#else // other systems
+# include <archive.h>
+# include <archive_entry.h>
+#endif
+
+#include <regex.h>
+#include <glob.h>
+
+#include <cairo.h> // To get the size of icons, move_icon_to_destination()
+
+#define FNM_FILE_NAME 2
+
+#define URI_MAX (FILE_MAX * 3 + 8)
+
+char *vendorprefix = "appimagekit";
+
+/* Search and replace on a string, this really should be in Glib */
+gchar* replace_str(const gchar const *src, const gchar const *find, const gchar const *replace){
+    gchar **split = g_strsplit(src, find, -1);
+    gchar *retval = g_strjoinv(replace, split);
+
+    g_strfreev(split);
+    return retval;
+}
+
+/* Return the md5 hash constructed according to
+ * https://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html#THUMBSAVE
+ * This can be used to identify files that are related to a given AppImage at a given location */
+char *appimage_get_md5(const char* path)
+{
+    char* absolute_path;
+
+    if ((absolute_path = realpath(path, NULL)) == NULL)
+        absolute_path = strdup(path);
+
+    gchar *uri = g_filename_to_uri(absolute_path, NULL, NULL);
+
+    free(absolute_path);
+
+    if (uri != NULL)
+    {
+        GChecksum *checksum;
+        checksum = g_checksum_new(G_CHECKSUM_MD5);
+        guint8 digest[16];
+        gsize digest_len = sizeof (digest);
+        g_checksum_update(checksum, (const guchar *) uri, strlen (uri));
+        g_checksum_get_digest(checksum, digest, &digest_len);
+        g_assert(digest_len == 16);
+        gchar *result = g_strdup(g_checksum_get_string(checksum));
+        g_checksum_free(checksum);
+        g_free(uri);
+        return result;
+    } else {
+        return NULL;
+    }
+}
+
+/* Return the path of the thumbnail regardless whether it already exists; may be useful because
+ * G*_FILE_ATTRIBUTE_THUMBNAIL_PATH only exists if the thumbnail is already there.
+ * Check libgnomeui/gnome-thumbnail.h for actually generating thumbnails in the correct
+ * sizes at the correct locations automatically; which would draw in a dependency on gdk-pixbuf.
+ */
+char *get_thumbnail_path(const char *path, char *thumbnail_size, gboolean verbose)
+{
+    char *md5 = appimage_get_md5(path);
+    char *file  = g_strconcat(md5, ".png", NULL);
+    char* cache_home = xdg_cache_home();
+    gchar *thumbnail_path = g_build_filename(cache_home, "thumbnails", thumbnail_size, file, NULL);
+    g_free(cache_home);
+    g_free(file);
+    g_free(md5);
+    return thumbnail_path;
+}
+
+/* Move an icon file to the path where a given icon can be installed in $HOME.
+ * This is needed because png and xpm icons cannot be installed in a generic
+ * location but are only picked up in directories that have the size of
+ * the icon as part of their directory name, as specified in the theme.index
+ * See https://github.com/AppImage/AppImageKit/issues/258
+ */
+
+void move_icon_to_destination(gchar *icon_path, gboolean verbose)
+{
+    // FIXME: This default location is most likely wrong, but at least the icons with unknown size can go somewhere
+    char* data_home = xdg_data_home();
+    gchar *dest_dir = g_build_path("/", data_home, "/icons/hicolor/48x48/apps/", NULL);
+
+    if((g_str_has_suffix (icon_path, ".svg")) || (g_str_has_suffix (icon_path, ".svgz"))) {
+        g_free(dest_dir);
+        dest_dir = g_build_path("/", data_home, "/icons/hicolor/scalable/apps/", NULL);
+    }
+    g_free(data_home);
+
+    if((g_str_has_suffix (icon_path, ".png")) || (g_str_has_suffix (icon_path, ".xpm"))) {
+
+        cairo_surface_t *image;
+
+        int w = 0;
+        int h = 0;
+
+        if(g_str_has_suffix (icon_path, ".xpm")) {
+            // TODO: GdkPixbuf has a convenient way to load XPM data. Then you can call
+            // gdk_cairo_set_source_pixbuf() to transfer the data to a Cairo surface.
+#ifdef STANDALONE
+            fprintf(stderr, "XPM size parsing not yet implemented\n");
+#endif
+        }
+
+        if(g_str_has_suffix (icon_path, ".png")) {
+            image = cairo_image_surface_create_from_png(icon_path);
+            w = cairo_image_surface_get_width (image);
+            h = cairo_image_surface_get_height (image);
+            cairo_surface_destroy (image);
+        }
+
+        // FIXME: The following sizes are taken from the hicolor icon theme.
+        // Probably the right thing to do would be to figure out at runtime which icon sizes are allowable.
+        // Or could we put our own index.theme into .local/share/icons/ and have it observed?
+        if((w != h) || ((w != 16) && (w != 24) && (w != 32) && (w != 36) && (w != 48) && (w != 64) && (w != 72) && (w != 96) && (w != 128) && (w != 192) && (w != 256) && (w != 512))){
+#ifdef STANDALONE
+            fprintf(stderr, "%s has nonstandard size w = %i, h = %i; please fix it\n", icon_path, w, h);
+#endif
+        } else {
+            g_free(dest_dir);
+            char* data_home = xdg_data_home();
+            dest_dir = g_build_path("/", data_home, "/icons/hicolor/", g_strdup_printf("%ix%i", w, h), "/apps/", NULL);
+            free(data_home);
+        }
+    }
+    if(verbose)
+        fprintf(stderr, "dest_dir %s\n", dest_dir);
+
+    gchar *basename = g_path_get_basename(icon_path);
+
+    gchar* icon_dest_path = g_build_path("/", dest_dir, basename, NULL);
+
+    g_free(basename);
+    if(verbose)
+        fprintf(stderr, "Move from %s to %s\n", icon_path, icon_dest_path);
+    gchar *dirname = g_path_get_dirname(dest_dir);
+    if(g_mkdir_with_parents(dirname, 0755)) {
+#ifdef STANDALONE
+        fprintf(stderr, "Could not create directory: %s\n", dest_dir);
+#endif
+    }
+
+    g_free(dirname);
+    g_free(dest_dir);
+
+    // This is required only for old versions of glib2 and is harmless for newer
+    g_type_init();
+
+    GError *error = NULL;
+    GFile *icon_file = g_file_new_for_path(icon_path);
+    GFile *target_file = g_file_new_for_path(icon_dest_path);
+    if(!g_file_move(icon_file, target_file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error)){
+#ifdef STANDALONE
+        fprintf(stderr, "Error moving file: %s\n", error->message);
+#endif
+        g_error_free(error);
+    }
+    g_object_unref(icon_file);
+    g_object_unref(target_file);
+    g_free(icon_dest_path);
+
+}
+
+/* Get filename extension */
+static gchar *get_file_extension(const gchar *filename)
+{
+    gchar **tokens;
+    gchar *extension;
+    tokens = g_strsplit(filename, ".", 2);
+    if (tokens[0] == NULL)
+        extension = NULL;
+    else
+        extension = g_strdup(tokens[1]);
+    g_strfreev(tokens);
+    return extension;
+}
+
+// Read the file in chunks
+void squash_extract_inode_to_file(sqfs *fs, sqfs_inode *inode, const gchar *dest) {
+    off_t bytes_already_read = 0;
+    sqfs_off_t bytes_at_a_time = 64*1024;
+    FILE * f;
+    f = fopen (dest, "w+");
+    if (f == NULL){
+#ifdef STANDALONE
+        fprintf(stderr, "fopen error\n");
+#endif
+        return;
+    }
+    while (bytes_already_read < (*inode).xtra.reg.file_size)
+    {
+        char buf[bytes_at_a_time];
+        if (sqfs_read_range(fs, inode, (sqfs_off_t) bytes_already_read, &bytes_at_a_time, buf) != SQFS_OK) {
+#ifdef STANDALONE
+            fprintf(stderr, "sqfs_read_range error\n");
+#endif
+        }
+        fwrite(buf, 1, (size_t) bytes_at_a_time, f);
+        bytes_already_read = bytes_already_read + bytes_at_a_time;
+    }
+    fclose(f);
+}
+
+/* Find files in the squashfs matching to the regex pattern.
+ * Returns a newly-allocated NULL-terminated array of strings.
+ * Use g_strfreev() to free it.
+ *
+ * The following is done within the sqfs_traverse run for performance reaons:
+ * 1.) For found files that are in usr/share/icons, install those icons into the system
+ * with a custom name that involves the md5 identifier to tie them to a particular
+ * AppImage.
+ * 2.) For found files that are in usr/share/mime/packages, install those icons into the system
+ * with a custom name that involves the md5 identifier to tie them to a particular
+ * AppImage.
+ */
+gchar **squash_get_matching_files_install_icons_and_mime_data(sqfs* fs, char* pattern,
+                                                              gchar* desktop_icon_value_original, char* md5,
+                                                              gboolean verbose) {
+    GPtrArray *array = g_ptr_array_new();
+    sqfs_traverse trv;
+    sqfs_err err = sqfs_traverse_open(&trv, fs, sqfs_inode_root(fs));
+    if (err!= SQFS_OK) {
+#ifdef STANDALONE
+        fprintf(stderr, "sqfs_traverse_open error\n");
+#endif
+    }
+    while (sqfs_traverse_next(&trv, &err)) {
+        if (!trv.dir_end) {
+            int r;
+            regex_t regex;
+            regmatch_t match[2];
+            regcomp(&regex, pattern, REG_ICASE | REG_EXTENDED);
+            r = regexec(&regex, trv.path, 2, match, 0);
+            regfree(&regex);
+            sqfs_inode inode;
+            if(sqfs_inode_get(fs, &inode, trv.entry.inode)) {
+#ifdef STANDALONE
+                fprintf(stderr, "sqfs_inode_get error\n");
+#endif
+            }
+            if(r == 0){
+                // We have a match
+                if(verbose)
+                    fprintf(stderr, "squash_get_matching_files found: %s\n", trv.path);
+                g_ptr_array_add(array, g_strdup(trv.path));
+                gchar *dest = NULL;
+                if(inode.base.inode_type == SQUASHFS_REG_TYPE || inode.base.inode_type == SQUASHFS_LREG_TYPE) {
+                    if(g_str_has_prefix(trv.path, "usr/share/icons/") || g_str_has_prefix(trv.path, "usr/share/pixmaps/") || (g_str_has_prefix(trv.path, "usr/share/mime/") && g_str_has_suffix(trv.path, ".xml"))){
+                        char* data_home = xdg_data_home();
+                        gchar *path = replace_str(trv.path, "usr/share", data_home);
+                        free(data_home);
+                        char *dest_dirname = g_path_get_dirname(path);
+                        g_free(path);
+                        gchar *base_name = g_path_get_basename(trv.path);
+                        gchar *dest_basename = g_strdup_printf("%s_%s_%s", vendorprefix, md5, base_name);
+
+                        dest = g_build_path("/", dest_dirname, dest_basename, NULL);
+
+                        g_free(base_name);
+                        g_free(dest_basename);
+                    }
+                    /* According to https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#install_icons
+                     * share/pixmaps is ONLY searched in /usr but not in $XDG_DATA_DIRS and hence $HOME and this seems to be true at least in XFCE */
+                    if(g_str_has_prefix (trv.path, "usr/share/pixmaps/")){
+                        gchar *dest_basename = g_strdup_printf("%s_%s_%s", vendorprefix, md5, g_path_get_basename(trv.path));
+
+                        dest = g_build_path("/", "/tmp", dest_basename, NULL);
+
+                        g_free(dest_basename);
+                    }
+                    /* Some AppImages only have the icon in their root directory, so we have to get it from there */
+                    if((g_str_has_prefix(trv.path, desktop_icon_value_original)) && (! strstr(trv.path, "/")) && ( (g_str_has_suffix(trv.path, ".png")) || (g_str_has_suffix(trv.path, ".xpm")) || (g_str_has_suffix(trv.path, ".svg")) || (g_str_has_suffix(trv.path, ".svgz")))){
+                        gchar *ext = get_file_extension(trv.path);
+                        gchar *dest_basename = g_strdup_printf("%s_%s_%s.%s", vendorprefix, md5, desktop_icon_value_original, ext);
+
+                        dest = g_build_path("/", "/tmp", dest_basename, NULL);
+
+                        g_free(dest_basename);
+                        g_free(ext);
+                    }
+
+                    if(dest){
+                        if(verbose)
+                            fprintf(stderr, "install: %s\n", dest);
+
+                        gchar *dirname = g_path_get_dirname(dest);
+                        if(g_mkdir_with_parents(dirname, 0755)) {
+#ifdef STANDALONE
+                            fprintf(stderr, "Could not create directory: %s\n", dirname);
+#endif
+                        }
+
+                        g_free(dirname);
+
+                        squash_extract_inode_to_file(fs, &inode, dest);
+
+                        chmod (dest, 0644);
+
+                        if(verbose)
+                            fprintf(stderr, "Installed: %s\n", dest);
+
+                        // If we were unsure about the size of an icon, we temporarily installed
+                        // it to /tmp and now move it into the proper place
+                        if(g_str_has_prefix (dest, "/tmp/")) {
+                            move_icon_to_destination(dest, verbose);
+                        }
+
+                        g_free(dest);
+                    }
+                }
+            }
+        }
+    }
+    g_ptr_array_add(array, NULL);
+    if (err) {
+#ifdef STANDALONE
+        fprintf(stderr, "sqfs_traverse_next error\n");
+#endif
+    }
+    sqfs_traverse_close(&trv);
+    return (gchar **) g_ptr_array_free(array, FALSE);
+}
+
+
+
+/**
+ * Lookup a given <path> in <fs>. If the path points to a symlink it is followed until a regular file is found.
+ * This method is aware of symlink loops and will fail properly in such case.
+ * @param fs
+ * @param path
+ * @param inode [RETURN PARAMETER] will be filled with a regular file inode. It cannot be NULL
+ * @return succeed true if the file is found, otherwise false
+ */
+bool sqfs_lookup_path_resolving_symlinks(sqfs* fs, char* path, sqfs_inode* inode) {
+    g_assert(fs != NULL);
+    g_assert(inode != NULL);
+
+    bool found = false;
+    sqfs_inode root_inode;
+    sqfs_err err = sqfs_inode_get(fs, &root_inode, fs->sb.root_inode);
+    if (err != SQFS_OK) {
+#ifdef STANDALONE
+        g_warning("sqfs_inode_get root inode error\n");
+#endif
+        return false;
+    }
+
+    *inode = root_inode;
+    err = sqfs_lookup_path(fs, inode, path, &found);
+
+    if (!found) {
+#ifdef STANDALONE
+        g_warning("sqfs_lookup_path path not found: %s\n", path);
+#endif
+        return false;
+    }
+
+    if (err != SQFS_OK) {
+#ifdef STANDALONE
+        g_warning("sqfs_lookup_path error: %s\n", path);
+#endif
+
+        return false;
+    }
+
+    // Save visited inode numbers to prevent loops
+    GSList* inodes_visited = g_slist_append(NULL, (gpointer) inode->base.inode_number);
+
+    while (inode->base.inode_type == SQUASHFS_SYMLINK_TYPE || inode->base.inode_type == SQUASHFS_LSYMLINK_TYPE) {
+        // Read symlink
+        size_t size;
+        // read twice, once to find out right amount of memory to allocate
+        err = sqfs_readlink(fs, inode, NULL, &size);
+        if (err != SQFS_OK) {
+#ifdef STANDALONE
+            fprintf(stderr, "sqfs_readlink error: %s\n", path);
+#endif
+            g_slist_free(inodes_visited);
+            return false;
+        }
+
+        char symlink_target_path[size];
+        // then to populate the buffer
+        err = sqfs_readlink(fs, inode, symlink_target_path, &size);
+        if (err != SQFS_OK) {
+#ifdef STANDALONE
+            g_warning("sqfs_readlink error: %s\n", path);
+#endif
+            g_slist_free(inodes_visited);
+            return false;
+        }
+
+        // lookup symlink target path
+        *inode = root_inode;
+        err = sqfs_lookup_path(fs, inode, symlink_target_path, &found);
+
+        if (!found) {
+#ifdef STANDALONE
+            g_warning("sqfs_lookup_path path not found: %s\n", symlink_target_path);
+#endif
+            g_slist_free(inodes_visited);
+            return false;
+        }
+
+        if (err != SQFS_OK) {
+#ifdef  STANDALONE
+            g_warning("sqfs_lookup_path error: %s\n", symlink_target_path);
+#endif
+            g_slist_free(inodes_visited);
+            return false;
+        }
+
+        // check if we felt into a loop
+        if (g_slist_find(inodes_visited, (gconstpointer) inode->base.inode_number)) {
+#ifdef STANDALONE
+            g_warning("Symlinks loop found while trying to resolve: %s", path);
+#endif
+            g_slist_free(inodes_visited);
+            return false;
+        } else
+            inodes_visited = g_slist_append(inodes_visited, (gpointer) inode->base.inode_number);
+    }
+
+    g_slist_free(inodes_visited);
+    return true;
+}
+
+/**
+ * Read a regular <inode> from <fs> in chunks of <buf_size> and merge them into one.
+ *
+ * @param fs
+ * @param inode
+ * @param buffer [RETURN PARAMETER]
+ * @param buffer_size [RETURN PARAMETER]
+ * @return succeed true, buffer pointing to the memory, buffer_size holding the actual size in memory
+ *          if all was ok. Otherwise succeed false, buffer pointing NULL and buffer_size = 0.
+ *          The buffer MUST BE FREED using g_free().
+ */
+bool sqfs_read_regular_inode(sqfs* fs, sqfs_inode *inode, char **buffer, off_t *buffer_size) {
+    GSList *blocks = NULL;
+
+    off_t bytes_already_read = 0;
+    unsigned long read_buf_size = 256*1024;
+
+    // This has a double role in sqfs_read_range it's used to set the max_bytes_to_be_read and
+    // after complete it's set to the number ob bytes actually read.
+    sqfs_off_t size = 0;
+    sqfs_err err;
+
+    // Read chunks until the end of the file.
+    do {
+        size = read_buf_size;
+        char* buf_read = malloc(sizeof(char) * size);
+        if (buf_read != NULL) {
+            err = sqfs_read_range(fs, inode, (sqfs_off_t) bytes_already_read, &size, buf_read);
+            if (err != SQFS_OK) {
+#ifdef STANDALONE
+                g_warning("sqfs_read_range failed\n");
+#endif
+            }
+            else
+                blocks = g_slist_append(blocks, buf_read);
+            bytes_already_read += size;
+        } else { // handle not enough memory properly
+#ifdef STANDALONE
+            g_warning("sqfs_read_regular_inode: Unable to allocate enough memory.\n");
+#endif
+            err = SQFS_ERR;
+        }
+    } while ( (err == SQFS_OK) && (size == read_buf_size) );
+
+
+    bool succeed = false;
+    *buffer_size = 0;
+    *buffer = NULL;
+
+    if (err == SQFS_OK) {
+        // Put all the memory blocks together
+        guint length = g_slist_length(blocks);
+
+        *buffer = malloc(sizeof(char) * bytes_already_read);
+        if (*buffer != NULL) { // Prevent crash if the
+            GSList *ptr = blocks;
+            for (int i = 0; i < (length-1); i ++) {
+                memcpy(*buffer + (i*read_buf_size), ptr->data, read_buf_size);
+                ptr = ptr->next;
+            }
+
+            memcpy(*buffer + ((length-1)*read_buf_size), ptr->data, (size_t) size);
+
+            succeed = true;
+            *buffer_size = bytes_already_read;
+        } else { // handle not enough memory properly
+#ifdef STANDALONE
+            g_warning("sqfs_read_regular_inode: Unable to allocate enough memory.\n");
+#endif
+            succeed = false;
+        }
+    }
+
+    g_slist_free_full(blocks, &g_free);
+    return  succeed;
+}
+
+/**
+ * Loads a desktop file from <fs> into an empty GKeyFile structure. In case of symlinks they are followed.
+ * This function is capable of detecting loops and will return false in such cases.
+ *
+ * @param fs
+ * @param path
+ * @param key_file_structure [OUTPUT PARAMETER]
+ * @param verbose
+ * @return true if all when ok, otherwise false.
+ */
+gboolean g_key_file_load_from_squash(sqfs* fs, char* path, GKeyFile* key_file_structure, gboolean verbose) {
+    sqfs_inode inode;
+    if (!sqfs_lookup_path_resolving_symlinks(fs, path, &inode))
+        return false;
+
+    gboolean success = false;
+    if (inode.base.inode_type == SQUASHFS_REG_TYPE || inode.base.inode_type == SQUASHFS_LREG_TYPE ) {
+        char* buf = NULL;
+        off_t buf_size;
+        sqfs_read_regular_inode(fs, &inode, &buf, &buf_size);
+        if (buf != NULL) {
+            success = g_key_file_load_from_data(key_file_structure, buf, (gsize) buf_size,
+                                                G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
+
+            g_free(buf);
+        } else
+            success = false;
+    }
+
+    return success;
+}
+
+gchar *build_installed_desktop_file_path(gchar* md5, gchar* desktop_filename) {
+    gchar *partial_path;
+    partial_path = g_strdup_printf("applications/appimagekit_%s-%s", md5, desktop_filename);
+
+    char *data_home = xdg_data_home();
+    gchar *destination = g_build_filename(data_home, partial_path, NULL);
+    g_free(data_home);
+
+    g_free(partial_path);
+
+    return destination;
+}
+
+/* Write a modified desktop file to disk that points to the AppImage */
+bool write_edited_desktop_file(GKeyFile *key_file_structure, const char* appimage_path, gchar* desktop_filename, int appimage_type, char *md5, gboolean verbose) {
+    if(!g_key_file_has_key(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, NULL)){
+#ifdef STANDALONE
+        fprintf(stderr, "Desktop file has no Exec key\n");
+#endif
+        return false;
+    }
+
+    // parse [Try]Exec= value, replace executable by AppImage path, append parameters
+    // TODO: should respect quoted strings within value
+
+    {
+        char* field_value = g_key_file_get_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, NULL);
+
+        // saving a copy for later free() call
+        char* original_field_value = field_value;
+
+        char* executable = strsep(&field_value, " ");
+
+        // error handling
+        if (executable == NULL) {
+#ifdef STANDALONE
+            fprintf(stderr, "Invalid value for Exec= entry in Desktop file\n");
+#endif
+            return false;
+        }
+
+        unsigned long new_exec_value_size = strlen(appimage_path) + 1;
+
+        if (field_value != NULL)
+            new_exec_value_size += strlen(field_value) + 1;
+
+        gchar* new_exec_value = calloc(new_exec_value_size, sizeof(gchar));
+
+        // build new value
+        strcpy(new_exec_value, appimage_path);
+
+        if (field_value != NULL && strlen(field_value) > 0) {
+            strcat(new_exec_value, " ");
+            strcat(new_exec_value, field_value);
+        }
+
+        if (original_field_value != NULL)
+            free(original_field_value);
+
+        g_key_file_set_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, new_exec_value);
+
+        g_free(new_exec_value);
+    }
+
+    // force add a TryExec= key
+    g_key_file_set_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, appimage_path);
+
+#ifdef APPIMAGED
+    /* If firejail is on the $PATH, then use it to run AppImages */
+    if(g_find_program_in_path ("firejail")){
+        char *firejail_exec;
+        firejail_exec = g_strdup_printf("firejail --env=DESKTOPINTEGRATION=appimaged --noprofile --appimage '%s'", appimage_path);
+        g_key_file_set_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, firejail_exec);
+
+        gchar *firejail_profile_group = "Desktop Action FirejailProfile";
+        gchar *firejail_profile_exec = g_strdup_printf("firejail --env=DESKTOPINTEGRATION=appimaged --private --appimage '%s'", appimage_path);
+        gchar *firejail_tryexec = "firejail";
+        g_key_file_set_value(key_file_structure, firejail_profile_group, G_KEY_FILE_DESKTOP_KEY_NAME, "Run without sandbox profile");
+        g_key_file_set_value(key_file_structure, firejail_profile_group, G_KEY_FILE_DESKTOP_KEY_EXEC, firejail_profile_exec);
+        g_key_file_set_value(key_file_structure, firejail_profile_group, G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, firejail_tryexec);
+        g_key_file_set_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, "Actions", "FirejailProfile;");
+    }
+#endif
+
+#ifdef APPIMAGED
+    /* Add AppImageUpdate desktop action
+     * https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s10.html
+     * This will only work if AppImageUpdate is on the user's $PATH.
+     * TODO: we could have it call this appimaged instance instead of AppImageUpdate and let it
+     * figure out how to update the AppImage */
+    unsigned long upd_offset = 0;
+    unsigned long upd_length = 0;
+    if(g_find_program_in_path ("AppImageUpdate")){
+        if(appimage_type == 2){
+            if (!appimage_get_elf_section_offset_and_length(appimage_path, ".upd_info", &upd_offset, &upd_length) || upd_offset == 0 || upd_length == 0) {
+                fprintf(stderr, "Could not read .upd_info section in AppImage's header\n");
+            }
+            if(upd_length != 1024) {
+#ifdef STANDALONE
+                fprintf(stderr,
+                    "WARNING: .upd_info length is %lu rather than 1024, this might be a bug in the AppImage\n",
+                    upd_length);
+#endif
+            }
+        }
+        if(appimage_type == 1){
+            /* If we have a type 1 AppImage, then we hardcode the offset and length */
+            upd_offset = 33651; // ISO 9660 Volume Descriptor field
+            upd_length = 512; // Might be wrong
+        }
+#ifdef STANDALONE
+        fprintf(stderr, ".upd_info offset: %lu\n", upd_offset);
+        fprintf(stderr, ".upd_info length: %lu\n", upd_length);
+#endif
+        char buffer[3];
+        FILE *binary = fopen(appimage_path, "rt");
+        if (binary != NULL)
+        {
+            /* Check whether the first three bytes at the offset are not NULL */
+            fseek(binary, upd_offset, SEEK_SET);
+            fread(buffer, 1, 3, binary);
+            fclose(binary);
+            if((buffer[0] != 0x00) && (buffer[1] != 0x00) && (buffer[2] != 0x00)){
+                gchar *appimageupdate_group = "Desktop Action AppImageUpdate";
+                gchar *appimageupdate_exec = g_strdup_printf("%s %s", "AppImageUpdate", appimage_path);
+                g_key_file_set_value(key_file_structure, appimageupdate_group, G_KEY_FILE_DESKTOP_KEY_NAME, "Update");
+                g_key_file_set_value(key_file_structure, appimageupdate_group, G_KEY_FILE_DESKTOP_KEY_EXEC, appimageupdate_exec);
+                g_key_file_set_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, "Actions", "AppImageUpdate;");
+            }
+        }
+    }
+#endif
+
+    {
+        // parse desktop files and generate a list of locales representing all localized Name/Icon entries
+        // NULL refers to the key without the locale tag
+        // the locales for both entry types need to be tracked separately due to a limitation in the GLib API, see
+        // the comment below for more information
+        gchar* nameLocales[256] = {NULL};
+        gchar* iconLocales[256] = {NULL};
+        gint nameLocalesCount = 1;
+        gint iconLocalesCount = 1;
+
+        {
+            // create temporary in-memory copy of the keyfile
+            gsize bufsize;
+            char* orig_buffer = g_key_file_to_data(key_file_structure, &bufsize, NULL);
+
+            if (orig_buffer == NULL) {
+                fprintf(stderr, "Failed to create in-memory copy of desktop file\n");
+                return false;
+            }
+
+            // need to keep original reference to buffer to be able to free() it later
+            char* buffer = orig_buffer;
+
+            // parse line by line
+            char* line = NULL;
+            while ((line = strsep(&buffer, "\n")) != NULL) {
+                const bool is_name_entry = strncmp(line, "Name[", 5) == 0;
+                const bool is_icon_entry = strncmp(line, "Icon[", 5) == 0;
+
+                // the only keys for which we're interested in localizations is Name and Icon
+                if (!(is_name_entry || is_icon_entry))
+                    continue;
+
+                // python: s = split(line, "[")[1]
+                char* s = strsep(&line, "[");
+                s = strsep(&line, "[");
+
+                // python: locale = split(s, "]")[0]
+                char* locale = strsep(&s, "]");
+
+                // create references to the right variables
+                gchar** locales = NULL;
+                gint* localesCount = NULL;
+
+                if (is_name_entry) {
+                    locales = nameLocales;
+                    localesCount = &nameLocalesCount;
+                } else if (is_icon_entry) {
+                    locales = iconLocales;
+                    localesCount = &iconLocalesCount;
+                }
+
+                // avoid duplicates in list of locales
+                bool is_duplicate = false;
+
+                // unfortunately, the list of locales is not sorted, therefore a linear search is required
+                // however, this won't have a significant impact on the performance
+                // start at index 1, first entry is NULL
+                for (int i = 1; i < *localesCount; i++) {
+                    if (strcmp(locale, locales[i]) == 0) {
+                        is_duplicate = true;
+                        break;
+                    }
+                }
+
+                if (is_duplicate)
+                    continue;
+
+                locales[(*localesCount)++] = strdup(locale);
+            }
+
+            free(orig_buffer);
+        }
+
+        // iterate over locales, check whether name or icon entries exist, and edit accordingly
+        for (int i = 0; i < iconLocalesCount; i++) {
+            const gchar* locale = iconLocales[i];
+
+            // check whether the key is set at all
+            gchar* old_contents = NULL;
+
+            // it's a little annoying that the GLib functions don't simply work with NULL as the locale, that'd
+            // make the following if-else construct unnecessary
+            if (locale == NULL) {
+                old_contents = g_key_file_get_string(
+                    key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, NULL
+                );
+            } else {
+                // please note that the following call will return a value even if there is no localized version
+                // probably to save one call when you're just interested in getting _some_ value while reading
+                // a desktop file
+                old_contents = g_key_file_get_locale_string(
+                    key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, locale, NULL
+                );
+            }
+
+            // continue to next key if not set
+            if (old_contents == NULL) {
+                g_free(old_contents);
+                continue;
+            }
+
+            // copy key's original contents
+            static const gchar old_key[] = "X-AppImage-Old-Icon";
+
+            // append AppImage version
+            gchar* basename = g_path_get_basename(old_contents);
+            gchar* new_contents = g_strdup_printf("%s_%s_%s", vendorprefix, md5, basename);
+            g_free(basename);
+
+            // see comment for above if-else construct
+            if (locale == NULL) {
+                g_key_file_set_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, old_key, old_contents);
+                g_key_file_set_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON,
+                    new_contents);
+            } else {
+                g_key_file_set_locale_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, old_key, locale, old_contents);
+                g_key_file_set_locale_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, locale, new_contents);
+            }
+
+            // cleanup
+            g_free(old_contents);
+            g_free(new_contents);
+        }
+
+        char* appimage_version = g_key_file_get_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, "X-AppImage-Version", NULL);
+        // check for name entries and append version suffix
+        if (appimage_version != NULL) {
+            for (int i = 0; i < nameLocalesCount; i++) {
+                const gchar* locale = nameLocales[i];
+
+                // check whether the key is set at all
+                gchar* old_contents;
+
+                // it's a little annoying that the GLib functions don't simply work with NULL as the locale, that'd
+                // make the following if-else construct unnecessary
+                if (locale == NULL) {
+                    old_contents = g_key_file_get_string(
+                        key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, NULL
+                    );
+                } else {
+                    // please note that the following call will return a value even if there is no localized version
+                    // probably to save one call when you're just interested in getting _some_ value while reading
+                    // a desktop file
+                    old_contents = g_key_file_get_locale_string(
+                        key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, locale, NULL
+                    );
+                }
+
+                // continue to next key if not set
+                if (old_contents == NULL) {
+                    g_free(old_contents);
+                    continue;
+                }
+
+                gchar* version_suffix = g_strdup_printf("(%s)", appimage_version);
+
+                // check if version suffix has been appended already
+                // this makes sure that the version suffix isn't added more than once
+                if (strlen(version_suffix) > strlen(old_contents) && strcmp(old_contents + (strlen(old_contents) - strlen(version_suffix)), version_suffix) != 0) {
+                    // copy key's original contents
+                    static const gchar old_key[] = "X-AppImage-Old-Name";
+
+                    // append AppImage version
+                    gchar* new_contents = g_strdup_printf("%s %s", old_contents, version_suffix);
+
+                    // see comment for above if-else construct
+                    if (locale == NULL) {
+                        g_key_file_set_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, old_key, old_contents);
+                        g_key_file_set_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, new_contents);
+                    } else {
+                        g_key_file_set_locale_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, old_key, locale, old_contents);
+                        g_key_file_set_locale_string(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, locale, new_contents);
+                    }
+
+                    // cleanup
+                    g_free(new_contents);
+                }
+
+                // cleanup
+                g_free(old_contents);
+                g_free(version_suffix);
+            }
+        }
+
+        for (int i = 1; i < iconLocalesCount; i++)
+            free(iconLocales[i]);
+
+        for (int i = 1; i < nameLocalesCount; i++)
+            free(nameLocales[i]);
+
+        // cleanup
+        g_free(appimage_version);
+    }
+
+#ifdef APPIMAGED
+    {
+        gchar *generated_by = g_strdup_printf("Generated by appimaged %s", GIT_COMMIT);
+        g_key_file_set_value(key_file_structure, "Desktop Entry", "X-AppImage-Comment", generated_by);
+        g_free(generated_by);
+    }
+#endif
+    g_key_file_set_value(key_file_structure, "Desktop Entry", "X-AppImage-Identifier", md5);
+#ifdef STANDALONE
+    fprintf(stderr, "Installing desktop file\n");
+#endif
+    if(verbose) {
+        gchar *buf  = g_key_file_to_data(key_file_structure, NULL, NULL);
+        fprintf(stderr, "%s", buf);
+        g_free(buf);
+    }
+
+    /* https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html#paths says:
+     *
+     * $XDG_DATA_DIRS/applications/
+     * When two desktop entries have the same name, the one appearing earlier in the path is used.
+     *
+     * --
+     *
+     * https://developer.gnome.org/integration-guide/stable/desktop-files.html.en says:
+     *
+     * Place this file in the /usr/share/applications directory so that it is accessible
+     * by everyone, or in ~/.local/share/applications if you only wish to make it accessible
+     * to a single user. Which is used should depend on whether your application is
+     * installed systemwide or into a user's home directory. GNOME monitors these directories
+     * for changes, so simply copying the file to the right location is enough to register it
+     * with the desktop.
+     *
+     * Note that the ~/.local/share/applications location is not monitored by versions of GNOME
+     * prior to version 2.10 or on Fedora Core Linux, prior to version 2.8.
+     * These versions of GNOME follow the now-deprecated vfolder standard,
+     * and so desktop files must be installed to ~/.gnome2/vfolders/applications.
+     * This location is not supported by GNOME 2.8 on Fedora Core nor on upstream GNOME 2.10
+     * so for maximum compatibility with deployed desktops, put the file in both locations.
+     *
+     * Note that the KDE Desktop requires one to run kbuildsycoca to force a refresh of the menus.
+     *
+     * --
+     *
+     * https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html says:
+     *
+     * To prevent that a desktop entry from one party inadvertently cancels out
+     * the desktop entry from another party because both happen to get the same
+     * desktop-file id it is recommended that providers of desktop-files ensure
+     * that all desktop-file ids start with a vendor prefix.
+     * A vendor prefix consists of [a-zA-Z] and is terminated with a dash ("-").
+     * For example, to ensure that GNOME applications start with a vendor prefix of "gnome-",
+     * it could either add "gnome-" to all the desktop files it installs
+     * in datadir/applications/ or it could install desktop files in a
+     * datadir/applications/gnome subdirectory.
+     *
+     * --
+     *
+     * https://specifications.freedesktop.org/desktop-entry-spec/latest/ape.html says:
+     * The desktop file ID is the identifier of an installed desktop entry file.
+     *
+     * To determine the ID of a desktop file, make its full path relative
+     * to the $XDG_DATA_DIRS component in which the desktop file is installed,
+     * remove the "applications/" prefix, and turn '/' into '-'.
+     * For example /usr/share/applications/foo/bar.desktop has the desktop file ID
+     * foo-bar.desktop.
+     * If multiple files have the same desktop file ID, the first one in the
+     * $XDG_DATA_DIRS precedence order is used.
+     * For example, if $XDG_DATA_DIRS contains the default paths
+     * /usr/local/share:/usr/share, then /usr/local/share/applications/org.foo.bar.desktop
+     * and /usr/share/applications/org.foo.bar.desktop both have the same desktop file ID
+     * org.foo.bar.desktop, but only the first one will be used.
+     *
+     * --
+     *
+     * https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s07.html says:
+     *
+     * The application must name its desktop file in accordance with the naming
+     * recommendations in the introduction section (e.g. the filename must be like
+     * org.example.FooViewer.desktop). The application must have a D-Bus service
+     * activatable at the well-known name that is equal to the desktop file name
+     * with the .desktop portion removed (for our example, org.example.FooViewer).
+     *
+     * --
+     *
+     * Can it really be that no one thought about having multiple versions of the same
+     * application installed? What are we supposed to do if we want
+     * a) have desktop files installed by appimaged not interfere with desktop files
+     *    provided by the system, i.e., if an application is installed in the system
+     *    and the user also installs the AppImage, then both should be available to the user
+     * b) both should be D-Bus activatable
+     * c) the one installed by appimaged should have an AppImage vendor prefix to make
+     *    it easy to distinguish it from system- or upstream-provided ones
+     */
+
+    /* FIXME: The following is most likely not correct; see the comments above.
+     * Open a GitHub issue or send a pull request if you would like to propose asolution. */
+    /* TODO: Check for consistency of the id with the AppStream file, if it exists in the AppImage */
+    gchar *destination = build_installed_desktop_file_path(md5, desktop_filename);
+
+    /* When appimaged sees itself, then do nothing here */
+    if(strcmp ("appimaged.desktop", desktop_filename) == 0) {
+        g_free(destination);
+#ifdef STANDALONE
+        fprintf(stderr, "appimaged's desktop file found -- not installing desktop file for myself\n");
+#endif
+        return true;
+    }
+
+    if(verbose)
+        fprintf(stderr, "install: %s\n", destination);
+
+    gchar *dirname = g_path_get_dirname(destination);
+    if(g_mkdir_with_parents(dirname, 0755)) {
+#ifdef STANDALONE
+        fprintf(stderr, "Could not create directory: %s\n", dirname);
+#endif
+    }
+    g_free(dirname);
+
+    // g_key_file_save_to_file(key_file_structure, destination, NULL);
+    // g_key_file_save_to_file is too new, only since 2.40
+    /* Write config file on disk */
+    gsize length;
+    gchar *buf;
+    GIOChannel *file;
+    buf = g_key_file_to_data(key_file_structure, &length, NULL);
+    file = g_io_channel_new_file(destination, "w", NULL);
+    g_io_channel_write_chars(file, buf, length, NULL, NULL);
+    g_io_channel_shutdown(file, TRUE, NULL);
+    g_io_channel_unref(file);
+
+    g_free(buf);
+
+    /* GNOME shows the icon and name on the desktop file only if it is executable */
+    chmod(destination, 0755);
+
+    g_free(destination);
+
+    return true;
+}
+
+bool appimage_type1_get_desktop_filename_and_key_file(struct archive** a, gchar** desktop_filename, GKeyFile** key_file) {
+    // iterate over all files ("entries") in the archive
+    // looking for a file with .desktop extension in the root directory
+
+    // must not be freed
+    struct archive_entry* entry;
+
+    gchar* filename;
+
+    for (;;) {
+        int r = archive_read_next_header(*a, &entry);
+
+        if (r == ARCHIVE_EOF) {
+            return false;
+        }
+
+        if (r != ARCHIVE_OK) {
+            fprintf(stderr, "%s\n", archive_error_string(*a));
+            return false;
+        }
+
+        /* Skip all but regular files; FIXME: Also handle symlinks correctly */
+        if (archive_entry_filetype(entry) != AE_IFREG)
+            continue;
+
+        filename = replace_str(archive_entry_pathname(entry), "./", "");
+
+        /* Get desktop file(s) in the root directory of the AppImage and act on it in one go */
+        if ((g_str_has_suffix(filename, ".desktop") && (NULL == strstr(filename, "/")))) {
+#ifdef STANDALONE
+            fprintf(stderr, "Got root desktop: %s\n", filename);
+#endif
+
+            const void* buff;
+
+            size_t size = 1024 * 1024;
+            int64_t offset = 0;
+
+            r = archive_read_data_block(*a, &buff, &size, &offset);
+
+            if (r == ARCHIVE_EOF) {
+                // cleanup
+                g_free(filename);
+
+                return true;
+            }
+
+            if (r != ARCHIVE_OK) {
+                fprintf(stderr, "%s", archive_error_string(*a));
+                break;
+            }
+
+            *desktop_filename = g_path_get_basename(filename);
+
+            // a structure that will hold the information from the desktop file
+            *key_file = g_key_file_new();
+
+            gboolean success = g_key_file_load_from_data(*key_file, buff, size,
+                G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
+
+            if (!success) {
+                // cleanup
+                g_free(key_file);
+                key_file = NULL;
+
+                break;
+            }
+
+            // cleanup
+            g_free(filename);
+
+            return true;
+        }
+    }
+
+    g_free(filename);
+
+    return false;
+}
+
+/* Register a type 1 AppImage in the system
+ * DEPRECATED, it should be removed ASAP
+ * */
+bool appimage_type1_register_in_system(const char *path, bool verbose)
+{
+    return appimage_register_in_system(path, verbose) == 0;
+}
+
+bool appimage_type2_get_desktop_filename_and_key_file(sqfs* fs, gchar** desktop_filename, gchar* md5, GKeyFile** key_file, gboolean verbose) {
+    /* TOOO: Change so that only one run of squash_get_matching_files is needed in total,
+     * this should hopefully improve performance */
+
+    /* Get desktop file(s) in the root directory of the AppImage */
+    // Only in root dir
+    gchar** str_array = squash_get_matching_files_install_icons_and_mime_data(fs, "(^[^/]*?.desktop$)", "", md5, verbose);
+
+    bool errored = false;
+
+    // gchar **str_array = squash_get_matching_files(&fs, "(^.*?.desktop$)", md5, verbose); // Not only there
+    /* Work trough the NULL-terminated array of strings */
+    for (int i = 0; str_array[i]; ++i) {
+#ifdef STANDALONE
+        fprintf(stderr, "Got root desktop: %s\n", str_array[i]);
+#endif
+
+        if (!g_key_file_load_from_squash(fs, str_array[i], *key_file, verbose))
+            errored = true;
+        else
+            *desktop_filename = g_path_get_basename(str_array[i]);
+    }
+
+    /* Free the NULL-terminated array of strings and its contents */
+    g_strfreev(str_array);
+
+    return !errored;
+}
+
+/* Register a type 2 AppImage in the system
+ * DEPRECATED it should be removed ASAP
+ * */
+bool appimage_type2_register_in_system(const char *path, bool verbose) {
+    return appimage_register_in_system(path, verbose) == 0;
+}
+
+int appimage_type1_is_terminal_app(const char* path) {
+    // check if file exists
+    if (!g_file_test(path, G_FILE_TEST_IS_REGULAR))
+        return -1;
+
+    // check if file is of correct type
+    if (appimage_get_type(path, false) != 1)
+        return -1;
+
+    char* md5 = appimage_get_md5(path);
+
+    if (md5 == NULL)
+        return -1;
+
+    // open ISO9660 image using libarchive
+    struct archive *a = archive_read_new();
+    archive_read_support_format_iso9660(a);
+
+    // libarchive status int -- passed to called functions
+    int r;
+
+    if ((r = archive_read_open_filename(a, path, 10240)) != ARCHIVE_OK) {
+        // cleanup
+        free(md5);
+        archive_read_free(a);
+
+        return -1;
+    }
+    // search image for root desktop file, and read it into key file structure so it can be edited eventually
+    gchar *desktop_filename = NULL;
+    GKeyFile *key_file = NULL;
+
+    if (!appimage_type1_get_desktop_filename_and_key_file(&a, &desktop_filename, &key_file)) {
+        // cleanup
+        free(md5);
+        archive_read_free(a);
+        g_free(desktop_filename);
+        g_key_file_free(key_file);
+
+        return -1;
+    }
+
+    // validate that both have been set to a non-NULL value
+    if (desktop_filename == NULL || key_file == NULL) {
+        // cleanup
+        free(md5);
+        archive_read_free(a);
+        g_free(desktop_filename);
+        g_key_file_free(key_file);
+
+        return -1;
+    }
+
+    GError *error = NULL;
+    gboolean rv = g_key_file_get_boolean(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TERMINAL, &error);
+
+    // cleanup
+    free(md5);
+    archive_read_free(a);
+    g_free(desktop_filename);
+    g_key_file_free(key_file);
+
+    int result;
+
+    if (!rv) {
+        // if the key file hasn't been found and the error is not set to NOT_FOUND, return an error
+        if (error != NULL && error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND)
+            result = -1;
+        else
+            result = 0;
+    } else {
+        result = 1;
+    }
+
+    if (error != NULL)
+        g_error_free(error);
+
+    return result;
+};
+
+int appimage_type2_is_terminal_app(const char* path) {
+    // check if file exists
+    if (!g_file_test(path, G_FILE_TEST_IS_REGULAR))
+        return -1;
+
+    // check if file is of correct type
+    if (appimage_get_type(path, false) != 2)
+        return -1;
+
+    char* md5 = appimage_get_md5(path);
+
+    if (md5 == NULL)
+        return -1;
+
+    ssize_t fs_offset = appimage_get_elf_size(path);
+
+    // error check
+    if (fs_offset < 0)
+        return -1;
+
+    sqfs fs;
+
+    sqfs_err err = sqfs_open_image(&fs, path, (size_t) fs_offset);
+
+    if (err != SQFS_OK) {
+        free(md5);
+        sqfs_destroy(&fs);
+        return -1;
+    }
+
+    gchar* desktop_filename = NULL;
+
+    // a structure that will hold the information from the desktop file
+    GKeyFile* key_file = g_key_file_new();
+
+    if (!appimage_type2_get_desktop_filename_and_key_file(&fs, &desktop_filename, md5, &key_file, false)) {
+        // cleanup
+        free(md5);
+        free(desktop_filename);
+        sqfs_destroy(&fs);
+        g_key_file_free(key_file);
+
+        return -1;
+    }
+
+    // validate that both have been set to a non-NULL value
+    if (desktop_filename == NULL || key_file == NULL) {
+        // cleanup
+        free(md5);
+        sqfs_destroy(&fs);
+        g_free(desktop_filename);
+        g_key_file_free(key_file);
+
+        return -1;
+    }
+
+    // no longer used
+    free(md5);
+
+    GError *error = NULL;
+    gboolean rv = g_key_file_get_boolean(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TERMINAL, &error);
+
+    // cleanup
+    free(desktop_filename);
+    sqfs_destroy(&fs);
+    g_key_file_free(key_file);
+
+    int result;
+
+    if (!rv) {
+        // if the key file hasn't been found and the error is not set to NOT_FOUND, return an error
+        if (error != NULL && error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND)
+            result = -1;
+        else
+            result = 0;
+    } else {
+        result = 1;
+    }
+
+    if (error != NULL)
+        g_error_free(error);
+
+    return result;
+};
+
+/*
+ * Checks whether an AppImage's desktop file has set Terminal=true.
+ * Useful to check whether the author of an AppImage doesn't want it to be integrated.
+ *
+ * Returns >0 if set, 0 if not set, <0 on errors.
+ */
+int appimage_is_terminal_app(const char *path) {
+    // check if file exists
+    if (!g_file_test(path, G_FILE_TEST_IS_REGULAR))
+        return -1;
+
+    int type = appimage_get_type(path, false);
+
+    switch (type) {
+        case 1:
+            return appimage_type1_is_terminal_app(path);
+        case 2:
+            return appimage_type2_is_terminal_app(path);
+        default:
+            return -1;
+    }
+}
+
+int appimage_type1_shall_not_be_integrated(const char* path) {
+    // check if file exists
+    if (!g_file_test(path, G_FILE_TEST_IS_REGULAR))
+        return -1;
+
+    // check if file is of correct type
+    if (appimage_get_type(path, false) != 1)
+        return -1;
+
+    char* md5 = appimage_get_md5(path);
+
+    if (md5 == NULL)
+        return -1;
+
+    // open ISO9660 image using libarchive
+    struct archive *a = archive_read_new();
+    archive_read_support_format_iso9660(a);
+
+    // libarchive status int -- passed to called functions
+    int r;
+
+    if ((r = archive_read_open_filename(a, path, 10240)) != ARCHIVE_OK) {
+        // cleanup
+        free(md5);
+        archive_read_free(a);
+
+        return -1;
+    }
+    // search image for root desktop file, and read it into key file structure so it can be edited eventually
+    gchar *desktop_filename = NULL;
+    GKeyFile *key_file = NULL;
+
+    if (!appimage_type1_get_desktop_filename_and_key_file(&a, &desktop_filename, &key_file)) {
+        // cleanup
+        free(md5);
+        archive_read_free(a);
+        g_free(desktop_filename);
+        g_key_file_free(key_file);
+
+        return -1;
+    }
+
+    // validate that both have been set to a non-NULL value
+    if (desktop_filename == NULL || key_file == NULL) {
+        // cleanup
+        free(md5);
+        archive_read_free(a);
+        g_free(desktop_filename);
+        g_key_file_free(key_file);
+
+        return -1;
+    }
+
+    GError *error = NULL;
+    gboolean rv = g_key_file_get_boolean(key_file, G_KEY_FILE_DESKTOP_GROUP, "X-AppImage-Integrate", &error);
+
+    // cleanup
+    free(md5);
+    archive_read_free(a);
+    g_free(desktop_filename);
+    g_key_file_free(key_file);
+
+    int result;
+
+    if (!rv) {
+        // if the key file hasn't been found and the error is not set to NOT_FOUND, return an error
+        if (error != NULL) {
+            if (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND)
+                result = 0;
+            else
+                result = -1;
+        }
+        else {
+            result = 1;
+        }
+    } else {
+        result = 0;
+    }
+
+    if (error != NULL)
+        g_error_free(error);
+
+    return result;
+};
+
+int appimage_type2_shall_not_be_integrated(const char* path) {
+    // check if file exists
+    if (!g_file_test(path, G_FILE_TEST_IS_REGULAR))
+        return -1;
+
+    // check if file is of correct type
+    if (appimage_get_type(path, false) != 2)
+        return -1;
+
+    char* md5 = appimage_get_md5(path);
+
+    if (md5 == NULL)
+        return -1;
+
+    ssize_t fs_offset = appimage_get_elf_size(path);
+
+    if (fs_offset < 0)
+        return -1;
+
+    sqfs fs;
+
+    sqfs_err err = sqfs_open_image(&fs, path, (size_t) fs_offset);
+
+    if (err != SQFS_OK) {
+        free(md5);
+        sqfs_destroy(&fs);
+        return -1;
+    }
+
+    gchar* desktop_filename = NULL;
+
+    // a structure that will hold the information from the desktop file
+    GKeyFile* key_file = g_key_file_new();
+
+    if (!appimage_type2_get_desktop_filename_and_key_file(&fs, &desktop_filename, md5, &key_file, false)) {
+        // cleanup
+        free(md5);
+        free(desktop_filename);
+        sqfs_destroy(&fs);
+        g_key_file_free(key_file);
+
+        return -1;
+    }
+
+    // validate that both have been set to a non-NULL value
+    if (desktop_filename == NULL || key_file == NULL) {
+        // cleanup
+        free(md5);
+        sqfs_destroy(&fs);
+        g_free(desktop_filename);
+        g_key_file_free(key_file);
+
+        return -1;
+    }
+
+    // no longer used
+    free(md5);
+
+    GError *error = NULL;
+    gboolean rv = g_key_file_get_boolean(key_file, G_KEY_FILE_DESKTOP_GROUP, "X-AppImage-Integrate", &error);
+
+    // cleanup
+    free(desktop_filename);
+    sqfs_destroy(&fs);
+    g_key_file_free(key_file);
+
+    int result;
+
+    if (!rv) {
+        // if the key file hasn't been found and the error is not set to NOT_FOUND, return an error
+        if (error != NULL) {
+            if (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND)
+                result = 0;
+            else
+                result = -1;
+        } else {
+            result = 1;
+        }
+    } else {
+        result = 0;
+    }
+
+    if (error != NULL)
+        g_error_free(error);
+
+    return result;
+};
+
+/*
+ * Checks whether an AppImage's desktop file has set X-AppImage-Integrate=false.
+ * Useful to check whether the author of an AppImage doesn't want it to be integrated.
+ *
+ * Returns >0 if set, 0 if not set, <0 on errors.
+ */
+int appimage_shall_not_be_integrated(const char *path) {
+    // check if file exists
+    if (!g_file_test(path, G_FILE_TEST_IS_REGULAR))
+        return -1;
+
+    int type = appimage_get_type(path, false);
+
+    switch (type) {
+        case 1:
+            return appimage_type1_shall_not_be_integrated(path);
+        case 2:
+            return appimage_type2_shall_not_be_integrated(path);
+        default:
+            return -1;
+    }
+}
+
+char* appimage_registered_desktop_file_path(const char *path, char *md5, bool verbose) {
+    glob_t pglob = {};
+
+    // if md5 has been calculated before, we can just use it to save these extra calculations
+    // if not, we need to calculate it here
+    if (md5 == NULL)
+        md5 = appimage_get_md5(path);
+
+    // sanity check
+    if (md5 == NULL) {
+        if (verbose)
+            fprintf(stderr, "appimage_get_md5() failed\n");
+        return NULL;
+    }
+
+    char *data_home = xdg_data_home();
+
+    // TODO: calculate this value exactly
+    char *glob_pattern = malloc(PATH_MAX);
+    sprintf(glob_pattern, "%s/applications/appimagekit_%s-*.desktop", data_home, md5);
+
+    glob(glob_pattern, 0, NULL, &pglob);
+
+    char* rv = NULL;
+
+    if (pglob.gl_pathc <= 0) {
+        if (verbose) {
+            fprintf(stderr, "No results found by glob()");
+        }
+    } else if (pglob.gl_pathc >= 1) {
+        if (pglob.gl_pathc > 1 && verbose) {
+            fprintf(stderr, "Too many results returned by glob(), returning first result found");
+        }
+
+        // need to copy value to be able to globfree() later on
+        rv = strdup(pglob.gl_pathv[0]);
+    }
+
+    globfree(&pglob);
+
+    return rv;
+};
+
+/* Check whether AppImage is registered in the system already */
+bool appimage_is_registered_in_system(const char* path) {
+    // To check whether an AppImage has been integrated, we just have to check whether the desktop file is in place
+
+    if (!g_file_test(path, G_FILE_TEST_IS_REGULAR))
+        return false;
+
+    gchar* md5 = appimage_get_md5(path);
+
+    GKeyFile* key_file = g_key_file_new();
+    gchar* desktop_file_path = appimage_registered_desktop_file_path(path, md5, false);
+
+    bool rv = true;
+
+    if (!g_file_test(desktop_file_path, G_FILE_TEST_IS_REGULAR))
+        rv = false;
+
+    g_free(md5);
+    g_free(desktop_file_path);
+    g_key_file_free(key_file);
+
+    return rv;
+}
+
+/*
+ * Register an AppImage in the system
+ * Returns 0 on success, non-0 otherwise.
+ */
+int appimage_register_in_system(const char* path, bool verbose) {
+    if ((g_str_has_suffix(path, ".part")) ||
+        g_str_has_suffix(path, ".tmp") ||
+        g_str_has_suffix(path, ".download") ||
+        g_str_has_suffix(path, ".zs-old") ||
+        g_str_has_suffix(path, ".~")
+        ) {
+        return 1;
+    }
+
+    int type = appimage_get_type(path, verbose);
+    bool succeed = true;
+
+    if (type != -1) {
+#ifdef STANDALONE
+        fprintf(stderr, "\n-> Registering type %d AppImage: %s\n", type, path);
+#endif
+        appimage_create_thumbnail(path, false);
+
+        char* temp_dir = desktop_integration_create_tempdir();
+        char* md5 = appimage_get_md5(path);
+        char* data_home = xdg_data_home();
+
+        // Files are extracted to a temporary dir to avoid several traversals on the AppImage file
+        // Also, they need to be edited by us anyway, and to avoid confusing desktop environments with
+        // too many different desktop files, we edit them beforehand and move them into their target
+        // destination afterwards only.
+        // (Yes, it _could_ probably be done without tempfiles, but given the amount of desktop registrations,
+        // we consider the file I/O overhead to be acceptable.)
+        desktop_integration_extract_relevant_files(path, temp_dir);
+        succeed = succeed && desktop_integration_modify_desktop_file(path, temp_dir, md5);
+        succeed = succeed && desktop_integration_move_files_to_user_data_dir(temp_dir, data_home, md5);
+        desktop_integration_remove_tempdir(temp_dir);
+
+        free(data_home);
+        free(md5);
+        free(temp_dir);
+    } else {
+#ifdef STANDALONE
+        fprintf(stderr, "Error: unknown AppImage type %d\n", type);
+#endif
+        if (verbose)
+            fprintf(stderr, "-> Skipping file %s\n", path);
+        return 0;
+    }
+
+    return succeed ? 0 : 1;
+}
+
+/* Delete the thumbnail for a given file and size if it exists */
+void delete_thumbnail(char *path, char *size, gboolean verbose)
+{
+    gchar *thumbnail_path = get_thumbnail_path(path, size, verbose);
+    if(verbose)
+        fprintf(stderr, "get_thumbnail_path: %s\n", thumbnail_path);
+    if(g_file_test(thumbnail_path, G_FILE_TEST_IS_REGULAR)){
+        g_unlink(thumbnail_path);
+        if(verbose)
+            fprintf(stderr, "deleted: %s\n", thumbnail_path);
+    }
+    g_free(thumbnail_path);
+}
+
+/* Recursively delete files in path and subdirectories that contain the given md5
+ */
+void unregister_using_md5_id(const char *name, int level, char* md5, gboolean verbose)
+{
+    DIR *dir;
+    struct dirent *entry;
+
+    if (!(dir = opendir(name)))
+        return;
+    if (!(entry = readdir(dir)))
+        return;
+
+    do {
+        if (entry->d_type == DT_DIR) {
+            char path[1024];
+            int len = snprintf(path, sizeof(path)-1, "%s/%s", name, entry->d_name);
+            path[len] = 0;
+            if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
+                continue;
+            unregister_using_md5_id(path, level + 1, md5, verbose);
+        }
+
+        else {
+            gchar *needle = g_strdup_printf("%s_%s", vendorprefix, md5);
+            if(strstr(entry->d_name, needle)) {
+                    gchar *path_to_be_deleted = g_strdup_printf("%s/%s", name, entry->d_name);
+                    if(g_file_test(path_to_be_deleted, G_FILE_TEST_IS_REGULAR)){
+                        g_unlink(path_to_be_deleted);
+                        if(verbose)
+                            fprintf(stderr, "deleted: %s\n", path_to_be_deleted);
+                    }
+                    g_free(path_to_be_deleted);
+                }
+            g_free(needle);
+        }
+    } while ((entry = readdir(dir)) != NULL);
+    closedir(dir);
+}
+
+
+/* Unregister an AppImage in the system */
+int appimage_unregister_in_system(const char *path, bool verbose)
+{
+    char *md5 = appimage_get_md5(path);
+
+    /* The file is already gone by now, so we can't determine its type anymore */
+#ifdef STANDALONE
+    fprintf(stderr, "_________________________\n");
+    fprintf(stderr, "\n");
+    fprintf(stderr, "-> UNREGISTER %s\n", path);
+#endif
+    /* Could use gnome_desktop_thumbnail_factory_lookup instead of the next line */
+
+    /* Delete the thumbnails if they exist */
+    delete_thumbnail(path, "normal", verbose); // 128x128
+    delete_thumbnail(path, "large", verbose); // 256x256
+
+    char* data_home = xdg_data_home();
+    unregister_using_md5_id(data_home, 0, md5, verbose);
+    g_free(data_home);
+
+    g_free(md5);
+
+    return 0;
+}
+
+bool move_file(const char* source, const char* target) {
+    g_type_init();
+    bool succeed = true;
+    GError *error = NULL;
+    GFile *icon_file = g_file_new_for_path(source);
+    GFile *target_file = g_file_new_for_path(target);
+    if (!g_file_move (icon_file, target_file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error)) {
+#ifdef STANDALONE
+        fprintf(stderr, "Error moving file: %s\n", error->message);
+#endif
+        succeed = false;
+        g_clear_error (&error);
+    }
+
+    g_object_unref(icon_file);
+    g_object_unref(target_file);
+
+    return succeed;
+}
+
+struct extract_appimage_file_command_data {
+    const char *path;
+    const char *destination;
+    char *link;
+};
+struct read_appimage_file_into_buffer_command_data {
+    char* file_path;
+    char* out_buffer;
+    char* link_path;
+    unsigned long out_buf_size;
+    bool success;
+};
+
+void extract_appimage_file_command(void* handler_data, void* entry_data, void* user_data) {
+    appimage_handler* h = handler_data;
+    struct extract_appimage_file_command_data * params = user_data;
+
+    char* filename = h->get_file_name(h, entry_data);
+    if (strcmp(params->path, filename) == 0) {
+        params->link = h->get_file_link(handler_data, entry_data);
+
+        h->extract_file(h, entry_data, params->destination);
+    }
+
+
+    free(filename);
+}
+
+void read_appimage_file_into_buffer_command(void* handler_data, void* entry_data, void* user_data) {
+    appimage_handler* h = handler_data;
+    struct read_appimage_file_into_buffer_command_data* params = user_data;
+
+    if (h->read_file_into_new_buffer == NULL) {
+#ifdef STANDALONE
+        fprintf(stderr, "read_file_into_new_buffer is NULL, go fix that!\n");
+#endif
+        return;
+    }
+
+    char* filename = h->get_file_name(h, entry_data);
+    if (strcmp(params->file_path, filename) == 0) {
+        params->link_path = h->get_file_link(h, entry_data);
+        params->success = h->read_file_into_new_buffer(h, entry_data, &params->out_buffer, &(params->out_buf_size));
+    }
+
+
+    free(filename);
+}
+
+void extract_appimage_icon_command(void *handler_data, void *entry_data, void *user_data) {
+    appimage_handler *h = handler_data;
+    struct archive_entry *entry = entry_data;
+    gchar *path = user_data;
+
+    char *filename = h->get_file_name(h, entry);
+    if (strcmp(".DirIcon", filename) == 0)
+        h->extract_file(h, entry, path);
+
+    free(filename);
+}
+
+void extract_appimage_icon(appimage_handler *h, gchar *target) {
+    h->traverse(h, extract_appimage_icon_command, target);
+}
+
+/* Create AppImage thumbanil according to
+ * https://specifications.freedesktop.org/thumbnail-spec/0.8.0/index.html
+ */
+void appimage_create_thumbnail(const char *appimage_file_path, bool verbose) {
+    // extract AppImage icon to /tmp
+    appimage_handler handler = create_appimage_handler(appimage_file_path);
+
+    char *tmp_path = "/tmp/appimage_thumbnail_tmp";
+    extract_appimage_icon(&handler, tmp_path);
+
+    if (g_file_test(tmp_path, G_FILE_TEST_EXISTS) ) {
+        // TODO: transform it to png with sizes 128x128 and 254x254
+        gchar *target_path = get_thumbnail_path(appimage_file_path, "normal", verbose);
+
+        mk_base_dir(target_path);
+
+        // deploy icon as thumbnail
+        move_file (tmp_path, target_path);
+
+        // clean up
+        g_free(target_path);
+    } else {
+#ifdef STANDALONE
+        fprintf(stderr, "ERROR: Icon file not extracted: %s", tmp_path);
+#endif
+    }
+
+}
+
+void appimage_extract_file_following_symlinks(const gchar* appimage_file_path, const char* file_path,
+                                              const char* target_file_path) {
+
+    struct extract_appimage_file_command_data data;
+    data.link = strdup(file_path);
+    data.destination = target_file_path;
+
+    bool looping = false;
+    GSList* visited_entries = NULL;
+
+    do {
+        visited_entries = g_slist_prepend(visited_entries, data.link);
+        data.path = data.link;
+        data.link = NULL;
+
+        appimage_handler handler = create_appimage_handler(appimage_file_path);
+        handler.traverse(&handler, extract_appimage_file_command, &data);
+
+        if (data.link != NULL) {
+            if (visited_entries != NULL && g_slist_find_custom(visited_entries, data.link, (GCompareFunc) strcmp))
+                looping = true;
+
+            g_remove(target_file_path);
+        }
+
+    } while (data.link != NULL && !looping);
+
+    if (visited_entries != NULL)
+        g_slist_free_full(visited_entries, free);
+}
+
+bool appimage_read_file_into_buffer_following_symlinks(const char* appimage_file_path, const char* file_path,
+                                                       char** buffer, unsigned long* buf_size) {
+
+    struct read_appimage_file_into_buffer_command_data data;
+    data.link_path = strdup(file_path);
+
+    data.out_buffer = NULL;
+    GSList *visited_entries = NULL;
+
+    do {
+        visited_entries = g_slist_prepend(visited_entries, data.link_path);
+
+        // prepare an empty struct
+        data.file_path = data.link_path;
+        data.link_path = NULL;
+
+        // release any data that could be allocated in previous iterations
+        if (data.out_buffer != NULL) {
+            free(data.out_buffer);
+            data.out_buffer = NULL;
+            data.out_buf_size = 0;
+        }
+
+        data.success = false;
+
+        appimage_handler handler = create_appimage_handler(appimage_file_path);
+        handler.traverse(&handler, &read_appimage_file_into_buffer_command, &data);
+
+        // Find loops
+        if (data.link_path != NULL && visited_entries &&
+            g_slist_find_custom(visited_entries, data.link_path, (GCompareFunc) strcmp))
+                data.success = false;
+    } while (data.success && data.link_path != NULL);
+
+    if (visited_entries != NULL)
+        g_slist_free_full(visited_entries, free);
+
+    if (!data.success) {
+        free(data.out_buffer);
+
+        *buffer = NULL;
+        *buf_size = 0;
+    } else {
+        *buffer = data.out_buffer;
+        *buf_size = data.out_buf_size;
+    }
+
+    return data.success;
+}
+
+void extract_appimage_file_name(void *handler_data, void *entry_data, void *user_data) {
+    appimage_handler *h = handler_data;
+    struct archive_entry *entry = entry_data;
+    GList **list = user_data;
+
+    char *filename = h->get_file_name(h, entry);
+
+    GList* ptr = g_list_find_custom (*list, filename, g_strcmp0);
+
+    if (ptr == NULL)
+        *list = g_list_append(*list, filename);
+    else
+        free(filename);
+}
+
+
+char** appimage_list_files(const char *path) {
+    GList *list = NULL;
+    appimage_handler handler = create_appimage_handler(path);
+
+    handler.traverse(&handler, extract_appimage_file_name, &list);
+
+    int n = g_list_length(list);
+    char **result = malloc(sizeof(char*) * (n+1) );
+    result[n] = NULL;
+
+    GList *itr = list;
+    for (int i = 0; i < n; i ++) {
+        result[i] = (char *) itr->data;
+        itr = itr->next;
+    }
+
+
+    g_list_free(list);
+
+    return result;
+}
+
+void appimage_string_list_free(char** list) {
+    for (char **ptr = list; ptr != NULL && *ptr != NULL; ptr ++)
+        free(*ptr);
+
+    free(list);
+}
+
+
+/* Check if a file is an AppImage. Returns the image type if it is, or -1 if it isn't */
+int appimage_get_type(const char* path, bool verbose)
+{
+    FILE *f = fopen(path, "rt");
+    if (f != NULL)
+    {
+        char buffer[3] = {0};
+
+        /* Check magic bytes at offset 8 */
+        fseek(f, 8, SEEK_SET);
+        fread(buffer, 1, 3, f);
+        fclose(f);
+        if(match_type_1_magic_bytes(buffer)){
+#ifdef STANDALONE
+            fprintf(stderr, "_________________________\n");
+#endif
+            if(verbose){
+                fprintf(stderr, "AppImage type 1\n");
+            }
+            return 1;
+        } else if((buffer[0] == 0x41) && (buffer[1] == 0x49) && (buffer[2] == 0x02)){
+#ifdef STANDALONE
+            fprintf(stderr, "_________________________\n");
+#endif
+            if(verbose){
+                fprintf(stderr, "AppImage type 2\n");
+            }
+            return 2;
+        } else {
+            if (is_iso_9660_file(path) && (appimage_get_elf_size(path) != -1)) {
+#ifdef STANDALONE
+                fprintf(stderr, "_________________________\n");
+#endif
+                if (verbose) {
+                    fprintf(stderr, "This file seems to be an AppImage type 1 without magic bytes\n");
+                    fprintf(stderr, "The AppImage author should embed the magic bytes,"
+                                    " see https://github.com/AppImage/AppImageSpec\n");
+                }
+                return 1;
+            } else {
+#ifdef STANDALONE
+                fprintf(stderr, "_________________________\n");
+#endif
+                if(verbose){
+                    fprintf(stderr, "Unrecognized file '%s'\n", path);
+                }
+                return -1;
+            }
+        }
+    }
+    return -1;
+}
diff --git a/src/libappimage/libappimage_private.h b/src/libappimage/libappimage_private.h
new file mode 100644 (file)
index 0000000..0aceb6a
--- /dev/null
@@ -0,0 +1 @@
+bool move_file(const char* source, const char* target);
diff --git a/src/libappimage/type1.c b/src/libappimage/type1.c
new file mode 100644 (file)
index 0000000..1b09f00
--- /dev/null
@@ -0,0 +1,205 @@
+// system includes
+#include <fcntl.h>
+
+// library includes
+#include <archive.h>
+#include <archive_entry.h>
+
+// local includes
+#include "type2.h"
+#include "type1.h"
+
+
+void appimage_type1_open(appimage_handler* handler) {
+    if (is_handler_valid(handler) && !handler->is_open) {
+#ifdef STANDALONE
+        fprintf(stderr, "Opening %s as Type 1 AppImage\n", handler->path);
+#endif
+        struct archive* a;
+        a = archive_read_new();
+        archive_read_support_format_iso9660(a);
+        if (archive_read_open_filename(a, handler->path, 10240) != ARCHIVE_OK) {
+            fprintf(stderr, "%s", archive_error_string(a));
+            handler->cache = NULL;
+            handler->is_open = false;
+        } else {
+            handler->cache = a;
+            handler->is_open = true;
+        }
+    }
+}
+
+void appimage_type1_close(appimage_handler* handler) {
+    if (is_handler_valid(handler) && handler->is_open) {
+#ifdef STANDALONE
+        fprintf(stderr, "Closing %s\n", handler->path);
+#endif
+        struct archive* a = handler->cache;
+        archive_read_close(a);
+        archive_read_free(a);
+
+        handler->cache = NULL;
+        handler->is_open = false;
+    }
+}
+
+void type1_traverse(appimage_handler* handler, traverse_cb command, void* command_data) {
+    appimage_type1_open(handler);
+
+    if (!command) {
+#ifdef STANDALONE
+        fprintf(stderr, "No traverse command set.\n");
+#endif
+        return;
+    }
+
+    if (handler->is_open) {
+        struct archive* a = handler->cache;
+        struct archive_entry* entry;
+        int r;
+
+        for (;;) {
+            r = archive_read_next_header(a, &entry);
+            if (r == ARCHIVE_EOF) {
+                break;
+            }
+            if (r != ARCHIVE_OK) {
+                fprintf(stderr, "%s\n", archive_error_string(a));
+                break;
+            }
+
+            /* Skip all but regular files; FIXME: Also handle symlinks correctly */
+            if (archive_entry_filetype(entry) != AE_IFREG) {
+                continue;
+            }
+
+            command(handler, entry, command_data);
+        }
+    }
+
+    appimage_type1_close(handler);
+}
+
+// TODO: remove forward declaration
+gchar* replace_str(const gchar* src, const gchar* find, const gchar* replace);
+
+char* type1_get_file_name(appimage_handler* handler, void* data) {
+    (void) handler;
+
+    struct archive_entry* entry = (struct archive_entry*) data;
+
+    char* filename = replace_str(archive_entry_pathname(entry), "./", "");
+    return filename;
+}
+
+void type1_extract_file(appimage_handler* handler, void* data, const char* target) {
+    (void) data;
+
+    struct archive* a = handler->cache;
+    mk_base_dir(target);
+
+    mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+    int f = open(target, O_WRONLY | O_CREAT | O_TRUNC, mode);
+
+    if (f == -1){
+#ifdef STANDALONE
+        fprintf(stderr, "open error: %s\n", target);
+#endif
+        return;
+    }
+
+    archive_read_data_into_fd(a, f);
+    close(f);
+}
+
+bool type1_read_file_into_buf(struct appimage_handler* handler, void* data, char** buffer, unsigned long* buf_size) {
+    (void) data;
+
+    struct archive* a = handler->cache;
+
+    struct archive_entry* entry = data;
+
+    int64_t file_size = archive_entry_size(entry);
+
+    char* new_buffer = (char*) malloc(sizeof(char) * file_size);
+
+    if (new_buffer == NULL) {
+#ifdef STANDALONE
+        fprintf(stderr, "failed to allocate enough memory for buffer (required: %ul bytes)\n", file_size);
+#endif
+        return false;
+    }
+
+    if (archive_read_data(a, new_buffer, (size_t) file_size) < 0) {
+#ifdef STANDALONE
+        fprintf(stderr, "failed to read data into buffer: %s\n", archive_error_string(a));
+#endif
+        free(new_buffer);
+        return false;
+    }
+
+    *buffer = new_buffer;
+    *buf_size = (unsigned long) file_size;
+    return true;
+}
+
+char* type1_get_file_link(struct appimage_handler* handler, void* entry_ptr) {
+    struct archive_entry* entry = entry_ptr;
+
+    const char* link_path = archive_entry_symlink(entry) ?: archive_entry_hardlink(entry);
+
+    if (link_path) {
+        char* filename = replace_str(link_path, "./", "");
+        return filename;
+    }
+
+    return NULL;
+}
+
+appimage_handler appimage_type_1_create_handler() {
+    appimage_handler h;
+    h.traverse = type1_traverse;
+    h.get_file_name = type1_get_file_name;
+    h.extract_file = type1_extract_file;
+    h.get_file_link = type1_get_file_link;
+    h.read_file_into_new_buffer = type1_read_file_into_buf;
+    h.type = 1;
+
+    return h;
+}
+
+bool match_type_1_magic_bytes(const char* buffer) {
+    return buffer[0] == 0x41 && buffer[1] == 0x49 && buffer[2] == 0x01;
+}
+
+bool is_iso_9660_file(const char* path) {
+    /* Implementation of the signature matches expressed at https://www.garykessler.net/library/file_sigs.html
+     * Signature: 43 44 30 30 31               = "CD001"
+     * ISO             ISO-9660 CD Disc Image
+     * This signature usually occurs at byte offset 32769 (0x8001),
+     * 34817 (0x8801), or 36865 (0x9001).
+     * More information can be found at MacTech or at ECMA.
+     */
+
+    bool res = false;
+    FILE* f = fopen(path, "rt");
+    if (f != NULL) {
+        char buffer[5] = {0};
+
+        int positions[] = {32769, 34817, 36865};
+        const char signature[] = "CD001";
+        for (int i = 0; i < 3 && !res; i++) {
+            int fseekRes = fseek(f, positions[i], SEEK_SET);
+            if (!fseekRes) {
+                fread(buffer, 1, 5, f);
+                int strCmpRes = memcmp(signature, buffer, 5);
+                if (!strCmpRes)
+                    res = true;
+            }
+            memset(buffer, 0, 5);
+        }
+
+        fclose(f);
+    }
+    return res;
+}
diff --git a/src/libappimage/type1.h b/src/libappimage/type1.h
new file mode 100644 (file)
index 0000000..7b29e65
--- /dev/null
@@ -0,0 +1,28 @@
+#pragma once
+
+// local includes
+#include "appimage_handler.h"
+
+appimage_handler appimage_type_1_create_handler();
+
+/**
+ * According to the AppImage specification for type 1 files
+ * https://github.com/AppImage/AppImageSpec/blob/master/draft.md#type-1-image-format
+ *
+ * Match the buffer to 0x414901.
+ * @param buffer
+ * @return 1 if the values are the same, 0 otherwise
+ */
+bool match_type_1_magic_bytes(const char* buffer);
+
+/**
+ * Check for iso 9660 magic bytes.
+ *
+ * According to the AppImage specification for type 1 files
+ * https://github.com/AppImage/AppImageSpec/blob/master/draft.md#type-1-image-format
+ * the files must be valid ISO 9660 files.
+ *
+ * @param path path
+ * @return true if the file has the proper signature, false otherwise
+ */
+bool is_iso_9660_file(const char* path);
\ No newline at end of file
diff --git a/src/libappimage/type2.c b/src/libappimage/type2.c
new file mode 100644 (file)
index 0000000..ce32112
--- /dev/null
@@ -0,0 +1,250 @@
+// library includes
+#include <appimage/appimage.h>
+#include <squashfuse.h>
+#include <squashfs_fs.h>
+
+// local includes
+#include "type2.h"
+
+void appimage_type2_open(appimage_handler* handler) {
+    if (is_handler_valid(handler) && !handler->is_open) {
+#ifdef STANDALONE
+        fprintf(stderr, "Opening %s as Type 2 AppImage\n", handler->path);
+#endif
+        // The offset at which a squashfs image is expected
+        ssize_t fs_offset = appimage_get_elf_size(handler->path);
+
+        if (fs_offset < 0) {
+#ifdef STANDALONE
+            fprintf(stderr, "get_elf_size error\n");
+#endif
+            handler->is_open = false;
+            handler->cache = NULL;
+            return;
+        }
+
+        sqfs* fs = malloc(sizeof(sqfs));
+        sqfs_err err = sqfs_open_image(fs, handler->path, (size_t) fs_offset);
+        if (err != SQFS_OK) {
+#ifdef STANDALONE
+            fprintf(stderr, "sqfs_open_image error: %s\n", handler->path);
+#endif
+            free(fs);
+            handler->is_open = false;
+            handler->cache = NULL;
+        } else {
+            handler->is_open = true;
+            handler->cache = fs;
+        }
+    }
+}
+
+void appimage_type2_close(appimage_handler* handler) {
+    if (is_handler_valid(handler) && handler->is_open) {
+#ifdef STANDALONE
+        fprintf(stderr, "Closing %s\n", handler->path);
+#endif
+
+        sqfs_destroy(handler->cache);
+        free(handler->cache);
+
+        handler->is_open = false;
+        handler->cache = NULL;
+    }
+}
+
+// forward declaration, see below
+void appimage_type2_extract_symlink(sqfs* fs, sqfs_inode* inode, const char* target);
+// TODO: get rid of this forward declaration
+void squash_extract_inode_to_file(sqfs *fs, sqfs_inode *inode, const gchar *dest);
+
+void appimage_type2_extract_regular_file(sqfs* fs, sqfs_inode* inode, const char* target) {
+    mk_base_dir(target);
+
+    // Read the file in chunks
+    squash_extract_inode_to_file(fs, inode, target);
+}
+
+bool appimage_type2_resolve_symlink(sqfs* fs, sqfs_inode* inode) {
+    // no need to do anything if the passed inode is not a symlink
+    if (inode->base.inode_type != SQUASHFS_SYMLINK_TYPE)
+        return true;
+
+    // read twice: once to populate size to be able to allocate the right amount of memory, then to populate the buffer
+    size_t size;
+    sqfs_readlink(fs, inode, NULL, &size);
+
+    char buf[size];
+    int ret = sqfs_readlink(fs, inode, buf, &size);
+
+    if (ret != 0) {
+#ifdef STANDALONE
+        fprintf(stderr, "WARNING: Symlink error.");
+#endif
+        return false;
+    }
+
+    sqfs_err err = sqfs_inode_get(fs, inode, fs->sb.root_inode);
+    if (err != SQFS_OK) {
+#ifdef STANDALONE
+        fprintf(stderr, "WARNING: Unable to get the root inode. Error: %d", err);
+#endif
+        return false;
+    }
+
+    bool found = false;
+    err = sqfs_lookup_path(fs, inode, buf, &found);
+    if (err != SQFS_OK) {
+#ifdef STANDALONE
+        fprintf(stderr, "WARNING: There was an error while trying to lookup a symblink. Error: %d", err);
+#endif
+        return false;
+    }
+
+    return true;
+}
+
+bool appimage_type2_extract_file_following_symlinks(sqfs* fs, sqfs_inode* inode, const char* target) {
+    if (!appimage_type2_resolve_symlink(fs, inode)) {
+#ifdef STANDALONE
+        fprintf(stderr, "ERROR: Failed to resolve symlink");
+#endif
+        return false;
+    }
+
+    if (inode->base.inode_type != SQUASHFS_REG_TYPE && inode->base.inode_type != SQUASHFS_LREG_TYPE) {
+#ifdef STANDALONE
+        fprintf(stderr, "WARNING: Unable to extract file of type %d", inode->base.inode_type);
+#endif
+        return false;
+    }
+
+    appimage_type2_extract_regular_file(fs, inode, target);
+    return true;
+}
+
+void type2_traverse(appimage_handler* handler, traverse_cb command, void* command_data) {
+    appimage_type2_open(handler);
+
+    if (handler->is_open && handler->cache != NULL) {
+        sqfs* fs = handler->cache;
+        sqfs_traverse trv;
+        sqfs_inode_id root_inode = sqfs_inode_root(fs);
+        sqfs_err err = sqfs_traverse_open(&trv, fs, root_inode);
+        if (err != SQFS_OK) {
+#ifdef STANDALONE
+            fprintf(stderr, "sqfs_traverse_open error\n");
+#endif
+        }
+        while (sqfs_traverse_next(&trv, &err))
+            command(handler, &trv, command_data);
+
+        if (err) {
+#ifdef STANDALONE
+            fprintf(stderr, "sqfs_traverse_next error\n");
+#endif
+        }
+        sqfs_traverse_close(&trv);
+    }
+
+    appimage_type2_close(handler);
+}
+
+char* type2_get_file_name(appimage_handler* handler, void* data) {
+    (void) handler;
+    sqfs_traverse* trv = data;
+    return strdup(trv->path);
+}
+
+void type2_extract_file(appimage_handler* handler, void* data, const char* target) {
+    sqfs* fs = handler->cache;
+    sqfs_traverse* trv = data;
+
+    sqfs_inode inode;
+    if (sqfs_inode_get(fs, &inode, trv->entry.inode)) {
+#ifdef STANDALONE
+        fprintf(stderr, "sqfs_inode_get error\n");
+#endif
+    }
+
+    appimage_type2_extract_file_following_symlinks(fs, &inode, target);
+}
+
+bool type2_read_file_into_buf(struct appimage_handler* handler, void* traverse, char** buffer, unsigned long* buf_size) {
+    sqfs* fs = handler->cache;
+    sqfs_traverse* trv = traverse;
+
+    sqfs_inode inode;
+    if (sqfs_inode_get(fs, &inode, trv->entry.inode)) {
+#ifdef STANDALONE
+        fprintf(stderr, "sqfs_inode_get error\n");
+#endif
+    }
+
+    // resolve symlink if possible
+    if (!appimage_type2_resolve_symlink(fs, &inode)) {
+#ifdef STANDALONE
+        fprintf(stderr, "ERROR: Failed to resolve symlink");
+#endif
+        return false;
+    }
+
+    if (inode.base.inode_type != SQUASHFS_REG_TYPE && inode.base.inode_type != SQUASHFS_LREG_TYPE) {
+#ifdef STANDALONE
+        fprintf(stderr, "WARNING: Unable to extract file of type %d", inode->base.inode_type);
+#endif
+        return false;
+    }
+
+    uint64_t file_size = inode.xtra.reg.file_size;
+
+    char* new_buffer = (char*) malloc(sizeof(char) * file_size);
+
+    if (new_buffer == NULL) {
+#ifdef STANDALONE
+        fprintf(stderr, "failed to allocate enough memory for buffer (required: %ul bytes)\n", file_size);
+#endif
+        return false;
+    }
+
+    if (sqfs_read_range(fs, &inode, 0, (sqfs_off_t*) &file_size, new_buffer) != SQFS_OK) {
+#ifdef STANDALONE
+        fprintf(stderr, "failed to read data into buffer\n");
+#endif
+        free(new_buffer);
+        return false;
+    }
+
+    *buffer = new_buffer;
+    *buf_size = file_size;
+    return true;
+}
+
+char* type2_get_file_link(struct appimage_handler* handler, void* data) {
+    sqfs_traverse* trv = data;
+
+    sqfs_inode inode;
+    if (!sqfs_inode_get(trv->fs, &inode, trv->entry.inode))
+        return NULL;
+
+    // read twice: once to populate size to be able to allocate the right amount of memory, then to populate the buffer
+    size_t size;
+    sqfs_readlink(trv->fs, &inode, NULL, &size);
+
+    char* buf = malloc(sizeof(char) * size);
+    int ret = sqfs_readlink(trv->fs, &inode, buf, &size);
+
+    return buf;
+}
+
+appimage_handler appimage_type_2_create_handler() {
+    appimage_handler h;
+    h.traverse = type2_traverse;
+    h.get_file_name = type2_get_file_name;
+    h.get_file_link = type2_get_file_link;
+    h.extract_file = type2_extract_file;
+    h.read_file_into_new_buffer = type2_read_file_into_buf;
+    h.type = 2;
+
+    return h;
+}
diff --git a/src/libappimage/type2.h b/src/libappimage/type2.h
new file mode 100644 (file)
index 0000000..bee032b
--- /dev/null
@@ -0,0 +1,6 @@
+#pragma once
+
+// local includes
+#include "appimage_handler.h"
+
+appimage_handler appimage_type_2_create_handler();
diff --git a/src/libappimage_hashlib/CMakeLists.txt b/src/libappimage_hashlib/CMakeLists.txt
new file mode 100644 (file)
index 0000000..25006b3
--- /dev/null
@@ -0,0 +1,17 @@
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+
+set(public_header ${CMAKE_CURRENT_SOURCE_DIR}/include/hashlib.h)
+
+add_library(libappimage_hashlib STATIC md5.c ${public_header})
+set_target_properties(libappimage_hashlib PROPERTIES PREFIX "")
+target_include_directories(libappimage_hashlib
+    PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/>
+)
+
+# install libappimage
+install(TARGETS libappimage_hashlib
+    EXPORT libappimageTargets
+    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libappimage
+    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libappimage
+    PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/appimage COMPONENT libappimage-dev
+)
diff --git a/src/libappimage_hashlib/include/hashlib.h b/src/libappimage_hashlib/include/hashlib.h
new file mode 100644 (file)
index 0000000..9801b78
--- /dev/null
@@ -0,0 +1,4 @@
+#pragma once
+
+// include implementations
+#include "md5.h"
diff --git a/src/libappimage_hashlib/include/md5.h b/src/libappimage_hashlib/include/md5.h
new file mode 100644 (file)
index 0000000..ede1546
--- /dev/null
@@ -0,0 +1,34 @@
+#pragma once
+
+
+#include <stdint.h>
+#include <stdio.h>
+
+typedef struct {
+    uint32_t lo;
+    uint32_t hi;
+    uint32_t a;
+    uint32_t b;
+    uint32_t c;
+    uint32_t d;
+    uint8_t  buffer[64];
+    uint32_t block[16];
+} Md5Context;
+
+#define MD5_HASH_SIZE (128 / 8)
+
+typedef struct {
+    uint8_t bytes[MD5_HASH_SIZE];
+} MD5_HASH;
+
+// initialize new context
+void Md5Initialise(Md5Context* ctx);
+
+// add data to the context
+void Md5Update(Md5Context* ctx, void const* buf, uint32_t bufSize);
+
+// calculate final digest from context
+void Md5Finalise(Md5Context* ctx, MD5_HASH* digest);
+
+// create new context, add data from buffer to it, and calculate digest
+void Md5Calculate(void const* Buffer, uint32_t BufferSize, MD5_HASH* Digest);
diff --git a/src/libappimage_hashlib/md5.c b/src/libappimage_hashlib/md5.c
new file mode 100644 (file)
index 0000000..bb3cf18
--- /dev/null
@@ -0,0 +1,335 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//  WjCryptLib_Md5
+//
+//  Implementation of MD5 hash function. Originally written by Alexander Peslyak. Modified by WaterJuice retaining
+//  Public Domain license.
+//
+//  This is free and unencumbered software released into the public domain - June 2013 waterjuice.org
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//  IMPORTS
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "md5.h"
+#include <memory.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//  INTERNAL FUNCTIONS
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//  F, G, H, I
+//
+//  The basic MD5 functions. F and G are optimised compared to their RFC 1321 definitions for architectures that lack
+//  an AND-NOT instruction, just like in Colin Plumb's implementation.
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#define F( x, y, z )            ( (z) ^ ((x) & ((y) ^ (z))) )
+#define G( x, y, z )            ( (y) ^ ((z) & ((x) ^ (y))) )
+#define H( x, y, z )            ( (x) ^ (y) ^ (z) )
+#define I( x, y, z )            ( (y) ^ ((x) | ~(z)) )
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//  STEP
+//
+//  The MD5 transformation for all four rounds.
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#define STEP( f, a, b, c, d, x, t, s )                          \
+    (a) += f((b), (c), (d)) + (x) + (t);                        \
+    (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s))));  \
+    (a) += (b);
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//  TransformFunction
+//
+//  This processes one or more 64-byte data blocks, but does NOT update the bit counters. There are no alignment
+//  requirements.
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+static
+void*
+TransformFunction
+    (
+        Md5Context*     ctx,
+        void const*     data,
+        uintmax_t       size
+    )
+{
+    uint8_t*     ptr;
+    uint32_t     a;
+    uint32_t     b;
+    uint32_t     c;
+    uint32_t     d;
+    uint32_t     saved_a;
+    uint32_t     saved_b;
+    uint32_t     saved_c;
+    uint32_t     saved_d;
+
+    #define GET(n) (ctx->block[(n)])
+    #define SET(n) (ctx->block[(n)] =             \
+            ((uint32_t)ptr[(n)*4 + 0] << 0 )      \
+        |   ((uint32_t)ptr[(n)*4 + 1] << 8 )      \
+        |   ((uint32_t)ptr[(n)*4 + 2] << 16)      \
+        |   ((uint32_t)ptr[(n)*4 + 3] << 24) )
+
+    ptr = (uint8_t*)data;
+
+    a = ctx->a;
+    b = ctx->b;
+    c = ctx->c;
+    d = ctx->d;
+
+    do
+    {
+        saved_a = a;
+        saved_b = b;
+        saved_c = c;
+        saved_d = d;
+
+        // Round 1
+        STEP( F, a, b, c, d, SET(0),  0xd76aa478, 7 )
+        STEP( F, d, a, b, c, SET(1),  0xe8c7b756, 12 )
+        STEP( F, c, d, a, b, SET(2),  0x242070db, 17 )
+        STEP( F, b, c, d, a, SET(3),  0xc1bdceee, 22 )
+        STEP( F, a, b, c, d, SET(4),  0xf57c0faf, 7 )
+        STEP( F, d, a, b, c, SET(5),  0x4787c62a, 12 )
+        STEP( F, c, d, a, b, SET(6),  0xa8304613, 17 )
+        STEP( F, b, c, d, a, SET(7),  0xfd469501, 22 )
+        STEP( F, a, b, c, d, SET(8 ),  0x698098d8, 7 )
+        STEP( F, d, a, b, c, SET(9 ),  0x8b44f7af, 12 )
+        STEP( F, c, d, a, b, SET(10 ), 0xffff5bb1, 17 )
+        STEP( F, b, c, d, a, SET(11 ), 0x895cd7be, 22 )
+        STEP( F, a, b, c, d, SET(12 ), 0x6b901122, 7 )
+        STEP( F, d, a, b, c, SET(13 ), 0xfd987193, 12 )
+        STEP( F, c, d, a, b, SET(14 ), 0xa679438e, 17 )
+        STEP( F, b, c, d, a, SET(15 ), 0x49b40821, 22 )
+
+        // Round 2
+        STEP( G, a, b, c, d, GET(1),  0xf61e2562, 5 )
+        STEP( G, d, a, b, c, GET(6),  0xc040b340, 9 )
+        STEP( G, c, d, a, b, GET(11), 0x265e5a51, 14 )
+        STEP( G, b, c, d, a, GET(0),  0xe9b6c7aa, 20 )
+        STEP( G, a, b, c, d, GET(5),  0xd62f105d, 5 )
+        STEP( G, d, a, b, c, GET(10), 0x02441453, 9 )
+        STEP( G, c, d, a, b, GET(15), 0xd8a1e681, 14 )
+        STEP( G, b, c, d, a, GET(4),  0xe7d3fbc8, 20 )
+        STEP( G, a, b, c, d, GET(9),  0x21e1cde6, 5 )
+        STEP( G, d, a, b, c, GET(14), 0xc33707d6, 9 )
+        STEP( G, c, d, a, b, GET(3),  0xf4d50d87, 14 )
+        STEP( G, b, c, d, a, GET(8),  0x455a14ed, 20 )
+        STEP( G, a, b, c, d, GET(13), 0xa9e3e905, 5 )
+        STEP( G, d, a, b, c, GET(2),  0xfcefa3f8, 9 )
+        STEP( G, c, d, a, b, GET(7),  0x676f02d9, 14 )
+        STEP( G, b, c, d, a, GET(12), 0x8d2a4c8a, 20 )
+
+        // Round 3
+        STEP( H, a, b, c, d, GET(5),  0xfffa3942, 4 )
+        STEP( H, d, a, b, c, GET(8),  0x8771f681, 11 )
+        STEP( H, c, d, a, b, GET(11), 0x6d9d6122, 16 )
+        STEP( H, b, c, d, a, GET(14), 0xfde5380c, 23 )
+        STEP( H, a, b, c, d, GET(1),  0xa4beea44, 4 )
+        STEP( H, d, a, b, c, GET(4),  0x4bdecfa9, 11 )
+        STEP( H, c, d, a, b, GET(7),  0xf6bb4b60, 16 )
+        STEP( H, b, c, d, a, GET(10), 0xbebfbc70, 23 )
+        STEP( H, a, b, c, d, GET(13), 0x289b7ec6, 4 )
+        STEP( H, d, a, b, c, GET(0),  0xeaa127fa, 11 )
+        STEP( H, c, d, a, b, GET(3),  0xd4ef3085, 16 )
+        STEP( H, b, c, d, a, GET(6),  0x04881d05, 23 )
+        STEP( H, a, b, c, d, GET(9),  0xd9d4d039, 4 )
+        STEP( H, d, a, b, c, GET(12), 0xe6db99e5, 11 )
+        STEP( H, c, d, a, b, GET(15), 0x1fa27cf8, 16 )
+        STEP( H, b, c, d, a, GET(2),  0xc4ac5665, 23 )
+
+        // Round 4
+        STEP( I, a, b, c, d, GET(0),  0xf4292244, 6 )
+        STEP( I, d, a, b, c, GET(7),  0x432aff97, 10 )
+        STEP( I, c, d, a, b, GET(14), 0xab9423a7, 15 )
+        STEP( I, b, c, d, a, GET(5),  0xfc93a039, 21 )
+        STEP( I, a, b, c, d, GET(12), 0x655b59c3, 6 )
+        STEP( I, d, a, b, c, GET(3),  0x8f0ccc92, 10 )
+        STEP( I, c, d, a, b, GET(10), 0xffeff47d, 15 )
+        STEP( I, b, c, d, a, GET(1),  0x85845dd1, 21 )
+        STEP( I, a, b, c, d, GET(8),  0x6fa87e4f, 6 )
+        STEP( I, d, a, b, c, GET(15), 0xfe2ce6e0, 10 )
+        STEP( I, c, d, a, b, GET(6),  0xa3014314, 15 )
+        STEP( I, b, c, d, a, GET(13), 0x4e0811a1, 21 )
+        STEP( I, a, b, c, d, GET(4),  0xf7537e82, 6 )
+        STEP( I, d, a, b, c, GET(11), 0xbd3af235, 10 )
+        STEP( I, c, d, a, b, GET(2),  0x2ad7d2bb, 15 )
+        STEP( I, b, c, d, a, GET(9),  0xeb86d391, 21 )
+
+        a += saved_a;
+        b += saved_b;
+        c += saved_c;
+        d += saved_d;
+
+        ptr += 64;
+    } while( size -= 64 );
+
+    ctx->a = a;
+    ctx->b = b;
+    ctx->c = c;
+    ctx->d = d;
+
+    #undef GET
+    #undef SET
+
+    return ptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//  EXPORTED FUNCTIONS
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//  Md5Initialise
+//
+//  Initialises an MD5 Context. Use this to initialise/reset a context.
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void
+Md5Initialise
+    (
+        Md5Context*         Context         // [out]
+    )
+{
+    Context->a = 0x67452301;
+    Context->b = 0xefcdab89;
+    Context->c = 0x98badcfe;
+    Context->d = 0x10325476;
+
+    Context->lo = 0;
+    Context->hi = 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//  Md5Update
+//
+//  Adds data to the MD5 context. This will process the data and update the internal state of the context. Keep on
+//  calling this function until all the data has been added. Then call Md5Finalise to calculate the hash.
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void
+Md5Update
+    (
+        Md5Context*         Context,        // [in out]
+        void const*         Buffer,         // [in]
+        uint32_t            BufferSize      // [in]
+    )
+{
+    uint32_t    saved_lo;
+    uint32_t    used;
+    uint32_t    free;
+
+    saved_lo = Context->lo;
+    if( (Context->lo = (saved_lo + BufferSize) & 0x1fffffff) < saved_lo )
+    {
+        Context->hi++;
+    }
+    Context->hi += (uint32_t)( BufferSize >> 29 );
+
+    used = saved_lo & 0x3f;
+
+    if( used )
+    {
+        free = 64 - used;
+
+        if( BufferSize < free )
+        {
+            memcpy( &Context->buffer[used], Buffer, BufferSize );
+            return;
+        }
+
+        memcpy( &Context->buffer[used], Buffer, free );
+        Buffer = (uint8_t*)Buffer + free;
+        BufferSize -= free;
+        TransformFunction(Context, Context->buffer, 64);
+    }
+
+    if( BufferSize >= 64 )
+    {
+        Buffer = TransformFunction( Context, Buffer, BufferSize & ~(unsigned long)0x3f );
+        BufferSize &= 0x3f;
+    }
+
+    memcpy( Context->buffer, Buffer, BufferSize );
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//  Md5Finalise
+//
+//  Performs the final calculation of the hash and returns the digest (16 byte buffer containing 128bit hash). After
+//  calling this, Md5Initialised must be used to reuse the context.
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void
+Md5Finalise
+    (
+        Md5Context*         Context,        // [in out]
+        MD5_HASH*           Digest          // [in]
+    )
+{
+    uint32_t    used;
+    uint32_t    free;
+
+    used = Context->lo & 0x3f;
+
+    Context->buffer[used++] = 0x80;
+
+    free = 64 - used;
+
+    if(free < 8)
+    {
+        memset( &Context->buffer[used], 0, free );
+        TransformFunction( Context, Context->buffer, 64 );
+        used = 0;
+        free = 64;
+    }
+
+    memset( &Context->buffer[used], 0, free - 8 );
+
+    Context->lo <<= 3;
+    Context->buffer[56] = (uint8_t)( Context->lo );
+    Context->buffer[57] = (uint8_t)( Context->lo >> 8 );
+    Context->buffer[58] = (uint8_t)( Context->lo >> 16 );
+    Context->buffer[59] = (uint8_t)( Context->lo >> 24 );
+    Context->buffer[60] = (uint8_t)( Context->hi );
+    Context->buffer[61] = (uint8_t)( Context->hi >> 8 );
+    Context->buffer[62] = (uint8_t)( Context->hi >> 16 );
+    Context->buffer[63] = (uint8_t)( Context->hi >> 24 );
+
+    TransformFunction( Context, Context->buffer, 64 );
+
+    Digest->bytes[0]  = (uint8_t)( Context->a );
+    Digest->bytes[1]  = (uint8_t)( Context->a >> 8 );
+    Digest->bytes[2]  = (uint8_t)( Context->a >> 16 );
+    Digest->bytes[3]  = (uint8_t)( Context->a >> 24 );
+    Digest->bytes[4]  = (uint8_t)( Context->b );
+    Digest->bytes[5]  = (uint8_t)( Context->b >> 8 );
+    Digest->bytes[6]  = (uint8_t)( Context->b >> 16 );
+    Digest->bytes[7]  = (uint8_t)( Context->b >> 24 );
+    Digest->bytes[8]  = (uint8_t)( Context->c );
+    Digest->bytes[9]  = (uint8_t)( Context->c >> 8 );
+    Digest->bytes[10] = (uint8_t)( Context->c >> 16 );
+    Digest->bytes[11] = (uint8_t)( Context->c >> 24 );
+    Digest->bytes[12] = (uint8_t)( Context->d );
+    Digest->bytes[13] = (uint8_t)( Context->d >> 8 );
+    Digest->bytes[14] = (uint8_t)( Context->d >> 16 );
+    Digest->bytes[15] = (uint8_t)( Context->d >> 24 );
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//  Md5Calculate
+//
+//  Combines Md5Initialise, Md5Update, and Md5Finalise into one function. Calculates the MD5 hash of the buffer.
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void
+Md5Calculate
+    (
+        void  const*        Buffer,         // [in]
+        uint32_t            BufferSize,     // [in]
+        MD5_HASH*           Digest          // [in]
+    )
+{
+    Md5Context context;
+
+    Md5Initialise( &context );
+    Md5Update( &context, Buffer, BufferSize );
+    Md5Finalise( &context, Digest );
+}
diff --git a/src/libappimage_shared/CMakeLists.txt b/src/libappimage_shared/CMakeLists.txt
new file mode 100644 (file)
index 0000000..ef7238b
--- /dev/null
@@ -0,0 +1,27 @@
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+
+set(libappimage_shared_public_header ${PROJECT_SOURCE_DIR}/include/appimage/appimage_shared.h)
+
+add_library(libappimage_shared STATIC
+    ${libappimage_shared_public_header}
+    elf.c
+    hexlify.c
+    light_byteswap.h
+    light_elf.h
+    digest.c
+)
+set_target_properties(libappimage_shared PROPERTIES PREFIX "")
+target_include_directories(libappimage_shared PUBLIC
+    $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
+    $<INSTALL_INTERFACE:include>
+)
+set_property(TARGET libappimage_shared PROPERTY PUBLIC_HEADER ${libappimage_shared_public_header})
+target_link_libraries(libappimage_shared PRIVATE libappimage_hashlib)
+
+# install libappimage
+install(TARGETS libappimage_shared
+    EXPORT libappimageTargets
+    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libappimage
+    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libappimage
+    PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/appimage COMPONENT libappimage-dev
+)
diff --git a/src/libappimage_shared/digest.c b/src/libappimage_shared/digest.c
new file mode 100644 (file)
index 0000000..a9294b8
--- /dev/null
@@ -0,0 +1,139 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <appimage/appimage_shared.h>
+#include <hashlib.h>
+
+bool appimage_type2_digest_md5(const char* path, char* digest) {
+    // skip digest, signature and key sections in digest calculation
+    unsigned long digest_md5_offset = 0, digest_md5_length = 0;
+    if (!appimage_get_elf_section_offset_and_length(path, ".digest_md5", &digest_md5_offset, &digest_md5_length))
+        return false;
+
+    unsigned long signature_offset = 0, signature_length = 0;
+    if (!appimage_get_elf_section_offset_and_length(path, ".sha256_sig", &signature_offset, &signature_length))
+        return false;
+
+    unsigned long sig_key_offset = 0, sig_key_length = 0;
+    if (!appimage_get_elf_section_offset_and_length(path, ".sig_key", &sig_key_offset, &sig_key_length))
+        return false;
+
+    Md5Context md5_context;
+    Md5Initialise(&md5_context);
+
+    // read file in chunks
+    static const int chunk_size = 4096;
+
+    FILE *fp = fopen(path, "r");
+
+    // determine file size
+    fseek(fp, 0L, SEEK_END);
+    const long file_size = ftell(fp);
+    rewind(fp);
+
+    long bytes_left = file_size;
+
+    // if a section spans over more than a single chunk, we need emulate null bytes in the following chunks
+    ssize_t bytes_skip_following_chunks = 0;
+
+    while (bytes_left > 0) {
+        char buffer[chunk_size];
+
+        long current_position = ftell(fp);
+
+        ssize_t bytes_left_this_chunk = chunk_size;
+
+        // first, check whether there's bytes left that need to be skipped
+        if (bytes_skip_following_chunks > 0) {
+            ssize_t bytes_skip_this_chunk = (bytes_skip_following_chunks % chunk_size == 0) ? chunk_size : (bytes_skip_following_chunks % chunk_size);
+            bytes_left_this_chunk -= bytes_skip_this_chunk;
+
+            // we could just set it to 0 here, but it makes more sense to use -= for debugging
+            bytes_skip_following_chunks -= bytes_skip_this_chunk;
+
+            // make sure to skip these bytes in the file
+            fseek(fp, bytes_skip_this_chunk, SEEK_CUR);
+        }
+
+        // check whether there's a section in this chunk that we need to skip
+        if (digest_md5_offset != 0 && digest_md5_length != 0 && digest_md5_offset - current_position > 0 && digest_md5_offset - current_position < chunk_size) {
+            ssize_t begin_of_section = (digest_md5_offset - current_position) % chunk_size;
+            // read chunk before section
+            fread(buffer, sizeof(char), (size_t) begin_of_section, fp);
+
+            bytes_left_this_chunk -= begin_of_section;
+            bytes_left_this_chunk -= digest_md5_length;
+
+            // if bytes_left is now < 0, the section exceeds the current chunk
+            // this amount of bytes needs to be skipped in the future sections
+            if (bytes_left_this_chunk < 0) {
+                bytes_skip_following_chunks = (size_t) (-1 * bytes_left_this_chunk);
+                bytes_left_this_chunk = 0;
+            }
+
+            // if there's bytes left to read, we need to seek the difference between chunk's end and bytes_left
+            fseek(fp, (chunk_size - bytes_left_this_chunk - begin_of_section), SEEK_CUR);
+        }
+
+        // check whether there's a section in this chunk that we need to skip
+        if (signature_offset != 0 && signature_length != 0 && signature_offset - current_position > 0 && signature_offset - current_position < chunk_size) {
+            ssize_t begin_of_section = (signature_offset - current_position) % chunk_size;
+            // read chunk before section
+            fread(buffer, sizeof(char), (size_t) begin_of_section, fp);
+
+            bytes_left_this_chunk -= begin_of_section;
+            bytes_left_this_chunk -= signature_length;
+
+            // if bytes_left is now < 0, the section exceeds the current chunk
+            // this amount of bytes needs to be skipped in the future sections
+            if (bytes_left_this_chunk < 0) {
+                bytes_skip_following_chunks = (size_t) (-1 * bytes_left_this_chunk);
+                bytes_left_this_chunk = 0;
+            }
+
+            // if there's bytes left to read, we need to seek the difference between chunk's end and bytes_left
+            fseek(fp, (chunk_size - bytes_left_this_chunk - begin_of_section), SEEK_CUR);
+        }
+
+        // check whether there's a section in this chunk that we need to skip
+        if (sig_key_offset != 0 && sig_key_length != 0 && sig_key_offset - current_position > 0 && sig_key_offset - current_position < chunk_size) {
+            ssize_t begin_of_section = (sig_key_offset - current_position) % chunk_size;
+            // read chunk before section
+            fread(buffer, sizeof(char), (size_t) begin_of_section, fp);
+
+            bytes_left_this_chunk -= begin_of_section;
+            bytes_left_this_chunk -= sig_key_length;
+
+            // if bytes_left is now < 0, the section exceeds the current chunk
+            // this amount of bytes needs to be skipped in the future sections
+            if (bytes_left_this_chunk < 0) {
+                bytes_skip_following_chunks = (size_t) (-1 * bytes_left_this_chunk);
+                bytes_left_this_chunk = 0;
+            }
+
+            // if there's bytes left to read, we need to seek the difference between chunk's end and bytes_left
+            fseek(fp, (chunk_size - bytes_left_this_chunk - begin_of_section), SEEK_CUR);
+        }
+
+        // check whether we're done already
+        if (bytes_left_this_chunk > 0) {
+            // read data from file into buffer with the correct offset in case bytes have to be skipped
+            fread(buffer + (chunk_size - bytes_left_this_chunk), sizeof(char), (size_t) bytes_left_this_chunk, fp);
+        }
+
+        // feed buffer into checksum calculation
+        Md5Update(&md5_context, buffer, chunk_size);
+
+        bytes_left -= chunk_size;
+    }
+
+    MD5_HASH checksum;
+    Md5Finalise(&md5_context, &checksum);
+
+    memcpy(digest, (const char*) checksum.bytes, 16);
+
+    fclose(fp);
+
+    return true;
+}
diff --git a/src/libappimage_shared/elf.c b/src/libappimage_shared/elf.c
new file mode 100644 (file)
index 0000000..d5a0594
--- /dev/null
@@ -0,0 +1,252 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <memory.h>
+#include <sys/mman.h>
+
+#include "light_elf.h"
+#include "light_byteswap.h"
+
+
+typedef Elf32_Nhdr Elf_Nhdr;
+
+static char *fname;
+static Elf64_Ehdr ehdr;
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define ELFDATANATIVE ELFDATA2LSB
+#elif __BYTE_ORDER == __BIG_ENDIAN
+#define ELFDATANATIVE ELFDATA2MSB
+#else
+#error "Unknown machine endian"
+#endif
+
+static uint16_t file16_to_cpu(uint16_t val)
+{
+       if (ehdr.e_ident[EI_DATA] != ELFDATANATIVE)
+               val = bswap_16(val);
+       return val;
+}
+
+static uint32_t file32_to_cpu(uint32_t val)
+{
+       if (ehdr.e_ident[EI_DATA] != ELFDATANATIVE)
+               val = bswap_32(val);
+       return val;
+}
+
+static uint64_t file64_to_cpu(uint64_t val)
+{
+       if (ehdr.e_ident[EI_DATA] != ELFDATANATIVE)
+               val = bswap_64(val);
+       return val;
+}
+
+static off_t read_elf32(FILE* fd)
+{
+       Elf32_Ehdr ehdr32;
+       Elf32_Shdr shdr32;
+       off_t last_shdr_offset;
+       ssize_t ret;
+    off_t  sht_end, last_section_end;
+
+       fseeko(fd, 0, SEEK_SET);
+       ret = fread(&ehdr32, 1, sizeof(ehdr32), fd);
+       if (ret < 0 || (size_t)ret != sizeof(ehdr32)) {
+               fprintf(stderr, "Read of ELF header from %s failed: %s\n",
+                       fname, strerror(errno));
+               return -1;
+       }
+
+       ehdr.e_shoff            = file32_to_cpu(ehdr32.e_shoff);
+       ehdr.e_shentsize        = file16_to_cpu(ehdr32.e_shentsize);
+       ehdr.e_shnum            = file16_to_cpu(ehdr32.e_shnum);
+
+       last_shdr_offset = ehdr.e_shoff + (ehdr.e_shentsize * (ehdr.e_shnum - 1));
+    fseeko(fd, last_shdr_offset, SEEK_SET);
+    ret = fread(&shdr32, 1, sizeof(shdr32), fd);
+       if (ret < 0 || (size_t)ret != sizeof(shdr32)) {
+               fprintf(stderr, "Read of ELF section header from %s failed: %s\n",
+                       fname, strerror(errno));
+               return -1;
+       }
+
+       /* ELF ends either with the table of section headers (SHT) or with a section. */
+       sht_end = ehdr.e_shoff + (ehdr.e_shentsize * ehdr.e_shnum);
+       last_section_end = file64_to_cpu(shdr32.sh_offset) + file64_to_cpu(shdr32.sh_size);
+       return sht_end > last_section_end ? sht_end : last_section_end;
+}
+
+static off_t read_elf64(FILE* fd)
+{
+       Elf64_Ehdr ehdr64;
+       Elf64_Shdr shdr64;
+       off_t last_shdr_offset;
+       off_t ret;
+    off_t sht_end, last_section_end;
+
+       fseeko(fd, 0, SEEK_SET);
+       ret = fread(&ehdr64, 1, sizeof(ehdr64), fd);
+       if (ret < 0 || (size_t)ret != sizeof(ehdr64)) {
+               fprintf(stderr, "Read of ELF header from %s failed: %s\n",
+                       fname, strerror(errno));
+               return -1;
+       }
+
+       ehdr.e_shoff            = file64_to_cpu(ehdr64.e_shoff);
+       ehdr.e_shentsize        = file16_to_cpu(ehdr64.e_shentsize);
+       ehdr.e_shnum            = file16_to_cpu(ehdr64.e_shnum);
+
+       last_shdr_offset = ehdr.e_shoff + (ehdr.e_shentsize * (ehdr.e_shnum - 1));
+    fseeko(fd, last_shdr_offset, SEEK_SET);
+    ret = fread(&shdr64, 1, sizeof(shdr64), fd);
+       if (ret < 0 || ret != sizeof(shdr64)) {
+               fprintf(stderr, "Read of ELF section header from %s failed: %s\n",
+                       fname, strerror(errno));
+               return -1;
+       }
+
+       /* ELF ends either with the table of section headers (SHT) or with a section. */
+       sht_end = ehdr.e_shoff + (ehdr.e_shentsize * ehdr.e_shnum);
+       last_section_end = file64_to_cpu(shdr64.sh_offset) + file64_to_cpu(shdr64.sh_size);
+       return sht_end > last_section_end ? sht_end : last_section_end;
+}
+
+ssize_t appimage_get_elf_size(const char* fname) {
+    off_t ret;
+    FILE* fd = NULL;
+    off_t size = -1;
+
+       fd = fopen(fname, "rb");
+       if (fd == NULL) {
+               fprintf(stderr, "Cannot open %s: %s\n",
+                       fname, strerror(errno));
+               return -1;
+       }
+       ret = fread(ehdr.e_ident, 1, EI_NIDENT, fd);
+       if (ret != EI_NIDENT) {
+               fprintf(stderr, "Read of e_ident from %s failed: %s\n",
+                       fname, strerror(errno));
+               return -1;
+       }
+       if ((ehdr.e_ident[EI_DATA] != ELFDATA2LSB) &&
+               (ehdr.e_ident[EI_DATA] != ELFDATA2MSB)) {
+               fprintf(stderr, "Unknown ELF data order %u\n",
+                       ehdr.e_ident[EI_DATA]);
+               return -1;
+       }
+       if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
+               size = read_elf32(fd);
+       } else if (ehdr.e_ident[EI_CLASS] == ELFCLASS64) {
+               size = read_elf64(fd);
+       } else {
+               fprintf(stderr, "Unknown ELF class %u\n", ehdr.e_ident[EI_CLASS]);
+               return -1;
+       }
+
+       fclose(fd);
+       return size;
+}
+
+/* Return the offset, and the length of an ELF section with a given name in a given ELF file */
+bool appimage_get_elf_section_offset_and_length(const char* fname, const char* section_name, unsigned long* offset, unsigned long* length) {
+       uint8_t* data;
+       int i;
+       int fd = open(fname, O_RDONLY);
+       size_t map_size = (size_t) lseek(fd, 0, SEEK_END);
+
+       data = mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, 0);
+       close(fd);
+
+       // this trick works as both 32 and 64 bit ELF files start with the e_ident[EI_NINDENT] section
+       unsigned char class = data[EI_CLASS];
+
+       if (class == ELFCLASS32) {
+               Elf32_Ehdr* elf;
+               Elf32_Shdr* shdr;
+
+               elf = (Elf32_Ehdr*) data;
+               shdr = (Elf32_Shdr*) (data + ((Elf32_Ehdr*) elf)->e_shoff);
+
+               char* strTab = (char*) (data + shdr[elf->e_shstrndx].sh_offset);
+               for (i = 0; i < elf->e_shnum; i++) {
+                       if (strcmp(&strTab[shdr[i].sh_name], section_name) == 0) {
+                               *offset = shdr[i].sh_offset;
+                               *length = shdr[i].sh_size;
+                       }
+               }
+       } else if (class == ELFCLASS64) {
+               Elf64_Ehdr* elf;
+               Elf64_Shdr* shdr;
+
+               elf = (Elf64_Ehdr*) data;
+               shdr = (Elf64_Shdr*) (data + elf->e_shoff);
+
+               char* strTab = (char*) (data + shdr[elf->e_shstrndx].sh_offset);
+               for (i = 0; i < elf->e_shnum; i++) {
+                       if (strcmp(&strTab[shdr[i].sh_name], section_name) == 0) {
+                               *offset = shdr[i].sh_offset;
+                               *length = shdr[i].sh_size;
+                       }
+               }
+       } else {
+               fprintf(stderr, "Platforms other than 32-bit/64-bit are currently not supported!");
+               munmap(data, map_size);
+               return false;
+       }
+
+       munmap(data, map_size);
+       return true;
+}
+
+char* read_file_offset_length(const char* fname, unsigned long offset, unsigned long length) {
+       FILE* f;
+       if ((f = fopen(fname, "r")) == NULL) {
+               return NULL;
+       }
+
+       fseek(f, offset, SEEK_SET);
+
+       char* buffer = calloc(length + 1, sizeof(char));
+       fread(buffer, length, sizeof(char), f);
+
+       fclose(f);
+
+       return buffer;
+}
+
+int appimage_print_hex(char* fname, unsigned long offset, unsigned long length) {
+       char* data;
+       if ((data = read_file_offset_length(fname, offset, length)) == NULL) {
+               return 1;
+       }
+
+       for (long long k = 0; k < length && data[k] != '\0'; k++) {
+               printf("%x", data[k]);
+       }
+
+       free(data);
+
+       printf("\n");
+
+       return 0;
+}
+
+int appimage_print_binary(char* fname, unsigned long offset, unsigned long length) {
+       char* data;
+       if ((data = read_file_offset_length(fname, offset, length)) == NULL) {
+               return 1;
+       }
+
+       printf("%s\n", data);
+
+       free(data);
+
+       return 0;
+}
+
diff --git a/src/libappimage_shared/hexlify.c b/src/libappimage_shared/hexlify.c
new file mode 100644 (file)
index 0000000..113a595
--- /dev/null
@@ -0,0 +1,18 @@
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+char* appimage_hexlify(const char* bytes, const size_t numBytes) {
+    // first of all, allocate the new string
+    // a hexadecimal representation works like "every byte will be represented by two chars"
+    // additionally, we need to null-terminate the string
+    char* hexlified = (char*) calloc((2 * numBytes + 1), sizeof(char));
+
+    for (size_t i = 0; i < numBytes; i++) {
+        char buffer[3];
+        sprintf(buffer, "%02x", (unsigned char) bytes[i]);
+        strcat(hexlified, buffer);
+    }
+
+    return hexlified;
+}
diff --git a/src/libappimage_shared/light_byteswap.h b/src/libappimage_shared/light_byteswap.h
new file mode 100644 (file)
index 0000000..d0b98ba
--- /dev/null
@@ -0,0 +1,13 @@
+#pragma once
+
+#define bswap_16(value) \
+((((value) & 0xff) << 8) | ((value) >> 8))
+
+#define bswap_32(value) \
+(((uint32_t)bswap_16((uint16_t)((value) & 0xffff)) << 16) | \
+(uint32_t)bswap_16((uint16_t)((value) >> 16)))
+
+#define bswap_64(value) \
+(((uint64_t)bswap_32((uint32_t)((value) & 0xffffffff)) \
+<< 32) | \
+(uint64_t)bswap_32((uint32_t)((value) >> 32)))
diff --git a/src/libappimage_shared/light_elf.h b/src/libappimage_shared/light_elf.h
new file mode 100644 (file)
index 0000000..d6044a7
--- /dev/null
@@ -0,0 +1,119 @@
+/* 
+ * 
+ *  Linux kernel
+ *  Copyright (C) 2017 Linus Torvalds
+ *  Modified work Copyright (C) 2017 @teras (https://github.com/teras)
+ *  (Shortened version -- original work found here:
+ *   https://github.com/torvalds/linux/blob/master/include/uapi/linux/elf.h)
+ *
+ *  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.
+ *
+ *  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 St, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ */
+
+#ifndef LIGHT_ELF_H
+#define LIGHT_ELF_H
+
+#include <inttypes.h>
+
+__BEGIN_DECLS
+
+typedef uint16_t Elf32_Half;
+typedef uint16_t Elf64_Half;
+typedef uint32_t Elf32_Word;
+typedef uint32_t Elf64_Word;
+typedef uint64_t Elf64_Xword;
+typedef uint32_t Elf32_Addr;
+typedef uint64_t Elf64_Addr;
+typedef uint32_t Elf32_Off;
+typedef uint64_t Elf64_Off;
+
+#define EI_NIDENT 16
+
+typedef struct elf32_hdr {
+    unsigned char e_ident[EI_NIDENT];
+    Elf32_Half e_type;
+    Elf32_Half e_machine;
+    Elf32_Word e_version;
+    Elf32_Addr e_entry; /* Entry point */
+    Elf32_Off e_phoff;
+    Elf32_Off e_shoff;
+    Elf32_Word e_flags;
+    Elf32_Half e_ehsize;
+    Elf32_Half e_phentsize;
+    Elf32_Half e_phnum;
+    Elf32_Half e_shentsize;
+    Elf32_Half e_shnum;
+    Elf32_Half e_shstrndx;
+} Elf32_Ehdr;
+
+typedef struct elf64_hdr {
+    unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */
+    Elf64_Half e_type;
+    Elf64_Half e_machine;
+    Elf64_Word e_version;
+    Elf64_Addr e_entry; /* Entry point virtual address */
+    Elf64_Off e_phoff; /* Program header table file offset */
+    Elf64_Off e_shoff; /* Section header table file offset */
+    Elf64_Word e_flags;
+    Elf64_Half e_ehsize;
+    Elf64_Half e_phentsize;
+    Elf64_Half e_phnum;
+    Elf64_Half e_shentsize;
+    Elf64_Half e_shnum;
+    Elf64_Half e_shstrndx;
+} Elf64_Ehdr;
+
+typedef struct elf32_shdr {
+    Elf32_Word sh_name;
+    Elf32_Word sh_type;
+    Elf32_Word sh_flags;
+    Elf32_Addr sh_addr;
+    Elf32_Off sh_offset;
+    Elf32_Word sh_size;
+    Elf32_Word sh_link;
+    Elf32_Word sh_info;
+    Elf32_Word sh_addralign;
+    Elf32_Word sh_entsize;
+} Elf32_Shdr;
+
+typedef struct elf64_shdr {
+    Elf64_Word sh_name; /* Section name, index in string tbl */
+    Elf64_Word sh_type; /* Type of section */
+    Elf64_Xword sh_flags; /* Miscellaneous section attributes */
+    Elf64_Addr sh_addr; /* Section virtual addr at execution */
+    Elf64_Off sh_offset; /* Section file offset */
+    Elf64_Xword sh_size; /* Size of section in bytes */
+    Elf64_Word sh_link; /* Index of another section */
+    Elf64_Word sh_info; /* Additional section information */
+    Elf64_Xword sh_addralign; /* Section alignment */
+    Elf64_Xword sh_entsize; /* Entry size if section holds table */
+} Elf64_Shdr;
+
+/* Note header in a PT_NOTE section */
+typedef struct elf32_note {
+    Elf32_Word n_namesz; /* Name size */
+    Elf32_Word n_descsz; /* Content size */
+    Elf32_Word n_type; /* Content type */
+} Elf32_Nhdr;
+
+#define ELFCLASS32  1
+#define ELFDATA2LSB 1
+#define ELFDATA2MSB 2
+#define ELFCLASS64  2
+#define EI_CLASS    4
+#define EI_DATA     5
+
+__END_DECLS
+
+#endif /* elf.h */
diff --git a/src/patches/patch-squashfuse.sh.in b/src/patches/patch-squashfuse.sh.in
new file mode 100755 (executable)
index 0000000..bd3452f
--- /dev/null
@@ -0,0 +1,8 @@
+#! /bin/bash
+
+git checkout ll.c Makefile.am fuseprivate.c fuseprivate.h hl.c ll.h ll_inode.c nonstd-daemon.c
+
+patch -p1 < @PROJECT_SOURCE_DIR@/src/patches/squashfuse.patch
+patch -p1 < @PROJECT_SOURCE_DIR@/src/patches/squashfuse_dlopen.patch
+
+cp -v @PROJECT_SOURCE_DIR@/src/patches/squashfuse_dlopen.c @PROJECT_SOURCE_DIR@/src/patches/squashfuse_dlopen.h .
diff --git a/src/patches/squashfuse.patch b/src/patches/squashfuse.patch
new file mode 100644 (file)
index 0000000..db94d2f
--- /dev/null
@@ -0,0 +1,57 @@
+diff --git a/Makefile.am b/Makefile.am
+index f0d7cde..70c4aa0 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -14,6 +14,7 @@ bin_PROGRAMS =
+ noinst_PROGRAMS =
+ noinst_LTLIBRARIES = libsquashfuse.la
++noinst_LTLIBRARIES += libsquashfuse_ll.la
+ # Main library: libsquashfuse
+ libsquashfuse_la_SOURCES = swap.c cache.c table.c dir.c file.c fs.c \
+@@ -46,10 +47,9 @@ endif
+ # Low-level squashfuse_ll, if supported
+ if SQ_WANT_LOWLEVEL
+-bin_PROGRAMS += squashfuse_ll
+-squashfuse_ll_SOURCES = ll.c ll_inode.c nonstd-daemon.c ll.h
+-squashfuse_ll_CPPFLAGS = $(FUSE_CPPFLAGS)
+-squashfuse_ll_LDADD = libsquashfuse.la libfuseprivate.la $(COMPRESSION_LIBS) \
++libsquashfuse_ll_la_SOURCES = ll.c ll_inode.c nonstd-daemon.c ll.h
++libsquashfuse_ll_la_CPPFLAGS = $(FUSE_CPPFLAGS)
++libsquashfuse_ll_la_LIBADD = libsquashfuse.la libfuseprivate.la $(COMPRESSION_LIBS) \
+   $(FUSE_LIBS)
+ noinst_LTLIBRARIES += libfuseprivate.la
+diff --git a/ll.c b/ll.c
+index a2c7902..8fcb3f4 100644
+--- a/ll.c
++++ b/ll.c
+@@ -390,7 +390,7 @@ static sqfs_ll *sqfs_ll_open(const char *path, size_t offset) {
+       return NULL;
+ }
+-int main(int argc, char *argv[]) {
++int fusefs_main(int argc, char *argv[], void (*mounted) (void)) {
+       struct fuse_args args;
+       sqfs_opts opts;
+       
+@@ -451,6 +451,8 @@ int main(int argc, char *argv[]) {
+                               if (sqfs_ll_daemonize(fg) != -1) {
+                                       if (fuse_set_signal_handlers(se) != -1) {
+                                               fuse_session_add_chan(se, ch.ch);
++                              if (mounted)
++                                mounted ();
+                                               /* FIXME: multithreading */
+                                               err = fuse_session_loop(se);
+                                               fuse_remove_signal_handlers(se);
+@@ -466,6 +468,8 @@ int main(int argc, char *argv[]) {
+               }
+       }
+       fuse_opt_free_args(&args);
++      if (mounted)
++        rmdir (mountpoint);
+       free(ll);
+       free(mountpoint);
+       
diff --git a/src/patches/squashfuse_dlopen.c b/src/patches/squashfuse_dlopen.c
new file mode 100644 (file)
index 0000000..e4a9d6a
--- /dev/null
@@ -0,0 +1,11 @@
+#include "squashfuse_dlopen.h"
+
+int have_libloaded = 0;
+
+const char *load_library_errmsg =
+  "AppImages require FUSE to run. \n"
+  "You might still be able to extract the contents of this AppImage \n"
+  "if you run it with the --appimage-extract option. \n"
+  "See https://github.com/AppImage/AppImageKit/wiki/FUSE \n"
+  "for more information\n";
+
diff --git a/src/patches/squashfuse_dlopen.h b/src/patches/squashfuse_dlopen.h
new file mode 100644 (file)
index 0000000..da3c34f
--- /dev/null
@@ -0,0 +1,262 @@
+#ifndef SQFS_DLOPEN_H
+#define SQFS_DLOPEN_H
+
+//#define ENABLE_DLOPEN
+
+#ifdef ENABLE_DLOPEN
+
+#include <dlfcn.h>
+
+#include <time.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+
+/*** dlopen() stuff ***/
+
+#define LIBNAME "libfuse.so.2"
+
+void *libhandle;
+int have_libloaded;
+const char *load_library_errmsg;
+
+#define LOAD_LIBRARY \
+if (have_libloaded != 1) { \
+  if (!(libhandle = dlopen(LIBNAME, RTLD_LAZY))) { \
+    fprintf(stderr, "dlopen(): error loading " LIBNAME "\n\n%s", load_library_errmsg ); \
+    exit(1); \
+  } else { \
+    have_libloaded = 1; \
+  } \
+}
+
+#define STRINGIFY(x) #x
+
+#define LOAD_SYMBOL(type,x,param) \
+type (*dl_##x) param; \
+*(void **) (&dl_##x) = dlsym(libhandle, STRINGIFY(x)); \
+if (dlerror()) { \
+  fprintf(stderr, "dlsym(): error loading symbol from " LIBNAME "\n\n%s", load_library_errmsg ); \
+  CLOSE_LIBRARY; \
+  exit(1); \
+}
+
+#define DL(x) dl_##x
+#define CLOSE_LIBRARY dlclose(libhandle);
+
+
+/*** libfuse stuff ***/
+
+#define FUSE_ROOT_ID 1
+#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
+#define FUSE_OPT_KEY(templ, key) { templ, -1U, key }
+#define FUSE_OPT_KEY_OPT -1
+#define FUSE_OPT_KEY_NONOPT -2
+#define FUSE_OPT_END { NULL, 0, 0 }
+
+enum fuse_buf_flags {
+  FUSE_BUF_IS_FD               = (1 << 1),
+  FUSE_BUF_FD_SEEK     = (1 << 2),
+  FUSE_BUF_FD_RETRY    = (1 << 3),
+};
+
+typedef unsigned long fuse_ino_t;
+typedef struct fuse_req *fuse_req_t;
+
+struct fuse_chan;
+struct fuse_pollhandle;
+
+struct fuse_args {
+  int argc;
+  char **argv;
+  int allocated;
+};
+
+typedef int (*fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off);
+typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs);
+typedef struct fuse_dirhandle *fuse_dirh_t;
+typedef int (*fuse_dirfil_t) (fuse_dirh_t h, const char *name, int type, ino_t ino);
+
+struct fuse_file_info {
+  int flags;
+  unsigned long fh_old;
+  int writepage;
+  unsigned int direct_io : 1;
+  unsigned int keep_cache : 1;
+  unsigned int flush : 1;
+  unsigned int nonseekable : 1;
+  unsigned int flock_release : 1;
+  unsigned int padding : 27;
+  uint64_t fh;
+  uint64_t lock_owner;
+};
+
+struct fuse_entry_param {
+  fuse_ino_t ino;
+  unsigned long generation;
+  struct stat attr;
+  double attr_timeout;
+  double entry_timeout;
+};
+
+struct fuse_opt {
+  const char *templ;
+  unsigned long offset;
+  int value;
+};
+
+struct fuse_forget_data {
+  uint64_t ino;
+  uint64_t nlookup;
+};
+
+struct fuse_conn_info {
+  unsigned proto_major;
+  unsigned proto_minor;
+  unsigned async_read;
+  unsigned max_write;
+  unsigned max_readahead;
+  unsigned capable;
+  unsigned want;
+  unsigned max_background;
+  unsigned congestion_threshold;
+  unsigned reserved[23];
+};
+
+struct fuse_buf {
+  size_t size;
+  enum fuse_buf_flags flags;
+  void *mem;
+  int fd;
+  off_t pos;
+};
+
+struct fuse_bufvec {
+  size_t count;
+  size_t idx;
+  size_t off;
+  struct fuse_buf buf[1];
+};
+
+struct fuse_context {
+  struct fuse *fuse;
+  uid_t uid;
+  gid_t gid;
+  pid_t pid;
+  void *private_data;
+  mode_t umask;
+};
+
+struct fuse_operations {
+       int (*getattr) (const char *, struct stat *);
+       int (*readlink) (const char *, char *, size_t);
+       int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t);
+       int (*mknod) (const char *, mode_t, dev_t);
+       int (*mkdir) (const char *, mode_t);
+       int (*unlink) (const char *);
+       int (*rmdir) (const char *);
+       int (*symlink) (const char *, const char *);
+       int (*rename) (const char *, const char *);
+       int (*link) (const char *, const char *);
+       int (*chmod) (const char *, mode_t);
+       int (*chown) (const char *, uid_t, gid_t);
+       int (*truncate) (const char *, off_t);
+       int (*utime) (const char *, struct utimbuf *);
+       int (*open) (const char *, struct fuse_file_info *);
+       int (*read) (const char *, char *, size_t, off_t, struct fuse_file_info *);
+       int (*write) (const char *, const char *, size_t, off_t, struct fuse_file_info *);
+       int (*statfs) (const char *, struct statvfs *);
+       int (*flush) (const char *, struct fuse_file_info *);
+       int (*release) (const char *, struct fuse_file_info *);
+       int (*fsync) (const char *, int, struct fuse_file_info *);
+       int (*setxattr) (const char *, const char *, const char *, size_t, int);
+       int (*getxattr) (const char *, const char *, char *, size_t);
+       int (*listxattr) (const char *, char *, size_t);
+       int (*removexattr) (const char *, const char *);
+       int (*opendir) (const char *, struct fuse_file_info *);
+       int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *);
+       int (*releasedir) (const char *, struct fuse_file_info *);
+       int (*fsyncdir) (const char *, int, struct fuse_file_info *);
+       void *(*init) (struct fuse_conn_info *conn);
+       void (*destroy) (void *);
+       int (*access) (const char *, int);
+       int (*create) (const char *, mode_t, struct fuse_file_info *);
+       int (*ftruncate) (const char *, off_t, struct fuse_file_info *);
+       int (*fgetattr) (const char *, struct stat *, struct fuse_file_info *);
+       int (*lock) (const char *, struct fuse_file_info *, int cmd, struct flock *);
+       int (*utimens) (const char *, const struct timespec tv[2]);
+       int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
+       unsigned int flag_nullpath_ok:1;
+       unsigned int flag_nopath:1;
+       unsigned int flag_utime_omit_ok:1;
+       unsigned int flag_reserved:29;
+       int (*ioctl) (const char *, int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data);
+       int (*poll) (const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp);
+       int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *);
+       int (*read_buf) (const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *);
+       int (*flock) (const char *, struct fuse_file_info *, int op);
+       int (*fallocate) (const char *, int, off_t, off_t, struct fuse_file_info *);
+};
+
+struct fuse_lowlevel_ops {
+  void (*init) (void *userdata, struct fuse_conn_info *conn);
+  void (*destroy) (void *userdata);
+  void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name);
+  void (*forget) (fuse_req_t req, fuse_ino_t ino, unsigned long nlookup);
+  void (*getattr) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi);
+  void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi);
+  void (*readlink) (fuse_req_t req, fuse_ino_t ino);
+  void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev);
+  void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode);
+  void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name);
+  void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name);
+  void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent, const char *name);
+  void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname);
+  void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname);
+  void (*open) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi);
+  void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi);
+  void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi);
+  void (*flush) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi);
+  void (*release) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi);
+  void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi);
+  void (*opendir) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi);
+  void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi);
+  void (*releasedir) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi);
+  void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi);
+  void (*statfs) (fuse_req_t req, fuse_ino_t ino);
+  void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags);
+  void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size);
+  void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size);
+  void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name);
+  void (*access) (fuse_req_t req, fuse_ino_t ino, int mask);
+  void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi);
+  void (*getlk) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock);
+  void (*setlk) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep);
+  void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx);
+  void (*ioctl) (fuse_req_t req, fuse_ino_t ino, int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+  void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph);
+  void (*write_buf) (fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *fi);
+  void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv);
+  void (*forget_multi) (fuse_req_t req, size_t count, struct fuse_forget_data *forgets);
+  void (*flock) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op);
+  void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi);
+};
+
+#else  /* !ENABLE_DLOPEN */
+
+#define LOAD_LIBRARY
+#define LOAD_SYMBOL(x)
+#define DL(x)
+#define CLOSE_LIBRARY
+
+#endif  /* !ENABLE_DLOPEN */
+
+#endif /* SQFS_DLOPEN_H */
+
diff --git a/src/patches/squashfuse_dlopen.patch b/src/patches/squashfuse_dlopen.patch
new file mode 100644 (file)
index 0000000..59e1e6e
--- /dev/null
@@ -0,0 +1,640 @@
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -1,6 +1,7 @@
+ COMPRESSION_LIBS = $(ZLIB_LIBS) $(XZ_LIBS) $(LZO_LIBS) $(LZ4_LIBS)
+ ACLOCAL_AMFLAGS = -I m4 --install
++AM_CFLAGS = -fno-strict-aliasing -DENABLE_DLOPEN
+ # Suppress AppleDouble
+ if MAKE_EXPORT
+@@ -19,13 +20,13 @@
+ # Main library: libsquashfuse
+ libsquashfuse_la_SOURCES = swap.c cache.c table.c dir.c file.c fs.c \
+       decompress.c xattr.c hash.c stack.c traverse.c util.c \
+-      nonstd-pread.c nonstd-stat.c \
++      nonstd-pread.c nonstd-stat.c squashfuse_dlopen.c \
+       squashfs_fs.h common.h nonstd-internal.h nonstd.h swap.h cache.h table.h \
+       dir.h file.h decompress.h xattr.h squashfuse.h hash.h stack.h traverse.h \
+-      util.h fs.h
++      util.h fs.h squashfuse_dlopen.h
+ libsquashfuse_la_CPPFLAGS = $(ZLIB_CPPFLAGS) $(XZ_CPPFLAGS) $(LZO_CPPFLAGS) \
+       $(LZ4_CPPFLAGS)
+-libsquashfuse_la_LIBADD =
++libsquashfuse_la_LIBADD = -ldl
+ # Helper for FUSE clients: libfuseprivate
+ libfuseprivate_la_SOURCES = fuseprivate.c nonstd-makedev.c nonstd-enoattr.c \
+--- a/fuseprivate.c
++++ b/fuseprivate.c
+@@ -94,15 +94,17 @@
+ }
+ void sqfs_usage(char *progname, bool fuse_usage) {
++      LOAD_SYMBOL(int,fuse_opt_add_arg,(struct fuse_args *args, const char *arg));
++      LOAD_SYMBOL(int,fuse_parse_cmdline,(struct fuse_args *args, char **mountpoint, int *multithreaded, int *foreground));
+       fprintf(stderr, "%s (c) 2012 Dave Vasilevsky\n\n", PACKAGE_STRING);
+       fprintf(stderr, "Usage: %s [options] ARCHIVE MOUNTPOINT\n",
+               progname ? progname : PACKAGE_NAME);
+       if (fuse_usage) {
+               struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+-              fuse_opt_add_arg(&args, ""); /* progname */
+-              fuse_opt_add_arg(&args, "-ho");
++              DL(fuse_opt_add_arg)(&args, ""); /* progname */
++              DL(fuse_opt_add_arg)(&args, "-ho");
+               fprintf(stderr, "\n");
+-              fuse_parse_cmdline(&args, NULL, NULL, NULL);
++              DL(fuse_parse_cmdline)(&args, NULL, NULL, NULL);
+       }
+       exit(-2);
+ }
+--- a/fuseprivate.h
++++ b/fuseprivate.h
+@@ -27,7 +27,10 @@
+ #include "squashfuse.h"
+-#include <fuse.h>
++#include "squashfuse_dlopen.h"
++#ifndef ENABLE_DLOPEN
++#     include <fuse.h>
++#endif
+ #include <sys/stat.h>
+--- a/hl.c
++++ b/hl.c
+@@ -33,6 +33,7 @@
+ #include <stdlib.h>
+ #include <string.h>
++int have_libloaded = 0;
+ typedef struct sqfs_hl sqfs_hl;
+ struct sqfs_hl {
+@@ -42,9 +43,10 @@
+ static sqfs_err sqfs_hl_lookup(sqfs **fs, sqfs_inode *inode,
+               const char *path) {
++      LOAD_SYMBOL(struct fuse_context *,fuse_get_context,(void));
+       bool found;
+       
+-      sqfs_hl *hl = fuse_get_context()->private_data;
++      sqfs_hl *hl = DL(fuse_get_context)()->private_data;
+       *fs = &hl->fs;
+       if (inode)
+               *inode = hl->root; /* copy */
+@@ -67,7 +69,8 @@
+ }
+ static void *sqfs_hl_op_init(struct fuse_conn_info *conn) {
+-      return fuse_get_context()->private_data;
++      LOAD_SYMBOL(struct fuse_context *,fuse_get_context,(void));
++      return DL(fuse_get_context)()->private_data;
+ }
+ static int sqfs_hl_op_getattr(const char *path, struct stat *st) {
+@@ -264,7 +267,16 @@
+       return NULL;
+ }
++#ifdef ENABLE_DLOPEN
++#define fuse_main(argc, argv, op, user_data) \
++      DL(fuse_main_real)(argc, argv, op, sizeof(*(op)), user_data)
++#endif
++
+ int main(int argc, char *argv[]) {
++      LOAD_SYMBOL(int,fuse_opt_parse,(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc));
++      LOAD_SYMBOL(int,fuse_opt_add_arg,(struct fuse_args *args, const char *arg));
++      LOAD_SYMBOL(int,fuse_main_real,(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, void *user_data));  /* fuse_main */
++      LOAD_SYMBOL(void,fuse_opt_free_args,(struct fuse_args *args));
+       struct fuse_args args;
+       sqfs_opts opts;
+       sqfs_hl *hl;
+@@ -299,7 +311,7 @@
+       opts.image = NULL;
+       opts.mountpoint = 0;
+       opts.offset = 0;
+-      if (fuse_opt_parse(&args, &opts, fuse_opts, sqfs_opt_proc) == -1)
++      if (DL(fuse_opt_parse)(&args, &opts, fuse_opts, sqfs_opt_proc) == -1)
+               sqfs_usage(argv[0], true);
+       if (!opts.image)
+               sqfs_usage(argv[0], true);
+@@ -308,8 +320,9 @@
+       if (!hl)
+               return -1;
+       
+-      fuse_opt_add_arg(&args, "-s"); /* single threaded */
++      DL(fuse_opt_add_arg)(&args, "-s"); /* single threaded */
+       ret = fuse_main(args.argc, args.argv, &sqfs_hl_ops, hl);
+-      fuse_opt_free_args(&args);
++      DL(fuse_opt_free_args)(&args);
++      CLOSE_LIBRARY;
+       return ret;
+ }
+--- a/ll.h
++++ b/ll.h
+@@ -27,7 +27,10 @@
+ #include "squashfuse.h"
+-#include <fuse_lowlevel.h>
++#include "squashfuse_dlopen.h"
++#ifndef ENABLE_DLOPEN
++#     include <fuse_lowlevel.h>
++#endif
+ typedef struct sqfs_ll sqfs_ll;
+ struct sqfs_ll {
+--- a/ll_inode.c
++++ b/ll_inode.c
+@@ -348,12 +348,14 @@
+ sqfs_err sqfs_ll_iget(fuse_req_t req, sqfs_ll_i *lli, fuse_ino_t i) {
++      LOAD_SYMBOL(void *,fuse_req_userdata,(fuse_req_t req));
++      LOAD_SYMBOL(int,fuse_reply_err,(fuse_req_t req, int err));
+       sqfs_err err = SQFS_OK;
+-      lli->ll = fuse_req_userdata(req);
++      lli->ll = DL(fuse_req_userdata)(req);
+       if (i != SQFS_FUSE_INODE_NONE) {
+               err = sqfs_ll_inode(lli->ll, &lli->inode, i);
+               if (err)
+-                      fuse_reply_err(req, ENOENT);
++                      DL(fuse_reply_err)(req, ENOENT);
+       }
+       return err;
+ }
+--- a/nonstd-daemon.c
++++ b/nonstd-daemon.c
+@@ -28,11 +28,16 @@
+ #include "nonstd-internal.h"
+ #include <unistd.h>
+-#include <fuse_lowlevel.h>
++
++#include "squashfuse_dlopen.h"
++#ifndef ENABLE_DLOPEN
++#     include <fuse_lowlevel.h>
++#endif
+ int sqfs_ll_daemonize(int fg) {
+       #if HAVE_DECL_FUSE_DAEMONIZE
+-              return fuse_daemonize(fg);
++              LOAD_SYMBOL(int,fuse_daemonize,(int foreground));
++              return DL(fuse_daemonize)(fg);
+       #else
+               return daemon(0,0);
+       #endif
+--- a/ll.c
++++ b/ll.c
+@@ -38,37 +38,41 @@
+ static void sqfs_ll_op_getattr(fuse_req_t req, fuse_ino_t ino,
+               struct fuse_file_info *fi) {
++      LOAD_SYMBOL(int,fuse_reply_err,(fuse_req_t req, int err));
++      LOAD_SYMBOL(int,fuse_reply_attr,(fuse_req_t req, const struct stat *attr, double attr_timeout));
+       sqfs_ll_i lli;
+       struct stat st;
+       if (sqfs_ll_iget(req, &lli, ino))
+               return;
+       
+       if (sqfs_stat(&lli.ll->fs, &lli.inode, &st)) {
+-              fuse_reply_err(req, ENOENT);
++              DL(fuse_reply_err)(req, ENOENT);
+       } else {
+               st.st_ino = ino;
+-              fuse_reply_attr(req, &st, SQFS_TIMEOUT);
++              DL(fuse_reply_attr)(req, &st, SQFS_TIMEOUT);
+       }
+ }
+ static void sqfs_ll_op_opendir(fuse_req_t req, fuse_ino_t ino,
+               struct fuse_file_info *fi) {
++      LOAD_SYMBOL(int,fuse_reply_err,(fuse_req_t req, int err));
++      LOAD_SYMBOL(int,fuse_reply_open,(fuse_req_t req, const struct fuse_file_info *fi));
+       sqfs_ll_i *lli;
+       
+       fi->fh = (intptr_t)NULL;
+       
+       lli = malloc(sizeof(*lli));
+       if (!lli) {
+-              fuse_reply_err(req, ENOMEM);
++              DL(fuse_reply_err)(req, ENOMEM);
+               return;
+       }
+       
+       if (sqfs_ll_iget(req, lli, ino) == SQFS_OK) {
+               if (!S_ISDIR(lli->inode.base.mode)) {
+-                      fuse_reply_err(req, ENOTDIR);
++                      DL(fuse_reply_err)(req, ENOTDIR);
+               } else {
+                       fi->fh = (intptr_t)lli;
+-                      fuse_reply_open(req, fi);
++                      DL(fuse_reply_open)(req, fi);
+                       return;
+               }
+       }
+@@ -77,28 +81,35 @@
+ static void sqfs_ll_op_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+                             mode_t mode, struct fuse_file_info *fi) {
+-      fuse_reply_err(req, EROFS);
++      LOAD_SYMBOL(int,fuse_reply_err,(fuse_req_t req, int err));
++      DL(fuse_reply_err)(req, EROFS);
+ }
+ static void sqfs_ll_op_releasedir(fuse_req_t req, fuse_ino_t ino,
+               struct fuse_file_info *fi) {
+       free((sqfs_ll_i*)(intptr_t)fi->fh);
+-      fuse_reply_err(req, 0); /* yes, this is necessary */
++      LOAD_SYMBOL(int,fuse_reply_err,(fuse_req_t req, int err));
++      DL(fuse_reply_err)(req, 0); /* yes, this is necessary */
+ }
+ static size_t sqfs_ll_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+               const char *name, const struct stat *st, off_t off) {
+       #if HAVE_DECL_FUSE_ADD_DIRENTRY
+-              return fuse_add_direntry(req, buf, bufsize, name, st, off);
++              LOAD_SYMBOL(size_t,fuse_add_direntry,(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off));
++              return DL(fuse_add_direntry)(req, buf, bufsize, name, st, off);
+       #else
+-              size_t esize = fuse_dirent_size(strlen(name));
++              LOAD_SYMBOL(size_t,fuse_dirent_size(size_t namelen));
++              LOAD_SYMBOL(char *,fuse_add_dirent,(char *buf, const char *name, const struct stat *stbuf, off_t off));
++              size_t esize = DL(fuse_dirent_size)(strlen(name));
+               if (bufsize >= esize)
+-                      fuse_add_dirent(buf, name, st, off);
++                      DL(fuse_add_dirent)(buf, name, st, off);
+               return esize;
+       #endif
+ }
+ static void sqfs_ll_op_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+               off_t off, struct fuse_file_info *fi) {
++      LOAD_SYMBOL(int,fuse_reply_err,(fuse_req_t req, int err));
++      LOAD_SYMBOL(int,fuse_reply_buf,(fuse_req_t req, const char *buf, size_t size));
+       sqfs_err sqerr;
+       sqfs_dir dir;
+       sqfs_name namebuf;
+@@ -135,14 +146,16 @@
+       }
+       
+       if (err)
+-              fuse_reply_err(req, err);
++              DL(fuse_reply_err)(req, err);
+       else
+-              fuse_reply_buf(req, buf, bufpos - buf);
++              DL(fuse_reply_buf)(req, buf, bufpos - buf);
+       free(buf);
+ }
+ static void sqfs_ll_op_lookup(fuse_req_t req, fuse_ino_t parent,
+               const char *name) {
++      LOAD_SYMBOL(int,fuse_reply_err,(fuse_req_t req, int err));
++      LOAD_SYMBOL(int,fuse_reply_entry,(fuse_req_t req, const struct fuse_entry_param *e));
+       sqfs_ll_i lli;
+       sqfs_err sqerr;
+       sqfs_name namebuf;
+@@ -154,7 +167,7 @@
+               return;
+       
+       if (!S_ISDIR(lli.inode.base.mode)) {
+-              fuse_reply_err(req, ENOTDIR);
++              DL(fuse_reply_err)(req, ENOTDIR);
+               return;
+       }
+       
+@@ -162,55 +175,58 @@
+       sqerr = sqfs_dir_lookup(&lli.ll->fs, &lli.inode, name, strlen(name), &entry,
+               &found);
+       if (sqerr) {
+-              fuse_reply_err(req, EIO);
++              DL(fuse_reply_err)(req, EIO);
+               return;
+       }
+       if (!found) {
+-              fuse_reply_err(req, ENOENT);
++              DL(fuse_reply_err)(req, ENOENT);
+               return;
+       }
+       
+       if (sqfs_inode_get(&lli.ll->fs, &inode, sqfs_dentry_inode(&entry))) {
+-              fuse_reply_err(req, ENOENT);
++              DL(fuse_reply_err)(req, ENOENT);
+       } else {
+               struct fuse_entry_param fentry;
+               memset(&fentry, 0, sizeof(fentry));
+               if (sqfs_stat(&lli.ll->fs, &inode, &fentry.attr)) {
+-                      fuse_reply_err(req, EIO);
++                      DL(fuse_reply_err)(req, EIO);
+               } else {
+                       fentry.attr_timeout = fentry.entry_timeout = SQFS_TIMEOUT;
+                       fentry.ino = lli.ll->ino_register(lli.ll, &entry);
+                       fentry.attr.st_ino = fentry.ino;
+-                      fuse_reply_entry(req, &fentry);
++                      DL(fuse_reply_entry)(req, &fentry);
+               }
+       }
+ }
+ static void sqfs_ll_op_open(fuse_req_t req, fuse_ino_t ino,
+               struct fuse_file_info *fi) {
++      LOAD_SYMBOL(int,fuse_reply_err,(fuse_req_t req, int err));
++      LOAD_SYMBOL(int,fuse_reply_open,(fuse_req_t req, const struct fuse_file_info *fi));
++      LOAD_SYMBOL(void *,fuse_req_userdata,(fuse_req_t req));
+       sqfs_inode *inode;
+       sqfs_ll *ll;
+       
+       if (fi->flags & (O_WRONLY | O_RDWR)) {
+-              fuse_reply_err(req, EROFS);
++              DL(fuse_reply_err)(req, EROFS);
+               return;
+       }
+       
+       inode = malloc(sizeof(sqfs_inode));
+       if (!inode) {
+-              fuse_reply_err(req, ENOMEM);
++              DL(fuse_reply_err)(req, ENOMEM);
+               return;
+       }
+       
+-      ll = fuse_req_userdata(req);
++      ll = DL(fuse_req_userdata)(req);
+       if (sqfs_ll_inode(ll, inode, ino)) {
+-              fuse_reply_err(req, ENOENT);
++              DL(fuse_reply_err)(req, ENOENT);
+       } else if (!S_ISREG(inode->base.mode)) {
+-              fuse_reply_err(req, EISDIR);
++              DL(fuse_reply_err)(req, EISDIR);
+       } else {
+               fi->fh = (intptr_t)inode;
+               fi->keep_cache = 1;
+-              fuse_reply_open(req, fi);
++              DL(fuse_reply_open)(req, fi);
+               return;
+       }
+       free(inode);
+@@ -218,37 +234,43 @@
+ static void sqfs_ll_op_release(fuse_req_t req, fuse_ino_t ino,
+               struct fuse_file_info *fi) {
++      LOAD_SYMBOL(int,fuse_reply_err,(fuse_req_t req, int err));
+       free((sqfs_inode*)(intptr_t)fi->fh);
+       fi->fh = 0;
+-      fuse_reply_err(req, 0);
++      DL(fuse_reply_err)(req, 0);
+ }
+ static void sqfs_ll_op_read(fuse_req_t req, fuse_ino_t ino,
+               size_t size, off_t off, struct fuse_file_info *fi) {
+-      sqfs_ll *ll = fuse_req_userdata(req);
++      LOAD_SYMBOL(void *,fuse_req_userdata,(fuse_req_t req));
++      LOAD_SYMBOL(int,fuse_reply_err,(fuse_req_t req, int err));
++      LOAD_SYMBOL(int,fuse_reply_buf,(fuse_req_t req, const char *buf, size_t size));
++      sqfs_ll *ll = DL(fuse_req_userdata)(req);
+       sqfs_inode *inode = (sqfs_inode*)(intptr_t)fi->fh;
+       sqfs_err err = SQFS_OK;
+       
+       off_t osize;
+       char *buf = malloc(size);
+       if (!buf) {
+-              fuse_reply_err(req, ENOMEM);
++              DL(fuse_reply_err)(req, ENOMEM);
+               return;
+       }
+       
+       osize = size;
+       err = sqfs_read_range(&ll->fs, inode, off, &osize, buf);
+       if (err) {
+-              fuse_reply_err(req, EIO);
++              DL(fuse_reply_err)(req, EIO);
+       } else if (osize == 0) { /* EOF */
+-              fuse_reply_buf(req, NULL, 0);
++              DL(fuse_reply_buf)(req, NULL, 0);
+       } else {
+-              fuse_reply_buf(req, buf, osize);
++              DL(fuse_reply_buf)(req, buf, osize);
+       }
+       free(buf);
+ }
+ static void sqfs_ll_op_readlink(fuse_req_t req, fuse_ino_t ino) {
++      LOAD_SYMBOL(int,fuse_reply_err,(fuse_req_t req, int err));
++      LOAD_SYMBOL(int,fuse_reply_readlink,(fuse_req_t req, const char *link));
+       char *dst;
+       size_t size;
+       sqfs_ll_i lli;
+@@ -256,21 +278,24 @@
+               return;
+       
+       if (!S_ISLNK(lli.inode.base.mode)) {
+-              fuse_reply_err(req, EINVAL);
++              DL(fuse_reply_err)(req, EINVAL);
+       } else if (sqfs_readlink(&lli.ll->fs, &lli.inode, NULL, &size)) {
+-              fuse_reply_err(req, EIO);
++              DL(fuse_reply_err)(req, EIO);
+       } else if (!(dst = malloc(size + 1))) {
+-              fuse_reply_err(req, ENOMEM);
++              DL(fuse_reply_err)(req, ENOMEM);
+       } else if (sqfs_readlink(&lli.ll->fs, &lli.inode, dst, &size)) {
+-              fuse_reply_err(req, EIO);
++              DL(fuse_reply_err)(req, EIO);
+               free(dst);
+       } else {
+-              fuse_reply_readlink(req, dst);
++              DL(fuse_reply_readlink)(req, dst);
+               free(dst);
+       }
+ }
+ static void sqfs_ll_op_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) {
++      LOAD_SYMBOL(int,fuse_reply_err,(fuse_req_t req, int err));
++      LOAD_SYMBOL(int,fuse_reply_xattr,(fuse_req_t req, size_t count));
++      LOAD_SYMBOL(int,fuse_reply_buf,(fuse_req_t req, const char *buf, size_t size));
+       sqfs_ll_i lli;
+       char *buf;
+       int ferr;
+@@ -280,17 +305,17 @@
+       buf = NULL;
+       if (size && !(buf = malloc(size))) {
+-              fuse_reply_err(req, ENOMEM);
++              DL(fuse_reply_err)(req, ENOMEM);
+               return;
+       }
+       
+       ferr = sqfs_listxattr(&lli.ll->fs, &lli.inode, buf, &size);
+       if (ferr) {
+-              fuse_reply_err(req, ferr);
++              DL(fuse_reply_err)(req, ferr);
+       } else if (buf) {
+-              fuse_reply_buf(req, buf, size);
++              DL(fuse_reply_buf)(req, buf, size);
+       } else {
+-              fuse_reply_xattr(req, size);
++              DL(fuse_reply_xattr)(req, size);
+       }
+       free(buf);
+ }
+@@ -301,13 +326,16 @@
+               , uint32_t position
+ #endif
+               ) {
++      LOAD_SYMBOL(int,fuse_reply_err,(fuse_req_t req, int err));
++      LOAD_SYMBOL(int,fuse_reply_xattr,(fuse_req_t req, size_t count));
++      LOAD_SYMBOL(int,fuse_reply_buf,(fuse_req_t req, const char *buf, size_t size));
+       sqfs_ll_i lli;
+       char *buf = NULL;
+       size_t real = size;
+ #ifdef FUSE_XATTR_POSITION
+       if (position != 0) { /* We don't support resource forks */
+-              fuse_reply_err(req, EINVAL);
++              DL(fuse_reply_err)(req, EINVAL);
+               return;
+       }
+ #endif
+@@ -316,26 +344,27 @@
+               return;
+       
+       if (!(buf = malloc(size)))
+-              fuse_reply_err(req, ENOMEM);
++              DL(fuse_reply_err)(req, ENOMEM);
+       else if (sqfs_xattr_lookup(&lli.ll->fs, &lli.inode, name, buf, &real))
+-              fuse_reply_err(req, EIO);
++              DL(fuse_reply_err)(req, EIO);
+       else if (real == 0)
+-              fuse_reply_err(req, sqfs_enoattr());
++              DL(fuse_reply_err)(req, sqfs_enoattr());
+       else if (size == 0)
+-              fuse_reply_xattr(req, real);
++              DL(fuse_reply_xattr)(req, real);
+       else if (size < real)
+-              fuse_reply_err(req, ERANGE);
++              DL(fuse_reply_err)(req, ERANGE);
+       else
+-              fuse_reply_buf(req, buf, real);
++              DL(fuse_reply_buf)(req, buf, real);
+       free(buf);
+ }
+ static void sqfs_ll_op_forget(fuse_req_t req, fuse_ino_t ino,
+               unsigned long nlookup) {
++      LOAD_SYMBOL(void,fuse_reply_none,(fuse_req_t req));
+       sqfs_ll_i lli;
+       sqfs_ll_iget(req, &lli, SQFS_FUSE_INODE_NONE);
+       lli.ll->ino_forget(lli.ll, ino, nlookup);
+-      fuse_reply_none(req);
++      DL(fuse_reply_none)(req);
+ }
+@@ -348,23 +377,27 @@
+ static sqfs_err sqfs_ll_mount(sqfs_ll_chan *ch, const char *mountpoint,
+               struct fuse_args *args) {
++      LOAD_SYMBOL(struct fuse_chan *,fuse_mount,(const char *mountpoint, struct fuse_args *args));
+       #ifdef HAVE_NEW_FUSE_UNMOUNT
+-              ch->ch = fuse_mount(mountpoint, args);
++              ch->ch = DL(fuse_mount)(mountpoint, args);
+       #else
+-              ch->fd = fuse_mount(mountpoint, args);
++              LOAD_SYMBOL(struct fuse_chan *,fuse_kern_chan_new,(int fd));
++              ch->fd = DL(fuse_mount)(mountpoint, args);
+               if (ch->fd == -1)
+                       return SQFS_ERR;
+-              ch->ch = fuse_kern_chan_new(ch->fd);
++              ch->ch = DL(fuse_kern_chan_new)(ch->fd);
+       #endif
+       return ch->ch ? SQFS_OK : SQFS_ERR;
+ }
+ static void sqfs_ll_unmount(sqfs_ll_chan *ch, const char *mountpoint) {
+       #ifdef HAVE_NEW_FUSE_UNMOUNT
+-              fuse_unmount(mountpoint, ch->ch);
++              LOAD_SYMBOL(void,fuse_unmount,(const char *mountpoint, struct fuse_chan *ch));
++              DL(fuse_unmount)(mountpoint, ch->ch);
+       #else
++              LOAD_SYMBOL(void,fuse_unmount,(const char *mountpoint));
+               close(ch->fd);
+-              fuse_unmount(mountpoint);
++              DL(fuse_unmount)(mountpoint);
+       #endif
+ }
+@@ -391,6 +424,19 @@
+ }
+ int fusefs_main(int argc, char *argv[], void (*mounted) (void)) {
++      LOAD_SYMBOL(int,fuse_opt_parse,(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc));
++      LOAD_SYMBOL(int,fuse_parse_cmdline,(struct fuse_args *args, char **mountpoint, int *multithreaded, int *foreground));
++      LOAD_SYMBOL(struct fuse_session *,fuse_lowlevel_new,(struct fuse_args *args, const struct fuse_lowlevel_ops *op, size_t op_size, void *userdata));
++      LOAD_SYMBOL(int,fuse_set_signal_handlers,(struct fuse_session *se));
++      LOAD_SYMBOL(void,fuse_session_add_chan,(struct fuse_session *se, struct fuse_chan *ch));
++      LOAD_SYMBOL(int,fuse_session_loop,(struct fuse_session *se));
++      LOAD_SYMBOL(void,fuse_remove_signal_handlers,(struct fuse_session *se));
++#if HAVE_DECL_FUSE_SESSION_REMOVE_CHAN
++      LOAD_SYMBOL(void,fuse_session_remove_chan,(struct fuse_chan *ch));
++#endif
++      LOAD_SYMBOL(void,fuse_session_destroy,(struct fuse_session *se));
++      LOAD_SYMBOL(void,fuse_opt_free_args,(struct fuse_args *args));
++
+       struct fuse_args args;
+       sqfs_opts opts;
+       
+@@ -429,10 +475,10 @@
+       opts.image = NULL;
+       opts.mountpoint = 0;
+       opts.offset = 0;
+-      if (fuse_opt_parse(&args, &opts, fuse_opts, sqfs_opt_proc) == -1)
++      if (DL(fuse_opt_parse)(&args, &opts, fuse_opts, sqfs_opt_proc) == -1)
+               sqfs_usage(argv[0], true);
+-      if (fuse_parse_cmdline(&args, &mountpoint, &mt, &fg) == -1)
++      if (DL(fuse_parse_cmdline)(&args, &mountpoint, &mt, &fg) == -1)
+               sqfs_usage(argv[0], true);
+       if (mountpoint == NULL)
+               sqfs_usage(argv[0], true);
+@@ -445,33 +491,34 @@
+               sqfs_ll_chan ch;
+               err = -1;
+               if (sqfs_ll_mount(&ch, mountpoint, &args) == SQFS_OK) {
+-                      struct fuse_session *se = fuse_lowlevel_new(&args,
++                      struct fuse_session *se = DL(fuse_lowlevel_new)(&args,
+                               &sqfs_ll_ops, sizeof(sqfs_ll_ops), ll); 
+                       if (se != NULL) {
+                               if (sqfs_ll_daemonize(fg) != -1) {
+-                                      if (fuse_set_signal_handlers(se) != -1) {
+-                                              fuse_session_add_chan(se, ch.ch);
++                                      if (DL(fuse_set_signal_handlers)(se) != -1) {
++                                              DL(fuse_session_add_chan)(se, ch.ch);
+                               if (mounted)
+                                 mounted ();
+                                               /* FIXME: multithreading */
+-                                              err = fuse_session_loop(se);
+-                                              fuse_remove_signal_handlers(se);
++                                              err = DL(fuse_session_loop)(se);
++                                              DL(fuse_remove_signal_handlers)(se);
+                                               #if HAVE_DECL_FUSE_SESSION_REMOVE_CHAN
+-                                                      fuse_session_remove_chan(ch.ch);
++                                                      DL(fuse_session_remove_chan)(ch.ch);
+                                               #endif
+                                       }
+                               }
+-                              fuse_session_destroy(se);
++                              DL(fuse_session_destroy)(se);
+                       }
+                       sqfs_ll_destroy(ll);
+                       sqfs_ll_unmount(&ch, mountpoint);
+               }
+       }
+-      fuse_opt_free_args(&args);
++      DL(fuse_opt_free_args)(&args);
+       if (mounted)
+         rmdir (mountpoint);
+       free(ll);
+       free(mountpoint);
++      CLOSE_LIBRARY;
+       
+       return -err;
+ }
diff --git a/src/xdg-basedir/CMakeLists.txt b/src/xdg-basedir/CMakeLists.txt
new file mode 100644 (file)
index 0000000..064260d
--- /dev/null
@@ -0,0 +1,7 @@
+cmake_minimum_required(VERSION 3.5)
+
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+
+# force static linking
+add_library(xdg-basedir STATIC xdg-basedir.h xdg-basedir.c)
+target_include_directories(xdg-basedir PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/src/xdg-basedir/xdg-basedir.c b/src/xdg-basedir/xdg-basedir.c
new file mode 100644 (file)
index 0000000..c154c76
--- /dev/null
@@ -0,0 +1,67 @@
+#include "xdg-basedir.h"
+#include <string.h>
+#include <stdlib.h>
+
+char* user_home() {
+    return strdup(getenv("HOME"));
+}
+
+char* xdg_config_home()  {
+    char* config_home = getenv("XDG_CONFIG_HOME");
+
+    if (config_home == NULL) {
+        char* home = user_home();
+        static const char const* suffix = "/.config";
+
+        config_home = calloc(strlen(home) + strlen(suffix) + 1, sizeof(char));
+
+        strcpy(config_home, home);
+        strcat(config_home, suffix);
+
+        free(home);
+
+        return config_home;
+    } else {
+        return strdup(config_home);
+    }
+}
+
+char* xdg_data_home() {
+    char* data_home = getenv("XDG_DATA_HOME");
+
+    if (data_home == NULL) {
+        char* home = user_home();
+        static const char const* suffix = "/.local/share";
+
+        data_home = calloc(strlen(home) + strlen(suffix) + 1, sizeof(char));
+
+        strcpy(data_home, home);
+        strcat(data_home, suffix);
+
+        free(home);
+
+        return data_home;
+    } else {
+        return strdup(data_home);
+    }
+}
+
+char* xdg_cache_home() {
+    char* cache_home = getenv("XDG_CACHE_HOME");
+
+    if (cache_home == NULL) {
+        char* home = user_home();
+        static const char const* suffix = "/.cache";
+
+        cache_home = calloc(strlen(home) + strlen(suffix) + 1, sizeof(char));
+
+        strcpy(cache_home, home);
+        strcat(cache_home, suffix);
+
+        free(home);
+
+        return cache_home;
+    } else {
+        return strdup(cache_home);
+    }
+}
diff --git a/src/xdg-basedir/xdg-basedir.h b/src/xdg-basedir/xdg-basedir.h
new file mode 100644 (file)
index 0000000..17c5659
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef XDG_BASEDIR_H
+#define XDG_BASEDIR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Get user's home directory. Convenience wrapper for getenv("HOME").
+ * Returns a freshly allocated char array that must be free'd after usage.
+ */
+char* user_home();
+
+/*
+ * Get XDG config home directory using $XDG_CONFIG_HOME environment variable.
+ * Falls back to default value ~/.config if environment variable is not set.
+ * Returns a freshly allocated char array that must be free'd after usage.
+ */
+char* xdg_config_home();
+
+/*
+ * Get XDG data home directory using $XDG_DATA_HOME environment variable.
+ * Falls back to default value ~/.local/share if environment variable is not set.
+ * Returns a freshly allocated char array that must be free'd after usage.
+ */
+char* xdg_data_home();
+
+/*
+ * Get XDG cache home directory using $XDG_CACHE_HOME environment variable.
+ * Falls back to default value ~/.cache if environment variable is not set.
+ * Returns a freshly allocated char array that must be free'd after usage.
+ */
+char* xdg_cache_home();
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* XDG_BASEDIR_H */
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644 (file)
index 0000000..96f3931
--- /dev/null
@@ -0,0 +1,37 @@
+# build and add test only if tests are enabled
+include(CTest)
+if(BUILD_TESTING)
+    cmake_minimum_required(VERSION 3.5)
+
+    set(CMAKE_CXX_STANDARD 98)
+    set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+    # global definitions
+    add_definitions(
+        -DTEST_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data/"
+        -DGIT_COMMIT="AppImageKit unit tests"
+    )
+
+    add_library(fixtures INTERFACE)
+    target_sources(fixtures INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/fixtures.h)
+    set_property(TARGET fixtures PROPERTY INTERFACE_LINK_LIBRARIES xdg-basedir gtest)
+
+    add_executable(test_libappimage test_libappimage.cpp)
+    target_link_libraries(test_libappimage fixtures libappimage libsquashfuse)
+    add_test(test_libappimage test_libappimage)
+
+    add_executable(test_shared test_shared.cpp)
+    target_link_libraries(test_shared fixtures libappimage_shared)
+    add_test(test_shared test_shared)
+    # needed for some const->non-const conversions
+    target_compile_options(test_shared PRIVATE -fpermissive)
+
+    add_executable(test-xdg-basedir test-xdg-basedir.cpp)
+    target_link_libraries(test-xdg-basedir fixtures xdg-basedir)
+    add_test(test-xdg-basedir test-xdg-basedir)
+
+    add_executable(test_desktop_integration test_desktop_integration.cpp file_management_utils.hpp)
+    target_include_directories(test_desktop_integration PRIVATE "${PROJECT_SOURCE_DIR}/src/libappimage")
+    target_link_libraries(test_desktop_integration libappimage libappimage_shared libsquashfuse gtest gtest_main)
+    add_test(test_desktop_integration test_desktop_integration)
+endif()
diff --git a/tests/data/AppImageExtract_6-x86_64.AppImage b/tests/data/AppImageExtract_6-x86_64.AppImage
new file mode 100755 (executable)
index 0000000..20c2698
Binary files /dev/null and b/tests/data/AppImageExtract_6-x86_64.AppImage differ
diff --git a/tests/data/AppImageExtract_6_no_magic_bytes-x86_64.AppImage b/tests/data/AppImageExtract_6_no_magic_bytes-x86_64.AppImage
new file mode 100755 (executable)
index 0000000..f512825
Binary files /dev/null and b/tests/data/AppImageExtract_6_no_magic_bytes-x86_64.AppImage differ
diff --git a/tests/data/Cura.desktop b/tests/data/Cura.desktop
new file mode 100644 (file)
index 0000000..1a9b615
--- /dev/null
@@ -0,0 +1,15 @@
+[Desktop Entry]
+Name=Ultimaker Cura
+Name[de]=Ultimaker Cura
+GenericName=3D Printing Software
+GenericName[de]=3D-Druck-Software
+Comment=Cura converts 3D models into paths for a 3D printer. It prepares your print for maximum accuracy, minimum printing time and good reliability with many extra features that make your print come out great.
+Comment[de]=Cura wandelt 3D-Modelle in Pfade für einen 3D-Drucker um. Es bereitet Ihren Druck für maximale Genauigkeit, minimale Druckzeit und guter Zuverlässigkeit mit vielen zusätzlichen Funktionen vor, damit Ihr Druck großartig wird.
+Exec=cura %F
+TryExec=cura
+Icon=cura-icon
+Terminal=false
+Type=Application
+MimeType=application/sla;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;image/bmp;image/gif;image/jpeg;image/png;model/x3d+xml;
+Categories=Graphics;
+Keywords=3D;Printing;
diff --git a/tests/data/Echo-no-integrate-x86_64.AppImage b/tests/data/Echo-no-integrate-x86_64.AppImage
new file mode 100755 (executable)
index 0000000..ab9b793
Binary files /dev/null and b/tests/data/Echo-no-integrate-x86_64.AppImage differ
diff --git a/tests/data/Echo-test1234-x86_64.AppImage b/tests/data/Echo-test1234-x86_64.AppImage
new file mode 100755 (executable)
index 0000000..4e893a3
Binary files /dev/null and b/tests/data/Echo-test1234-x86_64.AppImage differ
diff --git a/tests/data/Echo-x86_64.AppImage b/tests/data/Echo-x86_64.AppImage
new file mode 100755 (executable)
index 0000000..bd07c23
Binary files /dev/null and b/tests/data/Echo-x86_64.AppImage differ
diff --git a/tests/data/appimaged-i686.AppImage b/tests/data/appimaged-i686.AppImage
new file mode 100755 (executable)
index 0000000..40cde74
Binary files /dev/null and b/tests/data/appimaged-i686.AppImage differ
diff --git a/tests/data/appimagetool-x86_64.AppImage b/tests/data/appimagetool-x86_64.AppImage
new file mode 100755 (executable)
index 0000000..d07dcc7
Binary files /dev/null and b/tests/data/appimagetool-x86_64.AppImage differ
diff --git a/tests/data/elffile b/tests/data/elffile
new file mode 100755 (executable)
index 0000000..ee3f388
Binary files /dev/null and b/tests/data/elffile differ
diff --git a/tests/data/minimal.iso b/tests/data/minimal.iso
new file mode 100644 (file)
index 0000000..a5150b5
Binary files /dev/null and b/tests/data/minimal.iso differ
diff --git a/tests/data/squashfs-root/.DirIcon b/tests/data/squashfs-root/.DirIcon
new file mode 120000 (symlink)
index 0000000..08ba74f
--- /dev/null
@@ -0,0 +1 @@
+utilities-terminal.svg
\ No newline at end of file
diff --git a/tests/data/squashfs-root/AppRun b/tests/data/squashfs-root/AppRun
new file mode 100755 (executable)
index 0000000..67e971b
Binary files /dev/null and b/tests/data/squashfs-root/AppRun differ
diff --git a/tests/data/squashfs-root/echo.desktop b/tests/data/squashfs-root/echo.desktop
new file mode 100644 (file)
index 0000000..0707d31
--- /dev/null
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Version=1.0
+Type=Application
+Name=Echo
+Name[de]=Echo DE
+Comment=Just echo.
+Exec=echo %F
+Icon=utilities-terminal
+X-AppImage-Version=1234
diff --git a/tests/data/squashfs-root/usr/bin/echo b/tests/data/squashfs-root/usr/bin/echo
new file mode 100755 (executable)
index 0000000..44f4be9
Binary files /dev/null and b/tests/data/squashfs-root/usr/bin/echo differ
diff --git a/tests/data/squashfs-root/utilities-terminal.svg b/tests/data/squashfs-root/utilities-terminal.svg
new file mode 100644 (file)
index 0000000..4a0250d
--- /dev/null
@@ -0,0 +1,320 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="48"
+   height="48"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="utilities-terminal.svg"
+   inkscape:export-filename="/home/uri/Documentos/icon-shadows/48/utilities-terminal.png"
+   inkscape:export-xdpi="720"
+   inkscape:export-ydpi="720">
+  <metadata
+     id="metadata12">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10000"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="2560"
+     inkscape:window-height="1018"
+     id="namedview10"
+     showgrid="true"
+     inkscape:showpageshadow="false"
+     borderlayer="true"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     inkscape:zoom="17.666667"
+     inkscape:cx="24"
+     inkscape:cy="24"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     showguides="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4141"
+       originx="20px"
+       originy="0px" />
+    <sodipodi:guide
+       position="0,48"
+       orientation="48,0"
+       id="guide4147" />
+    <sodipodi:guide
+       position="48,0"
+       orientation="-48,0"
+       id="guide4151" />
+    <sodipodi:guide
+       position="48,48"
+       orientation="0,-48"
+       id="guide4153" />
+    <sodipodi:guide
+       position="4,37"
+       orientation="40,0"
+       id="guide4219" />
+    <sodipodi:guide
+       position="6.9999962,3.999959"
+       orientation="0,34"
+       id="guide4221" />
+    <sodipodi:guide
+       position="44,32"
+       orientation="-29,0"
+       id="guide4223" />
+    <sodipodi:guide
+       position="29.999996,43.999958"
+       orientation="0,-23"
+       id="guide4227" />
+  </sodipodi:namedview>
+  <defs
+     id="defs4">
+    <linearGradient
+       inkscape:collect="always"
+       id="Shadow">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop4186" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0;"
+         offset="1"
+         id="stop4188" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#Shadow"
+       id="linearGradient4334"
+       x1="12"
+       y1="21"
+       x2="12"
+       y2="23"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#Shadow"
+       id="linearGradient4338"
+       gradientUnits="userSpaceOnUse"
+       x1="12"
+       y1="21"
+       x2="12"
+       y2="23"
+       gradientTransform="translate(0,3.9999904)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#Shadow"
+       id="linearGradient4342"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(0,5.9999816)"
+       x1="12"
+       y1="21"
+       x2="12"
+       y2="23" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#Shadow"
+       id="linearGradient4346"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(4,5.9999816)"
+       x1="12"
+       y1="21"
+       x2="12"
+       y2="23" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#Shadow"
+       id="linearGradient4350"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(10,5.9999816)"
+       x1="12"
+       y1="21"
+       x2="12"
+       y2="23" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#Shadow"
+       id="linearGradient4354"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(10,2.9999816)"
+       x1="12"
+       y1="21"
+       x2="12"
+       y2="23" />
+  </defs>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Layer 1">
+    <image
+       y="0"
+       x="0"
+       id="image3262"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz
+AABuugAAbroB1t6xFwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAvDSURB
+VHic7dzRcqO4GoVRksz7v3En52KaOhpFEiIGjLPXqnLZ7STYvvk/ELiXBQAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAACA87w9+w3s8ErvFcj29ew3MOPOQ3X2vd35MwC/2+ygv2UQ7jY8
+e+9n633e7XMAv9/WUO/9/DYxuMPgbL2H+rmZ3wF4ltZQr5+b+Z1LPXuIjgb92+C53r8BrjYa9F+D
+53r/vsyzBmhvkM/et7Yx8zoAe80M6NHQ37rf8zqHunpAzgz+mce97W29HsBee9b6y+E+87i1/ctC
+8M9VL7SMl3felvbAr2+tv60fA1xha6+/vtXP97b31vn54a4anL3h/9Nbb5sAVxnt+f/k1tvmaa44
+Apgd/u/Vff145kgA4Eoze/6fjcefnW2Ve/+nHwmcHYB6+JdDuzXo35fvIZiJQO81AY40WrrpDf/3
+5f+D/+3v47flexha2zw1AmcGoDX8W3v99dBvPWcZCLiT2eWfco9/HfzrUH8rnn8v/qbe7mkROCsA
+W8O/Hvgfy/fhLwDAXf00AOWtnGPrc73XOSUCVywBjfb6WwEoH28FYFkMfuB56hBsBeDP8n2OlYN/
+FIHDnRGA3t55Pfw/OvdbRwG91wB4ht6J4HL9vxz6ZQiW5fscWyNQ7/0ffhRwdADqD9Lb8y+H/sfS
+j0Bv+LdeB+BKrS9wlRFYh/96Xx8F/Blsu1wiKl/n0AiceQ5ga73/o3Fr7f3PXP0jAMDVet/gLZeA
+yr338jYa/us21nD0Xu9hRwag3kPfisBWAMq/K7dbvx7AM5Qz6Gv5vsdeLgX1zmHW1r8pl47qnx92
+FHD2ZaC95Z+tABj+wCsph3L9eF333xOA9XFrGegwRwVga++/HPwzyz8u+QReXRmC92V7iH8t/87C
+8t/L8v1I4LCjgLPPAdTf9h2dAG4FYGncj14T4Aqt4dubQeuwHg3sdfiPlo1ueQ5gtPe/deln77JP
+J3yBO+vNoa+Nn783fr8c/u/VfX0Cufy7h6Nw9vcAekP+J8Pf4Afurl6zL68CWpURKK8YWneO6wCc
+9m3gM84BrPe9o4CZSz0NfuCVlSFozbByNpYBWO9bXyCrLyt9OAb14cherUG9d/j3TvrW2wd4NfU8
+G10eP7OD3Nrmjz0agFrvA/aeq0/41tsCeHVbRwCz87I3K3/syAD0Sje71z868QvwylorJOW/e0cD
+p66MHBGA1ocZ3XofCiDJzN7+aCf54fn5SAB6Lz7zAXrRGG0X4JVtHQXMzszednc7eglodMLjlIIB
+vLDZELR+/2FHnwRelvm9/t7fAvxWW7PvJ0cAP3ZUAEZvzJEAwH/N7Plv/f3Dzr4M9JIPAfCi9uw8
+b/3+bmddBtr6mZO8AH1bqyKHz9AzzgGUWicwAGg77YRvy5H/FUT9/OwRAUCyR5bLH5qjR30RbOZ3
+DHyAbbMrJw/P1LOXgPYQCCDBbWbdnQIAwIXOugy0fg6AfVqz9LaXgQLwQgQAIJQAAIQSAIBQAgAQ
+SgAAQgkAQCgBAAglAAChBAAglAAAhBIAgFACABBKAABCCQBAKAEACCUAAKEEACCUAACEEgCAUAIA
+EEoAAEIJAEAoAQAIJQAAoQQAIJQAAIQSAIBQAgAQSgAAQgkAQCgBAAglAAChBAAglAAAhBIAgFAC
+ABBKAABCCQBAKAEACCUAAKEEACCUAACEEgCAUAIAEEoAAEIJAEAoAQAIJQAAoQQAIJQAAIQSAIBQ
+AgAQSgAAQgkAQCgBAAglAAChBAAglAAAhBIAgFACABBKAABCCQBAKAEACCUAAKEEACCUAACEEgCA
+UAIAEEoAAEIJAEAoAQAIJQAAoQQAIJQAAIQSAIBQAgAQSgAAQgkAQCgBAAglAAChBAAglAAAhBIA
+gFACABBKAABCCQBAKAEACCUAAKEEACCUAACEEgCAUAIAEEoAAEIJAEAoAQAIJQAAoQQAIJQAAIQS
+AIBQAgAQSgAAQgkAQCgBAAglAAChBAAglAAAhBIAgFACABBKAABCCQBAKAEACCUAAKEEACCUAACE
+EgCAUAIAEEoAAEIJAEAoAQAIJQAAoQQAIJQAAIQSAIBQAgAQSgAAQgkAQCgBAAglAAChBAAglAAA
+hBIAgFACABBKAABCCQBAKAEACCUAAKEEACCUAACEEgCAUAIAEEoAAEIJAEAoAQAIJQAAoQQAIJQA
+AIQSAIBQAgAQSgAAQgkAQCgBAAglAAChBAAglAAAhBIAgFACABBKAABCCQBAKAEACCUAAKEEACCU
+AACEEgCAUAIAEEoAAEIJAEAoAQAIJQAAoQQAIJQAAIQSAIBQAgAQSgAAQgkAQCgBAAglAAChBAAg
+lAAAhBIAgFACABBKAABCCQBAKAEACCUAAKEEACCUAACEEgCAUAIAEEoAAEIJAEAoAQAIJQAAoQQA
+IJQAAIQSAIBQAgAQSgAAQgkAQCgBAAglAAChBAAglAAAhBIAgFACABBKAABCCQBAKAEACCUAAKEE
+ACCUAACEEgCAUAIAEEoAAEIJAEAoAQAIJQAAoQQAIJQAAIQSAIBQAgAQSgAAQgkAQCgBAAglAACh
+BAAglAAAhBIAgFACABDq6AB8/b3VzwGwT2uWHjpPHQEAhLpTABwpAAluM+uOCMDMhzn80AXgl6rn
+ZW92PjxTHw3A6I2N3pwgAPxrZh6eEoGzl4BmSwbA93l56sw8MgCze/wiAPBdOSNbc/LwGXrWZaD1
+bfT7AKlmdpxP24E+KgAz61e9MDgfAKRpzcDejvPoaOAhZ5wD6H2YmYoJAfCbbc2+S3eUjz4H0DqB
+Yc8foG3mSOC0E8OPBGDrEtCZW/37o+0CvLLRzNszM3vb3e2oL4Lt+SCfjecA0szMxlNXUM66DLT+
+UOWt/qD179fbAnh1W3v/5Wys5+XMSeEfOfsy0FbRPjuPW9sCeHW9+Taah5esmHwcsI23v7elup+9
+1dtYBo8BXklvZaS11/+nuP9T/Ls8Gmht88eOCkDvfu+tt+36McCdtYZ075xoLwD1clC9rYcdHYDR
+41EY6m3V2229HsDd1IO5NfzrwV4GoIzA6DzAIY4IwLK0h/Ro6Ld+p7etrednfw5wlK1hXO+pzwz/
+1v3WlUAPOToAM+cDyp/Vf7/39QDuYmbZp7XH39rrP33vf1mOC8CyjIf6zFp+7+9dDQS8gtYJ3/Vx
+64Rvfeut/5+y978s5wTgqCt6Rme6RQG4kz3DvxeA0eA/xZEBWJa5K3n2RmDmpArA1Xpf6qqXe1pr
+/6Ph37v+//Avyv5z1IYq5Rv8XP79wtn6oUZ/87H898O+/f3b0XcGlsa/Ac7W2jltnfTdutyzd+XP
+6UcBRwdgHdrlv9f72eH/Xt3WgPROMAM80+iKn96VP72rfraWfw4NwRlHAGsEyhisb7qOwPoBy+Ff
+HgWsw/9zmfvuAMCVRt/07Z38bT1uDf/6NQ4/CjhrCWjVesPlN9p662fr3n+9BDS6nFQIgKvMXPI5
+exQws+5/ijPPAYwu4/xcvg/sdW+/vAkAcEePBqB+rvW3oyshD3H20OxdAVQP9nLY14O/FYB6e73X
+BDhSaxBvXQnUCkG9x1//Z2+nD/9luWZY9v5/n/rWGvj2/oG72nsU0ArC1mWepy4BXTU4ZyMwe+tt
+E+AKvatzeks5M7d6u6cO/2W5dnD2IrDet5Z3RoNfAIBnGl2pszXkW0P/0uG/LNcPzt4XuFrr+qPH
+ve1tvR7AHjODeLQUtPW49RqXDP9led6AnAnB6L61jZnXAdhjdhhvHQ2M7ve+1mGePSBH/6XDzNB/
+9vsHGA3ymeWdywf/6g4DdOZSTpd7AnfWuzx07+9c6m5DtPd+rPUDd7M1vHs/f+rQL915cM6+tzt/
+BuB3+8k5gtt4peH5Su8VyHbLgQ8AAAAAAAAAAAAAAAAAAAAAAAAAADzL/wA+ZrMqzkVe+gAAAABJ
+RU5ErkJggg==
+"
+       preserveAspectRatio="none"
+       height="48"
+       width="48" />
+    <rect
+       style="opacity:1;fill:#37474f;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect4272"
+       width="42"
+       height="36.000042"
+       x="3"
+       y="8"
+       ry="3" />
+    <path
+       style="opacity:1;fill:#455a64;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 44 14 L 4 14 L 4 41 C 4 42.108 4.892 43 6 43 L 42 43 C 43.108 43 44 42.108 44 41 L 44 14 z "
+       id="path4285" />
+    <path
+       style="opacity:1;fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 6 9 C 4.892 9 4 9.892 4 11 L 4 14 L 44 14 L 44 11 C 44 9.892 43.108 9 42 9 L 6 9 z "
+       id="rect4274" />
+    <rect
+       style="opacity:1;fill:#37474f;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect4276"
+       width="40"
+       height="1"
+       x="4"
+       y="14.000004"
+       ry="0" />
+    <circle
+       style="opacity:1;fill:#ef5350;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="path4278"
+       cx="41.499985"
+       cy="11.500004"
+       r="1.5" />
+    <circle
+       r="0.5"
+       cy="11.500004"
+       cx="41.499985"
+       id="circle4280"
+       style="opacity:1;fill:#ef9a9a;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    <rect
+       style="opacity:1;fill:url(#linearGradient4334);fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect4326"
+       width="10"
+       height="1"
+       x="7"
+       y="22" />
+    <rect
+       y="26.000004"
+       x="7"
+       height="1"
+       width="10"
+       id="rect4336"
+       style="opacity:1;fill:url(#linearGradient4338);fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    <path
+       style="opacity:1;fill:#b0bec5;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 9 18 L 9 20 L 7 20 L 7 22 L 9 22 L 9 24 L 7 24 L 7 26 L 9 26 L 9 28 L 11 28 L 11 26 L 13 26 L 13 28 L 15 28 L 15 26 L 17 26 L 17 24 L 15 24 L 15 22 L 17 22 L 17 20 L 15 20 L 15 18 L 13 18 L 13 20 L 11 20 L 11 18 L 9 18 z M 19 18 L 19 25 L 21 25 L 21 18 L 19 18 z M 11 22 L 13 22 L 13 24 L 11 24 L 11 22 z M 19 26 L 19 28 L 21 28 L 21 26 L 19 26 z "
+       id="rect4309" />
+    <rect
+       style="opacity:1;fill:url(#linearGradient4342);fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect4340"
+       width="2"
+       height="1"
+       x="9"
+       y="28" />
+    <rect
+       y="28"
+       x="13"
+       height="1"
+       width="2"
+       id="rect4344"
+       style="opacity:1;fill:url(#linearGradient4346);fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    <rect
+       style="opacity:1;fill:url(#linearGradient4350);fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect4348"
+       width="2"
+       height="1"
+       x="19"
+       y="28" />
+    <rect
+       y="25.000004"
+       x="19"
+       height="1"
+       width="2"
+       id="rect4352"
+       style="opacity:1;fill:url(#linearGradient4354);fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+  </g>
+</svg>
diff --git a/tests/file_management_utils.hpp b/tests/file_management_utils.hpp
new file mode 100644 (file)
index 0000000..313492b
--- /dev/null
@@ -0,0 +1,70 @@
+#include <string>
+#include <glib.h>
+#include <glib/gstdio.h>
+
+void removeDirRecursively(const std::string& path) {
+    GDir* tempdir = NULL;
+    GError* error;
+    tempdir = g_dir_open(path.c_str(), 0, &error);
+    if (!tempdir) {
+        g_warning("%s\n", error->message);
+        g_error_free(error);
+        return;
+    }
+
+    const char* entry;
+    while ((entry = g_dir_read_name(tempdir)) != NULL) {
+        char* full_path = g_strjoin("/", path.c_str(), entry, NULL);
+        if (g_file_test(full_path, G_FILE_TEST_IS_DIR)) {
+            removeDirRecursively(full_path);
+        } else
+            g_remove(full_path);
+
+        free(full_path);
+    }
+
+    g_rmdir(path.c_str());
+    g_dir_close(tempdir);
+}
+
+std::string createTempDir(const std::string& base_name) {
+    std::string result;
+    GError* error = NULL;
+    std::string template_name = base_name + "-XXXXXX";
+    char* path = g_dir_make_tmp(template_name.c_str(), &error);
+    if (error) {
+        g_warning("%s", error->message);
+        g_error_free(error);
+    } else {
+        result = path;
+        free(path);
+    }
+
+    return result;
+}
+
+bool copy_file(const char* source, const char* target) {
+    char *target_dir = g_path_get_dirname(target);
+    int res = g_mkdir_with_parents(target_dir, S_IRWXU);
+    g_free(target_dir);
+
+    if (res == -1)
+        return false;
+
+    int fin = open(source, O_RDONLY);
+    int fout = open(target, O_WRONLY | O_CREAT, S_IRWXU);
+
+    if (fin == -1 || fout == -1)
+        return  false;
+
+    const size_t BUF_SIZE = 1024;
+    char buf[BUF_SIZE];
+    size_t bs_read;
+    while ((bs_read = static_cast<size_t>(read(fin, &buf, BUF_SIZE))) != 0)
+        write(fout, &buf, bs_read);
+
+    close(fin);
+    close(fout);
+
+    return true;
+}
diff --git a/tests/fixtures.h b/tests/fixtures.h
new file mode 100644 (file)
index 0000000..dc40832
--- /dev/null
@@ -0,0 +1,191 @@
+#pragma once
+
+#include <cerrno>
+#include <ftw.h>
+#include <unistd.h>
+#include <xdg-basedir.h>
+#include <gtest/gtest.h>
+
+
+// fixture providing a temporary directory, and a temporary home directory within that directory
+// overwrites HOME environment variable to ensure desktop files etc. are not installed in the system
+class TestBase : public ::testing::Test {
+private:
+    char* oldHome;
+    char* oldXdgDataHome;
+    char* oldXdgConfigHome;
+
+public:
+    std::string tempDir;
+    std::string tempHome;
+
+protected:
+    std::string elf_file_path;
+    std::string iso_9660_file_path;
+    std::string appImage_type_1_file_path;
+    std::string appImage_type_1_no_magic_file_path;
+    std::string appImage_type_2_file_path;
+    std::string appImage_type_2_versioned_path;
+    std::string appImage_type_2_terminal_file_path;
+    std::string appImage_type_2_shall_not_integrate_path;
+
+public:
+    TestBase() {
+        char* tmpl = strdup("/tmp/AppImageKit-unit-tests-XXXXXX");
+        tempDir = mkdtemp(tmpl);
+        free(tmpl);
+
+        tempHome = tempDir + "/HOME";
+
+        mkdir(tempHome.c_str(), 0700);
+
+        oldHome = getenv("HOME");
+        oldXdgDataHome = getenv("XDG_DATA_HOME");
+        oldXdgConfigHome = getenv("XDG_CONFIG_HOME");
+
+        std::string newXdgDataHome = tempHome + "/.local/share";
+        std::string newXdgConfigHome = tempHome + "/.config";
+
+        setenv("HOME", tempHome.c_str(), true);
+        setenv("XDG_DATA_HOME", newXdgDataHome.c_str(), true);
+        setenv("XDG_CONFIG_HOME", newXdgConfigHome.c_str(), true);
+
+        char* xdgDataHome = xdg_data_home();
+        char* xdgConfigHome = xdg_config_home();
+
+        EXPECT_EQ(getenv("HOME"), tempHome);
+        EXPECT_EQ(newXdgDataHome, xdgDataHome);
+        EXPECT_EQ(newXdgConfigHome, xdgConfigHome);
+
+        free(xdgDataHome);
+        free(xdgConfigHome);
+
+        iso_9660_file_path = std::string(TEST_DATA_DIR) + "/minimal.iso";
+        elf_file_path = std::string(TEST_DATA_DIR) + "/elffile";
+        appImage_type_1_file_path = std::string(TEST_DATA_DIR) + "/AppImageExtract_6-x86_64.AppImage";
+        appImage_type_1_no_magic_file_path = std::string(TEST_DATA_DIR) + "/AppImageExtract_6_no_magic_bytes-x86_64.AppImage";
+        appImage_type_2_file_path = std::string(TEST_DATA_DIR) + "/Echo-x86_64.AppImage";
+        appImage_type_2_versioned_path = std::string(TEST_DATA_DIR) + "/Echo-test1234-x86_64.AppImage";
+        appImage_type_2_shall_not_integrate_path = std::string(TEST_DATA_DIR) + "/Echo-no-integrate-x86_64.AppImage";
+        appImage_type_2_terminal_file_path = std::string(TEST_DATA_DIR) + "/appimagetool-x86_64.AppImage";
+
+        EXPECT_TRUE(isFile(appImage_type_1_file_path));
+        EXPECT_TRUE(isFile(appImage_type_2_file_path));
+        EXPECT_TRUE(isFile(appImage_type_2_versioned_path));
+        EXPECT_TRUE(isFile(appImage_type_2_terminal_file_path));
+        EXPECT_TRUE(isFile(appImage_type_2_shall_not_integrate_path));
+    };
+
+    ~TestBase() {
+        if (isDir(tempDir)) {
+            rmTree(tempDir);
+        }
+
+        if (oldHome != NULL) {
+            setenv("HOME", oldHome, true);
+        } else {
+            unsetenv("HOME");
+        }
+
+        if (oldXdgDataHome != NULL) {
+            setenv("XDG_DATA_HOME", oldXdgDataHome, true);
+        } else {
+            unsetenv("XDG_DATA_HOME");
+        }
+
+        if (oldXdgConfigHome != NULL) {
+            setenv("XDG_CONFIG_HOME", oldXdgConfigHome, true);
+        } else {
+            unsetenv("XDG_CONFIG_HOME");
+        }
+    }
+
+private:
+    static const int rmTree(const std::string& path) {
+        int rv = nftw(path.c_str(), unlinkCb, 64, FTW_DEPTH|FTW_MOUNT|FTW_PHYS);
+
+        if (rv != 0) {
+            int error = errno;
+            std::cerr << "nftw() in rmTree(" << path << ") failed: " << strerror(error) << std::endl;
+            return rv;
+        }
+
+        return 0;
+    }
+
+    static int unlinkCb(const char* fpath, const struct stat* sb, int typeflag, struct FTW* ftwbuf) {
+        int rv;
+
+        switch (typeflag) {
+            case FTW_D:
+            case FTW_DNR:
+            case FTW_DP:
+                rv = rmdir(fpath);
+                break;
+            default:
+                rv = unlink(fpath);
+                break;
+        }
+
+        return rv;
+    };
+
+public:
+    static const bool isFile(const std::string& path) {
+        struct stat st;
+
+        if (stat(path.c_str(), &st) != 0) {
+            perror("Failed to call stat(): ");
+            return false;
+        }
+
+        return S_ISREG(st.st_mode);
+    }
+
+    static const bool isDir(const std::string& path) {
+        struct stat st;
+
+        if (stat(path.c_str(), &st) != 0) {
+            perror("Failed to call stat(): ");
+            return false;
+        }
+
+        return S_ISDIR(st.st_mode);
+    }
+
+    static const std::vector<std::string> splitString(const std::string& s, char delim = ' ') {
+        std::vector<std::string> result;
+
+        std::stringstream ss(s);
+        std::string item;
+
+        while (std::getline(ss, item, delim)) {
+            result.push_back(item);
+        }
+
+        return result;
+    }
+
+    static const bool isEmptyString(const std::string& str) {
+        // check whether string is empty beforehand, as the string is interpreted as C string and contains a trailing \0
+        if (str.empty())
+            return true;
+
+        for (int i = 0; i < str.length(); i++) {
+            char chr = str[i];
+            if (chr != ' ' && chr != '\t')
+                return false;
+        }
+
+        return true;
+    }
+
+    static const bool stringStartsWith(const std::string& str, const std::string& prefix) {
+        for (int i = 0; i < prefix.length(); i++) {
+            if (str[i] != prefix[i])
+                return false;
+        }
+
+        return true;
+    }
+};
diff --git a/tests/test-xdg-basedir.cpp b/tests/test-xdg-basedir.cpp
new file mode 100644 (file)
index 0000000..4ca3647
--- /dev/null
@@ -0,0 +1,166 @@
+// system headers
+#include <climits>
+#include <string>
+
+// library headers
+#include <gtest/gtest.h>
+
+// local headers
+#include "xdg-basedir.h"
+
+bool compareStrings(const char* str1, const char* str2) {
+    if (str1 == NULL || str2 == NULL)
+        return false;
+
+    return strcmp(str1, str2) == 0;
+}
+
+TEST(xdg_basedir_test, test_user_home_default_value) {
+    char* home = user_home();
+    EXPECT_PRED2(compareStrings, home, getenv("HOME"));
+    free(home);
+}
+
+TEST(xdg_basedir_test, test_user_home_custom_value) {
+    char* oldValue = strdup(getenv("HOME"));
+    setenv("HOME", "ABCDEFG", true);
+
+    char* currentValue = user_home();
+    EXPECT_PRED2(compareStrings, currentValue, getenv("HOME"));
+    EXPECT_PRED2(compareStrings, currentValue, "ABCDEFG");
+    free(currentValue);
+
+    setenv("HOME", oldValue, true);
+    free(oldValue);
+}
+
+TEST(xdg_basedir_test, test_xdg_data_home_default_value) {
+    // make sure env var is not set, to force function to use default value
+    char* oldValue;
+
+    if ((oldValue = getenv("XDG_DATA_HOME")) != NULL) {
+        unsetenv("XDG_DATA_HOME");
+    }
+
+    char* currentValue = xdg_data_home();
+
+    // too lazy to calculate size
+    char* expectedValue = static_cast<char*>(malloc(PATH_MAX));
+    strcpy(expectedValue, getenv("HOME"));
+    strcat(expectedValue, "/.local/share");
+
+    EXPECT_PRED2(compareStrings, currentValue, expectedValue);
+
+    free(expectedValue);
+    free(currentValue);
+
+    if (oldValue != NULL) {
+        setenv("XDG_DATA_HOME", oldValue, true);
+        free(oldValue);
+    }
+}
+
+TEST(xdg_basedir_test, test_xdg_data_home_custom_value) {
+    char* oldValue = getenv("XDG_DATA_HOME");
+    setenv("XDG_DATA_HOME", "HIJKLM", true);
+
+    char* currentValue = xdg_data_home();
+    EXPECT_PRED2(compareStrings, currentValue, "HIJKLM");
+    free(currentValue);
+
+    if (oldValue != NULL) {
+        setenv("XDG_DATA_HOME", oldValue, true);
+        free(oldValue);
+    } else {
+        unsetenv("XDG_DATA_HOME");
+    }
+}
+
+TEST(xdg_basedir_test, test_xdg_config_home_default_value) {
+    // make sure env var is not set, to force function to use default value
+    char* oldValue;
+
+    if ((oldValue = getenv("XDG_CONFIG_HOME")) != NULL) {
+        unsetenv("XDG_CONFIG_HOME");
+    }
+
+    char* currentValue = xdg_config_home();
+
+    // too lazy to calculate size
+    char* expectedValue = static_cast<char*>(malloc(PATH_MAX));
+    strcpy(expectedValue, getenv("HOME"));
+    strcat(expectedValue, "/.config");
+
+    EXPECT_PRED2(compareStrings, currentValue, expectedValue);
+
+    free(expectedValue);
+    free(currentValue);
+
+    if (oldValue != NULL) {
+        setenv("XDG_CONFIG_HOME", oldValue, true);
+        free(oldValue);
+    }
+}
+
+TEST(xdg_basedir_test, test_xdg_config_home_custom_value) {
+    char* oldValue = getenv("XDG_CONFIG_HOME");
+    setenv("XDG_CONFIG_HOME", "NOPQRS", true);
+
+    char* currentValue = xdg_config_home();
+    EXPECT_PRED2(compareStrings, currentValue, "NOPQRS");
+    free(currentValue);
+
+    if (oldValue != NULL) {
+        setenv("XDG_CONFIG_HOME", oldValue, true);
+        free(oldValue);
+    } else {
+        unsetenv("XDG_CONFIG_HOME");
+    }
+}
+
+TEST(xdg_basedir_test, test_xdg_cache_home_default_value) {
+    // make sure env var is not set, to force function to use default value
+    char* oldValue;
+
+    if ((oldValue = getenv("XDG_CACHE_HOME")) != NULL) {
+        unsetenv("XDG_CACHE_HOME");
+    }
+
+    char* currentValue = xdg_cache_home();
+
+    // too lazy to calculate size
+    char* expectedValue = static_cast<char*>(malloc(PATH_MAX));
+    strcpy(expectedValue, getenv("HOME"));
+    strcat(expectedValue, "/.cache");
+
+    EXPECT_PRED2(compareStrings, currentValue, expectedValue);
+
+    free(expectedValue);
+    free(currentValue);
+
+    if (oldValue != NULL) {
+        setenv("XDG_CACHE_HOME", oldValue, true);
+        free(oldValue);
+    }
+}
+
+TEST(xdg_basedir_test, test_xdg_cache_home_custom_value) {
+    char* oldValue = getenv("XDG_CACHE_HOME");
+    setenv("XDG_CACHE_HOME", "TUVWXY", true);
+
+    char* currentValue = xdg_cache_home();
+    EXPECT_PRED2(compareStrings, currentValue, "TUVWXY");
+    free(currentValue);
+
+    if (oldValue != NULL) {
+        setenv("XDG_CACHE_HOME", oldValue, true);
+        free(oldValue);
+    } else {
+        unsetenv("XDG_CACHE_HOME");
+    }
+}
+
+int main(int argc, char **argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/tests/test_desktop_integration.cpp b/tests/test_desktop_integration.cpp
new file mode 100644 (file)
index 0000000..6d85447
--- /dev/null
@@ -0,0 +1,183 @@
+// library headers
+#include <glib.h>
+#include <gtest/gtest.h>
+#include <glib/gstdio.h>
+#include <appimage/appimage.h>
+#include <fcntl.h>
+extern "C" {
+#include <unistd.h>
+}
+
+#include "desktop_integration.h"
+#include "desktop_file_integration_private.h"
+#include "file_management_utils.hpp"
+
+class DesktopIntegrationTests : public ::testing::Test {
+protected:
+    std::string appdir_path;
+    std::string user_dir_path;
+    char* appimage_path;
+
+    virtual void SetUp() {
+        appimage_path = g_strjoin("/", TEST_DATA_DIR, "Echo-x86_64.AppImage", NULL);
+
+        appdir_path = createTempDir("libappimage-di-appdir");
+        user_dir_path = createTempDir("libappimage-di-user-dir");
+
+        ASSERT_FALSE(appdir_path.empty());
+        ASSERT_FALSE(user_dir_path.empty());
+    }
+
+    virtual void TearDown() {
+        removeDirRecursively(appdir_path);
+        removeDirRecursively(user_dir_path);
+
+        g_free(appimage_path);
+    }
+
+    void fillMinimalAppDir() {
+        std::map<std::string, std::string> files;
+        files["squashfs-root/usr/bin/echo"] = "usr/bin/echo";
+        files["squashfs-root/utilities-terminal.svg"] = ".DirIcon";
+        files["squashfs-root/AppRun"] = "AppRun";
+        files["squashfs-root/echo.desktop"] = "echo.desktop";
+
+        copy_files(files);
+    }
+
+    void copy_files(std::map<std::string, std::string>& files) const {
+        for (std::map<std::string, std::string>::iterator itr = files.begin(); itr != files.end(); itr++) {
+            std::string source = std::string(TEST_DATA_DIR) + "/" + itr->first;
+            std::string target = appdir_path + "/" + itr->second;
+            g_info("Coping %s to %s", source.c_str(), target.c_str());
+            copy_file(source.c_str(), target.c_str());
+        }
+    }
+};
+
+TEST_F(DesktopIntegrationTests, create_remove_tempdir) {
+    char* tempdir = desktop_integration_create_tempdir();
+    ASSERT_TRUE(g_file_test(tempdir, G_FILE_TEST_IS_DIR));
+    ASSERT_TRUE(g_file_test(tempdir, G_FILE_TEST_EXISTS));
+
+    desktop_integration_remove_tempdir(tempdir);
+
+    ASSERT_FALSE(g_file_test(tempdir, G_FILE_TEST_IS_DIR));
+    ASSERT_FALSE(g_file_test(tempdir, G_FILE_TEST_EXISTS));
+
+    free(tempdir);
+}
+
+TEST_F(DesktopIntegrationTests, extract_relevant_files) {
+    // Test body
+    desktop_integration_extract_relevant_files(appimage_path, appdir_path.c_str());
+
+    GDir* tempdir = NULL;
+    tempdir = g_dir_open(appdir_path.c_str(), 0, NULL);
+    if (!tempdir)
+        FAIL();
+
+    const char* entry;
+    bool desktop_file_found = false;
+    while ((entry = g_dir_read_name(tempdir)) != NULL) {
+        if (g_str_has_suffix(entry, ".Desktop") || g_str_has_suffix(entry, ".desktop"))
+            desktop_file_found = true;
+    }
+
+    g_dir_close(tempdir);
+    ASSERT_TRUE(desktop_file_found);
+}
+
+
+char* extract_exec_args_from_desktop(GKeyFile* original_desktop_file) {
+    char* original_exec_value = g_key_file_get_string(original_desktop_file,
+                                                      G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, NULL);
+    g_key_file_free(original_desktop_file);
+
+    char** original_exec_value_parts = g_strsplit_set(original_exec_value, " ", 2);
+    char* original_exec_value_args = NULL;
+    char** ptr = original_exec_value_parts;
+    if (*ptr != NULL)
+        ptr++;
+    if (*ptr != NULL)
+        original_exec_value_args = g_strdup(*ptr);
+
+    for (ptr = original_exec_value_parts; *ptr != NULL; ptr++)
+        free(*ptr);
+    free(original_exec_value_parts);
+    free(original_exec_value);
+
+    return original_exec_value_args;
+}
+
+TEST_F(DesktopIntegrationTests, modify_desktop_file) {
+    // Test SetUp
+    fillMinimalAppDir();
+
+    // Test body
+    char* desktop_file_path = find_desktop_file(appdir_path.c_str());
+    ASSERT_TRUE(desktop_file_path);
+    GKeyFile* original_desktop_file = load_desktop_file(desktop_file_path);
+
+    char* original_desktop_file_args = extract_exec_args_from_desktop(original_desktop_file);
+
+    char* appimage_path_md5 = appimage_get_md5(appimage_path);
+    bool res = desktop_integration_modify_desktop_file(appimage_path, appdir_path.c_str(), appimage_path_md5);
+    ASSERT_TRUE(res);
+
+    GKeyFile* desktop_file = load_desktop_file(desktop_file_path);
+
+    char* tryExecValue = g_key_file_get_string(desktop_file,
+                                               G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, NULL);
+    ASSERT_STREQ(tryExecValue, appimage_path);
+    g_free(tryExecValue);
+
+    char* execValue = g_key_file_get_string(desktop_file,
+                                            G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, NULL);
+
+    ASSERT_TRUE(g_str_has_prefix(execValue, appimage_path));
+    ASSERT_TRUE(g_str_has_suffix(execValue, original_desktop_file_args));
+    g_free(original_desktop_file_args);
+    g_free(execValue);
+
+    char* iconValue = g_key_file_get_string(desktop_file,
+                                            G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, NULL);
+
+    char* expected_icon_prefix = g_strjoin("", "appimagekit_", appimage_path_md5, "_", NULL);
+    ASSERT_TRUE(g_str_has_prefix(iconValue, expected_icon_prefix));
+    g_free(expected_icon_prefix);
+    g_free(appimage_path_md5);
+    g_free(iconValue);
+
+    // Test Clean Up
+    g_key_file_free(desktop_file);
+    free(desktop_file_path);
+}
+
+
+TEST_F(DesktopIntegrationTests, move_files_to_user_data_dir) {
+    // Test SetUp
+    fillMinimalAppDir();
+
+    char* md5sum = appimage_get_md5(appimage_path);
+
+    desktop_integration_modify_desktop_file(appimage_path, appdir_path.c_str(), md5sum);
+    // Test body
+    ASSERT_TRUE(desktop_integration_move_files_to_user_data_dir(appdir_path.c_str(), user_dir_path.c_str(), md5sum));
+
+    /** Validate that the desktop file was copied */
+    char* path = g_strjoin("", user_dir_path.c_str(), "/applications/appimagekit_", md5sum, "-echo.desktop",
+                           NULL);
+    ASSERT_TRUE(g_file_test(path, G_FILE_TEST_EXISTS));
+    free(path);
+
+    /** Validate that the icon was copied */
+    path = g_strjoin("", user_dir_path.c_str(), "/icons/hicolor/32x32/apps/appimagekit_", md5sum,
+                     "_utilities-terminal.png",
+                     NULL);
+    ASSERT_TRUE(g_file_test(path, G_FILE_TEST_EXISTS));
+    free(path);
+
+    // Test Clean Up
+    free(md5sum);
+}
diff --git a/tests/test_getsection.cpp b/tests/test_getsection.cpp
new file mode 100644 (file)
index 0000000..4546e80
--- /dev/null
@@ -0,0 +1,27 @@
+// system headers
+#include <cmath>
+
+// library headers
+#include <gtest/gtest.h>
+
+// local headers
+#include <appimage/appimage.h>
+#include "fixtures.h"
+
+extern "C" {
+    #include "getsection.h"
+}
+
+
+using namespace std;
+
+
+// most simple derivative class for better naming of the tests in this file
+class GetSectionCTest : public AppImageKitTest {};
+
+
+
+int main(int argc, char **argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/tests/test_libappimage.cpp b/tests/test_libappimage.cpp
new file mode 100644 (file)
index 0000000..7ce7337
--- /dev/null
@@ -0,0 +1,760 @@
+#include "appimage/appimage.h"
+
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <cstdio>
+#include <cstring>
+#include <fstream>
+#include <memory>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include <squashfuse.h>
+#include <gtest/gtest.h>
+
+#include "fixtures.h"
+
+using namespace std;
+
+// forward declarations of non-publicly available functions which are needed by some of the tests
+// TODO: get rid of those
+extern "C" {
+    bool write_edited_desktop_file(GKeyFile*, const char*, gchar*, int, char*, gboolean);
+}
+
+class LibAppImageTest : public TestBase {
+protected:
+    void rm_file(const std::string &path) {
+        g_remove(path.c_str());
+    }
+
+    bool areIntegrationFilesDeployed(const std::string &path) {
+        gchar *sum = appimage_get_md5(path.c_str());
+
+        GDir *dir;
+        GError *error = NULL;
+        const gchar *filename = NULL;
+
+        char *data_home = xdg_data_home();
+        char *apps_path = g_strconcat(data_home, "/applications/", NULL);
+        free(data_home);
+
+        bool found = false;
+        dir = g_dir_open(apps_path, 0, &error);
+        if (dir != NULL) {
+            while ((filename = g_dir_read_name(dir))) {
+                gchar* m = g_strrstr(filename, sum);
+
+                if (m != NULL)
+                    found = true;
+            }
+            g_dir_close(dir);
+        }
+        g_free(apps_path);
+        g_free(sum);
+        return found;
+    }
+};
+
+TEST_F(LibAppImageTest, appimage_get_type_invalid) {
+    ASSERT_EQ(appimage_get_type("/tmp", false), -1);
+}
+
+TEST_F(LibAppImageTest, appimage_get_type_on_bare_iso_9660_file) {
+    ASSERT_EQ(appimage_get_type(iso_9660_file_path.c_str(), false), -1);
+}
+
+TEST_F(LibAppImageTest, appimage_get_type_on_bare_elf_file) {
+    ASSERT_EQ(appimage_get_type(elf_file_path.c_str(), false), -1);
+}
+
+TEST_F(LibAppImageTest, appimage_get_type_1) {
+    ASSERT_EQ(appimage_get_type(appImage_type_1_file_path.c_str(), false), 1);
+}
+
+TEST_F(LibAppImageTest, appimage_get_type_on_appimage_type_1_withouth_magic_bytes) {
+    ASSERT_EQ(appimage_get_type(appImage_type_1_no_magic_file_path.c_str(), false), 1);
+}
+
+TEST_F(LibAppImageTest, appimage_get_type_2) {
+    ASSERT_EQ(appimage_get_type(appImage_type_2_file_path.c_str(), false), 2);
+}
+
+TEST_F(LibAppImageTest, appimage_register_in_system_with_type1) {
+    ASSERT_EQ(appimage_register_in_system(appImage_type_1_file_path.c_str(), false), 0);
+
+    ASSERT_TRUE(areIntegrationFilesDeployed(appImage_type_1_file_path));
+
+    appimage_unregister_in_system(appImage_type_1_file_path.c_str(), false);
+}
+
+TEST_F(LibAppImageTest, appimage_register_in_system_with_type2) {
+    ASSERT_EQ(appimage_register_in_system(appImage_type_2_file_path.c_str(), false), 0);
+
+    ASSERT_TRUE(areIntegrationFilesDeployed(appImage_type_2_file_path));
+
+    appimage_unregister_in_system(appImage_type_2_file_path.c_str(), false);
+}
+
+TEST_F(LibAppImageTest, appimage_type1_register_in_system) {
+    ASSERT_TRUE(appimage_type1_register_in_system(appImage_type_1_file_path.c_str(), false));
+
+    ASSERT_TRUE(areIntegrationFilesDeployed(appImage_type_1_file_path));
+
+    appimage_unregister_in_system(appImage_type_1_file_path.c_str(), false);
+}
+
+TEST_F(LibAppImageTest, appimage_type2_register_in_system) {
+    EXPECT_TRUE(appimage_type2_register_in_system(appImage_type_2_file_path.c_str(), false));
+
+    EXPECT_TRUE(areIntegrationFilesDeployed(appImage_type_2_file_path));
+    appimage_unregister_in_system(appImage_type_2_file_path.c_str(), false);
+}
+
+TEST_F(LibAppImageTest, appimage_unregister_in_system) {
+    ASSERT_FALSE(areIntegrationFilesDeployed(appImage_type_1_file_path));
+    ASSERT_FALSE(areIntegrationFilesDeployed(appImage_type_2_file_path));
+}
+
+TEST_F(LibAppImageTest, appimage_get_md5) {
+    std::string pathToTestFile = "/some/fixed/path";
+
+    std::string expected = "972f4824b8e6ea26a55e9af60a285af7";
+
+    gchar *sum = appimage_get_md5(pathToTestFile.c_str());
+    EXPECT_EQ(sum, expected);
+    g_free(sum);
+
+    unlink(pathToTestFile.c_str());
+}
+
+TEST_F(LibAppImageTest, get_md5_invalid_file_path) {
+    gchar *sum = appimage_get_md5("");
+
+    ASSERT_TRUE(sum == NULL) << "sum is not NULL";
+}
+
+TEST_F(LibAppImageTest, create_thumbnail_appimage_type_1) {
+    appimage_create_thumbnail(appImage_type_1_file_path.c_str(), false);
+
+    gchar *sum = appimage_get_md5(appImage_type_1_file_path.c_str());
+
+    char *cache_home = xdg_cache_home();
+    std::string path = std::string(cache_home)
+                       + "/thumbnails/normal/"
+                       + std::string(sum) + ".png";
+
+    g_free(cache_home);
+    g_free(sum);
+
+    ASSERT_TRUE(g_file_test(path.c_str(), G_FILE_TEST_EXISTS));
+
+    // Clean
+    rm_file(path);
+}
+
+TEST_F(LibAppImageTest, create_thumbnail_appimage_type_2) {
+    appimage_create_thumbnail(appImage_type_2_file_path.c_str(), false);
+
+    gchar *sum = appimage_get_md5(appImage_type_2_file_path.c_str());
+
+    char* cache_home = xdg_cache_home();
+    std::string path = std::string(cache_home)
+                       + "/thumbnails/normal/"
+                       + std::string(sum) + ".png";
+
+    g_free(cache_home);
+    g_free(sum);
+
+    ASSERT_TRUE(g_file_test(path.c_str(), G_FILE_TEST_EXISTS));
+
+    // Clean
+    rm_file(path);
+}
+
+TEST_F(LibAppImageTest, appimage_extract_file_following_symlinks) {
+    std::string target_path = tempDir + "test_libappimage_tmp_file";
+    appimage_extract_file_following_symlinks(appImage_type_2_file_path.c_str(), "echo.desktop",
+                                             target_path.c_str());
+
+    const char expected[] = ("[Desktop Entry]\n"
+            "Version=1.0\n"
+            "Type=Application\n"
+            "Name=Echo\n"
+            "Comment=Just echo.\n"
+            "Exec=echo %F\n"
+            "Icon=utilities-terminal\n");
+
+    ASSERT_TRUE(g_file_test(target_path.c_str(), G_FILE_TEST_EXISTS));
+
+    std::ifstream file(target_path.c_str(), std::ios::binary | std::ios::ate);
+    std::streamsize size = file.tellg();
+    file.seekg(0, std::ios::beg);
+
+    std::vector<char> buffer(static_cast<unsigned long>(size));
+    if (file.read(buffer.data(), size))
+        ASSERT_TRUE(strncmp(expected, buffer.data(), strlen(expected)) == 0);
+    else
+        FAIL();
+
+    // Clean
+    remove(target_path.c_str());
+}
+
+TEST_F(LibAppImageTest, appimage_read_file_into_buffer_following_symlinks_type_2) {
+    char* buf = NULL;
+    unsigned long bufsize = 0;
+    bool rv = appimage_read_file_into_buffer_following_symlinks(appImage_type_2_file_path.c_str(), "echo.desktop", &buf, &bufsize);
+
+    // using EXPECT makes sure the free call is executed
+    EXPECT_TRUE(rv);
+    EXPECT_TRUE(buf != NULL);
+    EXPECT_TRUE(bufsize != 0);
+
+    static const char expected[] = ("[Desktop Entry]\n"
+                                    "Version=1.0\n"
+                                    "Type=Application\n"
+                                    "Name=Echo\n"
+                                    "Comment=Just echo.\n"
+                                    "Exec=echo %F\n"
+                                    "Icon=utilities-terminal\n");
+
+    EXPECT_EQ(bufsize, strlen(expected));
+    EXPECT_TRUE(buf != NULL && strncmp(expected, buf, bufsize) == 0);
+    free(buf);
+}
+
+TEST_F(LibAppImageTest, appimage_read_file_into_buffer_following_symlinks_type_1) {
+    char* buf = NULL;
+    unsigned long bufsize = 0;
+    bool rv = appimage_read_file_into_buffer_following_symlinks(appImage_type_1_file_path.c_str(), "AppImageExtract.desktop", &buf, &bufsize);
+
+    // using EXPECT makes sure the free call is executed
+    EXPECT_TRUE(rv);
+    EXPECT_TRUE(buf != NULL);
+    EXPECT_TRUE(bufsize != 0);
+
+    static const char expected[] = ("[Desktop Entry]\n"
+                                    "Name=AppImageExtract\n"
+                                    "Exec=appimageextract\n"
+                                    "Icon=AppImageExtract\n"
+                                    "Terminal=true\n"
+                                    "Type=Application\n"
+                                    "Categories=Development;\n"
+                                    "Comment=Extract AppImage contents, part of AppImageKit\n"
+                                    "StartupNotify=true\n");
+
+    EXPECT_EQ(bufsize, strlen(expected));
+    EXPECT_TRUE(buf != NULL && strncmp(expected, buf, bufsize) == 0);
+    free(buf);
+}
+
+
+TEST_F(LibAppImageTest, appimage_read_file_into_buffer_following_hardlinks_type_1) {
+    char* buf = NULL;
+    unsigned long bufsize = 0;
+    bool rv = appimage_read_file_into_buffer_following_symlinks(appImage_type_1_file_path.c_str(), "AppImageExtract.png", &buf, &bufsize);
+
+    // using EXPECT makes sure the free call is executed
+    EXPECT_TRUE(rv);
+    EXPECT_TRUE(buf != NULL);
+    EXPECT_TRUE(bufsize != 0);
+
+    free(buf);
+}
+
+TEST_F(LibAppImageTest, appimage_extract_file_following_hardlinks_type_1) {
+    const char target_file_path[] = "/tmp/appimage_tmp_file";
+    appimage_extract_file_following_symlinks(appImage_type_1_file_path.c_str(),
+                                             "AppImageExtract.png", target_file_path);
+
+
+    // using EXPECT makes sure the free call is executed
+    EXPECT_TRUE(g_file_test(target_file_path, G_FILE_TEST_EXISTS));
+    EXPECT_TRUE(g_file_test(target_file_path, G_FILE_TEST_IS_REGULAR));
+
+    struct stat stats = {};
+    lstat(target_file_path, &stats);
+    EXPECT_NE(stats.st_size, 0);
+
+    g_remove(target_file_path);
+}
+
+bool test_appimage_is_registered_in_system(const std::string& pathToAppImage, bool integrateAppImage) {
+    if (integrateAppImage) {
+        EXPECT_EQ(appimage_register_in_system(pathToAppImage.c_str(), false), 0);
+    }
+
+    return appimage_is_registered_in_system(pathToAppImage.c_str());
+}
+
+TEST_F(LibAppImageTest, appimage_is_registered_in_system) {
+    // make sure tested AppImages are not registered
+    appimage_unregister_in_system(appImage_type_1_file_path.c_str(), false);
+    appimage_unregister_in_system(appImage_type_2_file_path.c_str(), false);
+
+    // if the test order is false -> true, cleanup isn't necessary
+
+    // type 1 tests
+    EXPECT_FALSE(test_appimage_is_registered_in_system(appImage_type_1_file_path, false));
+    EXPECT_TRUE(test_appimage_is_registered_in_system(appImage_type_1_file_path, true));
+
+    // type 2 tests
+    EXPECT_FALSE(test_appimage_is_registered_in_system(appImage_type_2_file_path, false));
+    EXPECT_TRUE(test_appimage_is_registered_in_system(appImage_type_2_file_path, true));
+
+    // cleanup
+    appimage_unregister_in_system(appImage_type_1_file_path.c_str(), false);
+    appimage_unregister_in_system(appImage_type_2_file_path.c_str(), false);
+}
+
+TEST_F(LibAppImageTest, appimage_list_files_false_appimage) {
+
+    char **files = appimage_list_files("/bin/ls");
+
+    char *expected[] = {NULL};
+
+    int i = 0;
+    for (; files[i] != NULL && expected[i] != NULL; i++)
+        EXPECT_STREQ(files[i], expected[i]);
+
+    appimage_string_list_free(files);
+
+    if (i != 0)
+        FAIL();
+}
+
+TEST_F(LibAppImageTest, appimage_list_files_type_1) {
+
+    char **files = appimage_list_files(appImage_type_1_file_path.c_str());
+
+    const char *expected[] = {
+        (char *) "AppImageExtract.desktop",
+        (char *) ".DirIcon",
+        (char *) "AppImageExtract.png",
+        (char *) "usr/bin/appimageextract",
+        (char *) "AppRun",
+        (char *) "usr/bin/xorriso",
+        (char *) "usr/lib/libburn.so.4",
+        (char *) "usr/lib/libisoburn.so.1",
+        (char *) "usr/lib/libisofs.so.6",
+        NULL};
+
+    int i = 0;
+    for (; files[i] != NULL && expected[i] != NULL; i++)
+        EXPECT_STREQ(files[i], expected[i]);
+
+    appimage_string_list_free(files);
+    if (i != 9)
+        FAIL();
+}
+
+TEST_F(LibAppImageTest, appimage_list_files_type_2) {
+
+    char **files = appimage_list_files(appImage_type_2_file_path.c_str());
+
+    char *expected[] = {
+            (char *) ".DirIcon",
+            (char *) "AppRun",
+            (char *) "echo.desktop",
+            (char *) "usr",
+            (char *) "usr/bin",
+            (char *) "usr/bin/echo",
+            (char *) "usr/share",
+            (char *) "usr/share/applications",
+            (char *) "usr/share/applications/echo.desktop",
+            (char *) "utilities-terminal.svg",
+            NULL};
+
+    int i = 0;
+    for (; files[i] != NULL && expected[i] != NULL; i++)
+        EXPECT_STREQ(files[i], expected[i]);
+
+    appimage_string_list_free(files);
+    if (i != 10)
+        FAIL();
+}
+
+TEST_F(LibAppImageTest, test_appimage_registered_desktop_file_path_not_registered) {
+    EXPECT_TRUE(appimage_registered_desktop_file_path(appImage_type_1_file_path.c_str(), NULL, false) == NULL);
+    EXPECT_TRUE(appimage_registered_desktop_file_path(appImage_type_2_file_path.c_str(), NULL, false) == NULL);
+}
+
+TEST_F(LibAppImageTest, test_appimage_registered_desktop_file_path_type1) {
+    EXPECT_TRUE(appimage_type1_register_in_system(appImage_type_1_file_path.c_str(), false));
+
+    char* desktop_file_path = appimage_registered_desktop_file_path(appImage_type_1_file_path.c_str(), NULL, false);
+
+    EXPECT_TRUE(desktop_file_path != NULL);
+
+    free(desktop_file_path);
+}
+
+TEST_F(LibAppImageTest, test_appimage_registered_desktop_file_path_type2) {
+    EXPECT_TRUE(appimage_type2_register_in_system(appImage_type_2_file_path.c_str(), false));
+
+    char* desktop_file_path = appimage_registered_desktop_file_path(appImage_type_2_file_path.c_str(), NULL, false);
+
+    EXPECT_TRUE(desktop_file_path != NULL);
+
+    free(desktop_file_path);
+}
+
+TEST_F(LibAppImageTest, test_appimage_registered_desktop_file_path_type1_precalculated_md5) {
+    EXPECT_TRUE(appimage_type1_register_in_system(appImage_type_1_file_path.c_str(), false));
+
+    char* md5 = appimage_get_md5(appImage_type_1_file_path.c_str());
+    char* desktop_file_path = appimage_registered_desktop_file_path(appImage_type_1_file_path.c_str(), md5, false);
+    free(md5);
+
+    EXPECT_TRUE(desktop_file_path != NULL);
+
+    free(desktop_file_path);
+}
+
+TEST_F(LibAppImageTest, test_appimage_registered_desktop_file_path_type2_precalculated_md5) {
+    EXPECT_TRUE(appimage_type2_register_in_system(appImage_type_2_file_path.c_str(), false));
+
+    char* md5 = appimage_get_md5(appImage_type_2_file_path.c_str());
+    char* desktop_file_path = appimage_registered_desktop_file_path(appImage_type_2_file_path.c_str(), md5, false);
+    free(md5);
+
+    EXPECT_TRUE(desktop_file_path != NULL);
+
+    free(desktop_file_path);
+}
+
+TEST_F(LibAppImageTest, test_appimage_registered_desktop_file_path_type1_wrong_md5) {
+    EXPECT_TRUE(appimage_type1_register_in_system(appImage_type_1_file_path.c_str(), false));
+
+    char* md5 = strdup("abcdefg");
+    char* desktop_file_path = appimage_registered_desktop_file_path(appImage_type_1_file_path.c_str(), md5, false);
+    free(md5);
+
+    EXPECT_TRUE(desktop_file_path == NULL);
+
+    free(desktop_file_path);
+}
+
+TEST_F(LibAppImageTest, test_appimage_registered_desktop_file_path_type2_wrong_md5) {
+    EXPECT_TRUE(appimage_type2_register_in_system(appImage_type_2_file_path.c_str(), false));
+
+    char* md5 = strdup("abcdefg");
+    char* desktop_file_path = appimage_registered_desktop_file_path(appImage_type_2_file_path.c_str(), md5, false);
+    free(md5);
+
+    EXPECT_TRUE(desktop_file_path == NULL);
+
+    free(desktop_file_path);
+}
+
+TEST_F(LibAppImageTest, test_appimage_type2_appimage_version) {
+    EXPECT_TRUE(appimage_type2_register_in_system(appImage_type_2_versioned_path.c_str(), false));
+
+    char* desktopFilePath = appimage_registered_desktop_file_path(appImage_type_2_versioned_path.c_str(), NULL, false);
+
+    GKeyFile *desktopFile = g_key_file_new();
+
+    GError* error = NULL;
+
+    gboolean loaded = g_key_file_load_from_file(desktopFile, desktopFilePath, G_KEY_FILE_KEEP_TRANSLATIONS, &error);
+
+    if (!loaded) {
+        g_key_file_free(desktopFile);
+        ADD_FAILURE() << "Failed to read desktop file: " << error->message;
+        g_error_free(error);
+        return;
+    }
+
+    const std::string versionKey = "X-AppImage-Version";
+    const std::string oldNameKey = "X-AppImage-Old-Name";
+
+    std::string expectedVersion = "test1234";
+    gchar* actualVersion = g_key_file_get_string(desktopFile, G_KEY_FILE_DESKTOP_GROUP, versionKey.c_str(), &error);
+
+    if (actualVersion == NULL) {
+        g_key_file_free(desktopFile);
+        ADD_FAILURE() << "Failed to get " << versionKey << " key: " << error->message;
+        g_error_free(error);
+        return;
+    }
+
+    EXPECT_EQ(expectedVersion, std::string(actualVersion));
+
+    gchar* oldName = g_key_file_get_string(desktopFile, G_KEY_FILE_DESKTOP_GROUP, oldNameKey.c_str(), &error);
+
+    if (oldName == NULL) {
+        g_key_file_free(desktopFile);
+        ADD_FAILURE() << "Failed to get " << oldNameKey << " key: " << error->message;
+        g_error_free(error);
+        return;
+    }
+
+    gchar* newName = g_key_file_get_string(desktopFile, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, &error);
+
+    if (newName == NULL) {
+        g_key_file_free(desktopFile);
+        ADD_FAILURE() << "Failed to get " << G_KEY_FILE_DESKTOP_KEY_NAME << " key: " << error->message;
+        g_error_free(error);
+        return;
+    }
+
+    std::string expectedName = std::string(oldName) + " (" + expectedVersion + ")";
+
+    EXPECT_EQ(expectedName, std::string(newName));
+
+    // cleanup
+    g_key_file_free(desktopFile);
+    if (error != NULL)
+        g_error_free(error);
+}
+
+TEST_F(LibAppImageTest, test_try_exec_key_exists_type_1) {
+    const std::string& pathToAppImage = appImage_type_1_file_path;
+
+    ASSERT_EQ(appimage_register_in_system(pathToAppImage.c_str(), false), 0);
+
+    GKeyFile* kf = g_key_file_new();
+
+    const char* desktopFilePath = appimage_registered_desktop_file_path(pathToAppImage.c_str(), NULL, false);
+
+    ASSERT_TRUE(g_key_file_load_from_file(kf, desktopFilePath, G_KEY_FILE_NONE, NULL));
+
+    const char* expectedTryExecValue = g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, NULL);
+
+    EXPECT_EQ(expectedTryExecValue, pathToAppImage);
+}
+
+TEST_F(LibAppImageTest, test_try_exec_key_exists_type_2) {
+    const std::string& pathToAppImage = appImage_type_2_file_path;
+
+    ASSERT_EQ(appimage_register_in_system(pathToAppImage.c_str(), false), 0);
+
+    GKeyFile* kf = g_key_file_new();
+
+    const char* desktopFilePath = appimage_registered_desktop_file_path(pathToAppImage.c_str(), NULL, false);
+
+    ASSERT_TRUE(g_key_file_load_from_file(kf, desktopFilePath, G_KEY_FILE_NONE, NULL));
+
+    const char* expectedTryExecValue = g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, NULL);
+
+    EXPECT_EQ(expectedTryExecValue, pathToAppImage);
+}
+
+TEST_F(LibAppImageTest, test_appimage_type1_is_terminal_app) {
+    // TODO: add type 1 AppImage with Terminal=false
+    EXPECT_EQ(appimage_type1_is_terminal_app(appImage_type_1_file_path.c_str()), 1);
+    EXPECT_EQ(appimage_type1_is_terminal_app(appImage_type_2_file_path.c_str()), -1);
+    EXPECT_EQ(appimage_type1_is_terminal_app("/invalid/path"), -1);
+}
+
+TEST_F(LibAppImageTest, test_appimage_type2_is_terminal_app) {
+    EXPECT_EQ(appimage_type2_is_terminal_app(appImage_type_1_file_path.c_str()), -1);
+    EXPECT_EQ(appimage_type2_is_terminal_app(appImage_type_2_terminal_file_path.c_str()), 1);
+    EXPECT_EQ(appimage_type2_is_terminal_app(appImage_type_2_file_path.c_str()), 0);
+    EXPECT_EQ(appimage_type2_is_terminal_app("/invalid/path"), -1);
+}
+
+TEST_F(LibAppImageTest, test_appimage_is_terminal_app) {
+    EXPECT_EQ(appimage_is_terminal_app(appImage_type_1_file_path.c_str()), 1);
+    EXPECT_EQ(appimage_is_terminal_app(appImage_type_2_file_path.c_str()), 0);
+    // TODO: add type 1 AppImage with Terminal=true
+    //EXPECT_EQ(appimage_is_terminal_app(appImage_type_1_terminal_file_path.c_str()), 1);
+    EXPECT_EQ(appimage_is_terminal_app(appImage_type_2_terminal_file_path.c_str()), 1);
+    EXPECT_EQ(appimage_is_terminal_app("/invalid/path"), -1);
+}
+
+TEST_F(LibAppImageTest, test_appimage_type1_shall_not_integrate) {
+    // TODO: add type 1 AppImage with X-AppImage-Integrate=false
+    //EXPECT_EQ(appimage_is_terminal_app(appImage_type_1_shall_not_integrate_path.c_str()), 1);
+    EXPECT_EQ(appimage_type1_shall_not_be_integrated(appImage_type_1_file_path.c_str()), 0);
+    EXPECT_EQ(appimage_type1_shall_not_be_integrated(appImage_type_2_file_path.c_str()), -1);
+    EXPECT_EQ(appimage_type1_shall_not_be_integrated("/invalid/path"), -1);
+}
+
+TEST_F(LibAppImageTest, test_appimage_type2_shall_not_integrate) {
+    EXPECT_EQ(appimage_type2_shall_not_be_integrated(appImage_type_1_file_path.c_str()), -1);
+    EXPECT_EQ(appimage_type2_shall_not_be_integrated(appImage_type_2_shall_not_integrate_path.c_str()), 1);
+    EXPECT_EQ(appimage_type2_shall_not_be_integrated(appImage_type_2_file_path.c_str()), 0);
+    EXPECT_EQ(appimage_type2_shall_not_be_integrated("/invalid/path"), -1);
+}
+
+TEST_F(LibAppImageTest, test_appimage_shall_not_integrate) {
+    EXPECT_EQ(appimage_shall_not_be_integrated(appImage_type_1_file_path.c_str()), 0);
+    EXPECT_EQ(appimage_shall_not_be_integrated(appImage_type_2_file_path.c_str()), 0);
+    // TODO: add type 1 AppImage with X-AppImage-Integrate=false
+    //EXPECT_EQ(appimage_shall_not_be_integrated(appImage_type_1_shall_not_integrate_path.c_str()), 1);
+    EXPECT_EQ(appimage_shall_not_be_integrated(appImage_type_2_shall_not_integrate_path.c_str()), 1);
+    EXPECT_EQ(appimage_is_terminal_app("/invalid/path"), -1);
+}
+
+// compares whether the size first bytes of two given byte buffers are equal
+bool test_compare_bytes(const char* buf1, const char* buf2, int size) {
+    for (int i = 0; i < size; i++) {
+        if (buf1[i] != buf2[i]) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+TEST_F(LibAppImageTest, appimage_type2_digest_md5) {
+    char digest[16];
+    char expectedDigest[] = {-75,  -71,  106,  -93,  122,  114,  7,  127,  -40,  10,  -115,  -82,  -73,  115,  -19,  1};
+
+    EXPECT_TRUE(appimage_type2_digest_md5(appImage_type_2_file_path.c_str(), digest));
+    EXPECT_PRED3(test_compare_bytes, digest, expectedDigest, 16);
+}
+
+TEST_F(LibAppImageTest, test_write_desktop_file_exec) {
+    // install Cura desktop file into temporary HOME with some hardcoded paths
+    stringstream pathToOriginalDesktopFile;
+    pathToOriginalDesktopFile << TEST_DATA_DIR << "/" << "Cura.desktop";
+    ifstream ifs(pathToOriginalDesktopFile.str().c_str());
+
+    ASSERT_TRUE(ifs) << "Failed to open file: " << pathToOriginalDesktopFile.str();
+
+    ifs.seekg(0, ios::end);
+    unsigned long bufferSize = static_cast<unsigned long>(ifs.tellg() + 1);
+    ifs.seekg(0, ios::beg);
+
+    // should be large enough
+    vector<char> buffer(bufferSize, '\0');
+
+    // read in desktop file
+    ifs.read(buffer.data(), buffer.size());
+
+    GError* error = NULL;
+
+    GKeyFile *keyFile = g_key_file_new();
+    gboolean success = g_key_file_load_from_data(keyFile, buffer.data(), buffer.size(), (GKeyFileFlags) (G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS), &error);
+
+    ASSERT_TRUE(error == NULL) << "Error while creating key file from data: " << error->message;
+
+    gchar desktop_filename[] = "desktop_filename";
+    gchar md5testvalue[] = "md5testvalue";
+
+    if (success) {
+        write_edited_desktop_file(keyFile, "testpath", desktop_filename, 1, md5testvalue, false);
+    }
+
+    g_key_file_free(keyFile);
+
+    stringstream pathToInstalledDesktopFile;
+    pathToInstalledDesktopFile << tempHome << g_strdup_printf("/.local/share/applications/appimagekit_%s-%s", md5testvalue, desktop_filename);
+
+    // now, read original and installed desktop file, and compare both
+    ifstream originalStrm(pathToOriginalDesktopFile.str().c_str());
+    ifstream installedStrm(pathToInstalledDesktopFile.str().c_str());
+
+    ASSERT_TRUE(originalStrm) << "Failed to open desktop file " << pathToOriginalDesktopFile.str();
+    ASSERT_TRUE(installedStrm) << "Failed to open desktop file " << pathToInstalledDesktopFile.str();
+
+    originalStrm.seekg(0, ios::end);
+    unsigned long originalStrmSize = static_cast<unsigned long>(originalStrm.tellg() + 1);
+    originalStrm.seekg(0, ios::beg);
+
+    installedStrm.seekg(0, ios::end);
+    unsigned long installedStrmSize = static_cast<unsigned long>(installedStrm.tellg() + 1);
+    installedStrm.seekg(0, ios::beg);
+
+    // split both files by lines, then convert to key-value list, and check whether all lines from original file
+    // are also available in the installed file
+    // some values modified by write_edited_desktop_file need some extra checks, which can be performed then.
+    vector<char> originalData(originalStrmSize, '\0');
+    vector<char> installedData(installedStrmSize, '\0');
+
+    originalStrm.read(originalData.data(), originalData.size());
+    installedStrm.read(installedData.data(), installedData.size());
+
+    vector<string> originalLines = splitString(originalData.data(), '\n');
+    vector<string> installedLines = splitString(installedData.data(), '\n');
+    // first of all, remove all empty lines
+    // ancient glib versions like the ones CentOS 6 provides tend to introduce a blank line before the
+    // [Desktop Entry] header, hence the blank lines need to be stripped out before the next step
+    originalLines.erase(std::remove_if(originalLines.begin(), originalLines.end(), isEmptyString), originalLines.end());
+    installedLines.erase(std::remove_if(installedLines.begin(), installedLines.end(), isEmptyString), installedLines.end());
+    // first line should be "[Desktop Entry]" header
+    ASSERT_EQ(originalLines.front(), "[Desktop Entry]");
+    ASSERT_EQ(installedLines.front(), "[Desktop Entry]");
+    // drop "[Desktop Entry]" header
+    originalLines.erase(originalLines.begin());
+    installedLines.erase(installedLines.begin());
+
+    // now, create key-value maps
+    map<string, string> entries;
+
+    // sort original entries into map
+    for (vector<string>::const_iterator line = originalLines.begin(); line != originalLines.end(); line++) {
+        vector<string> lineSplit = splitString(*line, '=');
+        ASSERT_EQ(lineSplit.size(), 2) << "line: " << *line;
+        entries.insert(std::make_pair(lineSplit[0], lineSplit[1]));
+    }
+
+    // now, remove all entries found in installed desktop entry from entries
+    for (vector<string>::iterator line = installedLines.begin(); line != installedLines.end();) {
+        vector<string> lineSplit = splitString(*line, '=');
+        ASSERT_EQ(lineSplit.size(), 2) << "Condition failed for line: " << *line;
+
+        const string& key = lineSplit[0];
+        const string& value = lineSplit[1];
+
+        if (stringStartsWith(key, "X-AppImage-")) {
+            // skip this entry
+            line++;
+            continue;
+        }
+
+        map<string, string>::const_iterator entry = entries.find(key);
+
+        if (entry == entries.end())
+            FAIL() << "No such entry in desktop file: " << key;
+
+        if (key == "Exec" || key == "TryExec") {
+            vector<string> execSplit = splitString(value);
+            ASSERT_GT(execSplit.size(), 0) << "key: " << key;
+            ASSERT_EQ(execSplit[0], "testpath") << "key: " << key;
+
+            vector<string> originalExecSplit = splitString((*entry).second);
+            ASSERT_EQ(execSplit.size(), originalExecSplit.size())
+                                << key << ": " << value << " and " << (*entry).second << " contain different number of parameters";
+
+            // the rest of the split parts should be equal
+            for (int i = 1; i < execSplit.size(); i++) {
+                ASSERT_EQ(execSplit[i], originalExecSplit[i]);
+            }
+        } else if (key == "Icon") {
+            ASSERT_EQ(value, g_strdup_printf("appimagekit_%s_cura-icon", md5testvalue));
+        } else {
+            ASSERT_EQ(value, (*entry).second);
+        }
+
+        installedLines.erase(line);
+    }
+
+    // finally, handle X-AppImage- entries
+    for (vector<string>::iterator line = installedLines.begin(); line != installedLines.end();) {
+        if (stringStartsWith(*line, "X-AppImage-Comment")) {
+            ASSERT_EQ(*line, "X-AppImage-Comment=Generated by appimaged AppImageKit unit tests");
+        } else if (stringStartsWith(*line, "X-AppImage-Identifier")) {
+            ASSERT_EQ(*line, g_strdup_printf("X-AppImage-Identifier=%s", md5testvalue));
+        } else if (stringStartsWith(*line, "X-AppImage-Old-")) {
+            // skip "old" entries, created by localization support
+        } else {
+            line++;
+            continue;
+        }
+
+        installedLines.erase(line);
+    }
+
+    ASSERT_EQ(installedLines.size(), 0);
+}
+
+int main(int argc, char **argv) {
+    testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/tests/test_shared.cpp b/tests/test_shared.cpp
new file mode 100644 (file)
index 0000000..66fcc56
--- /dev/null
@@ -0,0 +1,94 @@
+#include <gtest/gtest.h>
+#include <fstream>
+#include <ftw.h>
+#include <string>
+#include <sstream>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "fixtures.h"
+
+extern "C" {
+    #include <appimage/appimage_shared.h>
+}
+
+
+using namespace std;
+
+
+// most simple derivative class for better naming of the tests in this file
+class LibAppImageSharedTest : public TestBase {};
+
+
+static bool test_strcmp(char* a, char* b) {
+    return strcmp(a, b) == 0;
+}
+
+TEST_F(LibAppImageSharedTest, test_appimage_hexlify) {
+    {
+        char bytesIn[] = "\x00\x01\x02\x03\x04\x05\x06\x07";
+        char expectedHex[] = "0001020304050607";
+
+        char* hexlified = appimage_hexlify(bytesIn, 8);
+        EXPECT_PRED2(test_strcmp, hexlified, expectedHex);
+
+        // cleanup
+        free(hexlified);
+    }
+    {
+        char bytesIn[] = "\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff";
+        char expectedHex[] = "f8f9fafbfcfdfeff";
+
+        char* hexlified = appimage_hexlify(bytesIn, 8);
+        EXPECT_PRED2(test_strcmp, hexlified, expectedHex);
+
+        // cleanup
+        free(hexlified);
+    }
+}
+
+
+bool isPowerOfTwo(int number) {
+    return (number & (number - 1)) == 0;
+}
+
+
+TEST_F(LibAppImageSharedTest, test_appimage_get_elf_section_offset_and_length) {
+    std::string appImagePath = std::string(TEST_DATA_DIR) + "/appimaged-i686.AppImage";
+
+    unsigned long offset, length;
+
+    ASSERT_TRUE(appimage_get_elf_section_offset_and_length(appImagePath.c_str(), ".upd_info", &offset, &length));
+
+    EXPECT_GT(offset, 0);
+    EXPECT_GT(length, 0);
+
+    EXPECT_PRED1(isPowerOfTwo, length);
+}
+
+
+TEST_F(LibAppImageSharedTest, test_print_binary) {
+    std::string appImagePath = std::string(TEST_DATA_DIR) + "/appimaged-i686.AppImage";
+
+    unsigned long offset, length;
+
+    ASSERT_TRUE(appimage_get_elf_section_offset_and_length(appImagePath.c_str(), ".upd_info", &offset, &length));
+
+    EXPECT_EQ(appimage_print_binary(appImagePath.c_str(), offset, length), 0);
+}
+
+
+TEST_F(LibAppImageSharedTest, test_print_hex) {
+    std::string appImagePath = std::string(TEST_DATA_DIR) + "/appimaged-i686.AppImage";
+
+    unsigned long offset, length;
+
+    ASSERT_TRUE(appimage_get_elf_section_offset_and_length(appImagePath.c_str(), ".sha256_sig", &offset, &length));
+
+    EXPECT_EQ(appimage_print_hex(appImagePath.c_str(), offset, length), 0);
+}
+
+int main(int argc, char **argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/travis/build-and-test.sh b/travis/build-and-test.sh
new file mode 100755 (executable)
index 0000000..5e8571d
--- /dev/null
@@ -0,0 +1,36 @@
+#! /bin/bash
+
+set -e
+set -x
+
+# use RAM disk if possible
+if [ "$CI" == "" ] && [ -d /dev/shm ]; then
+    TEMP_BASE=/dev/shm
+else
+    TEMP_BASE=/tmp
+fi
+
+BUILD_DIR=$(mktemp -d -p "$TEMP_BASE" AppImageUpdate-build-XXXXXX)
+
+cleanup () {
+    if [ -d "$BUILD_DIR" ]; then
+        rm -rf "$BUILD_DIR"
+    fi
+}
+
+trap cleanup EXIT
+
+# store repo root as variable
+REPO_ROOT=$(readlink -f $(dirname $(dirname $0)))
+OLD_CWD=$(readlink -f .)
+
+pushd "$BUILD_DIR"
+
+# configure build
+cmake "$REPO_ROOT"
+
+# build binaries
+make -j$(nproc)
+
+# run all unit tests
+ctest -V
diff --git a/travis/travis-build.sh b/travis/travis-build.sh
new file mode 100755 (executable)
index 0000000..d4f4bba
--- /dev/null
@@ -0,0 +1,19 @@
+#! /bin/bash
+
+set -e
+set -x
+
+# build libappimage, and run unit tests
+if [ "$DOCKER_IMAGE" != "" ]; then
+    docker run --rm \
+        --cap-add SYS_ADMIN \
+        --device /dev/fuse:mrw \
+        -e ARCH -e TRAVIS -e TRAVIS_BUILD_NUMBER \
+        -e CI=1 \
+        -i \
+        -v "${PWD}":/libappimage \
+        "$DOCKER_IMAGE" \
+        /bin/bash -xc "cd /libappimage && travis/build-and-test.sh"
+else
+    exec $(readlink -f $(dirname "$0"))/build-and-test.sh
+fi