LCOV - code coverage report
Current view: top level - foo/src - fileutils.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 194 346 56.1 %
Date: 2026-02-28 10:41:24 Functions: 49 91 53.8 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #ifdef HAVE_CONFIG_H
      19             : #include "config.h"
      20             : #endif
      21             : 
      22             : #include "fileutils.h"
      23             : #include "logger.h"
      24             : #include "archiver.h"
      25             : #include "compiler_intrinsics.h"
      26             : #include "base64.h"
      27             : 
      28             : #include <opendht/crypto.h>
      29             : 
      30             : #ifdef __APPLE__
      31             : #include <TargetConditionals.h>
      32             : #endif
      33             : 
      34             : #if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
      35             : #include "client/jami_signal.h"
      36             : #endif
      37             : 
      38             : #ifdef _WIN32
      39             : #include <windows.h>
      40             : #include "string_utils.h"
      41             : #endif
      42             : 
      43             : #include <sys/types.h>
      44             : #include <sys/stat.h>
      45             : 
      46             : #ifndef _MSC_VER
      47             : #include <libgen.h>
      48             : #endif
      49             : 
      50             : #ifdef _MSC_VER
      51             : #include "windirent.h"
      52             : #else
      53             : #include <dirent.h>
      54             : #endif
      55             : 
      56             : #include <signal.h>
      57             : #include <unistd.h>
      58             : #include <fcntl.h>
      59             : #ifndef _WIN32
      60             : #include <pwd.h>
      61             : #else
      62             : #include <shlobj.h>
      63             : #define NAME_MAX 255
      64             : #endif
      65             : #if !defined __ANDROID__ && !defined _WIN32
      66             : #include <wordexp.h>
      67             : #endif
      68             : 
      69             : #include <nettle/sha3.h>
      70             : 
      71             : #include <sstream>
      72             : #include <fstream>
      73             : #include <iostream>
      74             : #include <stdexcept>
      75             : #include <limits>
      76             : #include <array>
      77             : 
      78             : #include <cstdlib>
      79             : #include <cstring>
      80             : #include <cerrno>
      81             : #include <cstddef>
      82             : 
      83             : #include <pj/ctype.h>
      84             : #include <pjlib-util/md5.h>
      85             : 
      86             : #ifndef _MSC_VER
      87             : #define PROTECTED_GETENV(str) \
      88             :     ({ \
      89             :         char* envvar_ = getenv((str)); \
      90             :         envvar_ ? envvar_ : ""; \
      91             :     })
      92             : 
      93             : #define XDG_DATA_HOME   (PROTECTED_GETENV("XDG_DATA_HOME"))
      94             : #define XDG_CONFIG_HOME (PROTECTED_GETENV("XDG_CONFIG_HOME"))
      95             : #define XDG_CACHE_HOME  (PROTECTED_GETENV("XDG_CACHE_HOME"))
      96             : #else
      97             : const wchar_t*
      98             : winGetEnv(const wchar_t* name)
      99             : {
     100             :     const DWORD buffSize = 65535;
     101             :     static wchar_t buffer[buffSize];
     102             :     if (GetEnvironmentVariable(name, buffer, buffSize)) {
     103             :         return buffer;
     104             :     } else {
     105             :         return L"";
     106             :     }
     107             : }
     108             : 
     109             : #define PROTECTED_GETENV(str) winGetEnv(str)
     110             : 
     111             : #define JAMI_DATA_HOME   PROTECTED_GETENV(L"JAMI_DATA_HOME")
     112             : #define JAMI_CONFIG_HOME PROTECTED_GETENV(L"JAMI_CONFIG_HOME")
     113             : #define JAMI_CACHE_HOME  PROTECTED_GETENV(L"JAMI_CACHE_HOME")
     114             : #endif
     115             : 
     116             : #define PIDFILE     ".ring.pid"
     117             : #define ERASE_BLOCK 4096
     118             : 
     119             : namespace jami {
     120             : namespace fileutils {
     121             : 
     122             : static std::filesystem::path resource_dir_path;
     123             : 
     124             : void
     125           0 : set_resource_dir_path(const std::filesystem::path& resourceDirPath)
     126             : {
     127           0 :     resource_dir_path = resourceDirPath;
     128           0 : }
     129             : 
     130             : const std::filesystem::path&
     131         801 : get_resource_dir_path()
     132             : {
     133         801 :     static const std::filesystem::path jami_default_data_dir(JAMI_DATADIR);
     134         801 :     return resource_dir_path.empty() ? jami_default_data_dir : resource_dir_path;
     135             : }
     136             : 
     137             : std::string
     138           0 : expand_path(const std::string& path)
     139             : {
     140             : #if defined __ANDROID__ || defined _MSC_VER || defined WIN32 || defined __APPLE__
     141             :     JAMI_ERR("Path expansion not implemented, returning original");
     142             :     return path;
     143             : #else
     144             : 
     145           0 :     std::string result;
     146             : 
     147             :     wordexp_t p;
     148           0 :     int ret = wordexp(path.c_str(), &p, 0);
     149             : 
     150           0 :     switch (ret) {
     151           0 :     case WRDE_BADCHAR:
     152           0 :         JAMI_ERR("Illegal occurrence of newline or one of |, &, ;, <, >, "
     153             :                  "(, ), {, }.");
     154           0 :         return result;
     155           0 :     case WRDE_BADVAL:
     156           0 :         JAMI_ERR("An undefined shell variable was referenced");
     157           0 :         return result;
     158           0 :     case WRDE_CMDSUB:
     159           0 :         JAMI_ERR("Command substitution occurred");
     160           0 :         return result;
     161           0 :     case WRDE_SYNTAX:
     162           0 :         JAMI_ERR("Shell syntax error");
     163           0 :         return result;
     164           0 :     case WRDE_NOSPACE:
     165           0 :         JAMI_ERR("Out of memory.");
     166             :         // This is the only error where we must call wordfree
     167           0 :         break;
     168           0 :     default:
     169           0 :         if (p.we_wordc > 0)
     170           0 :             result = std::string(p.we_wordv[0]);
     171           0 :         break;
     172             :     }
     173             : 
     174           0 :     wordfree(&p);
     175             : 
     176           0 :     return result;
     177             : #endif
     178           0 : }
     179             : 
     180             : bool
     181           2 : isDirectoryWritable(const std::string& directory)
     182             : {
     183           2 :     return accessFile(directory, W_OK) == 0;
     184             : }
     185             : 
     186             : bool
     187          13 : createSymlink(const std::filesystem::path& linkFile, const std::filesystem::path& target)
     188             : {
     189          13 :     std::error_code ec;
     190          13 :     std::filesystem::create_symlink(target, linkFile, ec);
     191          13 :     if (ec) {
     192           0 :         JAMI_WARNING("Unable to create soft link from {} to {}: {}", linkFile, target, ec.message());
     193           0 :         return false;
     194             :     } else {
     195          52 :         JAMI_LOG("Created soft link from {} to {}", linkFile, target);
     196             :     }
     197          13 :     return true;
     198             : }
     199             : 
     200             : bool
     201          13 : createHardlink(const std::filesystem::path& linkFile, const std::filesystem::path& target)
     202             : {
     203          13 :     std::error_code ec;
     204          13 :     std::filesystem::create_hard_link(target, linkFile, ec);
     205          13 :     if (ec) {
     206           0 :         JAMI_WARNING("Unable to create hard link from {} to {}: {}", linkFile, target, ec.message());
     207           0 :         return false;
     208             :     } else {
     209          52 :         JAMI_LOG("Created hard link from {} to {}", linkFile, target);
     210             :     }
     211          13 :     return true;
     212             : }
     213             : 
     214             : bool
     215          28 : createFileLink(const std::filesystem::path& linkFile, const std::filesystem::path& target, bool hard)
     216             : {
     217          28 :     if (linkFile == target)
     218           0 :         return true;
     219          28 :     std::error_code ec;
     220             :     // Use symlink_status() because exists() could return false for broken symlinks
     221          28 :     auto status = std::filesystem::symlink_status(linkFile, ec);
     222          28 :     if (status.type() != std::filesystem::file_type::not_found) {
     223           6 :         if (status.type() == std::filesystem::file_type::symlink
     224           3 :             && std::filesystem::read_symlink(linkFile, ec) == target) {
     225           8 :             JAMI_DEBUG("createFileLink: {} symlink already points to target {}", linkFile, target);
     226           2 :             return true;
     227             :         }
     228             :         // Remove any existing file or symlink before creating a new one, as create_symlink()
     229             :         // will fail with "File exists" error if the linkFile path already exists.
     230           1 :         if (status.type() == std::filesystem::file_type::regular
     231           1 :             || status.type() == std::filesystem::file_type::symlink) {
     232           1 :             std::filesystem::remove(linkFile, ec);
     233             :         }
     234             :     }
     235             : 
     236             :     // Try to create a hard link if requested; fall back to symlink on failure
     237          26 :     if (not hard or not createHardlink(linkFile, target))
     238          13 :         return createSymlink(linkFile, target);
     239          13 :     return true;
     240             : }
     241             : 
     242             : std::string_view
     243          67 : getFileExtension(std::string_view filename)
     244             : {
     245          67 :     std::string_view result;
     246          67 :     auto sep = filename.find_last_of('.');
     247          67 :     if (sep != std::string_view::npos && sep != filename.size() - 1)
     248           0 :         result = filename.substr(sep + 1);
     249          67 :     if (result.size() >= 8)
     250           0 :         return {};
     251          67 :     return result;
     252             : }
     253             : 
     254             : bool
     255        9147 : isPathRelative(const std::filesystem::path& path)
     256             : {
     257        9147 :     return not path.empty() and path.is_relative();
     258             : }
     259             : 
     260             : std::string
     261       10219 : getCleanPath(const std::string& base, const std::string& path)
     262             : {
     263       10219 :     if (base.empty() or path.size() < base.size())
     264       10212 :         return path;
     265           7 :     auto base_sep = base + DIR_SEPARATOR_STR;
     266           7 :     if (path.compare(0, base_sep.size(), base_sep) == 0)
     267           7 :         return path.substr(base_sep.size());
     268             :     else
     269           0 :         return path;
     270           7 : }
     271             : 
     272             : std::filesystem::path
     273       14337 : getFullPath(const std::filesystem::path& base, const std::filesystem::path& path)
     274             : {
     275       14337 :     bool isRelative {not base.empty() and isPathRelative(path)};
     276       14336 :     return isRelative ? base / path : path;
     277             : }
     278             : 
     279             : std::vector<uint8_t>
     280        6824 : loadFile(const std::filesystem::path& path, const std::filesystem::path& default_dir)
     281             : {
     282       10973 :     return dhtnet::fileutils::loadFile(getFullPath(default_dir, path));
     283             : }
     284             : 
     285             : std::string
     286           7 : loadTextFile(const std::filesystem::path& path, const std::filesystem::path& default_dir)
     287             : {
     288           7 :     std::string buffer;
     289           7 :     auto fullPath = getFullPath(default_dir, path);
     290             : 
     291             :     // Open with explicit share mode to allow reading even if file is opened elsewhere
     292             : #ifdef _WIN32
     293             :     std::ifstream file(fullPath, std::ios::in | std::ios::binary, _SH_DENYNO);
     294             : #else
     295           7 :     std::ifstream file(fullPath);
     296             : #endif
     297             : 
     298           7 :     if (!file)
     299           3 :         throw std::runtime_error("Unable to read file: " + path.string());
     300             : 
     301           4 :     file.seekg(0, std::ios::end);
     302           4 :     auto size = file.tellg();
     303           4 :     if (size > std::numeric_limits<unsigned>::max())
     304           0 :         throw std::runtime_error("File is too big: " + path.string());
     305           4 :     buffer.resize(size);
     306           4 :     file.seekg(0, std::ios::beg);
     307           4 :     if (!file.read((char*) buffer.data(), size))
     308           0 :         throw std::runtime_error("Unable to load file: " + path.string());
     309           8 :     return buffer;
     310          13 : }
     311             : 
     312             : void
     313        1565 : saveFile(const std::filesystem::path& path, const uint8_t* data, size_t data_size, mode_t UNUSED mode)
     314             : {
     315        1565 :     std::ofstream file(path, std::ios::trunc | std::ios::binary);
     316        1565 :     if (!file.is_open()) {
     317           0 :         JAMI_ERROR("Unable to write data to {}", path);
     318           0 :         return;
     319             :     }
     320        1565 :     file.write((char*) data, data_size);
     321             : #ifndef _WIN32
     322        1565 :     file.close();
     323        1565 :     if (chmod(path.c_str(), mode) < 0)
     324           0 :         JAMI_WARNING("fileutils::saveFile(): chmod() failed on {}, {}", path, strerror(errno));
     325             : #endif
     326        1565 : }
     327             : 
     328             : std::vector<uint8_t>
     329           0 : loadCacheFile(const std::filesystem::path& path, std::chrono::system_clock::duration maxAge)
     330             : {
     331             :     // last_write_time throws exception if file doesn't exist
     332           0 :     std::error_code ec;
     333           0 :     auto writeTime = std::filesystem::last_write_time(path, ec);
     334           0 :     if (ec)
     335           0 :         throw std::runtime_error("unable to get last write time of file");
     336           0 :     auto now = decltype(writeTime)::clock::now();
     337           0 :     if (now - writeTime > maxAge)
     338           0 :         throw std::runtime_error("file too old " + dht::print_time_relative(now, writeTime));
     339             : 
     340           0 :     JAMI_LOG("Loading cache file '{}'", path);
     341           0 :     return dhtnet::fileutils::loadFile(path);
     342             : }
     343             : 
     344             : std::string
     345           0 : loadCacheTextFile(const std::filesystem::path& path, std::chrono::system_clock::duration maxAge)
     346             : {
     347             :     // last_write_time throws exception if file doesn't exist
     348           0 :     std::error_code ec;
     349           0 :     auto writeTime = std::filesystem::last_write_time(path, ec);
     350           0 :     if (ec)
     351           0 :         throw std::runtime_error("unable to get last write time of file");
     352           0 :     auto now = decltype(writeTime)::clock::now();
     353           0 :     if (now - writeTime > maxAge)
     354           0 :         throw std::runtime_error("file too old " + dht::print_time_relative(now, writeTime));
     355             : 
     356           0 :     JAMI_LOG("Loading cache file '{}'", path);
     357           0 :     return loadTextFile(path);
     358             : }
     359             : 
     360             : ArchiveStorageData
     361          97 : readArchive(const std::filesystem::path& path, std::string_view scheme, const std::string& pwd)
     362             : {
     363         388 :     JAMI_LOG("Reading archive from {} with scheme '{}'", path, scheme);
     364             : 
     365         185 :     auto isUnencryptedGzip = [](const std::vector<uint8_t>& data) {
     366             :         // NOTE: some webserver modify gzip files and this can end with a gunzip in a gunzip
     367             :         // file. So, to make the readArchive more robust, we can support this case by detecting
     368             :         // gzip header via 1f8b 08
     369             :         // We don't need to support more than 2 level, else somebody may be able to send
     370             :         // gunzip in loops and abuse.
     371         185 :         return data.size() > 3 && data[0] == 0x1f && data[1] == 0x8b && data[2] == 0x08;
     372             :     };
     373             : 
     374          98 :     auto decompress = [](std::vector<uint8_t>& data) {
     375             :         try {
     376          98 :             data = archiver::decompress(data);
     377           0 :         } catch (const std::exception& e) {
     378           0 :             JAMI_ERROR("Error decrypting archive: {}", e.what());
     379           0 :             throw e;
     380           0 :         }
     381          98 :     };
     382             : 
     383          97 :     std::vector<uint8_t> fileContent;
     384             : 
     385             :     // Read file
     386             :     try {
     387          97 :         fileContent = dhtnet::fileutils::loadFile(path);
     388           0 :     } catch (const std::exception& e) {
     389           0 :         JAMI_ERROR("Error loading archive: {}", e.what());
     390           0 :         throw;
     391           0 :     }
     392             : 
     393          97 :     if (isUnencryptedGzip(fileContent)) {
     394          89 :         if (!pwd.empty())
     395           8 :             JAMI_WARNING("A gunzip in a gunzip is detected. A webserver may have a bad config");
     396          89 :         decompress(fileContent);
     397             :     }
     398             : 
     399          97 :     ArchiveStorageData ret;
     400             :     // ret.data = {fileContent.data(), fileContent.data()+fileContent.size()};
     401             : 
     402          97 :     if (!pwd.empty()) {
     403             :         // Decrypt
     404           9 :         if (scheme == ARCHIVE_AUTH_SCHEME_KEY) {
     405             :             try {
     406           0 :                 ret.salt = dht::crypto::aesGetSalt(fileContent);
     407           0 :                 fileContent = dht::crypto::aesDecrypt(dht::crypto::aesGetEncrypted(fileContent), base64::decode(pwd));
     408           0 :             } catch (const std::exception& e) {
     409           0 :                 JAMI_ERROR("Error decrypting archive: {}", e.what());
     410           0 :                 throw;
     411           0 :             }
     412           9 :         } else if (scheme == ARCHIVE_AUTH_SCHEME_PASSWORD) {
     413             :             try {
     414           9 :                 ret.salt = dht::crypto::aesGetSalt(fileContent);
     415           9 :                 fileContent = dht::crypto::aesDecrypt(fileContent, pwd);
     416           1 :             } catch (const std::exception& e) {
     417           4 :                 JAMI_ERROR("Error decrypting archive: {}", e.what());
     418           1 :                 throw;
     419           1 :             }
     420             :         }
     421           8 :         decompress(fileContent);
     422          88 :     } else if (isUnencryptedGzip(fileContent)) {
     423           4 :         JAMI_WARNING("A gunzip in a gunzip is detected. A webserver may have a bad config");
     424           1 :         decompress(fileContent);
     425             :     }
     426          96 :     ret.data = {fileContent.data(), fileContent.data() + fileContent.size()};
     427         192 :     return ret;
     428          98 : }
     429             : 
     430             : bool
     431         815 : writeArchive(const std::string& archive_str,
     432             :              const std::filesystem::path& path,
     433             :              std::string_view scheme,
     434             :              const std::string& password,
     435             :              const std::vector<uint8_t>& password_salt)
     436             : {
     437        3260 :     JAMI_LOG("Writing archive to {} using scheme '{}'", path, scheme);
     438             : 
     439         815 :     if (scheme == ARCHIVE_AUTH_SCHEME_KEY) {
     440             :         // Encrypt using provided key
     441             :         try {
     442           0 :             auto key = base64::decode(password);
     443           0 :             auto newArchive = dht::crypto::aesEncrypt(archiver::compress(archive_str), key);
     444           0 :             saveFile(path, dht::crypto::aesBuildEncrypted(newArchive, password_salt));
     445           0 :         } catch (const std::runtime_error& ex) {
     446           0 :             JAMI_ERROR("Export failed: {}", ex.what());
     447           0 :             return false;
     448           0 :         }
     449         815 :     } else if (scheme == ARCHIVE_AUTH_SCHEME_PASSWORD and not password.empty()) {
     450             :         // Encrypt using provided password
     451             :         try {
     452          16 :             saveFile(path, dht::crypto::aesEncrypt(archiver::compress(archive_str), password, password_salt));
     453           0 :         } catch (const std::runtime_error& ex) {
     454           0 :             JAMI_ERROR("Export failed: {}", ex.what());
     455           0 :             return false;
     456           0 :         }
     457         799 :     } else if (scheme == ARCHIVE_AUTH_SCHEME_NONE || (scheme == ARCHIVE_AUTH_SCHEME_PASSWORD && password.empty())) {
     458        3196 :         JAMI_WARNING("Unsecured archiving (no password)");
     459         799 :         archiver::compressGzip(archive_str, path.string());
     460             :     } else {
     461           0 :         JAMI_ERROR("Unsupported scheme: {}", scheme);
     462           0 :         return false;
     463             :     }
     464         815 :     return true;
     465             : }
     466             : 
     467             : std::filesystem::path
     468          62 : get_cache_dir([[maybe_unused]] const char* pkg)
     469             : {
     470             : #if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
     471             :     std::vector<std::string> paths;
     472             :     paths.reserve(1);
     473             :     emitSignal<libjami::ConfigurationSignal::GetAppDataPath>("cache", &paths);
     474             :     if (not paths.empty())
     475             :         return paths[0];
     476             :     return {};
     477             : #elif defined(__APPLE__)
     478             :     return get_home_dir() / "Library" / "Caches" / pkg;
     479             : #else
     480             : #ifdef _WIN32
     481             :     const std::wstring cache_home(JAMI_CACHE_HOME);
     482             :     if (not cache_home.empty())
     483             :         return jami::to_string(cache_home);
     484             : #else
     485          62 :     const std::string cache_home(XDG_CACHE_HOME);
     486          62 :     if (not cache_home.empty())
     487           0 :         return cache_home;
     488             : #endif
     489         124 :     return get_home_dir() / ".cache" / pkg;
     490             : #endif
     491          62 : }
     492             : 
     493             : const std::filesystem::path&
     494        3236 : get_cache_dir()
     495             : {
     496        3236 :     static const std::filesystem::path cache_dir = get_cache_dir(PACKAGE);
     497        3236 :     return cache_dir;
     498             : }
     499             : 
     500             : std::filesystem::path
     501          32 : get_home_dir_impl()
     502             : {
     503             : #if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
     504             :     std::vector<std::string> paths;
     505             :     paths.reserve(1);
     506             :     emitSignal<libjami::ConfigurationSignal::GetAppDataPath>("files", &paths);
     507             :     if (not paths.empty())
     508             :         return paths[0];
     509             :     return {};
     510             : #elif defined _WIN32
     511             :     TCHAR path[MAX_PATH];
     512             :     if (SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_PROFILE, nullptr, 0, path))) {
     513             :         return jami::to_string(path);
     514             :     }
     515             :     return {};
     516             : #else
     517             : 
     518             :     // 1) try getting user's home directory from the environment
     519          32 :     std::string home(PROTECTED_GETENV("HOME"));
     520          32 :     if (not home.empty())
     521          32 :         return home;
     522             : 
     523             :     // 2) try getting it from getpwuid_r (i.e. /etc/passwd)
     524           0 :     const long max = sysconf(_SC_GETPW_R_SIZE_MAX);
     525           0 :     if (max != -1) {
     526           0 :         char buf[max];
     527             :         struct passwd pwbuf, *pw;
     528           0 :         if (getpwuid_r(getuid(), &pwbuf, buf, sizeof(buf), &pw) == 0 and pw != NULL)
     529           0 :             return pw->pw_dir;
     530           0 :     }
     531             : 
     532           0 :     return {};
     533             : #endif
     534          32 : }
     535             : 
     536             : const std::filesystem::path&
     537         189 : get_home_dir()
     538             : {
     539         189 :     static const std::filesystem::path home_dir = get_home_dir_impl();
     540         189 :     return home_dir;
     541             : }
     542             : 
     543             : std::filesystem::path
     544          62 : get_data_dir([[maybe_unused]] const char* pkg)
     545             : {
     546             : #if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
     547             :     std::vector<std::string> paths;
     548             :     paths.reserve(1);
     549             :     emitSignal<libjami::ConfigurationSignal::GetAppDataPath>("files", &paths);
     550             :     if (not paths.empty())
     551             :         return paths[0];
     552             :     return {};
     553             : #elif defined(__APPLE__)
     554             :     return get_home_dir() / "Library" / "Application Support" / pkg;
     555             : #elif defined(_WIN32)
     556             :     std::wstring data_home(JAMI_DATA_HOME);
     557             :     if (not data_home.empty())
     558             :         return std::filesystem::path(data_home) / pkg;
     559             : 
     560             :     if (!strcmp(pkg, "ring")) {
     561             :         return get_home_dir() / ".local" / "share" / pkg;
     562             :     } else {
     563             :         return get_home_dir() / "AppData" / "Local" / pkg;
     564             :     }
     565             : #else
     566          62 :     std::string_view data_home(XDG_DATA_HOME);
     567          62 :     if (not data_home.empty())
     568           0 :         return std::filesystem::path(data_home) / pkg;
     569             :     // "If $XDG_DATA_HOME is either not set or empty, a default equal to
     570             :     // $HOME/.local/share should be used."
     571         124 :     return get_home_dir() / ".local" / "share" / pkg;
     572             : #endif
     573             : }
     574             : 
     575             : const std::filesystem::path&
     576       61992 : get_data_dir()
     577             : {
     578       61992 :     static const std::filesystem::path data_dir = get_data_dir(PACKAGE);
     579       61991 :     return data_dir;
     580             : }
     581             : 
     582             : std::filesystem::path
     583          63 : get_config_dir([[maybe_unused]] const char* pkg)
     584             : {
     585          63 :     std::filesystem::path configdir;
     586             : #if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
     587             :     std::vector<std::string> paths;
     588             :     emitSignal<libjami::ConfigurationSignal::GetAppDataPath>("config", &paths);
     589             :     if (not paths.empty())
     590             :         configdir = std::filesystem::path(paths[0]);
     591             : #elif defined(__APPLE__)
     592             :     configdir = fileutils::get_home_dir() / "Library" / "Application Support" / pkg;
     593             : #elif defined(_WIN32)
     594             :     std::wstring xdg_env(JAMI_CONFIG_HOME);
     595             :     if (not xdg_env.empty()) {
     596             :         configdir = std::filesystem::path(xdg_env) / pkg;
     597             :     } else if (!strcmp(pkg, "ring")) {
     598             :         configdir = fileutils::get_home_dir() / ".config" / pkg;
     599             :     } else {
     600             :         configdir = fileutils::get_home_dir() / "AppData" / "Local" / pkg;
     601             :     }
     602             : #else
     603          63 :     std::string xdg_env(XDG_CONFIG_HOME);
     604          63 :     if (not xdg_env.empty())
     605           0 :         configdir = std::filesystem::path(xdg_env) / pkg;
     606             :     else
     607          63 :         configdir = fileutils::get_home_dir() / ".config" / pkg;
     608             : #endif
     609          63 :     if (!dhtnet::fileutils::recursive_mkdir(configdir, 0700)) {
     610             :         // If directory creation failed
     611           0 :         if (errno != EEXIST)
     612           0 :             JAMI_DBG("Unable to create directory: %s!", configdir.c_str());
     613             :     }
     614         126 :     return configdir;
     615          63 : }
     616             : 
     617             : const std::filesystem::path&
     618         249 : get_config_dir()
     619             : {
     620         249 :     static const std::filesystem::path config_dir = get_config_dir(PACKAGE);
     621         249 :     return config_dir;
     622             : }
     623             : 
     624             : #ifdef _WIN32
     625             : bool
     626             : eraseFile_win32(const std::string& path, bool dosync)
     627             : {
     628             :     // Note: from
     629             :     // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-deletefilea#remarks To
     630             :     // delete a read-only file, first you must remove the read-only attribute.
     631             :     SetFileAttributesA(path.c_str(), GetFileAttributesA(path.c_str()) & ~FILE_ATTRIBUTE_READONLY);
     632             :     HANDLE h = CreateFileA(path.c_str(), GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
     633             :     if (h == INVALID_HANDLE_VALUE) {
     634             :         JAMI_WARN("Unable to open file %s for erasing.", path.c_str());
     635             :         return false;
     636             :     }
     637             : 
     638             :     LARGE_INTEGER size;
     639             :     if (!GetFileSizeEx(h, &size)) {
     640             :         JAMI_WARN("Unable to erase file %s: GetFileSizeEx() failed.", path.c_str());
     641             :         CloseHandle(h);
     642             :         return false;
     643             :     }
     644             :     if (size.QuadPart == 0) {
     645             :         CloseHandle(h);
     646             :         return false;
     647             :     }
     648             : 
     649             :     uint64_t size_blocks = size.QuadPart / ERASE_BLOCK;
     650             :     if (size.QuadPart % ERASE_BLOCK)
     651             :         size_blocks++;
     652             : 
     653             :     char* buffer;
     654             :     try {
     655             :         buffer = new char[ERASE_BLOCK];
     656             :     } catch (std::bad_alloc& ba) {
     657             :         JAMI_WARN("Unable to allocate buffer for erasing %s.", path.c_str());
     658             :         CloseHandle(h);
     659             :         return false;
     660             :     }
     661             :     memset(buffer, 0x00, ERASE_BLOCK);
     662             : 
     663             :     OVERLAPPED ovlp;
     664             :     if (size.QuadPart < (1024 - 42)) { // a small file can be stored in the MFT record
     665             :         ovlp.Offset = 0;
     666             :         ovlp.OffsetHigh = 0;
     667             :         WriteFile(h, buffer, (DWORD) size.QuadPart, 0, &ovlp);
     668             :         FlushFileBuffers(h);
     669             :     }
     670             :     for (uint64_t i = 0; i < size_blocks; i++) {
     671             :         uint64_t offset = i * ERASE_BLOCK;
     672             :         ovlp.Offset = offset & 0x00000000FFFFFFFF;
     673             :         ovlp.OffsetHigh = offset >> 32;
     674             :         WriteFile(h, buffer, ERASE_BLOCK, 0, &ovlp);
     675             :     }
     676             : 
     677             :     delete[] buffer;
     678             : 
     679             :     if (dosync)
     680             :         FlushFileBuffers(h);
     681             : 
     682             :     CloseHandle(h);
     683             :     return true;
     684             : }
     685             : 
     686             : #else
     687             : 
     688             : bool
     689           0 : eraseFile_posix(const std::string& path, bool dosync)
     690             : {
     691             :     struct stat st;
     692           0 :     if (stat(path.c_str(), &st) == -1) {
     693           0 :         JAMI_WARN("Unable to erase file %s: fstat() failed.", path.c_str());
     694           0 :         return false;
     695             :     }
     696             :     // Remove read-only flag if possible
     697           0 :     chmod(path.c_str(), st.st_mode | (S_IWGRP + S_IWUSR));
     698             : 
     699           0 :     int fd = open(path.c_str(), O_WRONLY);
     700           0 :     if (fd == -1) {
     701           0 :         JAMI_WARN("Unable to open file %s for erasing.", path.c_str());
     702           0 :         return false;
     703             :     }
     704             : 
     705           0 :     if (st.st_size == 0) {
     706           0 :         close(fd);
     707           0 :         return false;
     708             :     }
     709             : 
     710           0 :     lseek(fd, 0, SEEK_SET);
     711             : 
     712             :     std::array<char, ERASE_BLOCK> buffer;
     713           0 :     buffer.fill(0);
     714           0 :     decltype(st.st_size) written(0);
     715           0 :     while (written < st.st_size) {
     716           0 :         auto ret = write(fd, buffer.data(), buffer.size());
     717           0 :         if (ret < 0) {
     718           0 :             JAMI_WARNING("Error while overriding file with zeros.");
     719           0 :             break;
     720             :         } else
     721           0 :             written += ret;
     722             :     }
     723             : 
     724           0 :     if (dosync)
     725           0 :         fsync(fd);
     726             : 
     727           0 :     close(fd);
     728           0 :     return written >= st.st_size;
     729             : }
     730             : #endif
     731             : 
     732             : bool
     733           0 : eraseFile(const std::string& path, bool dosync)
     734             : {
     735             : #ifdef _WIN32
     736             :     return eraseFile_win32(path, dosync);
     737             : #else
     738           0 :     return eraseFile_posix(path, dosync);
     739             : #endif
     740             : }
     741             : 
     742             : int
     743           0 : remove(const std::filesystem::path& path, bool erase)
     744             : {
     745           0 :     if (erase and dhtnet::fileutils::isFile(path, false) and !dhtnet::fileutils::hasHardLink(path))
     746           0 :         eraseFile(path.string(), true);
     747             : 
     748             : #ifdef _WIN32
     749             :     // use Win32 api since std::remove will not unlink directory in use
     750             :     if (std::filesystem::is_directory(path))
     751             :         return !RemoveDirectory(path.c_str());
     752             : #endif
     753             : 
     754           0 :     return std::remove(path.string().c_str());
     755             : }
     756             : 
     757             : int64_t
     758          55 : size(const std::filesystem::path& path)
     759             : {
     760          55 :     int64_t size = 0;
     761             :     try {
     762          55 :         std::ifstream file(path, std::ios::binary | std::ios::in);
     763          55 :         file.seekg(0, std::ios_base::end);
     764          55 :         size = file.tellg();
     765          55 :         file.close();
     766          55 :     } catch (...) {
     767           0 :     }
     768          55 :     return size;
     769             : }
     770             : 
     771             : std::string
     772          47 : sha3File(const std::filesystem::path& path)
     773             : {
     774             :     sha3_512_ctx ctx;
     775          47 :     sha3_512_init(&ctx);
     776             : 
     777             :     try {
     778          47 :         if (not std::filesystem::is_regular_file(path)) {
     779           0 :             JAMI_ERROR("Unable to compute sha3sum of {}: not a regular file", path);
     780           0 :             return {};
     781             :         }
     782          47 :         std::ifstream file(path, std::ios::binary | std::ios::in);
     783          47 :         if (!file) {
     784           0 :             JAMI_ERROR("Unable to compute sha3sum of {}: failed to open file", path);
     785           0 :             return {};
     786             :         }
     787          47 :         std::vector<char> buffer(8192, 0);
     788        6937 :         while (!file.eof()) {
     789        6890 :             file.read(buffer.data(), buffer.size());
     790        6890 :             std::streamsize readSize = file.gcount();
     791        6890 :             sha3_512_update(&ctx, readSize, (const uint8_t*) buffer.data());
     792             :         }
     793          47 :     } catch (const std::exception& e) {
     794           0 :         JAMI_ERROR("Unable to compute sha3sum of {}: {}", path, e.what());
     795           0 :         return {};
     796           0 :     }
     797             : 
     798             :     unsigned char digest[SHA3_512_DIGEST_SIZE];
     799          47 :     sha3_512_digest(&ctx, SHA3_512_DIGEST_SIZE, digest);
     800             : 
     801             :     char hash[SHA3_512_DIGEST_SIZE * 2];
     802             : 
     803        3055 :     for (int i = 0; i < SHA3_512_DIGEST_SIZE; ++i)
     804        3008 :         pj_val_to_hex_digit(digest[i], &hash[2 * i]);
     805             : 
     806          47 :     return {hash, SHA3_512_DIGEST_SIZE * 2};
     807             : }
     808             : 
     809             : std::string
     810           0 : sha3sum(const uint8_t* data, size_t size)
     811             : {
     812             :     sha3_512_ctx ctx;
     813           0 :     sha3_512_init(&ctx);
     814           0 :     sha3_512_update(&ctx, size, data);
     815             :     unsigned char digest[SHA3_512_DIGEST_SIZE];
     816           0 :     sha3_512_digest(&ctx, SHA3_512_DIGEST_SIZE, digest);
     817           0 :     return dht::toHex(digest, SHA3_512_DIGEST_SIZE);
     818             : }
     819             : 
     820             : int
     821           2 : accessFile(const std::string& file, int mode)
     822             : {
     823             : #ifdef _WIN32
     824             :     return _waccess(jami::to_wstring(file).c_str(), mode);
     825             : #else
     826           2 :     return access(file.c_str(), mode);
     827             : #endif
     828             : }
     829             : 
     830             : uint64_t
     831          16 : lastWriteTimeInSeconds(const std::filesystem::path& filePath)
     832             : {
     833          16 :     std::error_code ec;
     834          16 :     auto lastWrite = std::filesystem::last_write_time(filePath, ec);
     835          16 :     if (ec) {
     836           8 :         JAMI_WARNING("Unable to get last write time of {}: {}", filePath, ec.message());
     837           2 :         return 0;
     838             :     }
     839          14 :     return std::chrono::duration_cast<std::chrono::seconds>(lastWrite.time_since_epoch()).count();
     840             : }
     841             : 
     842             : std::string
     843          31 : getOrCreateLocalDeviceId()
     844             : {
     845          31 :     const auto& localDir = get_data_dir(); // ~/.local/share/jami/
     846          31 :     auto fullIdPath = localDir / "local_device_id";
     847          31 :     std::string localDeviceId;
     848             : 
     849          31 :     if (std::filesystem::exists(fullIdPath)) {
     850             :         // Get the id inside the file
     851           0 :         std::ifstream inStream(fullIdPath);
     852           0 :         std::getline(inStream, localDeviceId);
     853           0 :         inStream.close();
     854           0 :         if (!localDeviceId.empty())
     855           0 :             return localDeviceId;
     856           0 :     }
     857             : 
     858             :     // Generate a random hex string
     859             :     {
     860          31 :         std::random_device randomDevice;
     861          31 :         localDeviceId = to_hex_string(std::uniform_int_distribution<uint64_t>()(randomDevice));
     862          31 :     }
     863             : 
     864             :     // Create a new file and write the id in it
     865          31 :     std::error_code ec;
     866          31 :     std::filesystem::create_directories(localDir, ec);
     867          31 :     std::ofstream outStream(fullIdPath);
     868          31 :     if (outStream) {
     869          31 :         outStream << localDeviceId << std::endl;
     870          31 :         outStream.close();
     871             :     } else {
     872           0 :         JAMI_ERROR("Unable to create local device id file: {}", fullIdPath);
     873             :     }
     874          31 :     return localDeviceId;
     875          31 : }
     876             : 
     877             : } // namespace fileutils
     878             : } // namespace jami

Generated by: LCOV version 1.14