diff options
author | Daniel Wilhelm <shieldwed@outlook.com> | 2019-10-28 23:21:40 +0000 |
---|---|---|
committer | Daniel Wilhelm <shieldwed@outlook.com> | 2019-10-28 23:21:40 +0000 |
commit | 8d6d4e48a61fd974c3fb2a85254f9bedd796a2b7 (patch) | |
tree | 65292208a81994782e1c16dd84dfcdcc221d0cd7 /zen | |
parent | Merge branch '10.16' into 'master' (diff) | |
parent | add upstream 10.17 (diff) | |
download | FreeFileSync-8d6d4e48a61fd974c3fb2a85254f9bedd796a2b7.tar.gz FreeFileSync-8d6d4e48a61fd974c3fb2a85254f9bedd796a2b7.tar.bz2 FreeFileSync-8d6d4e48a61fd974c3fb2a85254f9bedd796a2b7.zip |
Merge branch '10.17' into 'master'10.17
10.17
See merge request opensource-tracking/FreeFileSync!14
Diffstat (limited to 'zen')
-rw-r--r-- | zen/basic_math.h | 3 | ||||
-rw-r--r-- | zen/build_info.h | 22 | ||||
-rw-r--r-- | zen/file_access.cpp | 4 | ||||
-rw-r--r-- | zen/i18n.h | 5 | ||||
-rw-r--r-- | zen/json.h | 4 | ||||
-rw-r--r-- | zen/legacy_compiler.cpp | 32 | ||||
-rw-r--r-- | zen/legacy_compiler.h | 10 | ||||
-rw-r--r-- | zen/open_ssl.cpp | 458 | ||||
-rw-r--r-- | zen/open_ssl.h | 10 | ||||
-rw-r--r-- | zen/shutdown.cpp | 5 | ||||
-rw-r--r-- | zen/socket.h | 2 | ||||
-rw-r--r-- | zen/stl_tools.h | 1 | ||||
-rw-r--r-- | zen/string_base.h | 11 | ||||
-rw-r--r-- | zen/string_tools.h | 64 | ||||
-rw-r--r-- | zen/string_traits.h | 67 | ||||
-rw-r--r-- | zen/sys_error.h | 4 | ||||
-rw-r--r-- | zen/thread.h | 6 | ||||
-rw-r--r-- | zen/time.h | 2 | ||||
-rw-r--r-- | zen/warn_static.h | 13 | ||||
-rw-r--r-- | zen/zlib_wrap.cpp | 4 | ||||
-rw-r--r-- | zen/zlib_wrap.h | 6 | ||||
-rw-r--r-- | zen/zstring.cpp | 2 |
22 files changed, 615 insertions, 120 deletions
diff --git a/zen/basic_math.h b/zen/basic_math.h index b9be28be..8a32ee69 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -59,6 +59,9 @@ const double e = 2.71828182845904523536; const double sqrt2 = 1.41421356237309504880; const double ln2 = 0.693147180559945309417; +#if __cpp_lib_math_constants //C++20 + #error implement math constants from <numbers> header +#endif //static_assert(pi + e + sqrt2 + ln2 == 7.9672352249818781, "whoopsie"); //---------------------------------------------------------------------------------- diff --git a/zen/build_info.h b/zen/build_info.h index e80f3721..01f1aeb8 100644 --- a/zen/build_info.h +++ b/zen/build_info.h @@ -7,20 +7,24 @@ #ifndef BUILD_INFO_H_5928539285603428657 #define BUILD_INFO_H_5928539285603428657 -//determine build info: defines ZEN_BUILD_32BIT or ZEN_BUILD_64BIT + #include <bit> //std::endian + +#define ZEN_ARCH_32BIT 32 +#define ZEN_ARCH_64BIT 64 #ifdef __LP64__ - #define ZEN_BUILD_64BIT + #define ZEN_BUILD_ARCH ZEN_ARCH_64BIT #else - #define ZEN_BUILD_32BIT + #define ZEN_BUILD_ARCH ZEN_ARCH_32BIT #endif -#ifdef ZEN_BUILD_32BIT - static_assert(sizeof(void*) == 4); -#endif +static_assert(ZEN_BUILD_ARCH == sizeof(void*) * 8); + +//-------------------------------------------------------------------- -#ifdef ZEN_BUILD_64BIT - static_assert(sizeof(void*) == 8); -#endif +constexpr bool usingLittleEndian() +{ + return std::endian::native == std::endian::little; +} #endif //BUILD_INFO_H_5928539285603428657 diff --git a/zen/file_access.cpp b/zen/file_access.cpp index 483d4b01..e23d48be 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -138,7 +138,7 @@ std::optional<ItemType> zen::itemStillExists(const Zstring& itemPath) //throw Fi } catch (const ItemType&) //finding the item after getItemType() previously failed is exceptional { - throw e; //yes, slicing + throw FileError(_("Temporary access error:") + L' ' + e.toString()); } return {}; } @@ -577,7 +577,7 @@ void zen::copySymlink(const Zstring& sourcePath, const Zstring& targetPath, bool if (::symlink(linkPath.c_str(), targetPath.c_str()) != 0) THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L"\n" + fmtPath(sourcePath)), L"%y", L"\n" + fmtPath(targetPath)), L"symlink"); - //allow only consistent objects to be created -> don't place before ::symlink, targetPath may already exist! + //allow only consistent objects to be created -> don't place before ::symlink(); targetPath may already exist! ZEN_ON_SCOPE_FAIL(try { removeSymlinkPlain(targetPath); /*throw FileError*/ } catch (FileError&) {}); @@ -22,6 +22,8 @@ //source and translation are required to use %x as number placeholder //for plural form, which will be substituted automatically!!! + static_assert(WXINTL_NO_GETTEXT_MACRO, "...must be defined to deactivate wxWidgets underscore macro"); + namespace zen { //implement handler to enable program-wide localizations: @@ -52,9 +54,6 @@ std::shared_ptr<const TranslationHandler> getTranslator(); - - - //######################## implementation ############################## namespace impl { @@ -403,7 +403,7 @@ public: for (auto it = pos_; it != stream_.begin(); ) { --it; - if (*it == '\r' || *it == '\n') + if (isLineBreak(*it)) return pos_ - it - 1; } return pos_ - stream_.begin(); @@ -418,7 +418,7 @@ private: bool startsWith(const std::string& prefix) const { - return zen::startsWith(StringRef<const char>(pos_, stream_.end()), prefix); + return zen::startsWith(makeStringView(pos_, stream_.end()), prefix); } const std::string stream_; diff --git a/zen/legacy_compiler.cpp b/zen/legacy_compiler.cpp new file mode 100644 index 00000000..3e3b7ba7 --- /dev/null +++ b/zen/legacy_compiler.cpp @@ -0,0 +1,32 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#include "legacy_compiler.h" +#include <charconv> +//1. including this one in string_tools.h blows up VC++: +// "An internal error has occurred in the compiler. (compiler file 'd:\agent\_work\1\s\src\vctools\Compiler\Utc\src\p2\p2symtab.c', line 2618)" +//2. using inside PCH: "fatal error C1076: compiler limit: internal heap limit reached" + + +#if __cpp_lib_to_chars + #error get rid of workarounds +#endif + +double zen::from_chars(const char* first, const char* last) +{ + return std::strtod(std::string(first, last).c_str(), nullptr); +} + + +const char* zen::to_chars(char* first, char* last, double num) +{ + const size_t bufSize = last - first; + const int charsWritten = std::snprintf(first, bufSize, "%g", num); + //C99: returns number of chars written if successful, < 0 or >= bufferSize on failure + + return 0 <= charsWritten && charsWritten < static_cast<int>(bufSize) ? + first + charsWritten : first; +} diff --git a/zen/legacy_compiler.h b/zen/legacy_compiler.h index 5b69ed94..d0b4d3fe 100644 --- a/zen/legacy_compiler.h +++ b/zen/legacy_compiler.h @@ -16,6 +16,10 @@ namespace std +#if __cpp_lib_span + #error get rid of workarounds +#endif + //requires C++20! until then, this should suffice... template <class T> class span @@ -49,4 +53,10 @@ private: }; } +namespace zen +{ +double from_chars(const char* first, const char* last); +const char* to_chars(char* first, char* last, double num); +} + #endif //LEGACY_COMPILER_H_839567308565656789 diff --git a/zen/open_ssl.cpp b/zen/open_ssl.cpp index f3fd7219..ce05de53 100644 --- a/zen/open_ssl.cpp +++ b/zen/open_ssl.cpp @@ -5,6 +5,8 @@ // ***************************************************************************** #include "open_ssl.h" +#include "base64.h" +#include "build_info.h" #include <openssl/pem.h> #include <openssl/err.h> #include <openssl/ssl.h> @@ -16,9 +18,7 @@ using namespace zen; #error FFS, we are royally screwed! #endif -#if OPENSSL_VERSION_NUMBER < 0x10100000L - #error OpenSSL version too old -#endif +static_assert(OPENSSL_VERSION_NUMBER >= 0x10100000L, "OpenSSL version too old"); void zen::openSslInit() @@ -108,11 +108,11 @@ std::shared_ptr<EVP_PKEY> streamToEvpKey(const std::string& keyStream, BioToEvpF throw SysError(formatLastOpenSSLError(L"BIO_new_mem_buf")); ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); - if (EVP_PKEY* evpKey = bioToEvp(bio, //BIO* bp, - nullptr, //EVP_PKEY** x, - nullptr, //pem_password_cb* cb, - nullptr)) //void* u - return std::shared_ptr<EVP_PKEY>(evpKey, ::EVP_PKEY_free); + if (EVP_PKEY* evp = bioToEvp(bio, //BIO* bp, + nullptr, //EVP_PKEY** x, + nullptr, //pem_password_cb* cb, + nullptr)) //void* u + return std::shared_ptr<EVP_PKEY>(evp, ::EVP_PKEY_free); throw SysError(formatLastOpenSSLError(functionName)); } @@ -134,12 +134,12 @@ std::shared_ptr<EVP_PKEY> streamToEvpKey(const std::string& keyStream, BioToRsaF throw SysError(formatLastOpenSSLError(functionName)); ZEN_ON_SCOPE_EXIT(::RSA_free(rsa)); - EVP_PKEY* evpKey = ::EVP_PKEY_new(); - if (!evpKey) + EVP_PKEY* evp = ::EVP_PKEY_new(); + if (!evp) throw SysError(formatLastOpenSSLError(L"EVP_PKEY_new")); - std::shared_ptr<EVP_PKEY> sharedKey(evpKey, ::EVP_PKEY_free); + std::shared_ptr<EVP_PKEY> sharedKey(evp, ::EVP_PKEY_free); - if (::EVP_PKEY_set1_RSA(evpKey, rsa) != 1) //calls RSA_up_ref() + transfers ownership to evpKey + if (::EVP_PKEY_set1_RSA(evp, rsa) != 1) //no ownership transfer (internally ref-counted) throw SysError(formatLastOpenSSLError(L"EVP_PKEY_set1_RSA")); return sharedKey; @@ -161,32 +161,32 @@ std::shared_ptr<EVP_PKEY> streamToKey(const std::string& keyStream, RsaStreamTyp streamToEvpKey(keyStream, ::PEM_read_bio_RSAPublicKey, L"PEM_read_bio_RSAPublicKey") : //throw SysError streamToEvpKey(keyStream, ::PEM_read_bio_RSAPrivateKey, L"PEM_read_bio_RSAPrivateKey"); // - case RsaStreamType::pkcs1_raw: + case RsaStreamType::raw: break; } auto tmp = reinterpret_cast<const unsigned char*>(keyStream.c_str()); - EVP_PKEY* evpKey = (publicKey ? ::d2i_PublicKey : ::d2i_PrivateKey)(EVP_PKEY_RSA, //int type, - nullptr, //EVP_PKEY** a, - &tmp, /*changes tmp pointer itself!*/ //const unsigned char** pp, - static_cast<long>(keyStream.size())); //long length - if (!evpKey) + 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, + static_cast<long>(keyStream.size())); //long length + if (!evp) throw SysError(formatLastOpenSSLError(publicKey ? L"d2i_PublicKey" : L"d2i_PrivateKey")); - return std::shared_ptr<EVP_PKEY>(evpKey, ::EVP_PKEY_free); + return std::shared_ptr<EVP_PKEY>(evp, ::EVP_PKEY_free); } //================================================================================ -using EvpToBioFunc = int (*)(BIO* bio, EVP_PKEY* evpKey); +using EvpToBioFunc = int (*)(BIO* bio, EVP_PKEY* evp); -std::string evpKeyToStream(EVP_PKEY* evpKey, EvpToBioFunc evpToBio, const wchar_t* functionName) //throw SysError +std::string evpKeyToStream(EVP_PKEY* evp, EvpToBioFunc evpToBio, const wchar_t* functionName) //throw SysError { BIO* bio = ::BIO_new(BIO_s_mem()); if (!bio) throw SysError(formatLastOpenSSLError(L"BIO_new")); ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); - if (evpToBio(bio, evpKey) != 1) + if (evpToBio(bio, evp) != 1) throw SysError(formatLastOpenSSLError(functionName)); //--------------------------------------------- const int keyLen = BIO_pending(bio); @@ -205,14 +205,14 @@ std::string evpKeyToStream(EVP_PKEY* evpKey, EvpToBioFunc evpToBio, const wchar_ using RsaToBioFunc = int (*)(BIO* bp, RSA* x); -std::string evpKeyToStream(EVP_PKEY* evpKey, RsaToBioFunc rsaToBio, const wchar_t* functionName) //throw SysError +std::string evpKeyToStream(EVP_PKEY* evp, RsaToBioFunc rsaToBio, const wchar_t* functionName) //throw SysError { BIO* bio = ::BIO_new(BIO_s_mem()); if (!bio) throw SysError(formatLastOpenSSLError(L"BIO_new")); ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio)); - RSA* rsa = ::EVP_PKEY_get0_RSA(evpKey); //unowned reference! + RSA* rsa = ::EVP_PKEY_get0_RSA(evp); //unowned reference! if (!rsa) throw SysError(formatLastOpenSSLError(L"EVP_PKEY_get0_RSA")); @@ -260,26 +260,26 @@ int PEM_write_bio_RSAPublicKey2(BIO* bio, RSA* rsa) { return ::PEM_write_bio_RSA //-------------------------------------------------------------------------------- -std::string keyToStream(EVP_PKEY* evpKey, RsaStreamType streamType, bool publicKey) //throw SysError +std::string keyToStream(EVP_PKEY* evp, RsaStreamType streamType, bool publicKey) //throw SysError { switch (streamType) { case RsaStreamType::pkix: return publicKey ? - evpKeyToStream(evpKey, ::PEM_write_bio_PUBKEY, L"PEM_write_bio_PUBKEY") : //throw SysError - evpKeyToStream(evpKey, ::PEM_write_bio_PrivateKey2, L"PEM_write_bio_PrivateKey"); // + evpKeyToStream(evp, ::PEM_write_bio_PUBKEY, L"PEM_write_bio_PUBKEY") : //throw SysError + evpKeyToStream(evp, ::PEM_write_bio_PrivateKey2, L"PEM_write_bio_PrivateKey"); // case RsaStreamType::pkcs1: return publicKey ? - evpKeyToStream(evpKey, ::PEM_write_bio_RSAPublicKey2, L"PEM_write_bio_RSAPublicKey") : //throw SysError - evpKeyToStream(evpKey, ::PEM_write_bio_RSAPrivateKey2, L"PEM_write_bio_RSAPrivateKey"); // + evpKeyToStream(evp, ::PEM_write_bio_RSAPublicKey2, L"PEM_write_bio_RSAPublicKey") : //throw SysError + evpKeyToStream(evp, ::PEM_write_bio_RSAPrivateKey2, L"PEM_write_bio_RSAPrivateKey"); // - case RsaStreamType::pkcs1_raw: + case RsaStreamType::raw: break; } unsigned char* buf = nullptr; - const int bufSize = (publicKey ? ::i2d_PublicKey : ::i2d_PrivateKey)(evpKey, &buf); + const int bufSize = (publicKey ? ::i2d_PublicKey : ::i2d_PrivateKey)(evp, &buf); if (bufSize <= 0) throw SysError(formatLastOpenSSLError(publicKey ? L"i2d_PublicKey" : L"i2d_PrivateKey")); ZEN_ON_SCOPE_EXIT(::OPENSSL_free(buf)); //memory is only allocated for bufSize > 0 @@ -359,8 +359,8 @@ void verifySignature(const std::string& message, const std::string& signature, E std::string zen::convertRsaKey(const std::string& keyStream, RsaStreamType typeFrom, RsaStreamType typeTo, bool publicKey) //throw SysError { assert(typeFrom != typeTo); - std::shared_ptr<EVP_PKEY> evpKey = streamToKey(keyStream, typeFrom, publicKey); //throw SysError - return keyToStream(evpKey.get(), typeTo, publicKey); //throw SysError + std::shared_ptr<EVP_PKEY> evp = streamToKey(keyStream, typeFrom, publicKey); //throw SysError + return keyToStream(evp.get(), typeTo, publicKey); //throw SysError } @@ -520,7 +520,7 @@ public: ::SSL_set_verify(ssl_, SSL_VERIFY_PEER, nullptr); //2. enable check that the certificate matches our host: see SSL_get_verify_result() - if (::SSL_set1_host(ssl_, server.c_str()) != 1) + if (::SSL_set1_host(ssl_, server.c_str()) != 1) //no ownership transfer throw SysError(L"SSL_set1_host failed."); //no more error details } @@ -615,3 +615,393 @@ zen::TlsContext::TlsContext(int socket, const Zstring& server, const Zstring* ca zen::TlsContext::~TlsContext() {} size_t zen::TlsContext::tryRead ( void* buffer, size_t bytesToRead ) { return pimpl_->tryRead(buffer, bytesToRead); } //throw SysError size_t zen::TlsContext::tryWrite(const void* buffer, size_t bytesToWrite) { return pimpl_->tryWrite(buffer, bytesToWrite); } //throw SysError + + +bool zen::isPuttyKeyStream(const std::string& keyStream) +{ + std::string firstLine(keyStream.begin(), std::find_if(keyStream.begin(), keyStream.end(), isLineBreak<char>)); + trim(firstLine); + return startsWith(firstLine, "PuTTY-User-Key-File-2:"); +} + + +std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::string& passphrase) //throw SysError +{ + std::vector<std::string> lines; + + for (auto it = keyStream.begin();;) //=> keep local: "warning: declaration of ‘it’ shadows a previous local" + { + auto itLineBegin = std::find_if_not(it, keyStream.end(), isLineBreak<char>); + if (itLineBegin == keyStream.end()) + break; + + it = std::find_if(itLineBegin + 1, keyStream.end(), isLineBreak<char>); + lines.emplace_back(itLineBegin, it); + } + //----------- 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 algorithm = afterFirst(*itLine, ' ', IF_MISSING_RETURN_NONE); + ++itLine; + + if (itLine == lines.end() || !startsWith(*itLine, "Encryption: ")) + throw SysError(L"Unknown key encryption"); + const std::string keyEncryption = afterFirst(*itLine, ' ', IF_MISSING_RETURN_NONE); + ++itLine; + + if (itLine == lines.end() || !startsWith(*itLine, "Comment: ")) + throw SysError(L"Invalid key comment"); + const std::string comment = afterFirst(*itLine, ' ', IF_MISSING_RETURN_NONE); + ++itLine; + + if (itLine == lines.end() || !startsWith(*itLine, "Public-Lines: ")) + throw SysError(L"Invalid key: invalid public lines"); + size_t pubLineCount = stringTo<size_t>(afterFirst(*itLine, ' ', IF_MISSING_RETURN_NONE)); + ++itLine; + + std::string publicBlob64; + while (pubLineCount-- != 0) + if (itLine != lines.end()) + publicBlob64 += *itLine++; + 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, ' ', IF_MISSING_RETURN_NONE)); + ++itLine; + + std::string privateBlob64; + while (privLineCount-- != 0) + if (itLine != lines.end()) + privateBlob64 += *itLine++; + 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 macHex = afterFirst(*itLine, ' ', IF_MISSING_RETURN_NONE); + ++itLine; + + //----------- 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"); + + std::string mac; + for (size_t i = 0; i < macHex.size(); i += 2) + mac += unhexify(macHex[i], macHex[i + 1]); + + const std::string publicBlob = stringDecodeBase64(publicBlob64); + const std::string privateBlobEnc = stringDecodeBase64(privateBlob64); + + std::string privateBlob; + if (!keyEncrypted) + privateBlob = privateBlobEnc; + else + { + 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; + + unsigned char key[2 * SHA_DIGEST_LENGTH] = {}; + SHA1(reinterpret_cast<const unsigned char*>(block1.c_str()), block1.size(), &key[0]); //no-fail + SHA1(reinterpret_cast<const unsigned char*>(block2.c_str()), block2.size(), &key[SHA_DIGEST_LENGTH]); // + + EVP_CIPHER_CTX* cipCtx = ::EVP_CIPHER_CTX_new(); + if (!cipCtx) + throw SysError(L"EVP_CIPHER_CTX_new failed."); //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 + throw SysError(formatLastOpenSSLError(L"EVP_DecryptInit_ex")); + + if (::EVP_CIPHER_CTX_set_padding(cipCtx, 0 /*padding*/) != 1) + throw SysError(L"EVP_CIPHER_CTX_set_padding failed."); //no more error details + + privateBlob.resize(privateBlobEnc.size() + ::EVP_CIPHER_block_size(EVP_aes_256_cbc())); + //"EVP_DecryptUpdate() should have room for (inl + cipher_block_size) bytes" + + int decLen1 = 0; + if (::EVP_DecryptUpdate(cipCtx, //EVP_CIPHER_CTX* ctx, + reinterpret_cast<unsigned char*>(&privateBlob[0]), //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(L"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 + throw SysError(formatLastOpenSSLError(L"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[0]); //no-fail + + auto numToBeString = [](size_t n) -> std::string + { + static_assert(usingLittleEndian()&& sizeof(n) >= 4); + const char* numStr = reinterpret_cast<const char*>(&n); + return { numStr[3], numStr[2], numStr[1], numStr[0] }; //big endian! + }; + + const std::string macData = numToBeString(algorithm .size()) + algorithm + + numToBeString(keyEncryption.size()) + keyEncryption + + numToBeString(comment .size()) + comment + + numToBeString(publicBlob .size()) + publicBlob + + 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, + 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 + throw SysError(L"HMAC failed."); //no more error details + + const bool hashValid = mac == std::string_view(md, mdLen); + if (!hashValid) + throw SysError(keyEncrypted ? L"MAC validation failed: wrong passphrase or corrupted key" : L"MAC validation failed: corrupted key"); + //---------------------------------------------------------- + + auto extractString = [](auto& it, auto itEnd) + { + uint32_t byteCount = 0; + if (itEnd - it < makeSigned(sizeof(byteCount))) + throw SysError(L"String extraction failed: unexpected end of stream"); + + static_assert(usingLittleEndian()); + char* numStr = reinterpret_cast<char*>(&byteCount); + numStr[3] = *it++; // + numStr[2] = *it++; //Putty uses big endian! + numStr[1] = *it++; // + numStr[0] = *it++; // + + if (makeUnsigned(itEnd - it) < byteCount) + throw SysError(L"String extraction failed: unexpected end of stream(2)"); + + std::string str(it, it + byteCount); + it += byteCount; + return str; + }; + + struct BnFree { void operator()(BIGNUM* num) const { ::BN_free(num); } }; + auto createBigNum = [] + { + BIGNUM* bn = ::BN_new(); + if (!bn) + throw SysError(formatLastOpenSSLError(L"BN_new")); + return std::unique_ptr<BIGNUM, BnFree>(bn); + }; + + auto extractBigNum = [&extractString](auto& it, auto itEnd) + { + const std::string bytes = extractString(it, itEnd); + + BIGNUM* bn = ::BN_bin2bn(reinterpret_cast<const unsigned char*>(&bytes[0]), static_cast<int>(bytes.size()), nullptr); + if (!bn) + throw SysError(formatLastOpenSSLError(L"BN_bin2bn")); + return std::unique_ptr<BIGNUM, BnFree>(bn); + }; + + auto itPub = publicBlob .begin(); + auto itPriv = privateBlob.begin(); + + auto extractStringPub = [&] { return extractString(itPub, publicBlob .end()); }; + auto extractStringPriv = [&] { return extractString(itPriv, privateBlob.end()); }; + + auto extractBigNumPub = [&] { return extractBigNum(itPub, publicBlob .end()); }; + auto extractBigNumPriv = [&] { return extractBigNum(itPriv, privateBlob.end()); }; + + //----------- parse public/private key blobs ---------------- + if (extractStringPub() != algorithm) + throw SysError(L"Invalid public key stream (header)"); + + if (algorithm == "ssh-rsa") + { + std::unique_ptr<BIGNUM, BnFree> e = extractBigNumPub (); // + std::unique_ptr<BIGNUM, BnFree> n = extractBigNumPub (); // + std::unique_ptr<BIGNUM, BnFree> d = extractBigNumPriv(); //throw SysError + std::unique_ptr<BIGNUM, BnFree> p = extractBigNumPriv(); // + std::unique_ptr<BIGNUM, BnFree> q = extractBigNumPriv(); // + std::unique_ptr<BIGNUM, BnFree> iqmp = extractBigNumPriv(); // + + //------ calculate missing numbers: dmp1, dmq1 ------------- + std::unique_ptr<BIGNUM, BnFree> dmp1 = createBigNum(); // + std::unique_ptr<BIGNUM, BnFree> dmq1 = createBigNum(); //throw SysError + std::unique_ptr<BIGNUM, BnFree> tmp = createBigNum(); // + + BN_CTX* bnCtx = BN_CTX_new(); + if (!bnCtx) + throw SysError(formatLastOpenSSLError(L"BN_CTX_new")); + ZEN_ON_SCOPE_EXIT(::BN_CTX_free(bnCtx)); + + if (::BN_sub(tmp.get(), p.get(), BN_value_one()) != 1) + throw SysError(formatLastOpenSSLError(L"BN_sub")); + + if (::BN_mod(dmp1.get(), d.get(), tmp.get(), bnCtx) != 1) + throw SysError(formatLastOpenSSLError(L"BN_mod")); + + if (::BN_sub(tmp.get(), q.get(), BN_value_one()) != 1) + throw SysError(formatLastOpenSSLError(L"BN_sub")); + + if (::BN_mod(dmq1.get(), d.get(), tmp.get(), bnCtx) != 1) + throw SysError(formatLastOpenSSLError(L"BN_mod")); + //---------------------------------------------------------- + + RSA* rsa = ::RSA_new(); + if (!rsa) + throw SysError(formatLastOpenSSLError(L"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(L"RSA_set0_key")); + + if (::RSA_set0_factors(rsa, p.release(), q.release()) != 1) + throw SysError(formatLastOpenSSLError(L"RSA_set0_factors")); + + if (::RSA_set0_crt_params(rsa, dmp1.release(), dmq1.release(), iqmp.release()) != 1) + throw SysError(formatLastOpenSSLError(L"RSA_set0_crt_params")); + + EVP_PKEY* evp = ::EVP_PKEY_new(); + if (!evp) + throw SysError(formatLastOpenSSLError(L"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(L"EVP_PKEY_set1_RSA")); + + return keyToStream(evp, RsaStreamType::pkix, false /*publicKey*/); //throw SysError + } + //---------------------------------------------------------- + else if (algorithm == "ssh-dss") + { + std::unique_ptr<BIGNUM, BnFree> p = extractBigNumPub (); // + std::unique_ptr<BIGNUM, BnFree> q = extractBigNumPub (); // + std::unique_ptr<BIGNUM, BnFree> g = extractBigNumPub (); //throw SysError + std::unique_ptr<BIGNUM, BnFree> pub = extractBigNumPub (); // + std::unique_ptr<BIGNUM, BnFree> pri = extractBigNumPriv(); // + //---------------------------------------------------------- + + DSA* dsa = ::DSA_new(); + if (!dsa) + throw SysError(formatLastOpenSSLError(L"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(L"DSA_set0_pqg")); + + if (::DSA_set0_key(dsa, pub.release(), pri.release()) != 1) + throw SysError(formatLastOpenSSLError(L"DSA_set0_key")); + + EVP_PKEY* evp = ::EVP_PKEY_new(); + if (!evp) + throw SysError(formatLastOpenSSLError(L"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(L"EVP_PKEY_set1_DSA")); + + return keyToStream(evp, RsaStreamType::pkix, false /*publicKey*/); //throw SysError + } + //---------------------------------------------------------- + else if (algorithm == "ecdsa-sha2-nistp256" || + algorithm == "ecdsa-sha2-nistp384" || + algorithm == "ecdsa-sha2-nistp521") + { + const std::string algoShort = afterLast(algorithm, '-', IF_MISSING_RETURN_NONE); + if (extractStringPub() != algoShort) + throw SysError(L"Invalid public key stream (header)"); + + const std::string pointStream = extractStringPub(); + std::unique_ptr<BIGNUM, BnFree> pri = extractBigNumPriv(); //throw SysError + //---------------------------------------------------------- + + 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(L"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(L"EC_KEY_get0_group")); + + EC_POINT* ecPoint = ::EC_POINT_new(ecGroup); + if (!ecPoint) + throw SysError(formatLastOpenSSLError(L"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[0]), //const unsigned char* buf, + pointStream.size(), //size_t len, + nullptr) != 1) //BN_CTX* ctx + throw SysError(formatLastOpenSSLError(L"EC_POINT_oct2point")); + + if (::EC_KEY_set_public_key(ecKey, ecPoint) != 1) //no ownership transfer (internally ref-counted) + throw SysError(formatLastOpenSSLError(L"EC_KEY_set_public_key")); + + if (::EC_KEY_set_private_key(ecKey, pri.get()) != 1) //no ownership transfer (internally ref-counted) + throw SysError(formatLastOpenSSLError(L"EC_KEY_set_private_key")); + + EVP_PKEY* evp = ::EVP_PKEY_new(); + if (!evp) + throw SysError(formatLastOpenSSLError(L"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(L"EVP_PKEY_set1_EC_KEY")); + + return keyToStream(evp, RsaStreamType::pkix, false /*publicKey*/); //throw SysError + } + //---------------------------------------------------------- + else if (algorithm == "ssh-ed25519") + { + //const std::string pubStream = extractStringPub(); -> we don't need the public key + const std::string priStream = extractStringPriv(); + + EVP_PKEY* evpPriv = ::EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, //int type, + nullptr, //ENGINE* e, + reinterpret_cast<const unsigned char*>(&priStream[0]), //const unsigned char* priv, + priStream.size()); //size_t len + if (!evpPriv) + throw SysError(formatLastOpenSSLError(L"EVP_PKEY_new_raw_private_key")); + ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evpPriv)); + + return keyToStream(evpPriv, RsaStreamType::pkix, false /*publicKey*/); //throw SysError + } + else + throw SysError(L"Unknown key algorithm: " + utfTo<std::wstring>(algorithm)); +} diff --git a/zen/open_ssl.h b/zen/open_ssl.h index 350e3776..2b0c7245 100644 --- a/zen/open_ssl.h +++ b/zen/open_ssl.h @@ -20,9 +20,9 @@ void openSslTearDown(); enum class RsaStreamType { - pkix, //base-64-encoded SubjectPublicKeyInfo structure ("BEGIN PUBLIC KEY") - pkcs1, //base-64-encoded RSA number and exponent ("BEGIN RSA PUBLIC KEY") - pkcs1_raw + pkix, //base-64-encoded X.509 SubjectPublicKeyInfo structure ("BEGIN PUBLIC KEY") + pkcs1, //base-64-encoded PKCS#1 RSAPublicKey: RSA number and exponent ("BEGIN RSA PUBLIC KEY") + raw //raw bytes: DER-encoded PKCS#1 }; //verify signatures produced with: "openssl dgst -sha256 -sign private.pem -out file.sig file.txt" @@ -34,6 +34,10 @@ void verifySignature(const std::string& message, std::string convertRsaKey(const std::string& keyStream, RsaStreamType typeFrom, RsaStreamType typeTo, bool publicKey); //throw SysError +bool isPuttyKeyStream(const std::string& keyStream); +std::string convertPuttyKeyToPkix(const std::string& keyStream, const std::string& passphrase); //throw SysError + + class TlsContext { public: diff --git a/zen/shutdown.cpp b/zen/shutdown.cpp index 4fc687d6..5ce586f0 100644 --- a/zen/shutdown.cpp +++ b/zen/shutdown.cpp @@ -33,8 +33,9 @@ void zen::suspendSystem() //throw FileError void zen::terminateProcess(int exitCode) { - std::exit(exitCode); //[[noreturn]]; "Stack is not unwound: destructors of variables with automatic storage duration are not called." => perfect - //don't use std::abort() => crashes process with "EXC_CRASH (SIGABRT)" on macOS + std::quick_exit(exitCode); //[[noreturn]]; "Causes normal program termination to occur without completely cleaning the resources." => perfect + + for (;;) //why still here?? => crash deliberately! *reinterpret_cast<volatile int*>(0) = 0; //crude but at least we'll get crash dumps if it ever happens } diff --git a/zen/socket.h b/zen/socket.h index 7ca0c93f..827d446b 100644 --- a/zen/socket.h +++ b/zen/socket.h @@ -142,7 +142,7 @@ size_t tryWriteSocket(SocketType socket, const void* buffer, size_t bytesToWrite inline void shutdownSocketSend(SocketType socket) //throw SysError { - if (::shutdown(socket, SHUT_WR) != 0) + if (::shutdown(socket, SHUT_WR) != 0) THROW_LAST_SYS_ERROR_WSA(L"shutdown"); } diff --git a/zen/stl_tools.h b/zen/stl_tools.h index 15e7f7ca..9014b0f7 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -15,7 +15,6 @@ #include <algorithm> #include <optional> #include "string_traits.h" -//#include "build_info.h" //enhancements for <algorithm> diff --git a/zen/string_base.h b/zen/string_base.h index 2247f93a..a417b7f6 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -189,8 +189,8 @@ private: length (static_cast<uint32_t>(len)), capacity(static_cast<uint32_t>(cap)) { - static_assert(ATOMIC_INT_LOCK_FREE == 2); //2: "The atomic type is always lock-free" - //static_assert(decltype(refCount)::is_always_lock_free); //C++17 variant (not yet supported on GCC 6.3) + //static_assert(ATOMIC_INT_LOCK_FREE == 2); //2: "The atomic type is always lock-free" + static_assert(decltype(refCount)::is_always_lock_free); //C++17 variant (not yet supported on GCC 6.3) } std::atomic<unsigned int> refCount { 1 }; //std:atomic is uninitialized by default! @@ -313,6 +313,11 @@ template <class Char, template <class> class SP> inline Zbase<Char, SP> operator template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+( Char lhs, const Zbase<Char, SP>& rhs) { return Zbase<Char, SP>(&lhs, 1) += rhs; } template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(const Char* lhs, const Zbase<Char, SP>& rhs) { return Zbase<Char, SP>(lhs ) += rhs; } +#if __cpp_impl_three_way_comparison +#error implement: +std::strong_ordering operator<=>(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs) +bool operator==(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs); +#endif @@ -329,7 +334,7 @@ template <class Char, template <class> class SP> inline Zbase<Char, SP> operator template <class Char, template <class> class SP> inline Zbase<Char, SP>::Zbase() { - //resist the temptation to avoid this allocation by referening a static global: NO performance advantage, MT issues! + //resist the temptation to avoid this allocation by referencing a static global: NO performance advantage, MT issues! rawStr_ = this->create(0); rawStr_[0] = 0; } diff --git a/zen/string_tools.h b/zen/string_tools.h index c3970d05..dcb5a54a 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -14,15 +14,16 @@ #include <algorithm> #include <cassert> #include <vector> -#include <sstream> //std::basic_ostringstream #include "stl_tools.h" #include "string_traits.h" +#include "legacy_compiler.h" //<charconv> (without compiler crashes) //enhance arbitray string class with useful non-member functions: namespace zen { template <class Char> bool isWhiteSpace(Char c); +template <class Char> bool isLineBreak (Char c); template <class Char> bool isDigit (Char c); //not exactly the same as "std::isdigit" -> we consider '0'-'9' only! template <class Char> bool isHexDigit (Char c); template <class Char> bool isAsciiAlpha(Char c); @@ -116,6 +117,14 @@ bool isWhiteSpace(Char c) } template <class Char> inline +bool isLineBreak(Char c) +{ + static_assert(std::is_same_v<Char, char> || std::is_same_v<Char, wchar_t>); + return c == static_cast<Char>('\r') || c == static_cast<Char>('\n'); +} + + +template <class Char> inline bool isDigit(Char c) //similar to implementation of std::isdigit()! { static_assert(std::is_same_v<Char, char> || std::is_same_v<Char, wchar_t>); @@ -552,6 +561,10 @@ int saferPrintf(wchar_t* buffer, size_t bufferSize, const wchar_t* format, const template <class S, class T, class Num> inline S printNumber(const T& format, const Num& number) //format a single number using ::sprintf { +#if __cpp_lib_format +#error refactor +#endif + static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const int BUFFER_SIZE = 128; @@ -573,22 +586,31 @@ enum class NumberType }; +template <class S, class Num> S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::OTHER>) = delete; +#if 0 //default number to string conversion using streams: convenient, but SLOW, SLOW, SLOW!!!! (~ factor of 20) template <class S, class Num> inline -S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::OTHER>) //default number to string conversion using streams: convenient, but SLOW, SLOW, SLOW!!!! (~ factor of 20) +S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::OTHER>) { std::basic_ostringstream<GetCharTypeT<S>> ss; ss << number; return copyStringTo<S>(ss.str()); } +#endif -template <class S, class Num> inline S floatToString(const Num& number, char ) { return printNumber<S>( "%g", static_cast<double>(number)); } -template <class S, class Num> inline S floatToString(const Num& number, wchar_t) { return printNumber<S>(L"%g", static_cast<double>(number)); } - template <class S, class Num> inline S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::FLOATING_POINT>) { - return floatToString<S>(number, GetCharTypeT<S>()); + //don't use sprintf("%g"): way SLOWWWWWWER than std::to_chars() + + char buffer[128]; //zero-initialize? + //let's give some leeway, but 24 chars should suffice: https://www.reddit.com/r/cpp/comments/dgj89g/cppcon_2019_stephan_t_lavavej_floatingpoint/f3j7d3q/ + const char* strEnd = zen::to_chars(std::begin(buffer), std::end(buffer), number); + + S output; + std::for_each(static_cast<const char*>(buffer), strEnd, + [&](char c) { output += static_cast<GetCharTypeT<S>>(c); }); + return output; } @@ -665,25 +687,46 @@ S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::UNS //-------------------------------------------------------------------------------- +template <class Num, class S> Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::OTHER>) = delete; +#if 0 //default string to number conversion using streams: convenient, but SLOW template <class Num, class S> inline -Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::OTHER>) //default string to number conversion using streams: convenient, but SLOW +Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::OTHER>) { using CharType = GetCharTypeT<S>; Num number = 0; std::basic_istringstream<CharType>(copyStringTo<std::basic_string<CharType>>(str)) >> number; return number; } +#endif -template <class Num> inline Num stringToFloat(const char* str) { return std::strtod(str, nullptr); } -template <class Num> inline Num stringToFloat(const wchar_t* str) { return std::wcstod(str, nullptr); } +inline +double stringToFloat(const char* first, const char* last) +{ + //don't use std::strtod(): 1. requires null-terminated string 2. SLOWER than std::from_chars() + return zen::from_chars(first, last); +} + + +inline +double stringToFloat(const wchar_t* first, const wchar_t* last) +{ + std::string buf(last - first, '\0'); + std::transform(first, last, buf.begin(), [](wchar_t c) { return static_cast<char>(c); }); + + return zen::from_chars(buf.c_str(), buf.c_str() + buf.size()); +} + template <class Num, class S> inline Num stringTo(const S& str, std::integral_constant<NumberType, NumberType::FLOATING_POINT>) { - return stringToFloat<Num>(strBegin(str)); + const auto* const first = strBegin(str); + const auto* const last = first + strLength(str); + return static_cast<Num>(stringToFloat(first, last)); } + template <class Num, class S> Num extractInteger(const S& str, bool& hasMinusSign) //very fast conversion to integers: slightly faster than std::atoi, but more importantly: generic { @@ -695,7 +738,6 @@ Num extractInteger(const S& str, bool& hasMinusSign) //very fast conversion to i while (first != last && isWhiteSpace(*first)) //skip leading whitespace ++first; - //handle minus sign hasMinusSign = false; if (first != last) { diff --git a/zen/string_traits.h b/zen/string_traits.h index 93cfd81c..d0f34d54 100644 --- a/zen/string_traits.h +++ b/zen/string_traits.h @@ -7,6 +7,7 @@ #ifndef STRING_TRAITS_H_813274321443234 #define STRING_TRAITS_H_813274321443234 +#include <string_view> #include <cstring> //strlen #include "type_traits.h" @@ -36,27 +37,9 @@ strBegin(): -> not null-terminated! -> may be nullptr if length is 0! //reference a sub-string for consumption by zen string_tools -template <class Char> -class StringRef -{ -public: - template <class Iterator> - StringRef(Iterator first, Iterator last) : len_(last - first), - str_(first != last ? &*first : reinterpret_cast<Char*>(this) /*Win32 APIs like CompareStringOrdinal() choke on nullptr!*/) - { - static_assert(alignof(StringRef) % alignof(Char) == 0); //even though str_ is never dereferenced, make sure the pointer value respects alignment (why? because we can) - } - //StringRef(const Char* str, size_t len) : str_(str), len_(len) {} -> needless constraint! Char* not available for empty range! - - Char* data () const { return str_; } //no null-termination! - size_t length() const { return len_; } - -private: - const size_t len_; - Char* const str_; -}; - - +//=> std::string_view seems decent, but of course fucks up in one regard: construction +template <class Iterator> auto makeStringView(Iterator first, Iterator last); +template <class Iterator> auto makeStringView(Iterator first, size_t len); @@ -84,7 +67,7 @@ public: }; -template <class S, bool isStringClass> struct GetCharTypeImpl { using Type = void; }; +template <class S, bool isStringClass> struct GetCharTypeImpl { using Type = void; }; template <class S> struct GetCharTypeImpl<S, true> @@ -103,10 +86,10 @@ struct GetCharTypeImpl<S, true> template <> struct GetCharTypeImpl<char, false> { using Type = char; }; template <> struct GetCharTypeImpl<wchar_t, false> { using Type = wchar_t; }; -template <> struct GetCharTypeImpl<StringRef<char >, false> { using Type = char; }; -template <> struct GetCharTypeImpl<StringRef<wchar_t >, false> { using Type = wchar_t; }; -template <> struct GetCharTypeImpl<StringRef<const char >, false> { using Type = char; }; -template <> struct GetCharTypeImpl<StringRef<const wchar_t>, false> { using Type = wchar_t; }; +template <> struct GetCharTypeImpl<std::basic_string_view<char >, false> { using Type = char; }; +template <> struct GetCharTypeImpl<std::basic_string_view<wchar_t >, false> { using Type = wchar_t; }; +template <> struct GetCharTypeImpl<std::basic_string_view<const char >, false> { using Type = char; }; +template <> struct GetCharTypeImpl<std::basic_string_view<const wchar_t>, false> { using Type = wchar_t; }; ZEN_INIT_DETECT_MEMBER_TYPE(value_type); @@ -184,11 +167,10 @@ inline const wchar_t* strBegin(const wchar_t* str) { return str; } inline const char* strBegin(const char& ch) { return &ch; } inline const wchar_t* strBegin(const wchar_t& ch) { return &ch; } -inline const char* strBegin(const StringRef<char >& ref) { return ref.data(); } -inline const wchar_t* strBegin(const StringRef<wchar_t >& ref) { return ref.data(); } -inline const char* strBegin(const StringRef<const char >& ref) { return ref.data(); } -inline const wchar_t* strBegin(const StringRef<const wchar_t>& ref) { return ref.data(); } - +inline const char* strBegin(const std::basic_string_view<char >& ref) { return ref.data(); } +inline const wchar_t* strBegin(const std::basic_string_view<wchar_t >& ref) { return ref.data(); } +inline const char* strBegin(const std::basic_string_view<const char >& ref) { return ref.data(); } +inline const wchar_t* strBegin(const std::basic_string_view<const wchar_t>& ref) { return ref.data(); } template <class S, typename = std::enable_if_t<StringTraits<S>::isStringClass>> inline size_t strLength(const S& str) //SFINAE: T must be a "string" @@ -201,15 +183,15 @@ inline size_t strLength(const wchar_t* str) { return cStringLength(str); } inline size_t strLength(char) { return 1; } inline size_t strLength(wchar_t) { return 1; } -inline size_t strLength(const StringRef<char >& ref) { return ref.length(); } -inline size_t strLength(const StringRef<wchar_t >& ref) { return ref.length(); } -inline size_t strLength(const StringRef<const char >& ref) { return ref.length(); } -inline size_t strLength(const StringRef<const wchar_t>& ref) { return ref.length(); } +inline size_t strLength(const std::basic_string_view<char >& ref) { return ref.length(); } +inline size_t strLength(const std::basic_string_view<wchar_t >& ref) { return ref.length(); } +inline size_t strLength(const std::basic_string_view<const char >& ref) { return ref.length(); } +inline size_t strLength(const std::basic_string_view<const wchar_t>& ref) { return ref.length(); } } template <class S> inline -auto strBegin(S&& str) -> const GetCharTypeT<S>* +auto strBegin(S&& str) { static_assert(IsStringLikeV<S>); return impl::strBegin(std::forward<S>(str)); @@ -222,6 +204,19 @@ size_t strLength(S&& str) static_assert(IsStringLikeV<S>); return impl::strLength(std::forward<S>(str)); } + + +template <class Iterator> inline +auto makeStringView(Iterator first, Iterator last) +{ + using CharType = GetCharTypeT<decltype(&*first)>; + + return std::basic_string_view<CharType>(first != last ? &*first : + reinterpret_cast<CharType*>(0x1000), /*Win32 APIs like CompareStringOrdinal() choke on nullptr!*/ + last - first); +} + +template <class Iterator> inline auto makeStringView(Iterator first, size_t len) { return makeStringView(first, first + len); } } #endif //STRING_TRAITS_H_813274321443234 diff --git a/zen/sys_error.h b/zen/sys_error.h index a087172f..7c746258 100644 --- a/zen/sys_error.h +++ b/zen/sys_error.h @@ -47,6 +47,10 @@ private: do { const ErrorCode ecInternal = getLastError(); throw SysError(formatSystemError(functionName, ecInternal)); } while (false) +//helper for error checking macros: +inline bool validatBool(bool b) { return b; } +inline bool validatBool(void* b) { return b != nullptr; } +bool validatBool(int) = delete; //catch unintended bool conversions, e.g. HRESULT diff --git a/zen/thread.h b/zen/thread.h index 791aec67..d6cafab7 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -18,6 +18,9 @@ namespace zen { class InterruptionStatus; +#if __cpp_lib_jthread + #error refactor! +#endif class InterruptibleThread { public: @@ -81,6 +84,7 @@ template <class Function> auto runAsync(Function&& fun); //wait for all with a time limit: return true if *all* results are available! +//TODO: use std::when_all when available template<class InputIterator, class Duration> bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& wait_duration); @@ -89,6 +93,7 @@ bool isReady(const std::future<T>& f) { return f.wait_for(std::chrono::seconds(0 //------------------------------------------------------------------------------------------ //wait until first job is successful or all failed: substitute until std::when_any is available +//TODO: use std::when_any when available template <class T> class AsyncFirstResult { @@ -113,6 +118,7 @@ private: //------------------------------------------------------------------------------------------ //value associated with mutex and guaranteed protected access: +//TODO: use std::synchronized_value when available template <class T> class Protected { @@ -335,7 +335,7 @@ TimeComp parseTime(const String& format, const String2& str, UserDefinedFormatTa if (!std::all_of(itStr, itStr + digitCount, isDigit<CharType>)) return false; - result = zen::stringTo<int>(StringRef<const CharType>(itStr, itStr + digitCount)); + result = zen::stringTo<int>(makeStringView(itStr, digitCount)); itStr += digitCount; return true; }; diff --git a/zen/warn_static.h b/zen/warn_static.h index 17e7cf25..d5f78b5d 100644 --- a/zen/warn_static.h +++ b/zen/warn_static.h @@ -8,17 +8,18 @@ #define WARN_STATIC_H_08724567834560832745 /* - Portable Compile-Time Warning - ----------------------------- - Usage: - warn_static("my message") + Portable Compile-Time Warning + ----------------------------- + Usage: + warn_static("my message") */ -#define ZEN_STATIC_WARNING_STRINGIZE(NUM) #NUM +#define ZEN_STRINGIZE_STRING(NUM) #NUM +#define ZEN_STRINGIZE_NUMBER(NUM) ZEN_STRINGIZE_STRING(NUM) #if defined __GNUC__ //Clang also defines __GNUC__! #define warn_static(MSG) \ - _Pragma(ZEN_STATIC_WARNING_STRINGIZE(GCC warning MSG)) + _Pragma(ZEN_STRINGIZE_STRING(GCC warning MSG)) #endif #endif //WARN_STATIC_H_08724567834560832745 diff --git a/zen/zlib_wrap.cpp b/zen/zlib_wrap.cpp index 8979efa6..f7418b88 100644 --- a/zen/zlib_wrap.cpp +++ b/zen/zlib_wrap.cpp @@ -6,7 +6,7 @@ #include "zlib_wrap.h" //Windows: use the SAME zlib version that wxWidgets is linking against! //C:\Data\Projects\wxWidgets\Source\src\zlib\zlib.h -//Linux/macOS: use zlib system header for both wxWidgets and libcurl (zlib is required for HTTP) +//Linux/macOS: use zlib system header for both wxWidgets and libcurl (zlib is required for HTTP, SFTP) // => don't compile wxWidgets with: --with-zlib=builtin #include <zlib.h> //https://www.zlib.net/manual.html #include <zen/scope_guard.h> @@ -53,7 +53,7 @@ size_t zen::impl::zlib_compress(const void* src, size_t srcLen, void* trg, size_ // Z_MEM_ERROR: not enough memory // Z_BUF_ERROR: not enough room in the output buffer if (rv != Z_OK || bufferSize > trgLen) - throw SysError(formatSystemError(L"compress2", formatZlibStatusCode(rv), L"zlib error")); + throw SysError(formatSystemError(L"compress2", formatZlibStatusCode(rv), L"zlib error")); return bufferSize; } diff --git a/zen/zlib_wrap.h b/zen/zlib_wrap.h index fbe26193..9d9229ac 100644 --- a/zen/zlib_wrap.h +++ b/zen/zlib_wrap.h @@ -90,14 +90,14 @@ BinContainer decompress(const BinContainer& stream) //throw SysError //retrieve size of uncompressed data uint64_t uncompressedSize = 0; //use portable number type! if (stream.size() < sizeof(uncompressedSize)) - throw SysError(L"zlib error: stream size < 8"); + throw SysError(L"zlib error: stream size < 8"); std::memcpy(&uncompressedSize, &*stream.begin(), sizeof(uncompressedSize)); //attention: contOut MUST NOT be empty! Else it will pass a nullptr to zlib_decompress() => Z_STREAM_ERROR although "uncompressedSize == 0"!!! //secondary bug: don't dereference iterator into empty container! if (uncompressedSize == 0) //cannot be 0: compress() directly maps empty -> empty container skipping zlib! - throw SysError(L"zlib error: uncompressed size == 0"); + throw SysError(L"zlib error: uncompressed size == 0"); try { @@ -105,7 +105,7 @@ BinContainer decompress(const BinContainer& stream) //throw SysError } catch (const std::bad_alloc& e) //most likely due to data corruption! { - throw SysError(L"zlib error: " + _("Out of memory.") + L" " + utfTo<std::wstring>(e.what())); + throw SysError(L"zlib error: " + _("Out of memory.") + L" " + utfTo<std::wstring>(e.what())); } const size_t bytesWritten = impl::zlib_decompress(&*stream.begin() + sizeof(uncompressedSize), diff --git a/zen/zstring.cpp b/zen/zstring.cpp index ad736d04..f018b14f 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -8,7 +8,7 @@ #include <stdexcept> #include "utf.h" -#include <glib.h> + #include <glib.h> #include "sys_error.h" using namespace zen; |