LCOV - code coverage report
Current view: top level - src - fileutils.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 57.7 % 338 195
Test Date: 2026-06-13 09:18:46 Functions: 48.1 % 106 51

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

Generated by: LCOV version 2.0-1