summaryrefslogtreecommitdiff
path: root/zen
diff options
context:
space:
mode:
Diffstat (limited to 'zen')
-rw-r--r--zen/dir_watcher.cpp13
-rw-r--r--zen/file_access.cpp262
-rw-r--r--zen/file_access.h9
-rw-r--r--zen/file_io.cpp1
-rw-r--r--zen/file_path.cpp26
-rw-r--r--zen/file_path.h5
-rw-r--r--zen/file_traverser.cpp27
-rw-r--r--zen/file_traverser.h11
-rw-r--r--zen/http.cpp14
-rw-r--r--zen/open_ssl.cpp461
-rw-r--r--zen/open_ssl.h10
-rw-r--r--zen/process_exec.cpp10
-rw-r--r--zen/socket.h78
-rw-r--r--zen/stl_tools.h4
-rw-r--r--zen/string_base.h2
-rw-r--r--zen/string_tools.h13
-rw-r--r--zen/sys_info.cpp18
-rw-r--r--zen/thread.h10
-rw-r--r--zen/zstring.cpp26
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
}
bgstack15