summaryrefslogtreecommitdiff
path: root/zen
diff options
context:
space:
mode:
authorB Stack <bgstack15@gmail.com>2020-02-15 11:50:31 -0500
committerB Stack <bgstack15@gmail.com>2020-02-15 11:50:31 -0500
commit791b90b9898cc41869538f1dfc303588436682b7 (patch)
tree02cc7f817d95ce3f21207cbaba130e3d537fc1eb /zen
parentMerge branch '10.19' into 'master' (diff)
downloadFreeFileSync-791b90b9898cc41869538f1dfc303588436682b7.tar.gz
FreeFileSync-791b90b9898cc41869538f1dfc303588436682b7.tar.bz2
FreeFileSync-791b90b9898cc41869538f1dfc303588436682b7.zip
add upstream 10.20
It is worth noting that the send email feature is not present in the GPL release.
Diffstat (limited to 'zen')
-rw-r--r--zen/error_log.h47
-rw-r--r--zen/http.cpp165
-rw-r--r--zen/http.h13
-rw-r--r--zen/json.h32
-rw-r--r--zen/serialize.h18
-rw-r--r--zen/shell_execute.h48
-rw-r--r--zen/shutdown.cpp4
-rw-r--r--zen/string_tools.h23
-rw-r--r--zen/sys_error.h3
-rw-r--r--zen/system.cpp84
-rw-r--r--zen/system.h33
-rw-r--r--zen/zlib_wrap.cpp20
-rw-r--r--zen/zlib_wrap.h12
-rw-r--r--zen/zstring.cpp4
14 files changed, 389 insertions, 117 deletions
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 <cassert>
#include <algorithm>
#include <vector>
-#include <string>
+//#include <string>
#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<int>(std::count_if(entries_.begin(), entries_.end(), [&](const LogEntry& e) { return e.type & typeFilter; }));
+ return static_cast<int>(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<std::wstring>(FORMAT_TIME, getLocalTime(entry.time)) + L"] " + getMessageTypeLabel(entry.type) + L": ";
+ const size_t prefixLen = unicodeLength(msgFmt); //consider Unicode!
- std::wstring msgFmt = L"[" + formatTime<std::wstring>(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<decltype(msg), const Zstringw>, "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<std::pair<std::string, std::string>>* 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<std::string, std::string, LessAsciiNoCase> headers;
+
+ if (postBuf && !contentType.empty())
+ headers["Content-Type"] = utfTo<std::string>(contentType);
+
if (useTls) //HTTP default port: 443, see %WINDIR%\system32\drivers\etc\services
{
socket_ = std::make_unique<Socket>(server, Zstr("https")); //throw SysError
@@ -49,27 +60,23 @@ public:
socket_ = std::make_unique<Socket>(server, Zstr("http")); //throw SysError
//we don't support "chunked and gzip transfer encoding" => HTTP 1.0
- std::map<std::string, std::string, LessAsciiNoCase> headers;
headers["Host" ] = utfTo<std::string>(server); //only required for HTTP/1.1 but a few servers expect it even for HTTP/1.0
headers["User-Agent"] = utfTo<std::string>(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<std::string>(postBuf.size());
- }
+
+ if (postBuf)
+ headers["Content-Length"] = numberTo<std::string>(postBuf->size());
//https://www.w3.org/Protocols/HTTP/1.0/spec.html#Request-Line
- std::string msg = (postParams ? "POST " : "GET ") + utfTo<std::string>(page) + " HTTP/1.0\r\n";
+ std::string msg = (postBuf ? "POST " : "GET ") + utfTo<std::string>(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<int64_t>(*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<std::string>(*pimpl
namespace
{
std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const Zstring& url,
- const std::vector<std::pair<std::string, std::string>>* 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<HttpInputStream::Impl>(urlRed, postParams, false /*disableGetCache*/, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError
+ auto response = std::make_unique<HttpInputStream::Impl>(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<HttpInputStream::Impl> 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<std::pair<std::string, std::string>> 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<std::pair<std::string, std::string>>& 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<HttpInputStream::Impl>(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<std::wstring>(L"HTTP status %x.", L"%x", numberTo<std::wstring>(sc)));
else
return trimCpy(replaceCpy<std::wstring>(L"HTTP status %x: ", L"%x", numberTo<std::wstring>(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<std::string>(beforeLast(email, Zstr('@'), IF_MISSING_RETURN_NONE));
+ std::string domain = utfTo<std::string>( 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 += "&amp;" ; break;
+ case '"': output += "&quot;"; break;
+ case '<': output += "&lt;" ; break;
+ case '>': output += "&gt;" ; break;
+ //case '\'': output += "&apos;"; 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<std::pair<std::string, std::string>>& 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<std::pair<std::string, std::string>>& paramPairs);
std::vector<std::pair<std::string, std::string>> 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<std::string>(num)) {}
- explicit JsonValue(double num) : type(Type::number), primVal(numberTo<std::string>(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<std::string>(num)) {}
+ explicit JsonValue(int64_t num) : type(Type::number), primVal(numberTo<std::string>(num)) {}
+ explicit JsonValue(double num) : type(Type::number), primVal(numberTo<std::string>(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<std::string, std::unique_ptr<JsonValue>> objectVal; //"[...] most implementations of JSON libraries do not accept duplicate keys [...]" => fine!
- std::vector<std::unique_ptr<JsonValue>> arrayVal;
+ std::string primVal; //for primitive types
+ std::map<std::string, JsonValue> objectVal; //"[...] most implementations of JSON libraries do not accept duplicate keys [...]" => fine!
+ std::vector<JsonValue> 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<JsonValue>(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<JsonValue>(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 <class BinContainer>
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<std::wstring>(command));
+ throw FileError(_("Incorrect command line:") + L' ' + utfTo<std::wstring>(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<std::wstring>(command), L"fork");
+ THROW_LAST_FILE_ERROR(_("Incorrect command line:") + L' ' + utfTo<std::wstring>(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 <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);
-template <class Char> bool isAsciiString(const Char* str);
+template <class S > bool isAsciiString(const S& str);
template <class Char> Char asciiToLower(Char c);
template <class Char> Char asciiToUpper(Char c);
@@ -150,14 +150,11 @@ bool isAsciiAlpha(Char c)
}
-template <class Char> inline
-bool isAsciiString(const Char* str)
+template <class S> inline
+bool isAsciiString(const S& str)
{
- static_assert(std::is_same_v<Char, char> || std::is_same_v<Char, wchar_t>);
- 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 <class Char> inline
- Char asciiToUpper(Char c)
+template <class Char> inline
+Char asciiToUpper(Char c)
{
if (static_cast<Char>('a') <= c && c <= static_cast<Char>('z'))
return static_cast<Char>(c - static_cast<Char>('a') + static_cast<Char>('A'));
@@ -604,7 +601,7 @@ S numberTo(const Num& number, std::integral_constant<NumberType, NumberType::FLO
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);
+ const char* strEnd = to_chars(std::begin(buffer), std::end(buffer), number);
S output;
std::for_each(static_cast<const char*>(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<char>(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<std::wstring>(ec);
- //const std::wstring errorCode = printNumber<std::wstring>(L"0x%08x", static_cast<int>(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 <ifaddrs.h>
+ #include <net/if.h> //IFF_LOOPBACK
+ #include <netpacket/packet.h> //sockaddr_ll
+
+ #include <unistd.h> //getuid()
+ #include <pwd.h> //getpwuid_r()
+ #include "shell_execute.h"
+
+using namespace zen;
+
+
+std::wstring zen::getUserName() //throw FileError
+{
+ const uid_t userIdNo = ::getuid(); //never fails
+
+ std::vector<char> buffer(std::max<long>(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<std::wstring>(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<std::string>(filePath, nullptr /*notifyUnbufferedIO*/); //throw FileError
+ return utfTo<std::wstring>(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<std::wstring>(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<size_t(void* buffer, size_t bytesToRead)>& readBlock /*throw X*/) : pimpl_(std::make_unique<Impl>(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<size_t(void* buffer, size_t bytesToRead)>& 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<const char*>(buffer) + bytesRead;
+ bufSize -= bytesRead;
+ return bytesRead; //returning 0 signals EOF: Posix read() semantics
+ });
+ return bufferedLoad<std::string>(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<size_t(void* buffer, size_t bytesToRead)>& readBlock /*throw X*/); //returning 0 signals EOF: Posix read() semantics
+ explicit InputStreamAsGzip( //throw SysError
+ const std::function<size_t(void* buffer, size_t bytesToRead)>& 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<Impl> 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<size_t>(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<std::wstring>(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<std::wstring>(e.what())); }
+ catch (const std::bad_alloc& e) { 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),
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";
bgstack15