--- /dev/null
+*build*/
+cmake-build-*/
+.idea/
+Testing/
--- /dev/null
+[submodule "lib/gtest"]
+ path = lib/gtest
+ url = https://github.com/google/googletest.git
--- /dev/null
+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
--- /dev/null
+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)
--- /dev/null
+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.
--- /dev/null
+# libappimage
--- /dev/null
+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}")
--- /dev/null
+# >= 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()
--- /dev/null
+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)
--- /dev/null
+# - 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)
--- /dev/null
+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()
--- /dev/null
+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()
--- /dev/null
+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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+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()
--- /dev/null
+# 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
+)
--- /dev/null
+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"
+)
--- /dev/null
+// 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
--- /dev/null
+#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);
+
--- /dev/null
+#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
--- /dev/null
+#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);
+}
--- /dev/null
+#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
--- /dev/null
+/**************************************************************************
+ *
+ * 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(®ex, pattern, REG_ICASE | REG_EXTENDED);
+ r = regexec(®ex, trv.path, 2, match, 0);
+ regfree(®ex);
+ 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, ¶ms->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;
+}
--- /dev/null
+bool move_file(const char* source, const char* target);
--- /dev/null
+// 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;
+}
--- /dev/null
+#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
--- /dev/null
+// 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;
+}
--- /dev/null
+#pragma once
+
+// local includes
+#include "appimage_handler.h"
+
+appimage_handler appimage_type_2_create_handler();
--- /dev/null
+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
+)
--- /dev/null
+#pragma once
+
+// include implementations
+#include "md5.h"
--- /dev/null
+#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);
--- /dev/null
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 );
+}
--- /dev/null
+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
+)
--- /dev/null
+#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;
+}
--- /dev/null
+#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;
+}
+
--- /dev/null
+#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;
+}
--- /dev/null
+#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)))
--- /dev/null
+/*
+ *
+ * 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 */
--- /dev/null
+#! /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 .
--- /dev/null
+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);
+
--- /dev/null
+#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";
+
--- /dev/null
+#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 */
+
--- /dev/null
+--- 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;
+ }
--- /dev/null
+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})
--- /dev/null
+#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);
+ }
+}
--- /dev/null
+#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 */
--- /dev/null
+# 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()
--- /dev/null
+[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;
--- /dev/null
+utilities-terminal.svg
\ No newline at end of file
--- /dev/null
+[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
--- /dev/null
+<?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>
--- /dev/null
+#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;
+}
--- /dev/null
+#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;
+ }
+};
--- /dev/null
+// 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();
+}
--- /dev/null
+// 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);
+}
--- /dev/null
+// 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();
+}
--- /dev/null
+#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();
+}
--- /dev/null
+#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();
+}
--- /dev/null
+#! /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
--- /dev/null
+#! /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