From 791b90b9898cc41869538f1dfc303588436682b7 Mon Sep 17 00:00:00 2001 From: B Stack Date: Sat, 15 Feb 2020 11:50:31 -0500 Subject: add upstream 10.20 It is worth noting that the send email feature is not present in the GPL release. --- zen/error_log.h | 47 ++++++++------- zen/http.cpp | 165 +++++++++++++++++++++++++++++++++++++++++----------- zen/http.h | 13 ++++- zen/json.h | 32 +++++----- zen/serialize.h | 18 +++--- zen/shell_execute.h | 48 ++++++++++++--- zen/shutdown.cpp | 4 +- zen/string_tools.h | 23 ++++---- zen/sys_error.h | 3 +- zen/system.cpp | 84 ++++++++++++++++++++++++++ zen/system.h | 33 +++++++++++ zen/zlib_wrap.cpp | 20 +++++++ zen/zlib_wrap.h | 12 ++-- zen/zstring.cpp | 4 +- 14 files changed, 389 insertions(+), 117 deletions(-) create mode 100644 zen/system.cpp create mode 100644 zen/system.h (limited to 'zen') diff --git a/zen/error_log.h b/zen/error_log.h index 5115e6ef..cc52fc6e 100644 --- a/zen/error_log.h +++ b/zen/error_log.h @@ -10,9 +10,10 @@ #include #include #include -#include +//#include #include "time.h" #include "i18n.h" +#include "utf.h" #include "zstring.h" @@ -30,7 +31,7 @@ struct LogEntry { time_t time = 0; MessageType type = MSG_TYPE_FATAL_ERROR; - Zstringw message; //std::wstring may employ small string optimization: we cannot accept bloating the "ErrorLog::entries" memory block below (think 1 million items) + Zstringw message; //std::wstring may employ small string optimization: we cannot accept bloating the "ErrorLog::entries_" memory block below (think 1 million items) }; std::wstring formatMessage(const LogEntry& entry); @@ -71,17 +72,14 @@ void ErrorLog::logMsg(const std::wstring& msg, MessageType type) inline int ErrorLog::getItemCount(int typeFilter) const { - return static_cast(std::count_if(entries_.begin(), entries_.end(), [&](const LogEntry& e) { return e.type & typeFilter; })); + return static_cast(std::count_if(entries_.begin(), entries_.end(), [typeFilter](const LogEntry& e) { return e.type & typeFilter; })); } -namespace +inline +std::wstring getMessageTypeLabel(MessageType type) { -std::wstring formatMessageImpl(const LogEntry& entry) -{ - auto getTypeName = [&] - { - switch (entry.type) + switch (type) { case MSG_TYPE_INFO: return _("Info"); @@ -94,32 +92,33 @@ std::wstring formatMessageImpl(const LogEntry& entry) } assert(false); return std::wstring(); - }; +} + + +inline +std::wstring formatMessage(const LogEntry& entry) +{ + std::wstring msgFmt = L"[" + formatTime(FORMAT_TIME, getLocalTime(entry.time)) + L"] " + getMessageTypeLabel(entry.type) + L": "; + const size_t prefixLen = unicodeLength(msgFmt); //consider Unicode! - std::wstring msgFmt = L"[" + formatTime(FORMAT_TIME, getLocalTime(entry.time)) + L"] " + getTypeName() + L": "; - const size_t prefixLen = msgFmt.size(); //considers UTF-16 only! + const Zstringw msg = trimCpy(entry.message); + static_assert(std::is_same_v, "don't worry about copying as long as we're using a ref-counted string!"); - for (auto it = entry.message.begin(); it != entry.message.end(); ) + for (auto it = msg.begin(); it != msg.end(); ) if (*it == L'\n') { msgFmt += L'\n'; msgFmt.append(prefixLen, L' '); - - do //skip duplicate newlines - { - ++it; - } - while (it != entry.message.end() && *it == L'\n'); + ++it; + //skip duplicate newlines + for (;it != msg.end() && *it == L'\n'; ++it) + ; } else msgFmt += *it++; - return msgFmt; -} + return msgFmt += L'\n'; } - -inline -std::wstring formatMessage(const LogEntry& entry) { return formatMessageImpl(entry); } } #endif //ERROR_LOG_H_8917590832147915 diff --git a/zen/http.cpp b/zen/http.cpp index 4f2c5205..8cd99d7a 100644 --- a/zen/http.cpp +++ b/zen/http.cpp @@ -18,15 +18,19 @@ class HttpInputStream::Impl { public: Impl(const Zstring& url, - const std::vector>* postParams, //issue POST if bound, GET otherwise + const std::string* postBuf /*issue POST if bound, GET otherwise*/, + const Zstring& contentType, //required for POST bool disableGetCache /*not relevant for POST (= never cached)*/, const Zstring& userAgent, const Zstring* caCertFilePath /*optional: enable certificate validation*/, - const IOCallback& notifyUnbufferedIO) : //throw SysError + const IOCallback& notifyUnbufferedIO) : //throw SysError, X notifyUnbufferedIO_(notifyUnbufferedIO) { ZEN_ON_SCOPE_FAIL(cleanup(); /*destructor call would lead to member double clean-up!!!*/); + //may be sending large POST: call back first + if (notifyUnbufferedIO_) notifyUnbufferedIO_(0); //throw X + const Zstring urlFmt = afterFirst(url, Zstr("://"), IF_MISSING_RETURN_NONE); const Zstring server = beforeFirst(urlFmt, Zstr('/'), IF_MISSING_RETURN_ALL); const Zstring page = Zstr('/') + afterFirst(urlFmt, Zstr('/'), IF_MISSING_RETURN_NONE); @@ -40,6 +44,13 @@ public: throw SysError(L"URL uses unexpected protocol."); }(); + assert(postBuf || contentType.empty()); + + std::map headers; + + if (postBuf && !contentType.empty()) + headers["Content-Type"] = utfTo(contentType); + if (useTls) //HTTP default port: 443, see %WINDIR%\system32\drivers\etc\services { socket_ = std::make_unique(server, Zstr("https")); //throw SysError @@ -49,27 +60,23 @@ public: socket_ = std::make_unique(server, Zstr("http")); //throw SysError //we don't support "chunked and gzip transfer encoding" => HTTP 1.0 - std::map headers; headers["Host" ] = utfTo(server); //only required for HTTP/1.1 but a few servers expect it even for HTTP/1.0 headers["User-Agent"] = utfTo(userAgent); headers["Accept" ] = "*/*"; //won't hurt? - const std::string postBuf = postParams ? xWwwFormUrlEncode(*postParams) : ""; - - if (!postParams /*HTTP GET*/ && disableGetCache) + if (!postBuf /*HTTP GET*/ && disableGetCache) headers["Pragma"] = "no-cache"; //HTTP 1.0 only! superseeded by "Cache-Control" - else //HTTP POST - { - headers["Content-Type"] = "application/x-www-form-urlencoded"; - headers["Content-Length"] = numberTo(postBuf.size()); - } + + if (postBuf) + headers["Content-Length"] = numberTo(postBuf->size()); //https://www.w3.org/Protocols/HTTP/1.0/spec.html#Request-Line - std::string msg = (postParams ? "POST " : "GET ") + utfTo(page) + " HTTP/1.0\r\n"; + std::string msg = (postBuf ? "POST " : "GET ") + utfTo(page) + " HTTP/1.0\r\n"; for (const auto& [name, value] : headers) msg += name + ": " + value + "\r\n"; msg += "\r\n"; - msg += postBuf; + if (postBuf) + msg += *postBuf; //send request for (size_t bytesToSend = msg.size(); bytesToSend > 0;) @@ -121,6 +128,9 @@ public: //try to get "Content-Length" header if available if (const std::string* value = getHeader("Content-Length")) contentRemaining_ = stringTo(*value) - (bufPosEnd_ - bufPos_); + + //let's not get too finicky: at least report the logical amount of bytes sent/received (excluding HTTP headers) + if (notifyUnbufferedIO_) notifyUnbufferedIO_(postBuf ? postBuf->size() : 0); //throw X } ~Impl() { cleanup(); } @@ -225,16 +235,17 @@ std::string HttpInputStream::readAll() { return bufferedLoad(*pimpl namespace { std::unique_ptr sendHttpRequestImpl(const Zstring& url, - const std::vector>* postParams /*issue POST if bound, GET otherwise*/, + const std::string* postBuf /*issue POST if bound, GET otherwise*/, + const Zstring& contentType, //required for POST const Zstring& userAgent, const Zstring* caCertFilePath /*optional: enable certificate validation*/, - const IOCallback& notifyUnbufferedIO) //throw SysError + const IOCallback& notifyUnbufferedIO) //throw SysError, X { Zstring urlRed = url; //"A user agent should not automatically redirect a request more than five times, since such redirections usually indicate an infinite loop." for (int redirects = 0; redirects < 6; ++redirects) { - auto response = std::make_unique(urlRed, postParams, false /*disableGetCache*/, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError + auto response = std::make_unique(urlRed, postBuf, contentType, false /*disableGetCache*/, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError, X //https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection const int httpStatusCode = response->getStatusCode(); @@ -258,48 +269,48 @@ std::unique_ptr sendHttpRequestImpl(const Zstring& url, } -//encode into "application/x-www-form-urlencoded" +//encode for "application/x-www-form-urlencoded" std::string urlencode(const std::string& str) { - std::string out; - for (const char c : str) //follow PHP spec: https://github.com/php/php-src/blob/master/ext/standard/url.c#L500 + std::string output; + for (const char c : str) //follow PHP spec: https://github.com/php/php-src/blob/e99d5d39239c611e1e7304e79e88545c4e71a073/ext/standard/url.c#L455 if (c == ' ') - out += '+'; + output += '+'; else if (('0' <= c && c <= '9') || ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || c == '-' || c == '.' || c == '_') //note: "~" is encoded by PHP! - out += c; + output += c; else { const auto [high, low] = hexify(c); - out += '%'; - out += high; - out += low; + output += '%'; + output += high; + output += low; } - return out; + return output; } std::string urldecode(const std::string& str) { - std::string out; + std::string output; for (size_t i = 0; i < str.size(); ++i) { const char c = str[i]; if (c == '+') - out += ' '; + output += ' '; else if (c == '%' && str.size() - i >= 3 && isHexDigit(str[i + 1]) && isHexDigit(str[i + 2])) { - out += unhexify(str[i + 1], str[i + 2]); + output += unhexify(str[i + 1], str[i + 2]); i += 2; } else - out += c; + output += c; } - return out; + return output; } } @@ -327,16 +338,24 @@ std::vector> zen::xWwwFormUrlDecode(const st } +HttpInputStream zen::sendHttpGet(const Zstring& url, const Zstring& userAgent, const Zstring* caCertFilePath, const IOCallback& notifyUnbufferedIO) //throw SysError, X +{ + return sendHttpRequestImpl(url, nullptr /*postBuf*/, Zstr("") /*contentType*/, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError, X, X +} + + HttpInputStream zen::sendHttpPost(const Zstring& url, const std::vector>& postParams, - const Zstring& userAgent, const Zstring* caCertFilePath, const IOCallback& notifyUnbufferedIO) //throw SysError + const Zstring& userAgent, const Zstring* caCertFilePath, const IOCallback& notifyUnbufferedIO) //throw SysError, X { - return sendHttpRequestImpl(url, &postParams, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError + return sendHttpPost(url, xWwwFormUrlEncode(postParams), Zstr("application/x-www-form-urlencoded"), userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError, X } -HttpInputStream zen::sendHttpGet(const Zstring& url, const Zstring& userAgent, const Zstring* caCertFilePath, const IOCallback& notifyUnbufferedIO) //throw SysError + +HttpInputStream zen::sendHttpPost(const Zstring& url, const std::string& postBuf, const Zstring& contentType, + const Zstring& userAgent, const Zstring* caCertFilePath, const IOCallback& notifyUnbufferedIO) //throw SysError, X { - return sendHttpRequestImpl(url, nullptr /*postParams*/, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError + return sendHttpRequestImpl(url, &postBuf, contentType, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError, X } @@ -346,6 +365,7 @@ bool zen::internetIsAlive() //noexcept { auto response = std::make_unique(Zstr("http://www.google.com/"), nullptr /*postParams*/, + Zstr("") /*contentType*/, true /*disableGetCache*/, Zstr("FreeFileSync"), nullptr /*caCertFilePath*/, @@ -439,4 +459,79 @@ std::wstring zen::formatHttpStatusCode(int sc) return trimCpy(replaceCpy(L"HTTP status %x.", L"%x", numberTo(sc))); else return trimCpy(replaceCpy(L"HTTP status %x: ", L"%x", numberTo(sc)) + statusText); -} \ No newline at end of file +} + + +bool zen::isValidEmail(const Zstring& email) +{ + //https://en.wikipedia.org/wiki/Email_address#Syntax + //https://tools.ietf.org/html/rfc3696 => note errata! https://www.rfc-editor.org/errata_search.php?rfc=3696 + //https://tools.ietf.org/html/rfc5321 + std::string local = utfTo(beforeLast(email, Zstr('@'), IF_MISSING_RETURN_NONE)); + std::string domain = utfTo( afterLast(email, Zstr('@'), IF_MISSING_RETURN_NONE)); + //consider: "t@st"@email.com t\@st@email.com" + + auto stripComments = [](std::string& part) + { + if (startsWith(part, '(')) + part = afterFirst(part, ')', IF_MISSING_RETURN_NONE); + + if (endsWith(part, ')')) + part = beforeLast(part, '(', IF_MISSING_RETURN_NONE); + }; + stripComments(local); + stripComments(domain); + + if (local .empty() || local .size() > 63 || // 64 octets -> 63 ASCII chars: https://devblogs.microsoft.com/oldnewthing/20120412-00/?p=7873 + domain.empty() || domain.size() > 253) //255 octets -> 253 ASCII chars + return false; + //--------------------------------------------------------------------- + + const bool quoted = (startsWith(local, '"') && endsWith(local, '"')) || + contains(local, '\\'); //e.g. "t\@st@email.com" + if (!quoted) //I'm not going to parse and validate this! + for (const std::string& comp : split(local, '.', SplitType::ALLOW_EMPTY)) + if (comp.empty() || !std::all_of(comp.begin(), comp.end(), [](char c) + { + const char printable[] = "!#$%&'*+-/=?^_`{|}~"; + return isAsciiAlpha(c) || isDigit(c) || makeUnsigned(c) >= 128 || + std::find(std::begin(printable), std::end(printable), c) != std::end(printable); + })) + return false; + //--------------------------------------------------------------------- + + //e.g. jsmith@[192.168.2.1] jsmith@[IPv6:2001:db8::1] + const bool likelyIp = startsWith(domain, '[') && endsWith(domain, ']'); + if (!likelyIp) //not interested in parsing IPs! + { + if (!contains(domain, '.')) + return false; + + for (const std::string& comp : split(domain, '.', SplitType::ALLOW_EMPTY)) + if (comp.empty() || comp.size() > 63 || + !std::all_of(comp.begin(), comp.end(), [](char c) { return isAsciiAlpha(c) ||isDigit(c) || makeUnsigned(c) >= 128 || c == '-'; })) + return false; + } + + return true; +} + + +std::string zen::htmlSpecialChars(const std::string& str) +{ + //mirror PHP: https://github.com/php/php-src/blob/e99d5d39239c611e1e7304e79e88545c4e71a073/ext/standard/html_tables.h#L6189 + std::string output; + for (const char c : str) + switch (c) + { + //*INDENT-OFF* + case '&': output += "&" ; break; + case '"': output += """; break; + case '<': output += "<" ; break; + case '>': output += ">" ; break; + //case '\'': output += "'"; break; -> not encoded by default (needs ENT_QUOTES) + default: output += c; break; + //*INDENT-ON* + } + return output; +} diff --git a/zen/http.h b/zen/http.h index 42b0e279..fbaa09de 100644 --- a/zen/http.h +++ b/zen/http.h @@ -39,15 +39,24 @@ private: HttpInputStream sendHttpGet(const Zstring& url, const Zstring& userAgent, const Zstring* caCertFilePath /*optional: enable certificate validation*/, - const IOCallback& notifyUnbufferedIO /*throw X*/); //throw SysError + const IOCallback& notifyUnbufferedIO /*throw X*/); //throw SysError, X HttpInputStream sendHttpPost(const Zstring& url, const std::vector>& postParams, const Zstring& userAgent, const Zstring* caCertFilePath /*optional: enable certificate validation*/, - const IOCallback& notifyUnbufferedIO /*throw X*/); + const IOCallback& notifyUnbufferedIO /*throw X*/); //throw SysError, X + +HttpInputStream sendHttpPost(const Zstring& url, + const std::string& postBuf, const Zstring& contentType, + const Zstring& userAgent, + const Zstring* caCertFilePath /*optional: enable certificate validation*/, + const IOCallback& notifyUnbufferedIO /*throw X*/); //throw SysError, X + bool internetIsAlive(); //noexcept std::wstring formatHttpStatusCode(int httpStatusCode); +bool isValidEmail(const Zstring& email); +std::string htmlSpecialChars(const std::string& str); std::string xWwwFormUrlEncode(const std::vector>& paramPairs); std::vector> xWwwFormUrlDecode(const std::string& str); diff --git a/zen/json.h b/zen/json.h index e6464286..725874f7 100644 --- a/zen/json.h +++ b/zen/json.h @@ -27,16 +27,18 @@ struct JsonValue }; explicit JsonValue() {} - explicit JsonValue(Type t) : type(t) {} - explicit JsonValue(bool b) : type(Type::boolean), primVal(b ? "true" : "false") {} - explicit JsonValue(int64_t num) : type(Type::number), primVal(numberTo(num)) {} - explicit JsonValue(double num) : type(Type::number), primVal(numberTo(num)) {} - explicit JsonValue(const std::string& str) : type(Type::string), primVal(str) {} + explicit JsonValue(Type t) : type(t) {} + explicit JsonValue(bool b) : type(Type::boolean), primVal(b ? "true" : "false") {} + explicit JsonValue(int num) : type(Type::number), primVal(numberTo(num)) {} + explicit JsonValue(int64_t num) : type(Type::number), primVal(numberTo(num)) {} + explicit JsonValue(double num) : type(Type::number), primVal(numberTo(num)) {} + explicit JsonValue(std::string str) : type(Type::string), primVal(std::move(str)) {} //unifying assignment + explicit JsonValue(const void*) = delete; //catch usage errors e.g. const char* -> JsonValue(bool) Type type = Type::null; - std::string primVal; //for primitive types - std::map> objectVal; //"[...] most implementations of JSON libraries do not accept duplicate keys [...]" => fine! - std::vector> arrayVal; + std::string primVal; //for primitive types + std::map objectVal; //"[...] most implementations of JSON libraries do not accept duplicate keys [...]" => fine! + std::vector arrayVal; }; @@ -66,7 +68,7 @@ const JsonValue* getChildFromJsonObject(const JsonValue& jvalue, const std::stri if (it == jvalue.objectVal.end()) return nullptr; - return it->second.get(); + return &it->second; } @@ -240,8 +242,8 @@ void serialize(const JsonValue& jval, std::string& stream, stream += '"' + jsonEscape(childName) + "\":"; - if ((childValue->type == JsonValue::Type::object && !childValue->objectVal.empty()) || - (childValue->type == JsonValue::Type::array && !childValue->arrayVal .empty())) + if ((childValue.type == JsonValue::Type::object && !childValue.objectVal.empty()) || + (childValue.type == JsonValue::Type::array && !childValue.arrayVal .empty())) { stream += lineBreak; writeIndent(indentLevel + 1); @@ -249,7 +251,7 @@ void serialize(const JsonValue& jval, std::string& stream, else if (!indent.empty()) stream += ' '; - serialize(*childValue, stream, lineBreak, indent, indentLevel + 1); + serialize(childValue, stream, lineBreak, indent, indentLevel + 1); } stream += lineBreak; writeIndent(indentLevel); @@ -263,7 +265,7 @@ void serialize(const JsonValue& jval, std::string& stream, { for (auto it = jval.arrayVal.begin(); it != jval.arrayVal.end(); ++it) { - const auto& childValue = **it; + const auto& childValue = *it; if (it != jval.arrayVal.begin()) stream += ','; @@ -462,7 +464,7 @@ private: consumeToken(Token::Type::colon); //throw JsonParsingError JsonValue value = parseValue(); //throw JsonParsingError - jval.objectVal.emplace(std::move(name), std::make_unique(std::move(value))); + jval.objectVal.emplace(std::move(name), std::move(value)); if (token().type != Token::Type::comma) break; @@ -482,7 +484,7 @@ private: for (;;) { JsonValue value = parseValue(); //throw JsonParsingError - jval.arrayVal.emplace_back(std::make_unique(std::move(value))); + jval.arrayVal.emplace_back(std::move(value)); if (token().type != Token::Type::comma) break; diff --git a/zen/serialize.h b/zen/serialize.h index bdeec858..dd884e3b 100644 --- a/zen/serialize.h +++ b/zen/serialize.h @@ -120,7 +120,7 @@ private: template struct MemoryStreamIn { - MemoryStreamIn(const BinContainer& cont) : buffer_(cont) {} //this better be cheap! + explicit MemoryStreamIn(const BinContainer& cont) : buffer_(cont) {} //this better be cheap! size_t read(void* buffer, size_t bytesToRead) //return "bytesToRead" bytes unless end of stream! { @@ -207,11 +207,12 @@ BinContainer bufferedLoad(BufferedInputStream& streamIn) //throw X for (;;) { buffer.resize(buffer.size() + blockSize); - const size_t bytesRead = streamIn.read(&*(buffer.end() - blockSize), blockSize); //throw X; return "bytesToRead" bytes unless end of stream! - buffer.resize(buffer.size() - blockSize + bytesRead); //caveat: unsigned arithmetics - + const size_t bytesRead = streamIn.read(&*(buffer.end() - blockSize), blockSize); //throw X; return "blockSize" bytes unless end of stream! if (bytesRead < blockSize) //end of file + { + buffer.resize(buffer.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics return buffer; + } } } @@ -270,12 +271,11 @@ C readContainer(BufferedInputStream& stream) //throw UnexpectedEndOfStreamError { try { - cont.resize(strLength); //throw std::bad_alloc - } - catch (std::bad_alloc&) //most likely this is due to data corruption! - { - throw UnexpectedEndOfStreamError(); + cont.resize(strLength); //throw std::length_error } + catch (std::length_error&) { throw UnexpectedEndOfStreamError(); } //most likely this is due to data corruption! + catch ( std::bad_alloc&) { throw UnexpectedEndOfStreamError(); } // + readArray(stream, &*cont.begin(), sizeof(typename C::value_type) * strLength); //throw UnexpectedEndOfStreamError } return cont; diff --git a/zen/shell_execute.h b/zen/shell_execute.h index 56322236..580c4558 100644 --- a/zen/shell_execute.h +++ b/zen/shell_execute.h @@ -19,15 +19,15 @@ namespace zen //Windows: COM needs to be initialized before calling this function! enum class ExecutionType { - SYNC, - ASYNC + sync, + async }; namespace { -void shellExecute(const Zstring& command, ExecutionType type, bool hideConsole) //throw FileError +int shellExecute(const Zstring& command, ExecutionType type, bool hideConsole) //throw FileError { /* we cannot use wxExecute due to various issues: @@ -35,21 +35,23 @@ void shellExecute(const Zstring& command, ExecutionType type, bool hideConsole) - does not provide any reasonable error information - uses a zero-sized dummy window as a hack to keep focus which leaves a useless empty icon in ALT-TAB list in Windows */ - if (type == ExecutionType::SYNC) + if (type == ExecutionType::sync) { //Posix ::system() - execute a shell command const int rv = ::system(command.c_str()); //do NOT use std::system as its documentation says nothing about "WEXITSTATUS(rv)", etc... if (rv == -1 || WEXITSTATUS(rv) == 127) - throw FileError(_("Incorrect command line:") + L"\n" + utfTo(command)); + throw FileError(_("Incorrect command line:") + L' ' + utfTo(command)); //https://linux.die.net/man/3/system "In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127)" //Bonus: For an incorrect command line /bin/sh also returns with 127! + + return /*int exitCode = */ WEXITSTATUS(rv); } else { //follow implemenation of ::system() except for waitpid(): const pid_t pid = ::fork(); if (pid < 0) //pids are never negative, empiric proof: https://linux.die.net/man/2/wait - THROW_LAST_FILE_ERROR(_("Incorrect command line:") + L"\n" + utfTo(command), L"fork"); + THROW_LAST_FILE_ERROR(_("Incorrect command line:") + L' ' + utfTo(command), L"fork"); if (pid == 0) //child process { @@ -64,7 +66,39 @@ void shellExecute(const Zstring& command, ExecutionType type, bool hideConsole) ::_exit(127); //[!] avoid flushing I/O buffers or doing other clean up from child process like with "exit(127)"! } //else //parent process + return 0; + } +} + + +std::string getCommandOutput(const Zstring& command) //throw SysError +{ + //https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/popen.3.html + FILE* pipe = ::popen(command.c_str(), "r"); + if (!pipe) + THROW_LAST_SYS_ERROR(L"popen"); + ZEN_ON_SCOPE_EXIT(::pclose(pipe)); + + std::string output; + const size_t blockSize = 64 * 1024; + do + { + output.resize(output.size() + blockSize); + + //caveat: SIGCHLD is NOT ignored under macOS debugger => EINTR inside fread() => call ::siginterrupt(SIGCHLD, false) during startup + const size_t bytesRead = ::fread(&*(output.end() - blockSize), 1, blockSize, pipe); + if (::ferror(pipe)) + THROW_LAST_SYS_ERROR(L"fread"); + + if (bytesRead > blockSize) + throw SysError(L"fread: buffer overflow"); + + if (bytesRead < blockSize) + output.resize(output.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics } + while (!::feof(pipe)); + + return output; } } @@ -72,7 +106,7 @@ void shellExecute(const Zstring& command, ExecutionType type, bool hideConsole) inline void openWithDefaultApplication(const Zstring& itemPath) //throw FileError { - shellExecute("xdg-open \"" + itemPath + '"', ExecutionType::ASYNC, false /*hideConsole*/); //throw FileError + shellExecute("xdg-open \"" + itemPath + '"', ExecutionType::async, false /*hideConsole*/); //throw FileError } } diff --git a/zen/shutdown.cpp b/zen/shutdown.cpp index 5ce586f0..89da55ee 100644 --- a/zen/shutdown.cpp +++ b/zen/shutdown.cpp @@ -18,7 +18,7 @@ void zen::shutdownSystem() //throw FileError //https://linux.die.net/man/2/reboot => needs admin rights! //"systemctl" should work without admin rights: - shellExecute("systemctl poweroff", ExecutionType::SYNC, false/*hideConsole*/); //throw FileError + shellExecute("systemctl poweroff", ExecutionType::sync, false/*hideConsole*/); //throw FileError } @@ -26,7 +26,7 @@ void zen::shutdownSystem() //throw FileError void zen::suspendSystem() //throw FileError { //"systemctl" should work without admin rights: - shellExecute("systemctl suspend", ExecutionType::SYNC, false/*hideConsole*/); //throw FileError + shellExecute("systemctl suspend", ExecutionType::sync, false/*hideConsole*/); //throw FileError } diff --git a/zen/string_tools.h b/zen/string_tools.h index 47271bc7..5c444830 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -27,7 +27,7 @@ template bool isLineBreak (Char c); template bool isDigit (Char c); //not exactly the same as "std::isdigit" -> we consider '0'-'9' only! template bool isHexDigit (Char c); template bool isAsciiAlpha(Char c); -template bool isAsciiString(const Char* str); +template bool isAsciiString(const S& str); template Char asciiToLower(Char c); template Char asciiToUpper(Char c); @@ -150,14 +150,11 @@ bool isAsciiAlpha(Char c) } -template inline -bool isAsciiString(const Char* str) +template inline +bool isAsciiString(const S& str) { - static_assert(std::is_same_v || std::is_same_v); - for (Char c = *str; c != 0; c = *++str) - if (zen::makeUnsigned(c) >= 128) - return false; - return true; + const auto* const first = strBegin(str); + return std::all_of(first, first + strLength(str), [](auto c) { return makeUnsigned(c) < 128; }); } @@ -170,8 +167,8 @@ Char asciiToLower(Char c) } - template inline - Char asciiToUpper(Char c) +template inline +Char asciiToUpper(Char c) { if (static_cast('a') <= c && c <= static_cast('z')) return static_cast(c - static_cast('a') + static_cast('A')); @@ -604,7 +601,7 @@ S numberTo(const Num& number, std::integral_constant(buffer), strEnd, @@ -703,7 +700,7 @@ 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); + return from_chars(first, last); } @@ -713,7 +710,7 @@ 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(c); }); - return zen::from_chars(buf.c_str(), buf.c_str() + buf.size()); + return from_chars(buf.c_str(), buf.c_str() + buf.size()); } diff --git a/zen/sys_error.h b/zen/sys_error.h index 57503732..a9347bdd 100644 --- a/zen/sys_error.h +++ b/zen/sys_error.h @@ -85,9 +85,8 @@ std::wstring formatSystemError(const std::wstring& functionName, long long lastE inline std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec) { - const std::wstring errorDescr = formatSystemErrorRaw(ec); const std::wstring errorCode = numberTo(ec); - //const std::wstring errorCode = printNumber(L"0x%08x", static_cast(ec)); + const std::wstring errorDescr = formatSystemErrorRaw(ec); return formatSystemError(functionName, replaceCpy(_("Error Code %x"), L"%x", errorCode), errorDescr); } diff --git a/zen/system.cpp b/zen/system.cpp new file mode 100644 index 00000000..5945484f --- /dev/null +++ b/zen/system.cpp @@ -0,0 +1,84 @@ +// ***************************************************************************** +// * 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 "system.h" +#include "file_access.h" +#include "crc.h" + + #include "symlink_target.h" + #include "file_io.h" + #include + #include //IFF_LOOPBACK + #include //sockaddr_ll + + #include //getuid() + #include //getpwuid_r() + #include "shell_execute.h" + +using namespace zen; + + +std::wstring zen::getUserName() //throw FileError +{ + const uid_t userIdNo = ::getuid(); //never fails + + std::vector buffer(std::max(10000, ::sysconf(_SC_GETPW_R_SIZE_MAX))); //::sysconf may return long(-1) + struct passwd buffer2 = {}; + struct passwd* pwsEntry = nullptr; + if (::getpwuid_r(userIdNo, &buffer2, &buffer[0], buffer.size(), &pwsEntry) != 0) //getlogin() is deprecated and not working on Ubuntu at all!!! + THROW_LAST_FILE_ERROR(_("Cannot get process information."), L"getpwuid_r"); + if (!pwsEntry) + throw FileError(_("Cannot get process information."), L"no login found"); //should not happen? + + return utfTo(pwsEntry->pw_name); +} + + +namespace +{ +} + + +ComputerModel zen::getComputerModel() //throw FileError +{ + try + { + auto tryGetInfo = [](const Zstring& filePath) + { + if (!fileAvailable(filePath)) + return std::wstring(); + try + { + const std::string stream = loadBinContainer(filePath, nullptr /*notifyUnbufferedIO*/); //throw FileError + return utfTo(trimCpy(stream)); + } + catch (const FileError& e) { throw SysError(e.toString()); } //errors should be further enriched by context info => SysError + }; + return { tryGetInfo("/sys/devices/virtual/dmi/id/product_name"), //throw SysError + tryGetInfo("/sys/devices/virtual/dmi/id/sys_vendor") }; // + + } + catch (const SysError& e) { throw FileError(_("Cannot get process information."), e.toString()); } +} + + + + +std::wstring zen::getOsDescription() //throw FileError +{ + try + { + const std::string osName = trimCpy(getCommandOutput("lsb_release --id -s" )); //throw SysError + const std::string osVersion = trimCpy(getCommandOutput("lsb_release --release -s")); // + return utfTo(osName + " " + osVersion); //e.g. "CentOS 7.7.1908" + + } + catch (const SysError& e) { throw FileError(_("Cannot get process information."), e.toString()); } +} + + + + diff --git a/zen/system.h b/zen/system.h new file mode 100644 index 00000000..f10a6a40 --- /dev/null +++ b/zen/system.h @@ -0,0 +1,33 @@ +// ***************************************************************************** +// * 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 * +// ***************************************************************************** + +#ifndef SYSTEM_H_4189731847832147508915 +#define SYSTEM_H_4189731847832147508915 + +#include "file_error.h" + + +namespace zen +{ +//COM needs to be initialized before calling any of these functions! CoInitializeEx/CoUninitialize + +std::wstring getUserName(); //throw FileError + +struct ComputerModel +{ + std::wstring model; //best-effort: empty if not available + std::wstring vendor; // +}; +ComputerModel getComputerModel(); //throw FileError + + + +std::wstring getOsDescription(); //throw FileError + + +} + +#endif //SYSTEM_H_4189731847832147508915 diff --git a/zen/zlib_wrap.cpp b/zen/zlib_wrap.cpp index f7418b88..57a0f33c 100644 --- a/zen/zlib_wrap.cpp +++ b/zen/zlib_wrap.cpp @@ -149,3 +149,23 @@ private: zen::InputStreamAsGzip::InputStreamAsGzip(const std::function& readBlock /*throw X*/) : pimpl_(std::make_unique(readBlock)) {} //throw SysError zen::InputStreamAsGzip::~InputStreamAsGzip() {} size_t zen::InputStreamAsGzip::read(void* buffer, size_t bytesToRead) { return pimpl_->read(buffer, bytesToRead); } //throw SysError, X + + +std::string zen::compressAsGzip(const void* buffer, size_t bufSize) //throw SysError +{ + struct MemoryStreamAsGzip : InputStreamAsGzip + { + explicit MemoryStreamAsGzip(const std::function& readBlock /*throw X*/) : InputStreamAsGzip(readBlock) {} //throw SysError + static size_t getBlockSize() { return 128 * 1024; } //InputStreamAsGzip has no idea what it's wrapping => has no getBlockSize() member! + }; + + MemoryStreamAsGzip gzipStream([&](void* bufIn, size_t bytesToRead) //throw SysError + { + const size_t bytesRead = std::min(bufSize, bytesToRead); + std::memcpy(bufIn, buffer, bytesRead); + buffer = static_cast(buffer) + bytesRead; + bufSize -= bytesRead; + return bytesRead; //returning 0 signals EOF: Posix read() semantics + }); + return bufferedLoad(gzipStream); //throw SysError +} diff --git a/zen/zlib_wrap.h b/zen/zlib_wrap.h index 9d9229ac..b820a4f8 100644 --- a/zen/zlib_wrap.h +++ b/zen/zlib_wrap.h @@ -27,8 +27,8 @@ BinContainer decompress(const BinContainer& stream); //throw SysError class InputStreamAsGzip //convert input stream into gzip on the fly { public: - InputStreamAsGzip( //throw SysError - const std::function& readBlock /*throw X*/); //returning 0 signals EOF: Posix read() semantics + explicit InputStreamAsGzip( //throw SysError + const std::function& readBlock /*throw X; returning 0 signals EOF: Posix read() semantics*/); ~InputStreamAsGzip(); size_t read(void* buffer, size_t bytesToRead); //throw SysError, X; return "bytesToRead" bytes unless end of stream! @@ -38,6 +38,7 @@ private: const std::unique_ptr pimpl_; }; +std::string compressAsGzip(const void* buffer, size_t bufSize); //throw SysError @@ -103,10 +104,9 @@ BinContainer decompress(const BinContainer& stream) //throw SysError { contOut.resize(static_cast(uncompressedSize)); //throw std::bad_alloc } - catch (const std::bad_alloc& e) //most likely due to data corruption! - { - throw SysError(L"zlib error: " + _("Out of memory.") + L" " + utfTo(e.what())); - } + //most likely this is due to data corruption: + catch (const std::length_error& e) { throw SysError(L"zlib error: " + _("Out of memory.") + L" " + utfTo(e.what())); } + catch (const std::bad_alloc& e) { throw SysError(L"zlib error: " + _("Out of memory.") + L" " + utfTo(e.what())); } const size_t bytesWritten = impl::zlib_decompress(&*stream.begin() + sizeof(uncompressedSize), stream.size() - sizeof(uncompressedSize), diff --git a/zen/zstring.cpp b/zen/zstring.cpp index f018b14f..ff20b8cf 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -17,7 +17,7 @@ using namespace zen; Zstring makeUpperCopy(const Zstring& str) { //fast pre-check: - if (isAsciiString(str.c_str())) //perf: in the range of 3.5ns + if (isAsciiString(str)) //perf: in the range of 3.5ns { Zstring output = str; for (Zchar& c : output) c = asciiToUpper(c); @@ -49,7 +49,7 @@ Zstring makeUpperCopy(const Zstring& str) Zstring getUnicodeNormalForm(const Zstring& str) { //fast pre-check: - if (isAsciiString(str.c_str())) //perf: in the range of 3.5ns + if (isAsciiString(str)) //perf: in the range of 3.5ns return str; //god bless our ref-counting! => save output string memory consumption! //Example: const char* decomposed = "\x6f\xcc\x81"; -- cgit