diff options
Diffstat (limited to 'zen')
-rw-r--r-- | zen/dir_watcher.cpp | 13 | ||||
-rw-r--r-- | zen/file_access.cpp | 262 | ||||
-rw-r--r-- | zen/file_access.h | 9 | ||||
-rw-r--r-- | zen/file_io.cpp | 1 | ||||
-rw-r--r-- | zen/file_path.cpp | 26 | ||||
-rw-r--r-- | zen/file_path.h | 5 | ||||
-rw-r--r-- | zen/file_traverser.cpp | 27 | ||||
-rw-r--r-- | zen/file_traverser.h | 11 | ||||
-rw-r--r-- | zen/http.cpp | 14 | ||||
-rw-r--r-- | zen/open_ssl.cpp | 461 | ||||
-rw-r--r-- | zen/open_ssl.h | 10 | ||||
-rw-r--r-- | zen/process_exec.cpp | 10 | ||||
-rw-r--r-- | zen/socket.h | 78 | ||||
-rw-r--r-- | zen/stl_tools.h | 4 | ||||
-rw-r--r-- | zen/string_base.h | 2 | ||||
-rw-r--r-- | zen/string_tools.h | 13 | ||||
-rw-r--r-- | zen/sys_info.cpp | 18 | ||||
-rw-r--r-- | zen/thread.h | 10 | ||||
-rw-r--r-- | zen/zstring.cpp | 26 |
19 files changed, 507 insertions, 493 deletions
diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index e0b4a338..235c466c 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -37,15 +37,18 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError { std::function<void(const Zstring& path)> traverse; - traverse = [&traverse, &fullFolderList](const Zstring& path) + traverse = [&traverse, &fullFolderList](const Zstring& path) //throw FileError { traverseFolder(path, nullptr, - [&](const FolderInfo& fi ) { fullFolderList.push_back(fi.fullPath); traverse(fi.fullPath); }, - nullptr, //don't traverse into symlinks (analog to windows build) - [&](const std::wstring& errorMsg) { throw FileError(errorMsg); }); + [&](const FolderInfo& fi ) + { + fullFolderList.push_back(fi.fullPath); + traverse(fi.fullPath); //throw FileError + }, + nullptr /*don't traverse into symlinks (analog to Windows)*/); //throw FileError }; - traverse(baseDirPath_); + traverse(baseDirPath_); //throw FileError } //init diff --git a/zen/file_access.cpp b/zen/file_access.cpp index 0c28f325..d06202ba 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -31,51 +31,19 @@ namespace { -std::pair<Zstring, ItemType> getExistingPath(const Zstring& itemPath) //throw FileError +struct SysErrorCode : public zen::SysError { - try - { - return {itemPath, getItemType(itemPath)}; //throw FileError - } - catch (const FileError& e) //not existing or access error - { - const std::optional<Zstring> parentPath = getParentFolderPath(itemPath); - if (!parentPath) //device root - throw; - //else: let's dig deeper... don't bother checking Win32 codes; e.g. not existing item may have the codes: - // ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND, ERROR_INVALID_NAME, ERROR_INVALID_DRIVE, - // ERROR_NOT_READY, ERROR_INVALID_PARAMETER, ERROR_BAD_PATHNAME, ERROR_BAD_NETPATH => not reliable - - auto [existingPath, existingType] = getExistingPath(*parentPath); //throw FileError - - if (existingPath == *parentPath && existingType != ItemType::file /*obscure, but possible (and not an error)*/) - try - { - const Zstring itemName = afterLast(itemPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); - assert(!itemName.empty()); - - traverseFolder(*parentPath, - [&](const FileInfo& fi) { if (fi.itemName == itemName) throw ItemType::file; }, //case-sensitive! itemPath must be normalized! - [&](const FolderInfo& fi) { if (fi.itemName == itemName) throw ItemType::folder; }, - [&](const SymlinkInfo& si) { if (si.itemName == itemName) throw ItemType::symlink; }, - [](const std::wstring& errorMsg) { throw FileError(errorMsg); }); - } - catch (const ItemType&) //finding the item after getItemType() previously failed is exceptional - { - throw FileError(_("Temporary access error:") + L' ' + e.toString()); - } + SysErrorCode(const std::string& functionName, ErrorCode ec) : SysError(formatSystemError(functionName, ec)), errorCode(ec) {} - return {std::move(existingPath), existingType}; - } -} -} + const ErrorCode errorCode; +}; -ItemType zen::getItemType(const Zstring& itemPath) //throw FileError +ItemType getItemTypeImpl(const Zstring& itemPath) //throw SysErrorCode { struct stat itemInfo = {}; if (::lstat(itemPath.c_str(), &itemInfo) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), "lstat"); + throw SysErrorCode("lstat", errno); if (S_ISLNK(itemInfo.st_mode)) return ItemType::symlink; @@ -83,35 +51,72 @@ ItemType zen::getItemType(const Zstring& itemPath) //throw FileError return ItemType::folder; return ItemType::file; //S_ISREG || S_ISCHR || S_ISBLK || S_ISFIFO || S_ISSOCK } +} -std::optional<ItemType> zen::itemStillExists(const Zstring& itemPath) //throw FileError +ItemType zen::getItemType(const Zstring& itemPath) //throw FileError { - const auto& [existingPath, existingType] = getExistingPath(itemPath); //throw FileError - if (existingPath == itemPath) - return existingType; - else - return {}; + try + { + return getItemTypeImpl(itemPath); //throw SysErrorCode + } + catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), e.toString()); } } -bool zen::fileAvailable(const Zstring& filePath) //noexcept +std::variant<ItemType, Zstring /*last existing parent path*/> zen::getItemTypeIfExists(const Zstring& itemPath) //throw FileError { - //symbolic links (broken or not) are also treated as existing files! - struct stat fileInfo = {}; - if (::stat(filePath.c_str(), &fileInfo) == 0) //follow symlinks! - return S_ISREG(fileInfo.st_mode); - return false; + try + { + try + { + //fast check: 1. perf 2. expected by getFolderStatusNonBlocking() + return getItemTypeImpl(itemPath); //throw SysErrorCode + } + catch (const SysErrorCode& e) //let's dig deeper, but *only* if error code sounds like "not existing" + { + const std::optional<Zstring> parentPath = getParentFolderPath(itemPath); + if (!parentPath) //device root => quick access test + throw; + if (e.errorCode == ENOENT) + { + const std::variant<ItemType, Zstring /*last existing parent path*/> parentTypeOrPath = getItemTypeIfExists(*parentPath); //throw FileError + + if (const ItemType* parentType = std::get_if<ItemType>(&parentTypeOrPath)) + { + if (*parentType == ItemType::file /*obscure, but possible*/) + throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(*parentPath)))); + + const Zstring itemName = getItemName(itemPath); + assert(!itemName.empty()); + + traverseFolder(*parentPath, //throw FileError + [&](const FileInfo& fi) { if (fi.itemName == itemName) throw SysError(_("Temporary access error:") + L' ' + e.toString()); }, + [&](const FolderInfo& fi) { if (fi.itemName == itemName) throw SysError(_("Temporary access error:") + L' ' + e.toString()); }, + [&](const SymlinkInfo& si) { if (si.itemName == itemName) throw SysError(_("Temporary access error:") + L' ' + e.toString()); }); + //- case-sensitive comparison! itemPath must be normalized! + //- finding the item after getItemType() previously failed is exceptional + + return *parentPath; + } + else + return parentTypeOrPath; + } + else + throw; + } + } + catch (const SysError& e) + { + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), e.toString()); + } } -bool zen::dirAvailable(const Zstring& dirPath) //noexcept +bool zen::itemExists(const Zstring& itemPath) //throw FileError { - //symbolic links (broken or not) are also treated as existing directories! - struct stat dirInfo = {}; - if (::stat(dirPath.c_str(), &dirInfo) == 0) //follow symlinks! - return S_ISDIR(dirInfo.st_mode); - return false; + const std::variant<ItemType, Zstring /*last existing parent path*/> typeOrPath = getItemTypeIfExists(itemPath); //throw FileError + return std::get_if<ItemType>(&typeOrPath); } @@ -125,7 +130,14 @@ namespace //- folderPath does not need to exist (yet) int64_t zen::getFreeDiskSpace(const Zstring& folderPath) //throw FileError { - const auto& [existingPath, existingType] = getExistingPath(folderPath); //throw FileError + const Zstring existingPath = [&] + { + const std::variant<ItemType, Zstring /*last existing parent path*/> typeOrPath = getItemTypeIfExists(folderPath); //throw FileError + if (std::get_if<ItemType>(&typeOrPath)) + return folderPath; + else + return std::get<Zstring>(typeOrPath); + }(); try { struct statfs info = {}; @@ -165,52 +177,42 @@ Zstring zen::getTempFolderPath() //throw FileError -void zen::removeFilePlain(const Zstring& filePath) //throw FileError +namespace { - const char* functionName = "unlink"; - if (::unlink(filePath.c_str()) != 0) - { - ErrorCode ec = getLastError(); //copy before directly/indirectly making other system calls! - //begin of "regular" error reporting - std::wstring errorDescr = formatSystemError(functionName, ec); - - throw FileError(replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(filePath)), errorDescr); - } } -void zen::removeSymlinkPlain(const Zstring& linkPath) //throw FileError +void zen::removeFilePlain(const Zstring& filePath) //throw FileError { - removeFilePlain(linkPath); //throw FileError + try + { + if (::unlink(filePath.c_str()) != 0) + THROW_LAST_SYS_ERROR("unlink"); + } + catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(filePath)), e.toString()); } } + void zen::removeDirectoryPlain(const Zstring& dirPath) //throw FileError { - const char* functionName = "rmdir"; - if (::rmdir(dirPath.c_str()) != 0) + try { - ErrorCode ec = getLastError(); //copy before making other system calls! - bool symlinkExists = false; - try { symlinkExists = getItemType(dirPath) == ItemType::symlink; } /*throw FileError*/ catch (FileError&) {} //previous exception is more relevant + if (::rmdir(dirPath.c_str()) != 0) + THROW_LAST_SYS_ERROR("rmdir"); + } + catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), e.toString()); } +} - if (symlinkExists) - { - if (::unlink(dirPath.c_str()) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), "unlink"); - return; - } - //if (ec == ERROR_SHARING_VIOLATION) => getLockingProcesses() can't handle directory paths! :( RmGetList() fails with ERROR_ACCESS_DENIED - //https://blog.yaakov.online/failed-experiment-what-processes-have-a-lock-on-this-folder/ - throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), formatSystemError(functionName, ec)); +void zen::removeSymlinkPlain(const Zstring& linkPath) //throw FileError +{ + try + { + if (::unlink(linkPath.c_str()) != 0) + THROW_LAST_SYS_ERROR("unlink"); } - /* Windows: may spuriously fail with ERROR_DIR_NOT_EMPTY(145) even though all child items have - successfully been *marked* for deletion, but some application still has a handle open! - e.g. Open "C:\Test\Dir1\Dir2" (filled with lots of files) in Explorer, then delete "C:\Test\Dir1" via ::RemoveDirectory() => Error 145 - Sample code: http://us.generation-nt.com/answer/createfile-directory-handles-removing-parent-help-29126332.html - Alternatives: 1. move file/empty folder to some other location, then DeleteFile()/RemoveDirectory() - 2. use CreateFile/FILE_FLAG_DELETE_ON_CLOSE *without* FILE_SHARE_DELETE instead of DeleteFile() => early failure */ + catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot delete symbolic link %x."), L"%x", fmtPath(linkPath)), e.toString()); } } @@ -218,22 +220,23 @@ namespace { void removeDirectoryImpl(const Zstring& folderPath) //throw FileError { - std::vector<Zstring> filePaths; - std::vector<Zstring> symlinkPaths; std::vector<Zstring> folderPaths; + { + std::vector<Zstring> filePaths; + std::vector<Zstring> symlinkPaths; - //get all files and directories from current directory (WITHOUT subdirectories!) - traverseFolder(folderPath, - [&](const FileInfo& fi) { filePaths.push_back(fi.fullPath); }, - [&](const FolderInfo& fi) { folderPaths.push_back(fi.fullPath); }, //defer recursion => save stack space and allow deletion of extremely deep hierarchies! - [&](const SymlinkInfo& si) { symlinkPaths.push_back(si.fullPath); }, - [](const std::wstring& errorMsg) { throw FileError(errorMsg); }); + //get all files and directories from current directory (WITHOUT subdirectories!) + traverseFolder(folderPath, + [&](const FileInfo& fi) { filePaths.push_back(fi.fullPath); }, + [&](const FolderInfo& fi) { folderPaths.push_back(fi.fullPath); }, + [&](const SymlinkInfo& si) { symlinkPaths.push_back(si.fullPath); }); //throw FileError - for (const Zstring& filePath : filePaths) - removeFilePlain(filePath); //throw FileError + for (const Zstring& filePath : filePaths) + removeFilePlain(filePath); //throw FileError - for (const Zstring& symlinkPath : symlinkPaths) - removeSymlinkPlain(symlinkPath); //throw FileError + for (const Zstring& symlinkPath : symlinkPaths) + removeSymlinkPlain(symlinkPath); //throw FileError + } //=> save stack space and allow deletion of extremely deep hierarchies! //delete directories recursively for (const Zstring& subFolderPath : folderPaths) @@ -486,7 +489,7 @@ void zen::createDirectory(const Zstring& dirPath) //throw FileError, ErrorTarget try { //don't allow creating irregular folders! - const Zstring dirName = afterLast(dirPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); + const Zstring dirName = getItemName(dirPath); //e.g. "...." https://social.technet.microsoft.com/Forums/windows/en-US/ffee2322-bb6b-4fdf-86f9-8f93cf1fa6cb/ if (std::all_of(dirName.begin(), dirName.end(), [](Zchar c) { return c == Zstr('.'); })) @@ -505,8 +508,6 @@ void zen::createDirectory(const Zstring& dirPath) //throw FileError, ErrorTarget const int ec = errno; //copy before directly or indirectly making other system calls! if (ec == EEXIST) throw ErrorTargetExisting(replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(dirPath)), formatSystemError("mkdir", ec)); - //else if (ec == ENOENT) - // throw ErrorTargetPathMissing(errorMsg, errorDescr); THROW_LAST_SYS_ERROR("mkdir"); } } @@ -516,34 +517,43 @@ void zen::createDirectory(const Zstring& dirPath) //throw FileError, ErrorTarget void zen::createDirectoryIfMissingRecursion(const Zstring& dirPath) //throw FileError { - const std::optional<Zstring> parentPath = getParentFolderPath(dirPath); - if (!parentPath) //device root - return; + //expect that path already exists (see: versioning, base folder, log file path) => check first + const std::variant<ItemType, Zstring /*last existing parent path*/> typeOrPath = getItemTypeIfExists(dirPath); //throw FileError - try //generally expect folder already exists (see: ffs_paths.cpp) => check first + if (const ItemType* type = std::get_if<ItemType>(&typeOrPath)) { - if (getItemType(dirPath) != ItemType::file) //throw FileError - return; + if (*type == ItemType::file /*obscure, but possible*/) + throw FileError(replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(dirPath)), + replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(dirPath)))); } - catch (FileError&) {} //not yet existing or access error? let's find out... + else + { + const Zstring existingDirPath = std::get<Zstring>(typeOrPath); + assert(startsWith(dirPath, existingDirPath)); - createDirectoryIfMissingRecursion(*parentPath); //throw FileError + const ZstringView relPath = makeStringView(dirPath.begin() + existingDirPath.size(), dirPath.end()); + const std::vector<ZstringView> namesMissing = splitCpy(relPath, FILE_NAME_SEPARATOR, SplitOnEmpty::skip); + assert(!namesMissing.empty()); - try - { - createDirectory(dirPath); //throw FileError, ErrorTargetExisting - return; - } - catch (FileError&) - { - try - { - if (getItemType(dirPath) != ItemType::file) //throw FileError - return; //already existing => possible, if createDirectoryIfMissingRecursion() is run in parallel - } - catch (FileError&) {} //not yet existing or access error + Zstring dirPathNew = existingDirPath; + for (const ZstringView folderName : namesMissing) + try + { + dirPathNew = appendPath(dirPathNew, Zstring(folderName)); - throw; + createDirectory(dirPathNew); //throw FileError + } + catch (FileError&) + { + try + { + if (getItemType(dirPathNew) != ItemType::file /*obscure, but possible*/) //throw FileError + continue; //already existing => possible, if createFolderIfMissingRecursion() is run in parallel + } + catch (FileError&) {} //not yet existing or access error + + throw; + } } } diff --git a/zen/file_access.h b/zen/file_access.h index d87fcd0d..5af8b879 100644 --- a/zen/file_access.h +++ b/zen/file_access.h @@ -8,6 +8,7 @@ #define FILE_ACCESS_H_8017341345614857 #include <functional> +#include <variant> #include "file_path.h" #include "file_error.h" #include "serialize.h" //IoCallback @@ -17,10 +18,6 @@ namespace zen { //note: certain functions require COM initialization! (vista_file_op.h) -//POSITIVE existence checks; if false: 1. item not existing 2. different type 3.device access error or similar -bool fileAvailable(const Zstring& filePath); //noexcept -bool dirAvailable (const Zstring& dirPath ); // - //FAT/FAT32: "Why does the timestamp of a file *increase* by up to 2 seconds when I copy it to a USB thumb drive?" const int FAT_FILE_TIME_PRECISION_SEC = 2; //https://devblogs.microsoft.com/oldnewthing/?p=83 //https://web.archive.org/web/20141127143832/http://support.microsoft.com/kb/127830 @@ -42,7 +39,9 @@ ItemType getItemType(const Zstring& itemPath); //throw FileError //execute potentially SLOW folder traversal but distinguish error/not existing: // - all child item path parts must correspond to folder traversal // => we can conclude whether an item is *not* existing anymore by doing a *case-sensitive* name search => potentially SLOW! -std::optional<ItemType> itemStillExists(const Zstring& itemPath); //throw FileError +std::variant<ItemType, Zstring /*last existing parent path*/> getItemTypeIfExists(const Zstring& itemPath); //throw FileError + +bool itemExists(const Zstring& itemPath); //throw FileError enum class ProcSymlink { diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 7dd11a1d..910b75e7 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -199,7 +199,6 @@ FileBase::FileHandle openHandleForWrite(const Zstring& filePath) //throw FileErr const int ec = errno; //copy before making other system calls! if (ec == EEXIST) throw ErrorTargetExisting(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filePath)), formatSystemError("open", ec)); - //if (ec == ENOENT) throw ErrorTargetPathMissing(errorMsg, errorDescr); THROW_LAST_SYS_ERROR("open"); } diff --git a/zen/file_path.cpp b/zen/file_path.cpp index 73a3e923..d06ab6bd 100644 --- a/zen/file_path.cpp +++ b/zen/file_path.cpp @@ -75,6 +75,13 @@ std::optional<Zstring> zen::getParentFolderPath(const Zstring& itemPath) } +Zstring zen::getFileExtension(const ZstringView filePath) +{ + const ZstringView fileName = afterLast(filePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); + return Zstring(afterLast(fileName, Zstr('.'), IfNotFoundReturn::none)); +} + + Zstring zen::appendSeparator(Zstring path) //support rvalue references! { assert(!endsWith(path, FILE_NAME_SEPARATOR == Zstr('/') ? Zstr('\\' ) : Zstr('/' ))); @@ -116,25 +123,6 @@ Zstring zen::appendPath(const Zstring& basePath, const Zstring& relPath) } -Zstring zen::getFileExtension(const Zstring& filePath) -{ - //const Zstring fileName = afterLast(filePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); - //return afterLast(fileName, Zstr('.'), IfNotFoundReturn::none); - - auto it = zen::findLast(filePath.begin(), filePath.end(), FILE_NAME_SEPARATOR); - if (it == filePath.end()) - it = filePath.begin(); - else - ++it; - - auto it2 = zen::findLast(it, filePath.end(), Zstr('.')); - if (it2 != filePath.end()) - ++it2; - - return Zstring(it2, filePath.end()); -} - - /* https://docs.microsoft.com/de-de/windows/desktop/Intl/handling-sorting-in-your-applications Perf test: compare strings 10 mio times; 64 bit build diff --git a/zen/file_path.h b/zen/file_path.h index 85af251d..d67a49d0 100644 --- a/zen/file_path.h +++ b/zen/file_path.h @@ -22,6 +22,9 @@ struct PathComponents std::optional<PathComponents> parsePathComponents(const Zstring& itemPath); //no value on failure std::optional<Zstring> getParentFolderPath(const Zstring& itemPath); +inline Zstring getItemName(const Zstring& itemPath) { return afterLast(itemPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); } + +Zstring getFileExtension(const ZstringView filePath); Zstring appendSeparator(Zstring path); //support rvalue references! @@ -29,8 +32,6 @@ bool isValidRelPath(const Zstring& relPath); Zstring appendPath(const Zstring& basePath, const Zstring& relPath); -Zstring getFileExtension(const Zstring& filePath); - //------------------------------------------------------------------------------------------ /* Compare *local* file paths: Windows: igore case (but distinguish Unicode normalization forms!) diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index 3a6099d9..4b33d3e6 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -17,11 +17,8 @@ using namespace zen; void zen::traverseFolder(const Zstring& dirPath, const std::function<void(const FileInfo& fi)>& onFile, const std::function<void(const FolderInfo& fi)>& onFolder, - const std::function<void(const SymlinkInfo& si)>& onSymlink, - const std::function<void(const std::wstring& errorMsg)>& onError) + const std::function<void(const SymlinkInfo& si)>& onSymlink) //throw FileError { - try - { DIR* folder = ::opendir(dirPath.c_str()); //directory must NOT end with path separator, except "/" if (!folder) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), "opendir"); @@ -54,17 +51,8 @@ void zen::traverseFolder(const Zstring& dirPath, const Zstring& itemPath = appendPath(dirPath, itemName); struct stat statData = {}; - try - { if (::lstat(itemPath.c_str(), &statData) != 0) //lstat() does not resolve symlinks THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), "lstat"); - } - catch (const FileError& e) - { - if (onError) - onError(e.toString()); - continue; //ignore error: skip file - } if (S_ISLNK(statData.st_mode)) //on Linux there is no distinction between file and directory symlinks! { @@ -76,22 +64,15 @@ void zen::traverseFolder(const Zstring& dirPath, if (onFolder) onFolder({itemName, itemPath}); } - else //a file or named pipe, etc. + else //a file or named pipe, etc. S_ISREG, S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK { if (onFile) onFile({itemName, itemPath, makeUnsigned(statData.st_size), statData.st_mtime}); - } - /* It may be a good idea to not check "S_ISREG(statData.st_mode)" explicitly and to not issue an error message on other types to support these scenarios: - RTS setup watch (essentially wants to read directories only) - removeDirectory (wants to delete everything; pipes can be deleted just like files via "unlink") - However an "open" on a pipe will block (https://sourceforge.net/p/freefilesync/bugs/221/), so the copy routines need to be smarter!! */ + However an "open" on a pipe will block (https://sourceforge.net/p/freefilesync/bugs/221/), so the copy routines better be smart! */ + } } - } - catch (const FileError& e) - { - if (onError) - onError(e.toString()); - } } diff --git a/zen/file_traverser.h b/zen/file_traverser.h index 8bd32f2c..8903548d 100644 --- a/zen/file_traverser.h +++ b/zen/file_traverser.h @@ -8,6 +8,7 @@ #define FILER_TRAVERSER_H_127463214871234 #include <functional> +#include "file_error.h" #include "file_path.h" namespace zen @@ -34,12 +35,10 @@ struct SymlinkInfo }; //- non-recursive -//- directory path may end with PATH_SEPARATOR -void traverseFolder(const Zstring& dirPath, //noexcept - const std::function<void(const FileInfo& fi)>& onFile, // - const std::function<void(const FolderInfo& fi)>& onFolder, //optional - const std::function<void(const SymlinkInfo& si)>& onSymlink, // - const std::function<void(const std::wstring& errorMsg)>& onError); // +void traverseFolder(const Zstring& dirPath, + const std::function<void(const FileInfo& fi)>& onFile, /*optional*/ + const std::function<void(const FolderInfo& fi)>& onFolder,/*optional*/ + const std::function<void(const SymlinkInfo& si)>& onSymlink/*optional*/); //throw FileError } #endif //FILER_TRAVERSER_H_127463214871234 diff --git a/zen/http.cpp b/zen/http.cpp index 540b4ef6..7eb3fb76 100644 --- a/zen/http.cpp +++ b/zen/http.cpp @@ -12,7 +12,7 @@ using namespace zen; -const int HTTP_ACCESS_TIME_OUT_SEC = 20; +const int HTTP_ACCESS_TIMEOUT_SEC = 20; const size_t HTTP_BLOCK_SIZE_DOWNLOAD = 64 * 1024; //libcurl returns blocks of only 16 kB as returned by recv() even if we request larger blocks via CURLOPT_BUFFERSIZE //- InternetReadFile() is buffered + prefetching @@ -62,12 +62,12 @@ public: //caveat: INTERNET_FLAG_RELOAD issues "Pragma: no-cache" instead if "request is going through a proxy" - auto promiseHeader = std::make_shared<std::promise<std::string>>(); - std::future<std::string> futHeader = promiseHeader->get_future(); + auto promHeader = std::make_shared<std::promise<std::string>>(); + std::future<std::string> futHeader = promHeader->get_future(); auto postBytesSent = std::make_shared<std::atomic<int64_t>>(0); - worker_ = InterruptibleThread([asyncStreamOut = this->asyncStreamIn_, promiseHeader, headers = std::move(headers), postBytesSent, + worker_ = InterruptibleThread([asyncStreamOut = this->asyncStreamIn_, promHeader, headers = std::move(headers), postBytesSent, server, useTls, caCertFilePath, userAgent = utfTo<std::string>(userAgent), postBuf = postBuf ? std::optional<std::string>(*postBuf) : std::nullopt, //[!] life-time! serverRelPath = utfTo<std::string>(page)] @@ -112,7 +112,7 @@ public: if (headerLine == "\r\n") { headerReceived = true; - promiseHeader->set_value(std::move(headerBuf)); + promHeader->set_value(std::move(headerBuf)); } }; @@ -131,7 +131,7 @@ public: writeResponse /*throw ThreadStopRequest*/, readRequest, onHeaderData /*throw SysError*/, - HTTP_ACCESS_TIME_OUT_SEC); //throw SysError, ThreadStopRequest + HTTP_ACCESS_TIMEOUT_SEC); //throw SysError, ThreadStopRequest if (!headerReceived) throw SysError(L"HTTP response is missing header."); @@ -141,7 +141,7 @@ public: catch (SysError&) //let ThreadStopRequest pass through! { if (!headerReceived) - promiseHeader->set_exception(std::current_exception()); + promHeader->set_exception(std::current_exception()); asyncStreamOut->setWriteError(std::current_exception()); } diff --git a/zen/open_ssl.cpp b/zen/open_ssl.cpp index 9278b6dd..af4306b2 100644 --- a/zen/open_ssl.cpp +++ b/zen/open_ssl.cpp @@ -8,15 +8,15 @@ #include <bit> //std::endian (needed for macOS) #include "base64.h" #include "thread.h" +#include "argon2.h" +#include "serialize.h" #include <openssl/pem.h> #include <openssl/err.h> #include <openssl/ssl.h> -#if OPENSSL_VERSION_NUMBER >= 0x30000000L - #include <openssl/core_names.h> - #include <openssl/encoder.h> - #include <openssl/decoder.h> - #include <openssl/param_build.h> -#endif +#include <openssl/core_names.h> +#include <openssl/encoder.h> +#include <openssl/decoder.h> +#include <openssl/param_build.h> using namespace zen; @@ -26,7 +26,7 @@ using namespace zen; #error FFS, we are royally screwed! #endif -static_assert(OPENSSL_VERSION_NUMBER >= 0x10100000L, "OpenSSL version too old"); +static_assert(OPENSSL_VERSION_NUMBER >= 0x30000000L, "OpenSSL version too old"); void zen::openSslInit() @@ -79,7 +79,7 @@ std::wstring formatOpenSSLError(const char* functionName, unsigned long ec) std::wstring formatLastOpenSSLError(const char* functionName) { - const auto ec = ::ERR_peek_last_error(); + const auto ec = ::ERR_peek_last_error(); //"returns latest error code from the thread's error queue without modifying it" - unlike ERR_get_error() ::ERR_clear_error(); //clean up for next OpenSSL operation on this thread return formatOpenSSLError(functionName, ec); } @@ -110,13 +110,13 @@ std::shared_ptr<EVP_PKEY> generateRsaKeyPair(int bits) //throw SysError //================================================================================ -std::shared_ptr<EVP_PKEY> streamToKey(const std::string& keyStream, RsaStreamType streamType, bool publicKey) //throw SysError +std::shared_ptr<EVP_PKEY> streamToKey(const std::string_view keyStream, RsaStreamType streamType, bool publicKey) //throw SysError { switch (streamType) { case RsaStreamType::pkix: { - BIO* bio = ::BIO_new_mem_buf(keyStream.c_str(), static_cast<int>(keyStream.size())); + BIO* bio = ::BIO_new_mem_buf(keyStream.data(), static_cast<int>(keyStream.size())); if (!bio) throw SysError(formatLastOpenSSLError("BIO_new_mem_buf")); ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); @@ -134,7 +134,6 @@ std::shared_ptr<EVP_PKEY> streamToKey(const std::string& keyStream, RsaStreamTyp case RsaStreamType::pkcs1: { -#if OPENSSL_VERSION_NUMBER >= 0x30000000L EVP_PKEY* evp = nullptr; auto guardEvp = makeGuard<ScopeGuardRunMode::onExit>([&] { if (evp) ::EVP_PKEY_free(evp); }); @@ -159,46 +158,20 @@ std::shared_ptr<EVP_PKEY> streamToKey(const std::string& keyStream, RsaStreamTyp throw SysError(formatLastOpenSSLError("OSSL_DECODER_CTX_set_passphrase")); #endif - const unsigned char* keyBuf = reinterpret_cast<const unsigned char*>(keyStream.c_str()); + const unsigned char* keyBuf = reinterpret_cast<const unsigned char*>(keyStream.data()); size_t keyLen = keyStream.size(); if (::OSSL_DECODER_from_data(decCtx, &keyBuf, &keyLen) != 1) throw SysError(formatLastOpenSSLError("OSSL_DECODER_from_data")); guardEvp.dismiss(); //pass ownership return std::shared_ptr<EVP_PKEY>(evp, ::EVP_PKEY_free); // -#else - BIO* bio = ::BIO_new_mem_buf(keyStream.c_str(), static_cast<int>(keyStream.size())); - if (!bio) - throw SysError(formatLastOpenSSLError("BIO_new_mem_buf")); - ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); - - RSA* rsa = (publicKey ? - ::PEM_read_bio_RSAPublicKey : - ::PEM_read_bio_RSAPrivateKey)(bio, //BIO* bp - nullptr, //RSA** x - nullptr, //pem_password_cb* cb - nullptr); //void* u - if (!rsa) - throw SysError(formatLastOpenSSLError(publicKey ? "PEM_read_bio_RSAPublicKey" : "PEM_read_bio_RSAPrivateKey")); - ZEN_ON_SCOPE_EXIT(::RSA_free(rsa)); - - EVP_PKEY* evp = ::EVP_PKEY_new(); - if (!evp) - throw SysError(formatLastOpenSSLError("EVP_PKEY_new")); - std::shared_ptr<EVP_PKEY> sharedKey(evp, ::EVP_PKEY_free); - - if (::EVP_PKEY_set1_RSA(evp, rsa) != 1) //no ownership transfer (internally ref-counted) - throw SysError(formatLastOpenSSLError("EVP_PKEY_set1_RSA")); - - return sharedKey; -#endif } case RsaStreamType::raw: break; } - auto tmp = reinterpret_cast<const unsigned char*>(keyStream.c_str()); + auto tmp = reinterpret_cast<const unsigned char*>(keyStream.data()); EVP_PKEY* evp = (publicKey ? ::d2i_PublicKey : ::d2i_PrivateKey)(EVP_PKEY_RSA, //int type nullptr, //EVP_PKEY** a &tmp, /*changes tmp pointer itself!*/ //const unsigned char** pp @@ -255,7 +228,6 @@ std::string keyToStream(const EVP_PKEY* evp, RsaStreamType streamType, bool publ case RsaStreamType::pkcs1: { -#if OPENSSL_VERSION_NUMBER >= 0x30000000L const int selection = publicKey ? OSSL_KEYMGMT_SELECT_PUBLIC_KEY : OSSL_KEYMGMT_SELECT_PRIVATE_KEY; OSSL_ENCODER_CTX* encCtx = ::OSSL_ENCODER_CTX_new_for_pkey(evp, //const EVP_PKEY* pkey @@ -276,46 +248,6 @@ std::string keyToStream(const EVP_PKEY* evp, RsaStreamType streamType, bool publ ZEN_ON_SCOPE_EXIT(::OPENSSL_free(keyBuf)); return {reinterpret_cast<const char*>(keyBuf), keyLen}; -#else - //fix OpenSSL API inconsistencies: - auto PEM_write_bio_RSAPrivateKey2 = [](BIO* bio, const RSA* rsa) - { - return ::PEM_write_bio_RSAPrivateKey(bio, //BIO* bp - rsa, //const RSA* x - nullptr, //const EVP_CIPHER* enc - nullptr, //const unsigned char* kstr - 0, //int klen - nullptr, //pem_password_cb* cb - nullptr); //void* u - }; - auto PEM_write_bio_RSAPublicKey2 = [](BIO* bio, const RSA* rsa) { return ::PEM_write_bio_RSAPublicKey(bio, rsa); }; - - BIO* bio = ::BIO_new(BIO_s_mem()); - if (!bio) - throw SysError(formatLastOpenSSLError("BIO_new")); - ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); - - const RSA* rsa = ::EVP_PKEY_get0_RSA(evp); //unowned reference! - if (!rsa) - throw SysError(formatLastOpenSSLError("EVP_PKEY_get0_RSA")); - - if ((publicKey ? - PEM_write_bio_RSAPublicKey2 : - PEM_write_bio_RSAPrivateKey2)(bio, rsa) != 1) - throw SysError(formatLastOpenSSLError(publicKey ? "PEM_write_bio_RSAPublicKey" : "PEM_write_bio_RSAPrivateKey")); - //--------------------------------------------- - const int keyLen = BIO_pending(bio); - if (keyLen < 0) - throw SysError(formatLastOpenSSLError("BIO_pending")); - if (keyLen == 0) - throw SysError(formatSystemError("BIO_pending", L"", L"Unexpected failure.")); //no more error details - - std::string keyStream(keyLen, '\0'); - - if (::BIO_read(bio, keyStream.data(), keyLen) != keyLen) - throw SysError(formatLastOpenSSLError("BIO_read")); - return keyStream; -#endif } case RsaStreamType::raw: @@ -333,13 +265,51 @@ std::string keyToStream(const EVP_PKEY* evp, RsaStreamType streamType, bool publ //================================================================================ -std::string createSignature(const std::string& message, EVP_PKEY* privateKey) //throw SysError +std::string createHash(const std::string_view str, const EVP_MD* type) //throw SysError +{ + std::string output(EVP_MAX_MD_SIZE, '\0'); + unsigned int bytesWritten = 0; +#if 1 + //https://www.openssl.org/docs/manmaster/man3/EVP_Digest.html + if (::EVP_Digest(str.data(), //const void* data + str.size(), //size_t count + reinterpret_cast<unsigned char*>(output.data()), //unsigned char* md + &bytesWritten, //unsigned int* size + type, //const EVP_MD* type + nullptr) != 1) //ENGINE* impl + throw SysError(formatLastOpenSSLError("EVP_Digest")); +#else //streaming version + EVP_MD_CTX* mdctx = ::EVP_MD_CTX_new(); + if (!mdctx) + throw SysError(formatSystemError("EVP_MD_CTX_new", L"", L"Unexpected failure.")); //no more error details + ZEN_ON_SCOPE_EXIT(::EVP_MD_CTX_free(mdctx)); + + if (::EVP_DigestInit(mdctx, //EVP_MD_CTX* ctx + type) != 1) //const EVP_MD* type + throw SysError(formatLastOpenSSLError("EVP_DigestInit")); + + if (::EVP_DigestUpdate(mdctx, //EVP_MD_CTX* ctx + str.data(), //const void* + str.size()) != 1) //size_t cnt); + throw SysError(formatLastOpenSSLError("EVP_DigestUpdate")); + + if (::EVP_DigestFinal_ex(mdctx, //EVP_MD_CTX* ctx + reinterpret_cast<unsigned char*>(output.data()), //unsigned char* md + &bytesWritten) != 1) //unsigned int* s + throw SysError(formatLastOpenSSLError("EVP_DigestFinal_ex")); +#endif + output.resize(bytesWritten); + return output; +} + + +std::string createSignature(const std::string_view message, EVP_PKEY* privateKey) //throw SysError { //https://www.openssl.org/docs/manmaster/man3/EVP_DigestSign.html - EVP_MD_CTX* mdctx = ::EVP_MD_CTX_create(); + EVP_MD_CTX* mdctx = ::EVP_MD_CTX_new(); if (!mdctx) - throw SysError(formatSystemError("EVP_MD_CTX_create", L"", L"Unexpected failure.")); //no more error details - ZEN_ON_SCOPE_EXIT(::EVP_MD_CTX_destroy(mdctx)); + throw SysError(formatSystemError("EVP_MD_CTX_new", L"", L"Unexpected failure.")); //no more error details + ZEN_ON_SCOPE_EXIT(::EVP_MD_CTX_free(mdctx)); if (::EVP_DigestSignInit(mdctx, //EVP_MD_CTX* ctx nullptr, //EVP_PKEY_CTX** pctx @@ -349,7 +319,7 @@ std::string createSignature(const std::string& message, EVP_PKEY* privateKey) // throw SysError(formatLastOpenSSLError("EVP_DigestSignInit")); if (::EVP_DigestSignUpdate(mdctx, //EVP_MD_CTX* ctx - message.c_str(), //const void* d + message.data(), //const void* d message.size()) != 1) //size_t cnt throw SysError(formatLastOpenSSLError("EVP_DigestSignUpdate")); @@ -372,13 +342,13 @@ std::string createSignature(const std::string& message, EVP_PKEY* privateKey) // } -void verifySignature(const std::string& message, const std::string& signature, EVP_PKEY* publicKey) //throw SysError +void verifySignature(const std::string_view message, const std::string_view signature, EVP_PKEY* publicKey) //throw SysError { //https://www.openssl.org/docs/manmaster/man3/EVP_DigestVerify.html - EVP_MD_CTX* mdctx = ::EVP_MD_CTX_create(); + EVP_MD_CTX* mdctx = ::EVP_MD_CTX_new(); if (!mdctx) - throw SysError(formatSystemError("EVP_MD_CTX_create", L"", L"Unexpected failure.")); //no more error details - ZEN_ON_SCOPE_EXIT(::EVP_MD_CTX_destroy(mdctx)); + throw SysError(formatSystemError("EVP_MD_CTX_new", L"", L"Unexpected failure.")); //no more error details + ZEN_ON_SCOPE_EXIT(::EVP_MD_CTX_free(mdctx)); if (::EVP_DigestVerifyInit(mdctx, //EVP_MD_CTX* ctx nullptr, //EVP_PKEY_CTX** pctx @@ -388,19 +358,19 @@ void verifySignature(const std::string& message, const std::string& signature, E throw SysError(formatLastOpenSSLError("EVP_DigestVerifyInit")); if (::EVP_DigestVerifyUpdate(mdctx, //EVP_MD_CTX* ctx - message.c_str(), //const void* d + message.data(), //const void* d message.size()) != 1) //size_t cnt throw SysError(formatLastOpenSSLError("EVP_DigestVerifyUpdate")); - if (::EVP_DigestVerifyFinal(mdctx, //EVP_MD_CTX* ctx - reinterpret_cast<const unsigned char*>(signature.c_str()), //const unsigned char* sig - signature.size()) != 1) //size_t siglen + if (::EVP_DigestVerifyFinal(mdctx, //EVP_MD_CTX* ctx + reinterpret_cast<const unsigned char*>(signature.data()), //const unsigned char* sig + signature.size()) != 1) //size_t siglen throw SysError(formatLastOpenSSLError("EVP_DigestVerifyFinal")); } } -std::string zen::convertRsaKey(const std::string& keyStream, RsaStreamType typeFrom, RsaStreamType typeTo, bool publicKey) //throw SysError +std::string zen::convertRsaKey(const std::string_view keyStream, RsaStreamType typeFrom, RsaStreamType typeTo, bool publicKey) //throw SysError { assert(typeFrom != typeTo); std::shared_ptr<EVP_PKEY> evp = streamToKey(keyStream, typeFrom, publicKey); //throw SysError @@ -408,7 +378,7 @@ std::string zen::convertRsaKey(const std::string& keyStream, RsaStreamType typeF } -void zen::verifySignature(const std::string& message, const std::string& signature, const std::string& publicKeyStream, RsaStreamType streamType) //throw SysError +void zen::verifySignature(const std::string_view message, const std::string_view signature, const std::string_view publicKeyStream, RsaStreamType streamType) //throw SysError { std::shared_ptr<EVP_PKEY> publicKey = streamToKey(publicKeyStream, streamType, true /*publicKey*/); //throw SysError ::verifySignature(message, signature, publicKey.get()); //throw SysError @@ -417,11 +387,11 @@ void zen::verifySignature(const std::string& message, const std::string& signatu bool zen::isPuttyKeyStream(const std::string_view keyStream) { - return startsWith(trimCpy(keyStream, true, false), "PuTTY-User-Key-File-"); + return startsWith(trimCpy(keyStream, true, false), "PuTTY-User-Key-File-"); } -std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::string& passphrase) //throw SysError +std::string zen::convertPuttyKeyToPkix(const std::string_view keyStream, const std::string_view passphrase) //throw SysError { std::vector<std::string_view> lines; @@ -433,25 +403,39 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: //----------- parse PuTTY ppk structure ---------------------------------- auto itLine = lines.begin(); - if (itLine == lines.end() || !startsWith(*itLine, "PuTTY-User-Key-File-2: ")) - throw SysError(L"Unknown key file format"); - const std::string_view algorithm = afterFirst(*itLine, ' ', IfNotFoundReturn::none); - ++itLine; - if (itLine == lines.end() || !startsWith(*itLine, "Encryption: ")) + auto lineStartsWith = [&](const char* str) + { + return itLine != lines.end() && startsWith(*itLine, str); + }; + + const int ppkFormat = [&] + { + if (lineStartsWith("PuTTY-User-Key-File-2: ")) + return 2; + else if (lineStartsWith("PuTTY-User-Key-File-3: ")) + return 3; + else + throw SysError(L"Unknown key file format"); + }(); + + const std::string_view algorithm = afterFirst(*itLine++, ' ', IfNotFoundReturn::none); + + if (!lineStartsWith("Encryption: ")) + throw SysError(L"Missing key encryption"); + const std::string_view keyEncryption = afterFirst(*itLine++, ' ', IfNotFoundReturn::none); + + const bool keyEncrypted = keyEncryption == "aes256-cbc"; + if (!keyEncrypted && keyEncryption != "none") throw SysError(L"Unknown key encryption"); - const std::string_view keyEncryption = afterFirst(*itLine, ' ', IfNotFoundReturn::none); - ++itLine; - if (itLine == lines.end() || !startsWith(*itLine, "Comment: ")) - throw SysError(L"Invalid key comment"); - const std::string_view comment = afterFirst(*itLine, ' ', IfNotFoundReturn::none); - ++itLine; + if (!lineStartsWith("Comment: ")) + throw SysError(L"Missing comment"); + const std::string_view comment = afterFirst(*itLine++, ' ', IfNotFoundReturn::none); - if (itLine == lines.end() || !startsWith(*itLine, "Public-Lines: ")) - throw SysError(L"Invalid key: invalid public lines"); - size_t pubLineCount = stringTo<size_t>(afterFirst(*itLine, ' ', IfNotFoundReturn::none)); - ++itLine; + if (!lineStartsWith("Public-Lines: ")) + throw SysError(L"Missing public lines"); + size_t pubLineCount = stringTo<size_t>(afterFirst(*itLine++, ' ', IfNotFoundReturn::none)); std::string publicBlob64; while (pubLineCount-- != 0) @@ -460,10 +444,55 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: else throw SysError(L"Invalid key: incomplete public lines"); - if (itLine == lines.end() || !startsWith(*itLine, "Private-Lines: ")) - throw SysError(L"Invalid key: invalid private lines"); - size_t privLineCount = stringTo<size_t>(afterFirst(*itLine, ' ', IfNotFoundReturn::none)); - ++itLine; + Argon2Flavor argonFlavor = Argon2Flavor::d; + uint32_t argonMemory = 0; + uint32_t argonPasses = 0; + uint32_t argonParallelism = 0; + std::string argonSalt; + if (ppkFormat >= 3 && keyEncrypted) + { + if (!lineStartsWith("Key-Derivation: ")) + throw SysError(L"Missing Argon2 parameter: Key-Derivation"); + const std::string_view keyDerivation = afterFirst(*itLine++, ' ', IfNotFoundReturn::none); + + argonFlavor = [&] + { + if (keyDerivation == "Argon2d") + return Argon2Flavor::d; + else if (keyDerivation == "Argon2i") + return Argon2Flavor::i; + else if (keyDerivation == "Argon2id") + return Argon2Flavor::id; + else + throw SysError(L"Unexpected Argon2 parameter for Key-Derivation"); + }(); + + if (!lineStartsWith("Argon2-Memory: ")) + throw SysError(L"Missing Argon2 parameter: Argon2-Memory"); + argonMemory = stringTo<uint32_t>(afterFirst(*itLine++, ' ', IfNotFoundReturn::none)); + + if (!lineStartsWith("Argon2-Passes: ")) + throw SysError(L"Missing Argon2 parameter: Argon2-Passes"); + argonPasses = stringTo<uint32_t>(afterFirst(*itLine++, ' ', IfNotFoundReturn::none)); + + if (!lineStartsWith("Argon2-Parallelism: ")) + throw SysError(L"Missing Argon2 parameter: Argon2-Parallelism"); + argonParallelism = stringTo<uint32_t>(afterFirst(*itLine++, ' ', IfNotFoundReturn::none)); + + if (!lineStartsWith("Argon2-Salt: ")) + throw SysError(L"Missing Argon2 parameter: Argon2-Salt"); + const std::string_view argonSaltHex = afterFirst(*itLine++, ' ', IfNotFoundReturn::none); + + if (argonSaltHex.size() % 2 != 0 || !std::all_of(argonSaltHex.begin(), argonSaltHex.end(), isHexDigit<char>)) + throw SysError(L"Invalid Argon2 parameter: Argon2-Salt"); + + for (size_t i = 0; i < argonSaltHex.size(); i += 2) + argonSalt += unhexify(argonSaltHex[i], argonSaltHex[i + 1]); + } + + if (!lineStartsWith("Private-Lines: ")) + throw SysError(L"Missing private lines"); + size_t privLineCount = stringTo<size_t>(afterFirst(*itLine++, ' ', IfNotFoundReturn::none)); std::string privateBlob64; while (privLineCount-- != 0) @@ -472,16 +501,11 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: else throw SysError(L"Invalid key: incomplete private lines"); - if (itLine == lines.end() || !startsWith(*itLine, "Private-MAC: ")) - throw SysError(L"Invalid key: MAC missing"); - const std::string_view macHex = afterFirst(*itLine, ' ', IfNotFoundReturn::none); - ++itLine; + if (!lineStartsWith("Private-MAC: ")) + throw SysError(L"MAC missing"); //apparently "Private-Hash" is/was possible here: maybe with ppk version 1!? + const std::string_view macHex = afterFirst(*itLine++, ' ', IfNotFoundReturn::none); //----------- unpack key file elements --------------------- - const bool keyEncrypted = keyEncryption == "aes256-cbc"; - if (!keyEncrypted && keyEncryption != "none") - throw SysError(L"Unknown key encryption"); - if (macHex.size() % 2 != 0 || !std::all_of(macHex.begin(), macHex.end(), isHexDigit<char>)) throw SysError(L"Invalid key: invalid MAC"); @@ -493,6 +517,8 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: const std::string privateBlobEnc = stringDecodeBase64(privateBlob64); std::string privateBlob; + std::string macKeyFmt3; + if (!keyEncrypted) privateBlob = privateBlobEnc; else @@ -500,23 +526,40 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: if (passphrase.empty()) throw SysError(L"Passphrase required to access private key"); - const auto block1 = std::string("\0\0\0\0", 4) + passphrase; - const auto block2 = std::string("\0\0\0\1", 4) + passphrase; + const EVP_CIPHER* const cipher = EVP_aes_256_cbc(); + std::string decryptKey; + std::string iv; + if (ppkFormat >= 3) + { + decryptKey.resize(::EVP_CIPHER_get_key_length(cipher)); + iv .resize(::EVP_CIPHER_get_iv_length (cipher)); + macKeyFmt3.resize(32); + + const std::string argonBlob = zargon2(argonFlavor, argonMemory, argonPasses, argonParallelism, + static_cast<uint32_t>(decryptKey.size() + iv.size() + macKeyFmt3.size()), passphrase, argonSalt); + MemoryStreamIn streamIn(argonBlob); + readArray(streamIn, decryptKey.data(), decryptKey.size()); // + readArray(streamIn, iv .data(), iv .size()); //throw SysErrorUnexpectedEos + readArray(streamIn, macKeyFmt3.data(), macKeyFmt3.size()); // + } + else + { + decryptKey = createHash(std::string("\0\0\0\0", 4) + passphrase, EVP_sha1()) + //throw SysError + createHash(std::string("\0\0\0\1", 4) + passphrase, EVP_sha1()); // + decryptKey.resize(::EVP_CIPHER_get_key_length(cipher)); //PuTTYgen only uses first 32 bytes as key (== key length of EVP_aes_256_cbc) - unsigned char key[2 * SHA_DIGEST_LENGTH] = {}; - ::SHA1(reinterpret_cast<const unsigned char*>(block1.c_str()), block1.size(), key); //no-fail - ::SHA1(reinterpret_cast<const unsigned char*>(block2.c_str()), block2.size(), key + SHA_DIGEST_LENGTH); // + iv.assign(::EVP_CIPHER_get_iv_length(cipher), 0); //initialization vector is 16-byte-range of zeros (== default for EVP_aes_256_cbc) + } EVP_CIPHER_CTX* cipCtx = ::EVP_CIPHER_CTX_new(); if (!cipCtx) throw SysError(formatSystemError("EVP_CIPHER_CTX_new", L"", L"Unexpected failure.")); //no more error details ZEN_ON_SCOPE_EXIT(::EVP_CIPHER_CTX_free(cipCtx)); - if (::EVP_DecryptInit_ex(cipCtx, //EVP_CIPHER_CTX* ctx - EVP_aes_256_cbc(), //const EVP_CIPHER* type - nullptr, //ENGINE* impl - key, //const unsigned char* key => implied length of 256 bit! - nullptr) != 1) //const unsigned char* iv + if (::EVP_DecryptInit(cipCtx, //EVP_CIPHER_CTX* ctx + cipher, //const EVP_CIPHER* type + reinterpret_cast<const unsigned char*>(decryptKey.c_str()), //const unsigned char* key + reinterpret_cast<const unsigned char*>(iv.c_str())) != 1) //const unsigned char* iv => nullptr = 16-byte zeros for EVP_aes_256_cbc throw SysError(formatLastOpenSSLError("EVP_DecryptInit_ex")); if (::EVP_CIPHER_CTX_set_padding(cipCtx, 0 /*padding*/) != 1) @@ -527,28 +570,27 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: int decLen1 = 0; if (::EVP_DecryptUpdate(cipCtx, //EVP_CIPHER_CTX* ctx - reinterpret_cast<unsigned char*>(privateBlob.data()), //unsigned char* out + reinterpret_cast<unsigned char*>(privateBlob.data()), //unsigned char* out &decLen1, //int* outl reinterpret_cast<const unsigned char*>(privateBlobEnc.c_str()), //const unsigned char* in static_cast<int>(privateBlobEnc.size())) != 1) //int inl throw SysError(formatLastOpenSSLError("EVP_DecryptUpdate")); int decLen2 = 0; - if (::EVP_DecryptFinal_ex(cipCtx, //EVP_CIPHER_CTX* ctx - reinterpret_cast<unsigned char*>(&privateBlob[decLen1]), //unsigned char* outm - &decLen2) != 1) //int* outl + if (::EVP_DecryptFinal(cipCtx, //EVP_CIPHER_CTX* ctx + reinterpret_cast<unsigned char*>(&privateBlob[decLen1]), //unsigned char* outm + &decLen2) != 1) //int* outl throw SysError(formatLastOpenSSLError("EVP_DecryptFinal_ex")); privateBlob.resize(decLen1 + decLen2); } //----------- verify key consistency --------------------- - std::string macKeyBlob = "putty-private-key-file-mac-key"; - if (keyEncrypted) - macKeyBlob += passphrase; - - unsigned char macKey[SHA_DIGEST_LENGTH] = {}; - ::SHA1(reinterpret_cast<const unsigned char*>(macKeyBlob.c_str()), macKeyBlob.size(), macKey); //no-fail + std::string macKey; + if (ppkFormat >= 3) + macKey = macKeyFmt3; + else + macKey = createHash(std::string("putty-private-key-file-mac-key") + (keyEncrypted ? passphrase : ""), EVP_sha1()); //throw SysError auto numToBeString = [](size_t n) -> std::string { @@ -564,18 +606,17 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: numToBeString(privateBlob .size()) + privateBlob; char md[EVP_MAX_MD_SIZE] = {}; unsigned int mdLen = 0; - if (!::HMAC(EVP_sha1(), //const EVP_MD* evp_md - macKey, //const void* key - sizeof(macKey), //int key_len + if (!::HMAC(ppkFormat <= 2 ? EVP_sha1() : EVP_sha256(), //const EVP_MD* evp_md + macKey.c_str(), //const void* key + static_cast<int>(macKey.size()), //int key_len reinterpret_cast<const unsigned char*>(macData.c_str()), //const unsigned char* d - static_cast<int>(macData.size()), //int n - reinterpret_cast<unsigned char*>(md), //unsigned char* md - &mdLen)) //unsigned int* md_len + static_cast<int>(macData.size()), //int n + reinterpret_cast<unsigned char*>(md), //unsigned char* md + &mdLen)) //unsigned int* md_len throw SysError(formatSystemError("HMAC", L"", L"Unexpected failure.")); //no more error details - const bool hashValid = mac == std::string_view(md, mdLen); - if (!hashValid) - throw SysError(formatSystemError("HMAC", L"", keyEncrypted ? L"Validation failed: wrong passphrase or corrupted key" : L"Validation failed: corrupted key")); + if (mac != std::string_view(md, mdLen)) + throw SysError(keyEncrypted ? L"Wrong passphrase (or corrupted key)" : L"Validation failed: corrupted key"); //---------------------------------------------------------- auto extractString = [](auto& it, auto itEnd) @@ -663,17 +704,16 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: throw SysError(formatLastOpenSSLError("BN_mod")); //---------------------------------------------------------- -#if OPENSSL_VERSION_NUMBER >= 0x30000000L OSSL_PARAM_BLD* paramBld = ::OSSL_PARAM_BLD_new(); if (!paramBld) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_new")); ZEN_ON_SCOPE_EXIT(::OSSL_PARAM_BLD_free(paramBld)); - if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_N, n.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(n)")); - if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_E, e.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(e)")); - if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_D, d.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(d)")); - if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_FACTOR1, p.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(p)")); - if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_FACTOR2, q.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(q)")); + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_N, n.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(n)")); + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_E, e.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(e)")); + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_D, d.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(d)")); + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_FACTOR1, p.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(p)")); + if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_FACTOR2, q.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(q)")); if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_EXPONENT1, dmp1.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(dmp1)")); if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_EXPONENT2, dmq1.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(dmq1)")); if (::OSSL_PARAM_BLD_push_BN(paramBld, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, iqmp.get()) != 1) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_push_BN(iqmp)")); @@ -696,29 +736,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: if (::EVP_PKEY_fromdata(evpCtx, &evp, EVP_PKEY_KEYPAIR, sslParams) != 1) throw SysError(formatLastOpenSSLError("EVP_PKEY_fromdata")); ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evp)); -#else - RSA* rsa = ::RSA_new(); - if (!rsa) - throw SysError(formatLastOpenSSLError("RSA_new")); - ZEN_ON_SCOPE_EXIT(::RSA_free(rsa)); - - if (::RSA_set0_key(rsa, n.release(), e.release(), d.release()) != 1) //pass BIGNUM ownership - throw SysError(formatLastOpenSSLError("RSA_set0_key")); - - if (::RSA_set0_factors(rsa, p.release(), q.release()) != 1) - throw SysError(formatLastOpenSSLError("RSA_set0_factors")); - - if (::RSA_set0_crt_params(rsa, dmp1.release(), dmq1.release(), iqmp.release()) != 1) - throw SysError(formatLastOpenSSLError("RSA_set0_crt_params")); - - EVP_PKEY* evp = ::EVP_PKEY_new(); - if (!evp) - throw SysError(formatLastOpenSSLError("EVP_PKEY_new")); - ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evp)); - if (::EVP_PKEY_set1_RSA(evp, rsa) != 1) //no ownership transfer (internally ref-counted) - throw SysError(formatLastOpenSSLError("EVP_PKEY_set1_RSA")); -#endif return keyToStream(evp, RsaStreamType::pkix, false /*publicKey*/); //throw SysError } //---------------------------------------------------------- @@ -730,7 +748,6 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: std::unique_ptr<BIGNUM, BnFree> pub = extractBigNumPub (); // std::unique_ptr<BIGNUM, BnFree> pri = extractBigNumPriv(); // //---------------------------------------------------------- -#if OPENSSL_VERSION_NUMBER >= 0x30000000L OSSL_PARAM_BLD* paramBld = ::OSSL_PARAM_BLD_new(); if (!paramBld) throw SysError(formatLastOpenSSLError("OSSL_PARAM_BLD_new")); @@ -760,26 +777,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: if (::EVP_PKEY_fromdata(evpCtx, &evp, EVP_PKEY_KEYPAIR, sslParams) != 1) throw SysError(formatLastOpenSSLError("EVP_PKEY_fromdata")); ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evp)); -#else - DSA* dsa = ::DSA_new(); - if (!dsa) - throw SysError(formatLastOpenSSLError("DSA_new")); - ZEN_ON_SCOPE_EXIT(::DSA_free(dsa)); - - if (::DSA_set0_pqg(dsa, p.release(), q.release(), g.release()) != 1) //pass BIGNUM ownership - throw SysError(formatLastOpenSSLError("DSA_set0_pqg")); - - if (::DSA_set0_key(dsa, pub.release(), pri.release()) != 1) - throw SysError(formatLastOpenSSLError("DSA_set0_key")); - - EVP_PKEY* evp = ::EVP_PKEY_new(); - if (!evp) - throw SysError(formatLastOpenSSLError("EVP_PKEY_new")); - ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evp)); - if (::EVP_PKEY_set1_DSA(evp, dsa) != 1) //no ownership transfer (internally ref-counted) - throw SysError(formatLastOpenSSLError("EVP_PKEY_set1_DSA")); -#endif return keyToStream(evp, RsaStreamType::pkix, false /*publicKey*/); //throw SysError } //---------------------------------------------------------- @@ -794,7 +792,6 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: const std::string pointStream = extractStringPub(); std::unique_ptr<BIGNUM, BnFree> pri = extractBigNumPriv(); //throw SysError //---------------------------------------------------------- -#if OPENSSL_VERSION_NUMBER >= 0x30000000L const char* groupName = [&] { if (algoShort == "nistp256") @@ -838,53 +835,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: if (::EVP_PKEY_fromdata(evpCtx, &evp, EVP_PKEY_KEYPAIR, sslParams) != 1) throw SysError(formatLastOpenSSLError("EVP_PKEY_fromdata")); ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evp)); -#else - const int curveNid = [&] - { - if (algoShort == "nistp256") - return NID_X9_62_prime256v1; //same as SECG secp256r1 - if (algoShort == "nistp384") - return NID_secp384r1; - if (algoShort == "nistp521") - return NID_secp521r1; - throw SysError(L"Unknown elliptic curve: " + utfTo<std::wstring>(algorithm)); - }(); - - EC_KEY* ecKey = ::EC_KEY_new_by_curve_name(curveNid); - if (!ecKey) - throw SysError(formatLastOpenSSLError("EC_KEY_new_by_curve_name")); - ZEN_ON_SCOPE_EXIT(::EC_KEY_free(ecKey)); - - const EC_GROUP* ecGroup = ::EC_KEY_get0_group(ecKey); - if (!ecGroup) - throw SysError(formatLastOpenSSLError("EC_KEY_get0_group")); - - EC_POINT* ecPoint = ::EC_POINT_new(ecGroup); - if (!ecPoint) - throw SysError(formatLastOpenSSLError("EC_POINT_new")); - ZEN_ON_SCOPE_EXIT(::EC_POINT_free(ecPoint)); - - if (::EC_POINT_oct2point(ecGroup, //const EC_GROUP* group - ecPoint, //EC_POINT* p - reinterpret_cast<const unsigned char*>(pointStream.c_str()), //const unsigned char* buf - pointStream.size(), //size_t len - nullptr) != 1) //BN_CTX* ctx - throw SysError(formatLastOpenSSLError("EC_POINT_oct2point")); - - if (::EC_KEY_set_public_key(ecKey, ecPoint) != 1) //no ownership transfer (internally ref-counted) - throw SysError(formatLastOpenSSLError("EC_KEY_set_public_key")); - - if (::EC_KEY_set_private_key(ecKey, pri.get()) != 1) //no ownership transfer (internally ref-counted) - throw SysError(formatLastOpenSSLError("EC_KEY_set_private_key")); - - EVP_PKEY* evp = ::EVP_PKEY_new(); - if (!evp) - throw SysError(formatLastOpenSSLError("EVP_PKEY_new")); - ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evp)); - if (::EVP_PKEY_set1_EC_KEY(evp, ecKey) != 1) //no ownership transfer (internally ref-counted) - throw SysError(formatLastOpenSSLError("EVP_PKEY_set1_EC_KEY")); -#endif return keyToStream(evp, RsaStreamType::pkix, false /*publicKey*/); //throw SysError } //---------------------------------------------------------- @@ -904,5 +855,17 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: return keyToStream(evpPriv, RsaStreamType::pkix, false /*publicKey*/); //throw SysError } else - throw SysError(L"Unknown key algorithm: " + utfTo<std::wstring>(algorithm)); + throw SysError(L"Unsupported key algorithm: " + utfTo<std::wstring>(algorithm)); + /* PuTTYgen supports many more (which are not yet supported by libssh2): + - rsa-sha2-256 + - rsa-sha2-512 + - ssh-ed448 + - ssh-dss-cert-v01@openssh.com + - ssh-rsa-cert-v01@openssh.com + - rsa-sha2-256-cert-v01@openssh.com + - rsa-sha2-512-cert-v01@openssh.com + - ssh-ed25519-cert-v01@openssh.com + - ecdsa-sha2-nistp256-cert-v01@openssh.com + - ecdsa-sha2-nistp384-cert-v01@openssh.com + - ecdsa-sha2-nistp521-cert-v01@openssh.com */ } diff --git a/zen/open_ssl.h b/zen/open_ssl.h index 1a28f068..96e25ffc 100644 --- a/zen/open_ssl.h +++ b/zen/open_ssl.h @@ -25,16 +25,16 @@ enum class RsaStreamType }; //verify signatures produced with: "openssl dgst -sha256 -sign private.pem -out file.sig file.txt" -void verifySignature(const std::string& message, - const std::string& signature, - const std::string& publicKeyStream, +void verifySignature(const std::string_view message, + const std::string_view signature, + const std::string_view publicKeyStream, RsaStreamType streamType); //throw SysError -std::string convertRsaKey(const std::string& keyStream, RsaStreamType typeFrom, RsaStreamType typeTo, bool publicKey); //throw SysError +std::string convertRsaKey(const std::string_view keyStream, RsaStreamType typeFrom, RsaStreamType typeTo, bool publicKey); //throw SysError bool isPuttyKeyStream(const std::string_view keyStream); -std::string convertPuttyKeyToPkix(const std::string& keyStream, const std::string& passphrase); //throw SysError +std::string convertPuttyKeyToPkix(const std::string_view keyStream, const std::string_view passphrase); //throw SysError } #endif //OPEN_SSL_H_801974580936508934568792347506 diff --git a/zen/process_exec.cpp b/zen/process_exec.cpp index ffc90b4f..89df9f8b 100644 --- a/zen/process_exec.cpp +++ b/zen/process_exec.cpp @@ -154,7 +154,7 @@ std::pair<int /*exit code*/, std::string> processExecuteImpl(const Zstring& file THROW_LAST_SYS_ERROR("fcntl(F_SETFL, O_NONBLOCK)"); - const auto endTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(*timeoutMs); + const auto stopTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(*timeoutMs); for (;;) //EINTR handling? => allow interruption!? { //read until EAGAIN @@ -172,18 +172,18 @@ std::pair<int /*exit code*/, std::string> processExecuteImpl(const Zstring& file //wait for stream input const auto now = std::chrono::steady_clock::now(); - if (now > endTime) + if (now > stopTime) throw SysErrorTimeOut(_P("Operation timed out after 1 second.", "Operation timed out after %x seconds.", *timeoutMs / 1000)); - const auto waitTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - now).count(); + const auto waitTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - now).count(); timeval tv{.tv_sec = static_cast<long>(waitTimeMs / 1000)}; tv.tv_usec = static_cast<long>(waitTimeMs - tv.tv_sec * 1000) * 1000; - fd_set rfd = {}; //includes FD_ZERO + fd_set rfd{}; //includes FD_ZERO FD_SET(fdLifeSignR, &rfd); - if (const int rv = ::select(fdLifeSignR + 1, //int nfds + if (const int rv = ::select(fdLifeSignR + 1, //int nfds = "highest-numbered file descriptor in any of the three sets, plus 1" &rfd, //fd_set* readfds nullptr, //fd_set* writefds nullptr, //fd_set* exceptfds diff --git a/zen/socket.h b/zen/socket.h index 8e92b616..df8b768b 100644 --- a/zen/socket.h +++ b/zen/socket.h @@ -13,7 +13,6 @@ #include <netdb.h> //getaddrinfo - namespace zen { #define THROW_LAST_SYS_ERROR_WSA(functionName) \ @@ -26,14 +25,23 @@ const SocketType invalidSocket = -1; inline void closeSocket(SocketType s) { ::close(s); } warn_static("log on error!") +void setNonBlocking(SocketType socket, bool value); //throw SysError + //Winsock needs to be initialized before calling any of these functions! (WSAStartup/WSACleanup) + + class Socket //throw SysError { public: - Socket(const Zstring& server, const Zstring& serviceName) //throw SysError + Socket(const Zstring& server, const Zstring& serviceName, int timeoutSec) //throw SysError { + //GetAddrInfo(): "If the pNodeName parameter contains an empty string, all registered addresses on the local computer are returned." + // "If the pNodeName parameter points to a string equal to "localhost", all loopback addresses on the local computer are returned." + if (trimCpy(server).empty()) + throw SysError(_("Server name must not be empty.")); + const addrinfo hints { .ai_flags = AI_ADDRCONFIG, //save a AAAA lookup on machines that can't use the returned data anyhow @@ -49,23 +57,62 @@ public: if (!servinfo) throw SysError(formatSystemError("getaddrinfo", L"", L"Empty server info.")); - const auto getConnectedSocket = [](const auto& /*::addrinfo*/ ai) + const auto getConnectedSocket = [timeoutSec](const auto& /*addrinfo*/ ai) { SocketType testSocket = ::socket(ai.ai_family, //int socket_family - SOCK_CLOEXEC | + SOCK_CLOEXEC | SOCK_NONBLOCK | ai.ai_socktype, //int socket_type ai.ai_protocol); //int protocol if (testSocket == invalidSocket) THROW_LAST_SYS_ERROR_WSA("socket"); ZEN_ON_SCOPE_FAIL(closeSocket(testSocket)); - warn_static("support timeout! https://stackoverflow.com/questions/2597608/c-socket-connection-timeout") - if (::connect(testSocket, ai.ai_addr, static_cast<int>(ai.ai_addrlen)) != 0) - THROW_LAST_SYS_ERROR_WSA("connect"); + + if (::connect(testSocket, ai.ai_addr, static_cast<int>(ai.ai_addrlen)) != 0) //0 or SOCKET_ERROR(-1) + { + if (errno != EINPROGRESS) + THROW_LAST_SYS_ERROR_WSA("connect"); + + fd_set writefds{}; + fd_set exceptfds{}; //mostly only relevant for connect() + FD_SET(testSocket, &writefds); + FD_SET(testSocket, &exceptfds); + + /*const*/ timeval tv{.tv_sec = timeoutSec}; + + const int rv = ::select( + testSocket + 1, //int nfds = "highest-numbered file descriptor in any of the three sets, plus 1" + nullptr, //fd_set* readfds + &writefds, //fd_set* writefds + &exceptfds, //fd_set* exceptfds + &tv); //const timeval* timeout + if (rv < 0) + THROW_LAST_SYS_ERROR_WSA("select"); + + if (rv == 0) //time-out! + throw SysError(formatSystemError("select, " + utfTo<std::string>(_P("1 sec", "%x sec", timeoutSec)), ETIMEDOUT)); + int error = 0; + socklen_t optLen = sizeof(error); + if (::getsockopt(testSocket, //[in] SOCKET s + SOL_SOCKET, //[in] int level + SO_ERROR, //[in] int optname + reinterpret_cast<char*>(&error), //[out] char* optval + &optLen) //[in, out] socklen_t* optlen + != 0) + THROW_LAST_SYS_ERROR_WSA("getsockopt(SO_ERROR)"); + + if (error != 0) + throw SysError(formatSystemError("connect, SO_ERROR", static_cast<ErrorCode>(error))/*== system error code, apparently!?*/); + } + + setNonBlocking(testSocket, false); //throw SysError return testSocket; }; + /* getAddrInfo() often returns only one ai_family == AF_INET address, but more items are possible: + facebook.com: 1 x AF_INET6, 3 x AF_INET + microsoft.com: 5 x AF_INET => server not allowing connection: hanging for 5x timeoutSec :( */ std::optional<SysError> firstError; for (const auto* /*::addrinfo*/ si = servinfo; si; si = si->ai_next) try @@ -152,6 +199,23 @@ void shutdownSocketSend(SocketType socket) //throw SysError if (::shutdown(socket, SHUT_WR) != 0) THROW_LAST_SYS_ERROR_WSA("shutdown"); } + + +inline +void setNonBlocking(SocketType socket, bool nonBlocking) //throw SysError +{ + int flags = ::fcntl(socket, F_GETFL); + if (flags == -1) + THROW_LAST_SYS_ERROR("fcntl(F_GETFL)"); + + if (nonBlocking) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + + if (::fcntl(socket, F_SETFL, flags) != 0) + THROW_LAST_SYS_ERROR(nonBlocking ? "fcntl(F_SETFL, O_NONBLOCK)" : "fcntl(F_SETFL, ~O_NONBLOCK)"); +} } #endif //SOCKET_H_23498325972583947678456437 diff --git a/zen/stl_tools.h b/zen/stl_tools.h index bb005e34..03e7901a 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -251,7 +251,7 @@ RandomAccessIterator1 searchFirst(const RandomAccessIterator1 first, const const RandomAccessIterator2 needleFirst, const RandomAccessIterator2 needleLast, IsEq isEqual) { if (needleLast - needleFirst == 1) //don't use expensive std::search unless required! - return std::find_if(first, last, [needleFirst, isEqual](const auto c){ return isEqual(*needleFirst, c); }); + return std::find_if(first, last, [needleFirst, isEqual](const auto c) { return isEqual(*needleFirst, c); }); return std::search(first, last, needleFirst, needleLast, isEqual); @@ -335,7 +335,7 @@ class FNV1aHash //FNV-1a: https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%8 { public: FNV1aHash() {} - explicit FNV1aHash(Num startVal) : hashVal_(startVal) {} + explicit FNV1aHash(Num startVal) : hashVal_(startVal) { assert(startVal != 0); /*(yes, might be a real hash, but) most likely bad init value*/} void add(Num n) { diff --git a/zen/string_base.h b/zen/string_base.h index 676d7f7f..98544ab3 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -586,7 +586,7 @@ void Zbase<Char, SP>::reserve(size_t minCapacity) //make unshared and check capa //allocate a new string const size_t len = size(); Char* newStr = this->create(len, std::max(len, minCapacity)); //reserve() must NEVER shrink the string: logical const! - *std::copy(rawStr_, rawStr_ + len , newStr) = 0; + *std::copy(rawStr_, rawStr_ + len, newStr) = 0; this->destroy(rawStr_); rawStr_ = newStr; diff --git a/zen/string_tools.h b/zen/string_tools.h index 3975cc92..ca086efd 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -53,7 +53,6 @@ struct StringHashAsciiNoCase; struct StringEqualAsciiNoCase; template <class Num, class S> Num hashString(const S& str); -template <class Num, class S> Num appendHashString(Num hashVal, const S& str); enum class IfNotFoundReturn { @@ -315,7 +314,7 @@ bool contains(const S& str, const T& term) const auto* const strFirst = strBegin(str); const auto* const strLast = strFirst + strLen; const auto* const termFirst = strBegin(term); - + return searchFirst(strFirst, strLast, termFirst, termFirst + termLen) != strLast; } @@ -331,7 +330,7 @@ S afterLast(const S& str, const T& term, IfNotFoundReturn infr) const auto* const strFirst = strBegin(str); const auto* const strLast = strFirst + strLength(str); const auto* const termFirst = strBegin(term); - + const auto* it = searchLast(strFirst, strLast, termFirst, termFirst + termLen); if (it == strLast) @@ -348,7 +347,7 @@ S beforeLast(const S& str, const T& term, IfNotFoundReturn infr) static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t termLen = strLength(term); assert(termLen > 0); - + const auto* const strFirst = strBegin(str); const auto* const strLast = strFirst + strLength(str); const auto* const termFirst = strBegin(term); @@ -372,7 +371,7 @@ S afterFirst(const S& str, const T& term, IfNotFoundReturn infr) const auto* const strFirst = strBegin(str); const auto* const strLast = strFirst + strLength(str); const auto* const termFirst = strBegin(term); - + const auto* it = searchFirst(strFirst, strLast, termFirst, termFirst + termLen); if (it == strLast) @@ -393,7 +392,7 @@ S beforeFirst(const S& str, const T& term, IfNotFoundReturn infr) const auto* const strFirst = strBegin(str); const auto* const strLast = strFirst + strLength(str); const auto* const termFirst = strBegin(term); - + auto it = searchFirst(strFirst, strLast, termFirst, termFirst + termLen); if (it == strLast) @@ -642,7 +641,7 @@ S printNumber(const T& format, const Num& number) //format a single number using #error refactor? #endif static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); - assert(strBegin(format)[strLength(format)] == 0); //format must be null-terminated! + assert(strBegin(format)[strLength(format)] == 0); //format must be null-terminated! S buf(128, static_cast<GetCharTypeT<S>>('0')); const int charsWritten = impl::saferPrintf(buf.data(), buf.size(), strBegin(format), number); diff --git a/zen/sys_info.cpp b/zen/sys_info.cpp index df0e4612..244343f2 100644 --- a/zen/sys_info.cpp +++ b/zen/sys_info.cpp @@ -48,7 +48,7 @@ Zstring zen::getLoginUser() //throw FileError &pwEntry); //struct passwd** result rv != 0 || !pwEntry) { - //"If an error occurs, errno is set appropriately" => why the fuck, then also return errno as return value!? + //"If an error occurs, errno is set appropriately" => why the fuck, then, also return errno as return value!? errno = rv != 0 ? rv : ENOENT; THROW_LAST_FILE_ERROR(_("Cannot get process information."), "getpwuid_r(" + numberTo<std::string>(userIdNo) + ')'); } @@ -65,9 +65,9 @@ Zstring zen::getLoginUser() //throw FileError //BUT: getlogin() can fail with ENOENT on Linux Mint: https://freefilesync.org/forum/viewtopic.php?t=8181 //getting a little desperate: variables used by installer.sh - if (const char* userName = tryGetNonRootUser("USER")) return userName; - if (const char* userName = tryGetNonRootUser("SUDO_USER")) return userName; - if (const char* userName = tryGetNonRootUser("LOGNAME")) return userName; + if (const char* username = tryGetNonRootUser("USER")) return username; + if (const char* username = tryGetNonRootUser("SUDO_USER")) return username; + if (const char* username = tryGetNonRootUser("LOGNAME")) return username; //apparently the current user really IS root: https://freefilesync.org/forum/viewtopic.php?t=8405 @@ -78,7 +78,7 @@ Zstring zen::getLoginUser() //throw FileError Zstring zen::getUserDescription() //throw FileError { - const Zstring userName = getLoginUser(); //throw FileError + const Zstring username = getLoginUser(); //throw FileError const Zstring computerName = []() -> Zstring //throw FileError { std::vector<char> buf(10000); @@ -92,10 +92,10 @@ Zstring zen::getUserDescription() //throw FileError return hostName; }(); - if (contains(getUpperCase(computerName), getUpperCase(userName))) - return userName; //no need for text duplication! e.g. "Zenju (Zenju-PC)" + if (contains(getUpperCase(computerName), getUpperCase(username))) + return username; //no need for text duplication! e.g. "Zenju (Zenju-PC)" - return userName + Zstr(" (") + computerName + Zstr(')'); //e.g. "Admin (Zenju-PC)" + return username + Zstr(" (") + computerName + Zstr(')'); //e.g. "Admin (Zenju-PC)" } @@ -118,7 +118,7 @@ ComputerModel zen::getComputerModel() //throw FileError } catch (FileError&) { - if (!itemStillExists(filePath)) //throw FileError + if (!itemExists(filePath)) //throw FileError return std::wstring(); throw; diff --git a/zen/thread.h b/zen/thread.h index 1823eefc..25a6463a 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -161,7 +161,7 @@ public: ~ThreadGroup() { for (InterruptibleThread& w : worker_) - w.requestStop(); //stop *all* at the same time before join! + w.requestStop(); //similar, but not the same as ~InterruptibleThread: stop *all* at the same time before join! if (detach_) //detach() without requestStop() doesn't make sense for (InterruptibleThread& w : worker_) @@ -190,13 +190,13 @@ public: void wait() { //perf: no difference in xBRZ test case compared to std::condition_variable-based implementation - auto promiseDone = std::make_shared<std::promise<void>>(); // - std::future<void> allDone = promiseDone->get_future(); + auto promDone = std::make_shared<std::promise<void>>(); // + std::future<void> futDone = promDone->get_future(); - notifyWhenDone([promiseDone] { promiseDone->set_value(); }); //std::function doesn't support construction involving move-only types! + notifyWhenDone([promDone] { promDone->set_value(); }); //std::function doesn't support construction involving move-only types! //use reference? => potential lifetime issue, e.g. promise object theoretically might be accessed inside set_value() after future gets signalled - allDone.get(); + futDone.get(); } //non-blocking wait()-alternative: context of controlling thread: diff --git a/zen/zstring.cpp b/zen/zstring.cpp index 59e90956..019973e9 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -22,11 +22,17 @@ Zstring getUnicodeNormalForm_NonAsciiValidUtf(const Zstring& str, UnicodeNormalF try { - gchar* outStr = ::g_utf8_normalize(str.c_str(), str.length(), form == UnicodeNormalForm::nfc ? G_NORMALIZE_NFC : G_NORMALIZE_NFD); - if (!outStr) + gchar* strNorm = ::g_utf8_normalize(str.c_str(), str.length(), form == UnicodeNormalForm::nfc ? G_NORMALIZE_NFC : G_NORMALIZE_NFD); + if (!strNorm) throw SysError(formatSystemError("g_utf8_normalize", L"", L"Conversion failed.")); - ZEN_ON_SCOPE_EXIT(::g_free(outStr)); - return outStr; + ZEN_ON_SCOPE_EXIT(::g_free(strNorm)); + + const std::string_view strNormView(strNorm, strLength(strNorm)); + + if (equalString(str, strNormView)) //avoid extra memory allocation + return str; + + return Zstring(strNormView); } catch (const SysError& e) @@ -37,7 +43,7 @@ Zstring getUnicodeNormalForm_NonAsciiValidUtf(const Zstring& str, UnicodeNormalF } -Zstring getUnicodeNormalFormNonAscii(const Zstring& str, UnicodeNormalForm form) +Zstring getValidUtf(const Zstring& str) { /* 1. do NOT fail on broken UTF encoding, instead normalize using REPLACEMENT_CHAR! 2. NormalizeString() haateeez them Unicode non-characters: ERROR_NO_UNICODE_TRANSLATION! http://www.unicode.org/faq/private_use.html#nonchar1 @@ -68,10 +74,10 @@ Zstring getUnicodeNormalFormNonAscii(const Zstring& str, UnicodeNormalForm form) codePointToUtf<Zchar>(*cp, [&](Zchar ch) { validStr += ch; }); } - return getUnicodeNormalForm_NonAsciiValidUtf(validStr, form); + return validStr; } else - return getUnicodeNormalForm_NonAsciiValidUtf(str, form); + return str; } @@ -88,9 +94,11 @@ Zstring getUpperCaseAscii(const Zstring& str) Zstring getUpperCaseNonAscii(const Zstring& str) { - Zstring strNorm = getUnicodeNormalFormNonAscii(str, UnicodeNormalForm::native); + const Zstring& strValidUtf = getValidUtf(str); try { + const Zstring strNorm = getUnicodeNormalForm_NonAsciiValidUtf(strValidUtf, UnicodeNormalForm::native); + Zstring output; output.reserve(strNorm.size()); @@ -118,7 +126,7 @@ Zstring getUnicodeNormalForm(const Zstring& str, UnicodeNormalForm form) if (isAsciiString(str)) //fast path: in the range of 3.5ns return str; - return getUnicodeNormalFormNonAscii(str, form); //slow path + return getUnicodeNormalForm_NonAsciiValidUtf(getValidUtf(str), form); //slow path } |