diff --git a/CMakeLists.txt b/CMakeLists.txt index 682501b68bb24ecabe808f6efda5cf9b5e07cf4e..69cd73b4786ed6acd763f765988b692362e54982 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -235,9 +235,9 @@ if (BUILD_TESTING AND NOT MSVC) target_link_libraries(tests_connectionManager PRIVATE dhtnet fmt::fmt PkgConfig::Cppunit) add_test(NAME tests_connectionManager COMMAND tests_connectionManager) - #add_executable(tests_fileutils tests/testFileutils.cpp) - #target_link_libraries(tests_fileutils PRIVATE dhtnet fmt::fmt PkgConfig::Cppunit) - #add_test(NAME tests_fileutils COMMAND tests_fileutils) + add_executable(tests_fileutils tests/testFileutils.cpp) + target_link_libraries(tests_fileutils PRIVATE dhtnet fmt::fmt PkgConfig::Cppunit) + add_test(NAME tests_fileutils COMMAND tests_fileutils) #add_executable(tests_stringutils tests/testString_utils.cpp) #target_link_libraries(tests_stringutils PRIVATE dhtnet fmt::fmt PkgConfig::Cppunit) diff --git a/include/certstore.h b/include/certstore.h index f92c8a15fa0ab28d82709f44d79a2857a952aa83..1893ed6e8a400b2bc53457b685e1beffcfe577b1 100644 --- a/include/certstore.h +++ b/include/certstore.h @@ -24,6 +24,7 @@ #include <set> #include <future> #include <mutex> +#include <filesystem> namespace crypto = ::dht::crypto; @@ -49,7 +50,7 @@ const char* statusToStr(TrustStatus s); class CertificateStore { public: - explicit CertificateStore(const std::string& path, std::shared_ptr<Logger> logger); + explicit CertificateStore(const std::filesystem::path& path, std::shared_ptr<Logger> logger); std::vector<std::string> getPinnedCertificates() const; /** @@ -102,9 +103,9 @@ private: void pinRevocationList(const std::string& id, const dht::crypto::RevocationList& crl); std::shared_ptr<Logger> logger_; - const std::string certPath_; - const std::string crlPath_; - const std::string ocspPath_; + const std::filesystem::path certPath_; + const std::filesystem::path crlPath_; + const std::filesystem::path ocspPath_; mutable std::mutex lock_; std::map<std::string, std::shared_ptr<crypto::Certificate>> certs_; diff --git a/include/connectionmanager.h b/include/connectionmanager.h index b03c345fe8666c6e89da42050b52e96dce72bb7c..b42b6339f9512b2cda6bcdaf7530e2e96026cf48 100644 --- a/include/connectionmanager.h +++ b/include/connectionmanager.h @@ -292,7 +292,7 @@ struct ConnectionManager::Config std::shared_ptr<TurnCache> turnCache; - std::string cachePath {}; + std::filesystem::path cachePath {}; std::shared_ptr<asio::io_context> ioContext; std::shared_ptr<dht::DhtRunner> dht; diff --git a/include/diffie-hellman.h b/include/diffie-hellman.h index f6bd64d42ffd6ee21758848fa84c9618df4596af..92f29067f3b5c0313079057bcc9839d3c26e242b 100644 --- a/include/diffie-hellman.h +++ b/include/diffie-hellman.h @@ -22,6 +22,7 @@ #include <memory> #include <cstdint> #include <string> +#include <filesystem> namespace dhtnet { namespace tls { @@ -57,7 +58,7 @@ public: static DhParams generate(); - static DhParams loadDhParams(const std::string& path); + static DhParams loadDhParams(const std::filesystem::path& path); private: std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)*> diff --git a/include/fileutils.h b/include/fileutils.h index 7539e11c62c7c03e9341f66fdbe217b04562c449..8362cacbfd637935cd5e344f4f22b77423aa21eb 100644 --- a/include/fileutils.h +++ b/include/fileutils.h @@ -22,18 +22,7 @@ #include <mutex> #include <cstdio> #include <ios> - -#ifndef _WIN32 -#include <sys/stat.h> // mode_t -#define DIR_SEPARATOR_STR "/" // Directory separator string -#define DIR_SEPARATOR_CH '/' // Directory separator char -#define DIR_SEPARATOR_STR_ESC "\\/" // Escaped directory separator string -#else -#define mode_t unsigned -#define DIR_SEPARATOR_STR "\\" // Directory separator string -#define DIR_SEPARATOR_CH '\\' // Directory separator char -#define DIR_SEPARATOR_STR_ESC "//*" // Escaped directory separator string -#endif +#include <filesystem> namespace dhtnet { namespace fileutils { @@ -44,100 +33,70 @@ namespace fileutils { * @param dir last directory creation mode * @param parents default mode for all created directories except the last */ -bool check_dir(const char* path, mode_t dir = 0755, mode_t parents = 0755); -/*std::string expand_path(const std::string& path);*/ -bool isDirectoryWritable(const std::string& directory); - -bool recursive_mkdir(const std::string& path, mode_t mode = 0755); - -bool isPathRelative(const std::string& path); -/** - * If path is contained in base, return the suffix, otherwise return the full path. - * @param base must not finish with DIR_SEPARATOR_STR, can be empty - * @param path the path - */ -//std::string getCleanPath(const std::string& base, const std::string& path); -/** - * If path is relative, it is appended to base. - */ -//std::string getFullPath(const std::string& base, const std::string& path); +bool check_dir(const std::filesystem::path& path, mode_t dir = 0755, mode_t parents = 0755); -bool isFile(const std::string& path, bool resolveSymlink = true); -bool isDirectory(const std::string& path); -bool isSymLink(const std::string& path); -bool hasHardLink(const std::string& path); +bool recursive_mkdir(const std::filesystem::path& path, mode_t mode = 0755); -std::chrono::system_clock::time_point writeTime(const std::string& path); - -/*void createFileLink(const std::string& src, const std::string& dest, bool hard = false); +inline bool isPathRelative(const std::filesystem::path& path) { + return path.is_relative(); +} -std::string_view getFileExtension(std::string_view filename);*/ +bool isFile(const std::filesystem::path& path, bool resolveSymlink = true); +bool isDirectory(const std::filesystem::path& path); +bool isSymLink(const std::filesystem::path& path); +bool hasHardLink(const std::filesystem::path& path); /** * Read content of the directory. * The result is a list of relative (to @param dir) paths of all entries * in the directory, without "." and "..". */ -std::vector<std::string> readDirectory(const std::string& dir); +std::vector<std::string> readDirectory(const std::filesystem::path& dir); /** * Read the full content of a file at path. * If path is relative, it is appended to default_dir. */ -std::vector<uint8_t> loadFile(const std::string& path, const std::string& default_dir = {}); -std::string loadTextFile(const std::string& path, const std::string& default_dir = {}); +std::vector<uint8_t> loadFile(const std::filesystem::path& path); -void saveFile(const std::string& path, const uint8_t* data, size_t data_size, mode_t mode = 0644); +void saveFile(const std::filesystem::path& path, const uint8_t* data, size_t data_size, mode_t mode = 0644); inline void -saveFile(const std::string& path, const std::vector<uint8_t>& data, mode_t mode = 0644) +saveFile(const std::filesystem::path& path, const std::vector<uint8_t>& data, mode_t mode = 0644) { saveFile(path, data.data(), data.size(), mode); } -/*std::vector<uint8_t> loadCacheFile(const std::string& path, - std::chrono::system_clock::duration maxAge); -std::string loadCacheTextFile(const std::string& path, std::chrono::system_clock::duration maxAge); - -std::vector<uint8_t> readArchive(const std::string& path, const std::string& password = {}); -void writeArchive(const std::string& data, - const std::string& path, - const std::string& password = {});*/ - -std::mutex& getFileLock(const std::string& path); +std::mutex& getFileLock(const std::filesystem::path& path); /** * Remove a file with optional erasing of content. * Return the same value as std::remove(). */ -int remove(const std::string& path, bool erase = false); +int remove(const std::filesystem::path& path, bool erase = false); /** * Prune given directory's content and remove it, symlinks are not followed. * Return 0 if succeed, -1 if directory is not removed (content can be removed partially). */ -int removeAll(const std::string& path, bool erase = false); +int removeAll(const std::filesystem::path& path, bool erase = false); /** * Wrappers for fstream opening that will convert paths to wstring * on windows */ void openStream(std::ifstream& file, - const std::string& path, + const std::filesystem::path& path, std::ios_base::openmode mode = std::ios_base::in); void openStream(std::ofstream& file, - const std::string& path, + const std::filesystem::path& path, std::ios_base::openmode mode = std::ios_base::out); -std::ifstream ifstream(const std::string& path, std::ios_base::openmode mode = std::ios_base::in); -std::ofstream ofstream(const std::string& path, std::ios_base::openmode mode = std::ios_base::out); - -int64_t size(const std::string& path); +std::ifstream ifstream(const std::filesystem::path& path, std::ios_base::openmode mode = std::ios_base::in); +std::ofstream ofstream(const std::filesystem::path& path, std::ios_base::openmode mode = std::ios_base::out); /** * Windows compatibility wrapper for checking read-only attribute */ int accessFile(const std::string& file, int mode); -uint64_t lastWriteTime(const std::string& p); - } // namespace fileutils } // namespace dhtnet diff --git a/include/turn_cache.h b/include/turn_cache.h index deeb9197a8da7aba92f75b29a2b08667d7b420b7..a971e7f3d6722e1f6957c903094aa585a80626e8 100644 --- a/include/turn_cache.h +++ b/include/turn_cache.h @@ -28,6 +28,7 @@ #include <mutex> #include <optional> #include <string> +#include <filesystem> namespace dht { namespace log { @@ -65,7 +66,7 @@ public: private: std::string accountId_; - std::string cachePath_; + std::filesystem::path cachePath_; TurnTransportParams params_; std::atomic_bool enabled_ {false}; /** diff --git a/src/connectionmanager.cpp b/src/connectionmanager.cpp index baab5af5f579805f52c4e76e7a19385d817c3733..55a6929ebe1f66e8e8e297c2a0f21ea4e9a8c233 100644 --- a/src/connectionmanager.cpp +++ b/src/connectionmanager.cpp @@ -1300,7 +1300,7 @@ const std::shared_future<tls::DhParams> ConnectionManager::Impl::dhParams() const { return dht::ThreadPool::computation().get<tls::DhParams>( - std::bind(tls::DhParams::loadDhParams, config_->cachePath + DIR_SEPARATOR_STR "dhParams")); + std::bind(tls::DhParams::loadDhParams, config_->cachePath / "dhParams")); } template<typename ID = dht::Value::Id> @@ -1330,7 +1330,7 @@ loadIdList(const std::string& path) template<typename List = std::set<dht::Value::Id>> void -saveIdList(const std::string& path, const List& ids) +saveIdList(const std::filesystem::path& path, const List& ids) { std::ofstream file = fileutils::ofstream(path, std::ios::trunc | std::ios::binary); if (!file.is_open()) { @@ -1345,7 +1345,7 @@ void ConnectionManager::Impl::loadTreatedMessages() { std::lock_guard<std::mutex> lock(messageMutex_); - auto path = config_->cachePath + DIR_SEPARATOR_STR "treatedMessages"; + auto path = config_->cachePath / "treatedMessages"; treatedMessages_ = loadIdList<std::string>(path); if (treatedMessages_.empty()) { auto messages = loadIdList(path); @@ -1362,8 +1362,7 @@ ConnectionManager::Impl::saveTreatedMessages() const auto& this_ = *sthis; std::lock_guard<std::mutex> lock(this_.messageMutex_); fileutils::check_dir(this_.config_->cachePath.c_str()); - saveIdList<decltype(this_.treatedMessages_)>(this_.config_->cachePath - + DIR_SEPARATOR_STR "treatedMessages", + saveIdList<decltype(this_.treatedMessages_)>(this_.config_->cachePath / "treatedMessages", this_.treatedMessages_); } }); diff --git a/src/fileutils.cpp b/src/fileutils.cpp index c3aa05959a07b985bf615f1ef491edfb6c044c1c..6e60fd45b4e895c11d6cea449fb0d1f0c800f57b 100644 --- a/src/fileutils.cpp +++ b/src/fileutils.cpp @@ -15,6 +15,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ #include "fileutils.h" + #include <opendht/crypto.h> #ifdef RING_UWP @@ -32,29 +33,12 @@ #include <sys/types.h> #include <sys/stat.h> - #ifndef _MSC_VER #include <libgen.h> #endif - -#ifdef _MSC_VER -#include "windirent.h" -#else -#include <dirent.h> -#endif - +#include <fcntl.h> #include <signal.h> #include <unistd.h> -#include <fcntl.h> -#ifndef _WIN32 -#include <pwd.h> -#else -#include <shlobj.h> -#define NAME_MAX 255 -#endif -#if !defined __ANDROID__ && !defined _WIN32 -#include <wordexp.h> -#endif #include <sstream> #include <fstream> @@ -62,6 +46,7 @@ #include <stdexcept> #include <limits> #include <array> +#include <filesystem> #include <cstdlib> #include <cstring> @@ -69,218 +54,67 @@ #include <cstddef> #include <ciso646> -extern "C" { -#include <pj/ctype.h> -#include <pjlib-util/md5.h> -} -#include <filesystem> - -#define PIDFILE ".ring.pid" #define ERASE_BLOCK 4096 namespace dhtnet { namespace fileutils { -// returns true if directory exists +// returns true if directory exists or was created bool -check_dir(const char* path, [[maybe_unused]] mode_t dirmode, mode_t parentmode) -{ - DIR* dir = opendir(path); - - if (!dir) { // doesn't exist - if (not recursive_mkdir(path, parentmode)) { - perror(path); - return false; - } -#ifndef _WIN32 - if (chmod(path, dirmode) < 0) { - //JAMI_ERR("fileutils::check_dir(): chmod() failed on '%s', %s", path, strerror(errno)); - return false; - } -#endif - } else - closedir(dir); - return true; -} - -std::string -expand_path(const std::string& path) +check_dir(const std::filesystem::path& path, mode_t dirmode, mode_t parentmode) { -#if defined __ANDROID__ || defined _MSC_VER || defined WIN32 || defined __APPLE__ - //JAMI_ERR("Path expansion not implemented, returning original"); - return path; -#else - - std::string result; - - wordexp_t p; - int ret = wordexp(path.c_str(), &p, 0); - - switch (ret) { - case WRDE_BADCHAR: - /*JAMI_ERR("Illegal occurrence of newline or one of |, &, ;, <, >, " - "(, ), {, }.");*/ - return result; - case WRDE_BADVAL: - //JAMI_ERR("An undefined shell variable was referenced"); - return result; - case WRDE_CMDSUB: - //JAMI_ERR("Command substitution occurred"); - return result; - case WRDE_SYNTAX: - //JAMI_ERR("Shell syntax error"); - return result; - case WRDE_NOSPACE: - //JAMI_ERR("Out of memory."); - // This is the only error where we must call wordfree - break; - default: - if (p.we_wordc > 0) - result = std::string(p.we_wordv[0]); - break; + if (std::filesystem::exists(path)) + return true; + if (path.has_parent_path()) + check_dir(path.parent_path(), parentmode, parentmode); + if (std::filesystem::create_directory(path)) { + std::filesystem::permissions(path, (std::filesystem::perms)dirmode); + return true; } - - wordfree(&p); - - return result; -#endif + return false; } std::mutex& -getFileLock(const std::string& path) +getFileLock(const std::filesystem::path& path) { static std::mutex fileLockLock {}; static std::map<std::string, std::mutex> fileLocks {}; std::lock_guard<std::mutex> l(fileLockLock); - return fileLocks[path]; + return fileLocks[path.string()]; } bool -isFile(const std::string& path, bool resolveSymlink) +isFile(const std::filesystem::path& path, bool resolveSymlink) { - if (path.empty()) - return false; -#ifdef _WIN32 - if (resolveSymlink) { - struct _stat64i32 s; - if (_wstat(dhtnet::to_wstring(path).c_str(), &s) == 0) - return S_ISREG(s.st_mode); - } else { - DWORD attr = GetFileAttributes(dhtnet::to_wstring(path).c_str()); - if ((attr != INVALID_FILE_ATTRIBUTES) && !(attr & FILE_ATTRIBUTE_DIRECTORY) - && !(attr & FILE_ATTRIBUTE_REPARSE_POINT)) - return true; - } -#else - if (resolveSymlink) { - struct stat s; - if (stat(path.c_str(), &s) == 0) - return S_ISREG(s.st_mode); - } else { - struct stat s; - if (lstat(path.c_str(), &s) == 0) - return S_ISREG(s.st_mode); - } -#endif - - return false; -} - -bool -isDirectory(const std::string& path) -{ - struct stat s; - if (stat(path.c_str(), &s) == 0) - return s.st_mode & S_IFDIR; - return false; + auto status = resolveSymlink ? std::filesystem::status(path) : std::filesystem::symlink_status(path); + return std::filesystem::is_regular_file(status); } bool -isDirectoryWritable(const std::string& directory) +isDirectory(const std::filesystem::path& path) { - return accessFile(directory, W_OK) == 0; + return std::filesystem::is_directory(path); } bool -hasHardLink(const std::string& path) +hasHardLink(const std::filesystem::path& path) { -#ifndef _WIN32 - struct stat s; - if (lstat(path.c_str(), &s) == 0) - return s.st_nlink > 1; -#endif - return false; + return std::filesystem::hard_link_count(path) > 1; } bool -isSymLink(const std::string& path) +isSymLink(const std::filesystem::path& path) { -#ifndef _WIN32 - struct stat s; - if (lstat(path.c_str(), &s) == 0) - return S_ISLNK(s.st_mode); -#elif !defined(_MSC_VER) - DWORD attr = GetFileAttributes(dhtnet::to_wstring(path).c_str()); - if (attr & FILE_ATTRIBUTE_REPARSE_POINT) - return true; -#endif - return false; + return std::filesystem::is_symlink(path); } -std::chrono::system_clock::time_point -writeTime(const std::string& path) +template <typename TP> +std::chrono::system_clock::time_point to_sysclock(TP tp) { -#ifndef _WIN32 - struct stat s; - auto ret = stat(path.c_str(), &s); - if (ret) - throw std::runtime_error("Can't check write time for: " + path); - return std::chrono::system_clock::from_time_t(s.st_mtime); -#else -#if RING_UWP - _CREATEFILE2_EXTENDED_PARAMETERS ext_params = {0}; - ext_params.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS); - ext_params.dwFileAttributes = FILE_ATTRIBUTE_NORMAL; - ext_params.dwFileFlags = FILE_FLAG_NO_BUFFERING; - ext_params.dwSecurityQosFlags = SECURITY_ANONYMOUS; - ext_params.lpSecurityAttributes = nullptr; - ext_params.hTemplateFile = nullptr; - HANDLE h = CreateFile2(dhtnet::to_wstring(path).c_str(), - GENERIC_READ, - FILE_SHARE_READ, - OPEN_EXISTING, - &ext_params); -#elif _WIN32 - HANDLE h = CreateFileW(dhtnet::to_wstring(path).c_str(), - GENERIC_READ, - FILE_SHARE_READ, - nullptr, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - nullptr); -#endif - if (h == INVALID_HANDLE_VALUE) - throw std::runtime_error("Can't open: " + path); - FILETIME lastWriteTime; - if (!GetFileTime(h, nullptr, nullptr, &lastWriteTime)) - throw std::runtime_error("Can't check write time for: " + path); - CloseHandle(h); - SYSTEMTIME sTime; - if (!FileTimeToSystemTime(&lastWriteTime, &sTime)) - throw std::runtime_error("Can't check write time for: " + path); - struct tm tm - {}; - tm.tm_year = sTime.wYear - 1900; - tm.tm_mon = sTime.wMonth - 1; - tm.tm_mday = sTime.wDay; - tm.tm_hour = sTime.wHour; - tm.tm_min = sTime.wMinute; - tm.tm_sec = sTime.wSecond; - tm.tm_isdst = -1; - return std::chrono::system_clock::from_time_t(mktime(&tm)); -#endif + using namespace std::chrono; + return time_point_cast<system_clock::duration>(tp - TP::clock::now() + system_clock::now()); } bool @@ -314,85 +148,26 @@ createFileLink(const std::string& linkFile, const std::string& target, bool hard createSymlink(linkFile, target); } -std::string_view -getFileExtension(std::string_view filename) -{ - std::string_view result; - auto sep = filename.find_last_of('.'); - if (sep != std::string_view::npos && sep != filename.size() - 1) - result = filename.substr(sep + 1); - if (result.size() >= 8) - return {}; - return result; -} - -bool -isPathRelative(const std::string& path) -{ -#ifndef _WIN32 - return not path.empty() and not(path[0] == '/'); -#else - return not path.empty() and path.find(":") == std::string::npos; -#endif -} - -std::string -getCleanPath(const std::string& base, const std::string& path) -{ - if (base.empty() or path.size() < base.size()) - return path; - auto base_sep = base + DIR_SEPARATOR_STR; - if (path.compare(0, base_sep.size(), base_sep) == 0) - return path.substr(base_sep.size()); - else - return path; -} - -std::string -getFullPath(const std::string& base, const std::string& path) -{ - bool isRelative {not base.empty() and isPathRelative(path)}; - return isRelative ? base + DIR_SEPARATOR_STR + path : path; -} - std::vector<uint8_t> -loadFile(const std::string& path, const std::string& default_dir) +loadFile(const std::filesystem::path& path) { std::vector<uint8_t> buffer; - std::ifstream file = ifstream(getFullPath(default_dir, path), std::ios::binary); - if (!file) - throw std::runtime_error("Can't read file: " + path); - file.seekg(0, std::ios::end); - auto size = file.tellg(); - if (size > std::numeric_limits<unsigned>::max()) - throw std::runtime_error("File is too big: " + path); - buffer.resize(size); - file.seekg(0, std::ios::beg); - if (!file.read((char*) buffer.data(), size)) - throw std::runtime_error("Can't load file: " + path); - return buffer; -} - -std::string -loadTextFile(const std::string& path, const std::string& default_dir) -{ - std::string buffer; - std::ifstream file = ifstream(getFullPath(default_dir, path)); + std::ifstream file = ifstream(path, std::ios::binary); if (!file) - throw std::runtime_error("Can't read file: " + path); + throw std::runtime_error("Can't read file: " + path.string()); file.seekg(0, std::ios::end); auto size = file.tellg(); if (size > std::numeric_limits<unsigned>::max()) - throw std::runtime_error("File is too big: " + path); + throw std::runtime_error("File is too big: " + path.string()); buffer.resize(size); file.seekg(0, std::ios::beg); if (!file.read((char*) buffer.data(), size)) - throw std::runtime_error("Can't load file: " + path); + throw std::runtime_error("Can't load file: " + path.string()); return buffer; } void -saveFile(const std::string& path, const uint8_t* data, size_t data_size, [[maybe_unused]] mode_t mode) +saveFile(const std::filesystem::path& path, const uint8_t* data, size_t data_size, mode_t mode) { std::ofstream file = fileutils::ofstream(path, std::ios::trunc | std::ios::binary); if (!file.is_open()) { @@ -400,188 +175,32 @@ saveFile(const std::string& path, const uint8_t* data, size_t data_size, [[maybe return; } file.write((char*) data, data_size); -#ifndef _WIN32 - if (chmod(path.c_str(), mode) < 0) - /*JAMI_WARN("fileutils::saveFile(): chmod() failed on '%s', %s", - path.c_str(), - strerror(errno))*/; -#endif -} - -std::vector<uint8_t> -loadCacheFile(const std::string& path, std::chrono::system_clock::duration maxAge) -{ - // writeTime throws exception if file doesn't exist - auto duration = std::chrono::system_clock::now() - writeTime(path); - if (duration > maxAge) - throw std::runtime_error("file too old"); - - //JAMI_DBG("Loading cache file '%.*s'", (int) path.size(), path.c_str()); - return loadFile(path); -} - -std::string -loadCacheTextFile(const std::string& path, std::chrono::system_clock::duration maxAge) -{ - // writeTime throws exception if file doesn't exist - auto duration = std::chrono::system_clock::now() - writeTime(path); - if (duration > maxAge) - throw std::runtime_error("file too old"); - - //JAMI_DBG("Loading cache file '%.*s'", (int) path.size(), path.c_str()); - return loadTextFile(path); -} - -static size_t -dirent_buf_size([[maybe_unused]] DIR* dirp) -{ - long name_max; -#if defined(HAVE_FPATHCONF) && defined(HAVE_DIRFD) && defined(_PC_NAME_MAX) - name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX); - if (name_max == -1) -#if defined(NAME_MAX) - name_max = (NAME_MAX > 255) ? NAME_MAX : 255; -#else - return (size_t) (-1); -#endif -#else -#if defined(NAME_MAX) - name_max = (NAME_MAX > 255) ? NAME_MAX : 255; -#else -#error "buffer size for readdir_r cannot be determined" -#endif -#endif - size_t name_end = (size_t) offsetof(struct dirent, d_name) + name_max + 1; - return name_end > sizeof(struct dirent) ? name_end : sizeof(struct dirent); + file.close(); + std::filesystem::permissions(path, (std::filesystem::perms)mode); } std::vector<std::string> -readDirectory(const std::string& dir) +readDirectory(const std::filesystem::path& dir) { - DIR* dp = opendir(dir.c_str()); - if (!dp) - return {}; - - size_t size = dirent_buf_size(dp); - if (size == (size_t) (-1)) - return {}; - std::vector<uint8_t> buf(size); - dirent* entry; - std::vector<std::string> files; -#ifndef _WIN32 - while (!readdir_r(dp, reinterpret_cast<dirent*>(buf.data()), &entry) && entry) { -#else - while ((entry = readdir(dp)) != nullptr) { -#endif - std::string fname {entry->d_name}; + std::error_code ec; + for (const auto& entry : std::filesystem::directory_iterator(dir, ec)) { + std::string fname {entry.path().filename().string()}; if (fname == "." || fname == "..") continue; files.emplace_back(std::move(fname)); } - closedir(dp); return files; -} // namespace fileutils - -/* -std::vector<uint8_t> -readArchive(const std::string& path, const std::string& pwd) -{ - JAMI_DBG("Reading archive from %s", path.c_str()); - - auto isUnencryptedGzip = [](const std::vector<uint8_t>& data) { - // NOTE: some webserver modify gzip files and this can end with a gunzip in a gunzip - // file. So, to make the readArchive more robust, we can support this case by detecting - // gzip header via 1f8b 08 - // We don't need to support more than 2 level, else somebody may be able to send - // gunzip in loops and abuse. - return data.size() > 3 && data[0] == 0x1f && data[1] == 0x8b && data[2] == 0x08; - }; - - auto decompress = [](std::vector<uint8_t>& data) { - try { - data = archiver::decompress(data); - } catch (const std::exception& e) { - JAMI_ERR("Error decrypting archive: %s", e.what()); - throw e; - } - }; - - std::vector<uint8_t> data; - // Read file - try { - data = loadFile(path); - } catch (const std::exception& e) { - JAMI_ERR("Error loading archive: %s", e.what()); - throw e; - } - - if (isUnencryptedGzip(data)) { - if (!pwd.empty()) - JAMI_WARN() << "A gunzip in a gunzip is detected. A webserver may have a bad config"; - - decompress(data); - } - - if (!pwd.empty()) { - // Decrypt - try { - data = dht::crypto::aesDecrypt(data, pwd); - } catch (const std::exception& e) { - JAMI_ERR("Error decrypting archive: %s", e.what()); - throw e; - } - decompress(data); - } else if (isUnencryptedGzip(data)) { - JAMI_WARN() << "A gunzip in a gunzip is detected. A webserver may have a bad config"; - decompress(data); - } - return data; } -void -writeArchive(const std::string& archive_str, const std::string& path, const std::string& password) -{ - JAMI_DBG("Writing archive to %s", path.c_str()); - - if (not password.empty()) { - // Encrypt using provided password - std::vector<uint8_t> data = dht::crypto::aesEncrypt(archiver::compress(archive_str), - password); - // Write - try { - saveFile(path, data); - } catch (const std::runtime_error& ex) { - JAMI_ERR("Export failed: %s", ex.what()); - return; - } - } else { - JAMI_WARN("Unsecured archiving (no password)"); - archiver::compressGzip(archive_str, path); - } -}*/ - bool -recursive_mkdir(const std::string& path, mode_t mode) +recursive_mkdir(const std::filesystem::path& path, mode_t mode) { -#ifndef _WIN32 - if (mkdir(path.data(), mode) != 0) { -#else - if (_wmkdir(dhtnet::to_wstring(path.data()).c_str()) != 0) { -#endif - if (errno == ENOENT) { - recursive_mkdir(path.substr(0, path.find_last_of(DIR_SEPARATOR_CH)), mode); -#ifndef _WIN32 - if (mkdir(path.data(), mode) != 0) { -#else - if (_wmkdir(dhtnet::to_wstring(path.data()).c_str()) != 0) { -#endif - //JAMI_ERR("Could not create directory."); - return false; - } - } - } // namespace dhtnet - return true; + std::error_code ec; + std::filesystem::create_directories(path, ec); + if (!ec) + std::filesystem::permissions(path, (std::filesystem::perms)mode, ec); + return !ec; } #ifdef _WIN32 @@ -700,7 +319,7 @@ eraseFile(const std::string& path, bool dosync) } int -remove(const std::string& path, bool erase) +remove(const std::filesystem::path& path, bool erase) { if (erase and isFile(path, false) and !hasHardLink(path)) eraseFile(path, true); @@ -715,22 +334,25 @@ remove(const std::string& path, bool erase) } int -removeAll(const std::string& path, bool erase) +removeAll(const std::filesystem::path& path, bool erase) { if (path.empty()) return -1; - if (isDirectory(path) and !isSymLink(path)) { - auto dir = path; - if (dir.back() != DIR_SEPARATOR_CH) - dir += DIR_SEPARATOR_CH; - for (auto& entry : fileutils::readDirectory(dir)) - removeAll(dir + entry, erase); + + auto status = std::filesystem::status(path); + if (std::filesystem::is_directory(status) and not std::filesystem::is_symlink(status)) { + for (const auto& entry: std::filesystem::directory_iterator(path)) { + auto fname = entry.path().filename().string(); + if (fname == "." || fname == "..") + continue; + removeAll(entry.path(), erase); + } } return remove(path, erase); } void -openStream(std::ifstream& file, const std::string& path, std::ios_base::openmode mode) +openStream(std::ifstream& file, const std::filesystem::path& path, std::ios_base::openmode mode) { #ifdef _WIN32 file.open(dhtnet::to_wstring(path), mode); @@ -740,7 +362,7 @@ openStream(std::ifstream& file, const std::string& path, std::ios_base::openmode } void -openStream(std::ofstream& file, const std::string& path, std::ios_base::openmode mode) +openStream(std::ofstream& file, const std::filesystem::path& path, std::ios_base::openmode mode) { #ifdef _WIN32 file.open(dhtnet::to_wstring(path), mode); @@ -750,7 +372,7 @@ openStream(std::ofstream& file, const std::string& path, std::ios_base::openmode } std::ifstream -ifstream(const std::string& path, std::ios_base::openmode mode) +ifstream(const std::filesystem::path& path, std::ios_base::openmode mode) { #ifdef _WIN32 return std::ifstream(dhtnet::to_wstring(path), mode); @@ -760,7 +382,7 @@ ifstream(const std::string& path, std::ios_base::openmode mode) } std::ofstream -ofstream(const std::string& path, std::ios_base::openmode mode) +ofstream(const std::filesystem::path& path, std::ios_base::openmode mode) { #ifdef _WIN32 return std::ofstream(dhtnet::to_wstring(path), mode); @@ -769,23 +391,8 @@ ofstream(const std::string& path, std::ios_base::openmode mode) #endif } -int64_t -size(const std::string& path) -{ - int64_t size = 0; - try { - std::ifstream file; - openStream(file, path, std::ios::binary | std::ios::in); - file.seekg(0, std::ios_base::end); - size = file.tellg(); - file.close(); - } catch (...) { - } - return size; -} - int -accessFile(const std::string& file, int mode) +accessFile(const std::filesystem::path& file, int mode) { #ifdef _WIN32 return _waccess(dhtnet::to_wstring(file).c_str(), mode); @@ -794,20 +401,5 @@ accessFile(const std::string& file, int mode) #endif } -uint64_t -lastWriteTime(const std::string& p) -{ -#if USE_STD_FILESYSTEM - return std::chrono::duration_cast<std::chrono::milliseconds>( - std::filesystem::last_write_time(std::filesystem::path(p)).time_since_epoch()) - .count(); -#else - struct stat result; - if (stat(p.c_str(), &result) == 0) - return result.st_mtime; - return 0; -#endif -} - } // namespace fileutils } // namespace dhtnet diff --git a/src/security/certstore.cpp b/src/security/certstore.cpp index 9b6bb9698be903086d1f4a3a7f1b1d7027c5e927..aa3e14cb35dcb1b818c88c09cd1fffdad5bd108e 100644 --- a/src/security/certstore.cpp +++ b/src/security/certstore.cpp @@ -24,6 +24,8 @@ #include <gnutls/ocsp.h> +#include <fmt/std.h> + #include <thread> #include <sstream> #include <fmt/format.h> @@ -31,15 +33,15 @@ namespace dhtnet { namespace tls { -CertificateStore::CertificateStore(const std::string& path, std::shared_ptr<Logger> logger) +CertificateStore::CertificateStore(const std::filesystem::path& path, std::shared_ptr<Logger> logger) : logger_(std::move(logger)) - , certPath_(fmt::format("{}/certificates", path)) - , crlPath_(fmt::format("{}/crls", path)) - , ocspPath_(fmt::format("{}/oscp", path)) + , certPath_(path / "certificates") + , crlPath_(path /"crls") + , ocspPath_(path /"oscp") { - fileutils::check_dir(certPath_.c_str()); - fileutils::check_dir(crlPath_.c_str()); - fileutils::check_dir(ocspPath_.c_str()); + fileutils::check_dir(certPath_); + fileutils::check_dir(crlPath_); + fileutils::check_dir(ocspPath_); loadLocalCertificates(); } @@ -47,16 +49,20 @@ unsigned CertificateStore::loadLocalCertificates() { std::lock_guard<std::mutex> l(lock_); + if (logger_) + logger_->debug("CertificateStore: loading certificates from {}", certPath_); - auto dir_content = fileutils::readDirectory(certPath_); unsigned n = 0; - for (const auto& f : dir_content) { + std::error_code ec; + for (const auto& crtPath : std::filesystem::directory_iterator(certPath_, ec)) { + const auto& path = crtPath.path(); + auto fileName = path.filename().string(); try { auto crt = std::make_shared<crypto::Certificate>( - fileutils::loadFile(certPath_ + DIR_SEPARATOR_CH + f)); + fileutils::loadFile(crtPath)); auto id = crt->getId().toString(); auto longId = crt->getLongId().toString(); - if (id != f && longId != f) + if (id != fileName && longId != fileName) throw std::logic_error("Certificate id mismatch"); while (crt) { id = crt->getId().toString(); @@ -69,8 +75,8 @@ CertificateStore::loadLocalCertificates() } } catch (const std::exception& e) { if (logger_) - logger_->warn("Remove cert. {}", e.what()); - remove(fmt::format("{}/{}", certPath_, f).c_str()); + logger_->warn("loadLocalCertificates: error loading {}: {}", path, e.what()); + remove(path); } } if (logger_) @@ -81,21 +87,23 @@ CertificateStore::loadLocalCertificates() void CertificateStore::loadRevocations(crypto::Certificate& crt) const { - auto dir = fmt::format("{:s}/{:s}", crlPath_, crt.getId().toString()); - for (const auto& crl : fileutils::readDirectory(dir)) { + std::error_code ec; + auto dir = crlPath_ / crt.getId().toString(); + for (const auto& crl : std::filesystem::directory_iterator(dir, ec)) { try { crt.addRevocationList(std::make_shared<crypto::RevocationList>( - fileutils::loadFile(fmt::format("{}/{}", dir, crl)))); + fileutils::loadFile(crl))); } catch (const std::exception& e) { if (logger_) logger_->warn("Can't load revocation list: %s", e.what()); } } - auto ocsp_dir = ocspPath_ + DIR_SEPARATOR_CH + crt.getId().toString(); - for (const auto& ocsp : fileutils::readDirectory(ocsp_dir)) { + + auto ocsp_dir = ocspPath_ / crt.getId().toString(); + for (const auto& ocsp_filepath : std::filesystem::directory_iterator(ocsp_dir, ec)) { try { - auto ocsp_filepath = fmt::format("{}/{}", ocsp_dir, ocsp); - if (logger_) logger_->debug("Found {:s}", ocsp_filepath); + auto ocsp = ocsp_filepath.path().filename().string(); + if (logger_) logger_->debug("Found {}", ocsp_filepath.path()); auto serial = crt.getSerialNumber(); if (dht::toHex(serial.data(), serial.size()) != ocsp) continue; @@ -237,13 +245,12 @@ CertificateStore::findIssuer(const std::shared_ptr<crypto::Certificate>& crt) co } static std::vector<crypto::Certificate> -readCertificates(const std::string& path, const std::string& crl_path) +readCertificates(const std::filesystem::path& path, const std::string& crl_path) { std::vector<crypto::Certificate> ret; - if (fileutils::isDirectory(path)) { - auto files = fileutils::readDirectory(path); - for (const auto& file : files) { - auto certs = readCertificates(fmt::format("{}/{}", path, file), crl_path); + if (std::filesystem::is_directory(path)) { + for (const auto& file : std::filesystem::directory_iterator(path)) { + auto certs = readCertificates(file, crl_path); ret.insert(std::end(ret), std::make_move_iterator(std::begin(certs)), std::make_move_iterator(std::end(certs))); @@ -364,7 +371,7 @@ CertificateStore::pinCertificate(const std::shared_ptr<crypto::Certificate>& cer } if (local) { if (sig) - fileutils::saveFile(certPath_ + DIR_SEPARATOR_CH + ids.front(), cert->getPacked()); + fileutils::saveFile(certPath_ / ids.front(), cert->getPacked()); } } //for (const auto& id : ids) @@ -378,7 +385,7 @@ CertificateStore::unpinCertificate(const std::string& id) std::lock_guard<std::mutex> l(lock_); certs_.erase(id); - return remove((certPath_ + DIR_SEPARATOR_CH + id).c_str()) == 0; + return remove(certPath_ / id); } bool @@ -430,9 +437,8 @@ CertificateStore::pinRevocationList(const std::string& id, void CertificateStore::pinRevocationList(const std::string& id, const dht::crypto::RevocationList& crl) { - fileutils::check_dir((crlPath_ + DIR_SEPARATOR_CH + id).c_str()); - fileutils::saveFile(crlPath_ + DIR_SEPARATOR_CH + id + DIR_SEPARATOR_CH - + dht::toHex(crl.getNumber()), + fileutils::check_dir(crlPath_ / id); + fileutils::saveFile(crlPath_ / id / dht::toHex(crl.getNumber()), crl.getPacked()); } @@ -450,7 +456,7 @@ CertificateStore::pinOcspResponse(const dht::crypto::Certificate& cert) auto id = cert.getId().toString(); auto serial = cert.getSerialNumber(); auto serialhex = dht::toHex(serial); - auto dir = ocspPath_ + DIR_SEPARATOR_CH + id; + auto dir = ocspPath_ / id; if (auto localCert = getCertificate(id)) { // Update certificate in the local store if relevant @@ -461,7 +467,7 @@ CertificateStore::pinOcspResponse(const dht::crypto::Certificate& cert) } dht::ThreadPool::io().run([l=logger_, - path = dir + DIR_SEPARATOR_CH + serialhex, + path = dir / serialhex, dir = std::move(dir), id = std::move(id), serialhex = std::move(serialhex), diff --git a/src/security/diffie-hellman.cpp b/src/security/diffie-hellman.cpp index 78f901fbb2afe8f3d5e342e140095aeb3d9c912a..216e23380d6d5910cad2690527088f4fe2de052b 100644 --- a/src/security/diffie-hellman.cpp +++ b/src/security/diffie-hellman.cpp @@ -19,6 +19,7 @@ #include <chrono> #include <ciso646> +#include <filesystem> namespace dhtnet { namespace tls { @@ -103,12 +104,13 @@ DhParams::generate() } DhParams -DhParams::loadDhParams(const std::string& path) +DhParams::loadDhParams(const std::filesystem::path& path) { std::lock_guard<std::mutex> l(fileutils::getFileLock(path)); try { // writeTime throw exception if file doesn't exist - auto duration = std::chrono::system_clock::now() - fileutils::writeTime(path); + auto writeTime = std::filesystem::last_write_time(path); + auto duration = decltype(writeTime)::clock::now() - writeTime; if (duration >= std::chrono::hours(24 * 3)) // file is valid only 3 days throw std::runtime_error("file too old"); diff --git a/src/turn/turn_cache.cpp b/src/turn/turn_cache.cpp index f351347e1a97c0c219c729fb1a214739290a6759..65862cbf4da58d51be9c3da00cd0f1063b74aa4c 100644 --- a/src/turn/turn_cache.cpp +++ b/src/turn/turn_cache.cpp @@ -127,15 +127,15 @@ TurnCache::refresh(const asio::error_code& ec) return; } // Else cache resolution result - fileutils::recursive_mkdir(cachePath_ + DIR_SEPARATOR_STR + "domains", 0700); - auto pathV4 = cachePath_ + DIR_SEPARATOR_STR + "domains" + DIR_SEPARATOR_STR + "v4." + server; + fileutils::recursive_mkdir(cachePath_ / "domains", 0700); + auto pathV4 = cachePath_ / "domains" / ("v4." + server); IpAddr testV4, testV6; if (auto turnV4File = std::ifstream(pathV4)) { std::string content((std::istreambuf_iterator<char>(turnV4File)), std::istreambuf_iterator<char>()); testV4 = IpAddr(content, AF_INET); } - auto pathV6 = cachePath_ + DIR_SEPARATOR_STR + "domains" + DIR_SEPARATOR_STR + "v6." + server; + auto pathV6 = cachePath_ / "domains" / ("v6." + server); if (auto turnV6File = std::ifstream(pathV6)) { std::string content((std::istreambuf_iterator<char>(turnV6File)), std::istreambuf_iterator<char>()); diff --git a/tests/testFileutils.cpp b/tests/testFileutils.cpp index fa353a6ad2bb619aa98abd8e51638a89bd6caa49..9bc3367645c0e5f3da8e50480a1ab6d36742606c 100644 --- a/tests/testFileutils.cpp +++ b/tests/testFileutils.cpp @@ -40,22 +40,20 @@ private: void testPath(); void testReadDirectory(); void testLoadFile(); - void testIsDirectoryWritable(); CPPUNIT_TEST_SUITE(FileutilsTest); CPPUNIT_TEST(testCheckDir); CPPUNIT_TEST(testPath); CPPUNIT_TEST(testReadDirectory); CPPUNIT_TEST(testLoadFile); - CPPUNIT_TEST(testIsDirectoryWritable); CPPUNIT_TEST_SUITE_END(); static constexpr auto tmpFileName = "temp_file"; - std::string TEST_PATH; - std::string NON_EXISTANT_PATH_BASE; - std::string NON_EXISTANT_PATH; - std::string EXISTANT_FILE; + std::filesystem::path TEST_PATH; + std::filesystem::path NON_EXISTANT_PATH_BASE; + std::filesystem::path NON_EXISTANT_PATH; + std::filesystem::path EXISTANT_FILE; }; CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(FileutilsTest, FileutilsTest::name()); @@ -70,9 +68,9 @@ FileutilsTest::setUp() CPPUNIT_ASSERT(directory); TEST_PATH = directory; - EXISTANT_FILE = TEST_PATH + DIR_SEPARATOR_STR + tmpFileName; - NON_EXISTANT_PATH_BASE = TEST_PATH + DIR_SEPARATOR_STR + "not_existing_path"; - NON_EXISTANT_PATH = NON_EXISTANT_PATH_BASE + DIR_SEPARATOR_STR + "test"; + EXISTANT_FILE = TEST_PATH / tmpFileName; + NON_EXISTANT_PATH_BASE = TEST_PATH / "not_existing_path"; + NON_EXISTANT_PATH = NON_EXISTANT_PATH_BASE / "test"; auto* fd = fopen(EXISTANT_FILE.c_str(), "w"); fwrite("RING", 1, 4, fd); @@ -90,11 +88,11 @@ void FileutilsTest::testCheckDir() { // check existed directory - CPPUNIT_ASSERT(check_dir(TEST_PATH.c_str())); - CPPUNIT_ASSERT(isDirectory(TEST_PATH.c_str())); + CPPUNIT_ASSERT(check_dir(TEST_PATH)); + CPPUNIT_ASSERT(isDirectory(TEST_PATH)); // check non-existent directory CPPUNIT_ASSERT(!isDirectory(NON_EXISTANT_PATH)); - CPPUNIT_ASSERT(check_dir(NON_EXISTANT_PATH.c_str())); + CPPUNIT_ASSERT(check_dir(NON_EXISTANT_PATH)); CPPUNIT_ASSERT(isDirectory(NON_EXISTANT_PATH)); CPPUNIT_ASSERT(removeAll(NON_EXISTANT_PATH_BASE) == 0); CPPUNIT_ASSERT(!isDirectory(NON_EXISTANT_PATH_BASE)); @@ -114,14 +112,14 @@ FileutilsTest::testPath() void FileutilsTest::testReadDirectory() { - CPPUNIT_ASSERT(recursive_mkdir(TEST_PATH + DIR_SEPARATOR_STR + "readDirectory" + DIR_SEPARATOR_STR + "test1")); - CPPUNIT_ASSERT(recursive_mkdir(TEST_PATH + DIR_SEPARATOR_STR + "readDirectory" + DIR_SEPARATOR_STR + "test2")); - auto dirs = readDirectory(TEST_PATH + DIR_SEPARATOR_STR + "readDirectory"); + CPPUNIT_ASSERT(recursive_mkdir(TEST_PATH / "readDirectory" / "test1")); + CPPUNIT_ASSERT(recursive_mkdir(TEST_PATH / "readDirectory" / "test2")); + auto dirs = readDirectory(TEST_PATH / "readDirectory"); CPPUNIT_ASSERT(dirs.size() == 2); CPPUNIT_ASSERT( (dirs.at(0).compare("test1") == 0 && dirs.at(1).compare("test2") == 0) || (dirs.at(1).compare("test1") == 0 && dirs.at(0).compare("test2") == 0)); - CPPUNIT_ASSERT(removeAll(TEST_PATH + DIR_SEPARATOR_STR + "readDirectory") == 0); + CPPUNIT_ASSERT(removeAll(TEST_PATH / "readDirectory") == 0); } void @@ -135,18 +133,6 @@ FileutilsTest::testLoadFile() CPPUNIT_ASSERT(file.at(3) == 'G'); } -void -FileutilsTest::testIsDirectoryWritable() -{ - CPPUNIT_ASSERT(recursive_mkdir(NON_EXISTANT_PATH_BASE)); - CPPUNIT_ASSERT(isDirectoryWritable(NON_EXISTANT_PATH_BASE)); - CPPUNIT_ASSERT(removeAll(NON_EXISTANT_PATH_BASE) == 0); - // Create directory with permission: read by owner - CPPUNIT_ASSERT(recursive_mkdir(NON_EXISTANT_PATH_BASE, 0400)); - CPPUNIT_ASSERT(!isDirectoryWritable(NON_EXISTANT_PATH_BASE)); - CPPUNIT_ASSERT(removeAll(NON_EXISTANT_PATH_BASE) == 0); -} - }}} // namespace dhtnet::test::fileutils JAMI_TEST_RUNNER(dhtnet::fileutils::test::FileutilsTest::name());