LCOV - code coverage report
Current view: top level - src - fileutils.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 58.4 % 339 198
Test Date: 2026-09-13 09:08:58 Functions: 80.6 % 36 29

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

Generated by: LCOV version 2.0-1