diff options
Diffstat (limited to 'zen')
-rw-r--r-- | zen/http.cpp | 97 | ||||
-rw-r--r-- | zen/http.h | 1 | ||||
-rw-r--r-- | zen/json.h | 101 | ||||
-rw-r--r-- | zen/shell_execute.h | 2 | ||||
-rw-r--r-- | zen/zlib_wrap.cpp | 44 | ||||
-rw-r--r-- | zen/zlib_wrap.h | 33 |
6 files changed, 197 insertions, 81 deletions
diff --git a/zen/http.cpp b/zen/http.cpp index d4c30741..93651d0b 100644 --- a/zen/http.cpp +++ b/zen/http.cpp @@ -46,7 +46,7 @@ public: else //HTTP default port: 80, see %WINDIR%\system32\drivers\etc\services socket_ = std::make_unique<Socket>(server, Zstr("http")); //throw SysError - //we don't support "chunked and gzip transfer encoding" => HTTP 1.0 + //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); @@ -235,8 +235,8 @@ std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const Zstring& url, auto response = std::make_unique<HttpInputStream::Impl>(urlRed, postParams, false /*disableGetCache*/, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError //https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection - const int statusCode = response->getStatusCode(); - if (statusCode / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too! + const int httpStatusCode = response->getStatusCode(); + if (httpStatusCode / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too! { const std::string* value = response->getHeader("Location"); if (!value || value->empty()) @@ -246,9 +246,8 @@ std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const Zstring& url, } else { - if (statusCode != 200) //HTTP_STATUS_OK - throw SysError(replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(statusCode))); - //e.g. 404 - HTTP_STATUS_NOT_FOUND + if (httpStatusCode != 200) //HTTP_STATUS_OK(200) + throw SysError(formatHttpStatusCode(httpStatusCode)); //e.g. HTTP_STATUS_NOT_FOUND(404) return response; } @@ -271,7 +270,7 @@ std::string urlencode(const std::string& str) out += c; else { - const auto [high, low] = hexify(c); + const auto [high, low] = hexify(c); out += '%'; out += high; out += low; @@ -327,7 +326,7 @@ std::vector<std::pair<std::string, std::string>> zen::xWwwFormUrlDecode(const st 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 { return sendHttpRequestImpl(url, &postParams, userAgent, caCertFilePath, notifyUnbufferedIO); //throw SysError } @@ -357,3 +356,85 @@ bool zen::internetIsAlive() //noexcept } catch (SysError&) { return false; } } + + +std::wstring zen::formatHttpStatusCode(int sc) +{ + const wchar_t* statusText = [&] //https://en.wikipedia.org/wiki/List_of_HTTP_status_codes + { + switch (sc) + { + //*INDENT-OFF* + case 300: return L"Multiple choices."; + case 301: return L"Moved permanently."; + case 302: return L"Moved temporarily."; + case 303: return L"See other"; + case 304: return L"Not modified."; + case 305: return L"Use proxy."; + case 306: return L"Switch proxy."; + case 307: return L"Temporary redirect."; + case 308: return L"Permanent redirect."; + + case 400: return L"Bad request."; + case 401: return L"Unauthorized."; + case 402: return L"Payment required."; + case 403: return L"Forbidden."; + case 404: return L"Not found."; + case 405: return L"Method not allowed."; + case 406: return L"Not acceptable."; + case 407: return L"Proxy authentication required."; + case 408: return L"Request timeout."; + case 409: return L"Conflict."; + case 410: return L"Gone."; + case 411: return L"Length required."; + case 412: return L"Precondition failed."; + case 413: return L"Payload too large."; + case 414: return L"URI too long."; + case 415: return L"Unsupported media type."; + case 416: return L"Range not satisfiable."; + case 417: return L"Expectation failed."; + case 418: return L"I'm a teapot."; + case 421: return L"Misdirected request."; + case 422: return L"Unprocessable entity."; + case 423: return L"Locked."; + case 424: return L"Failed dependency."; + case 425: return L"Too early."; + case 426: return L"Upgrade required."; + case 428: return L"Precondition required."; + case 429: return L"Too many requests."; + case 431: return L"Request header fields too large."; + case 451: return L"Unavailable for legal reasons."; + + case 500: return L"Internal server error."; + case 501: return L"Not implemented."; + case 502: return L"Bad gateway."; + case 503: return L"Service unavailable."; + case 504: return L"Gateway timeout."; + case 505: return L"HTTP version not supported."; + case 506: return L"Variant also negotiates."; + case 507: return L"Insufficient storage."; + case 508: return L"Loop detected."; + case 510: return L"Not extended."; + case 511: return L"Network authentication required."; + + //Cloudflare errors regarding origin server: + case 520: return L"Unknown error (Cloudflare)"; + case 521: return L"Web server is down (Cloudflare)"; + case 522: return L"Connection timed out (Cloudflare)"; + case 523: return L"Origin is unreachable (Cloudflare)"; + case 524: return L"A timeout occurred (Cloudflare)"; + case 525: return L"SSL handshake failed (Cloudflare)"; + case 526: return L"Invalid SSL certificate (Cloudflare)"; + case 527: return L"Railgun error (Cloudflare)"; + case 530: return L"Origin DNS error (Cloudflare)"; + + default: return L""; + //*INDENT-ON* + } + }(); + + if (strLength(statusText) == 0) + 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 @@ -47,6 +47,7 @@ HttpInputStream sendHttpPost(const Zstring& url, const Zstring* caCertFilePath /*optional: enable certificate validation*/, const IOCallback& notifyUnbufferedIO /*throw X*/); bool internetIsAlive(); //noexcept +std::wstring formatHttpStatusCode(int httpStatusCode); 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); @@ -12,7 +12,8 @@ namespace zen { -//https://tools.ietf.org/html/rfc8259 +//Spec: https://tools.ietf.org/html/rfc8259 +//Test: http://seriot.ch/parsing_json.php struct JsonValue { enum class Type @@ -92,26 +93,31 @@ std::string jsonEscape(const std::string& str) { std::string output; for (const char c : str) - { - if (c == '"') output += "\\\""; //escaping mandatory - else if (c == '\\') output += "\\\\"; // - - else if (c == '\b') output += "\\b"; // - else if (c == '\f') output += "\\f"; // - else if (c == '\n') output += "\\n"; //prefer compact escaping - else if (c == '\r') output += "\\r"; // - else if (c == '\t') output += "\\t"; // - - else if (static_cast<unsigned char>(c) < 32) + switch (c) { - const auto [high, low] = hexify(c); - output += "\\u00"; - output += high; - output += low; + //*INDENT-OFF* + case '"': output += "\\\""; break; //escaping mandatory + case '\\': output += "\\\\"; break; // + + case '\b': output += "\\b"; break; // + case '\f': output += "\\f"; break; // + case '\n': output += "\\n"; break; //prefer compact escaping + case '\r': output += "\\r"; break; // + case '\t': output += "\\t"; break; // + + default: + if (static_cast<unsigned char>(c) < 32) + { + const auto [high, low] = hexify(c); + output += "\\u00"; + output += high; + output += low; + } + else + output += c; + break; + //*INDENT-ON* } - else - output += c; - } return output; } @@ -151,31 +157,36 @@ std::string jsonUnescape(const std::string& str) } const char c2 = *it; - if (c2 == '"' || - c2 == '\\' || - c2 == '/') - writeOut(c2); - else if (c2 == 'b') writeOut('\b'); - else if (c2 == 'f') writeOut('\f'); - else if (c2 == 'n') writeOut('\n'); - else if (c2 == 'r') writeOut('\r'); - else if (c2 == 't') writeOut('\t'); - - else if (c2 == 'u' && - str.end() - it >= 5 && - isHexDigit(it[1]) && - isHexDigit(it[2]) && - isHexDigit(it[3]) && - isHexDigit(it[4])) - { - utf16Buf += static_cast<impl::Char16>(static_cast<unsigned char>(unhexify(it[1], it[2])) * 256 + - static_cast<unsigned char>(unhexify(it[3], it[4]))); - it += 4; - } - else //unknown escape sequence! + switch (c2) { - writeOut(c); - writeOut(c2); + //*INDENT-OFF* + case '"': + case '\\': + case '/': writeOut(c2); break; + case 'b': writeOut('\b'); break; + case 'f': writeOut('\f'); break; + case 'n': writeOut('\n'); break; + case 'r': writeOut('\r'); break; + case 't': writeOut('\t'); break; + default: + if (c2 == 'u' && + str.end() - it >= 5 && + isHexDigit(it[1]) && + isHexDigit(it[2]) && + isHexDigit(it[3]) && + isHexDigit(it[4])) + { + utf16Buf += static_cast<impl::Char16>(static_cast<unsigned char>(unhexify(it[1], it[2])) * 256 + + static_cast<unsigned char>(unhexify(it[3], it[4]))); + it += 4; + } + else //unknown escape sequence! + { + writeOut(c); + writeOut(c2); + } + break; + //*INDENT-ON* } } else @@ -322,7 +333,7 @@ public: Token getNextToken() //throw JsonParsingError { //skip whitespace - pos_ = std::find_if(pos_, stream_.end(), std::not_fn(isJsonWhiteSpace)); + pos_ = std::find_if_not(pos_, stream_.end(), isJsonWhiteSpace); if (pos_ == stream_.end()) return Token::Type::eof; @@ -368,7 +379,7 @@ public: } //expect a number: - const auto itNumEnd = std::find_if(pos_, stream_.end(), std::not_fn(isJsonNumDigit)); + const auto itNumEnd = std::find_if_not(pos_, stream_.end(), isJsonNumDigit); if (itNumEnd == pos_) throw JsonParsingError(posRow(), posCol()); diff --git a/zen/shell_execute.h b/zen/shell_execute.h index 4875a039..56322236 100644 --- a/zen/shell_execute.h +++ b/zen/shell_execute.h @@ -72,7 +72,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*/); // + shellExecute("xdg-open \"" + itemPath + '"', ExecutionType::ASYNC, false /*hideConsole*/); //throw FileError } } diff --git a/zen/zlib_wrap.cpp b/zen/zlib_wrap.cpp index ff5799c3..8979efa6 100644 --- a/zen/zlib_wrap.cpp +++ b/zen/zlib_wrap.cpp @@ -9,17 +9,39 @@ //Linux/macOS: use zlib system header for both wxWidgets and libcurl (zlib is required for HTTP) // => don't compile wxWidgets with: --with-zlib=builtin #include <zlib.h> //https://www.zlib.net/manual.html +#include <zen/scope_guard.h> using namespace zen; +namespace +{ +std::wstring formatZlibStatusCode(int sc) +{ + switch (sc) + { + ZEN_CHECK_CASE_FOR_CONSTANT(Z_OK); + ZEN_CHECK_CASE_FOR_CONSTANT(Z_STREAM_END); + ZEN_CHECK_CASE_FOR_CONSTANT(Z_NEED_DICT); + ZEN_CHECK_CASE_FOR_CONSTANT(Z_ERRNO); + ZEN_CHECK_CASE_FOR_CONSTANT(Z_STREAM_ERROR); + ZEN_CHECK_CASE_FOR_CONSTANT(Z_DATA_ERROR); + ZEN_CHECK_CASE_FOR_CONSTANT(Z_MEM_ERROR); + ZEN_CHECK_CASE_FOR_CONSTANT(Z_BUF_ERROR); + ZEN_CHECK_CASE_FOR_CONSTANT(Z_VERSION_ERROR); + } + return replaceCpy<std::wstring>(L"zlib status %x.", L"%x", numberTo<std::wstring>(sc)); +} +} + + size_t zen::impl::zlib_compressBound(size_t len) { return ::compressBound(static_cast<uLong>(len)); //upper limit for buffer size, larger than input size!!! } -size_t zen::impl::zlib_compress(const void* src, size_t srcLen, void* trg, size_t trgLen, int level) //throw ZlibInternalError +size_t zen::impl::zlib_compress(const void* src, size_t srcLen, void* trg, size_t trgLen, int level) //throw SysError { uLongf bufferSize = static_cast<uLong>(trgLen); const int rv = ::compress2(static_cast<Bytef*>(trg), //Bytef* dest, @@ -31,12 +53,13 @@ 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 ZlibInternalError(); + throw SysError(formatSystemError(L"compress2", formatZlibStatusCode(rv), L"zlib error")); + return bufferSize; } -size_t zen::impl::zlib_decompress(const void* src, size_t srcLen, void* trg, size_t trgLen) //throw ZlibInternalError +size_t zen::impl::zlib_decompress(const void* src, size_t srcLen, void* trg, size_t trgLen) //throw SysError { uLongf bufferSize = static_cast<uLong>(trgLen); const int rv = ::uncompress(static_cast<Bytef*>(trg), //Bytef* dest, @@ -48,7 +71,8 @@ size_t zen::impl::zlib_decompress(const void* src, size_t srcLen, void* trg, siz // Z_BUF_ERROR: not enough room in the output buffer // Z_DATA_ERROR: input data was corrupted or incomplete if (rv != Z_OK || bufferSize > trgLen) - throw ZlibInternalError(); + throw SysError(formatSystemError(L"uncompress", formatZlibStatusCode(rv), L"zlib error")); + return bufferSize; } @@ -56,7 +80,7 @@ size_t zen::impl::zlib_decompress(const void* src, size_t srcLen, void* trg, siz class InputStreamAsGzip::Impl { public: - Impl(const std::function<size_t(void* buffer, size_t bytesToRead)>& readBlock /*throw X*/) : //throw ZlibInternalError; returning 0 signals EOF: Posix read() semantics + Impl(const std::function<size_t(void* buffer, size_t bytesToRead)>& readBlock /*throw X*/) : //throw SysError; returning 0 signals EOF: Posix read() semantics readBlock_(readBlock) { const int windowBits = MAX_WBITS + 16; //"add 16 to windowBits to write a simple gzip header" @@ -72,7 +96,7 @@ public: memLevel, //int memLevel Z_DEFAULT_STRATEGY); //int strategy if (rv != Z_OK) - throw ZlibInternalError(); + throw SysError(formatSystemError(L"deflateInit2", formatZlibStatusCode(rv), L"zlib error")); } ~Impl() @@ -81,7 +105,7 @@ public: assert(rv == Z_OK); } - size_t read(void* buffer, size_t bytesToRead) //throw ZlibInternalError, X; return "bytesToRead" bytes unless end of stream! + size_t read(void* buffer, size_t bytesToRead) //throw SysError, X; return "bytesToRead" bytes unless end of stream! { if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check! throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); @@ -107,7 +131,7 @@ public: if (rv == Z_STREAM_END) return bytesToRead - gzipStream_.avail_out; if (rv != Z_OK) - throw ZlibInternalError(); + throw SysError(formatSystemError(L"deflate", formatZlibStatusCode(rv), L"zlib error")); if (gzipStream_.avail_out == 0) return bytesToRead; @@ -122,6 +146,6 @@ private: }; -zen::InputStreamAsGzip::InputStreamAsGzip(const std::function<size_t(void* buffer, size_t bytesToRead)>& readBlock /*throw X*/) : pimpl_(std::make_unique<Impl>(readBlock)) {} //throw ZlibInternalError +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 ZlibInternalError, X +size_t zen::InputStreamAsGzip::read(void* buffer, size_t bytesToRead) { return pimpl_->read(buffer, bytesToRead); } //throw SysError, X diff --git a/zen/zlib_wrap.h b/zen/zlib_wrap.h index c8647baf..fbe26193 100644 --- a/zen/zlib_wrap.h +++ b/zen/zlib_wrap.h @@ -8,31 +8,30 @@ #define ZLIB_WRAP_H_428597064566 #include "serialize.h" +#include "sys_error.h" namespace zen { -class ZlibInternalError {}; - // compression level must be between 0 and 9: // 0: no compression // 9: best compression template <class BinContainer> //as specified in serialize.h -BinContainer compress(const BinContainer& stream, int level); //throw ZlibInternalError +BinContainer compress(const BinContainer& stream, int level); //throw SysError //caveat: output stream is physically larger than input! => strip additional reserved space if needed: "BinContainer(output.begin(), output.end())" template <class BinContainer> -BinContainer decompress(const BinContainer& stream); //throw ZlibInternalError +BinContainer decompress(const BinContainer& stream); //throw SysError class InputStreamAsGzip //convert input stream into gzip on the fly { public: - InputStreamAsGzip( //throw ZlibInternalError + 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 ZlibInternalError, X; return "bytesToRead" bytes unless end of stream! + size_t read(void* buffer, size_t bytesToRead); //throw SysError, X; return "bytesToRead" bytes unless end of stream! private: class Impl; @@ -49,13 +48,13 @@ private: namespace impl { size_t zlib_compressBound(size_t len); -size_t zlib_compress (const void* src, size_t srcLen, void* trg, size_t trgLen, int level); //throw ZlibInternalError -size_t zlib_decompress(const void* src, size_t srcLen, void* trg, size_t trgLen); //throw ZlibInternalError +size_t zlib_compress (const void* src, size_t srcLen, void* trg, size_t trgLen, int level); //throw SysError +size_t zlib_decompress(const void* src, size_t srcLen, void* trg, size_t trgLen); //throw SysError } template <class BinContainer> -BinContainer compress(const BinContainer& stream, int level) //throw ZlibInternalError +BinContainer compress(const BinContainer& stream, int level) //throw SysError { BinContainer contOut; if (!stream.empty()) //don't dereference iterator into empty container! @@ -73,7 +72,7 @@ BinContainer compress(const BinContainer& stream, int level) //throw ZlibInterna stream.size(), &*contOut.begin() + contOut.size() - bufferEstimate, bufferEstimate, - level); //throw ZlibInternalError + level); //throw SysError if (bytesWritten < bufferEstimate) contOut.resize(contOut.size() - (bufferEstimate - bytesWritten)); //caveat: unsigned arithmetics //caveat: physical memory consumption still *unchanged*! @@ -83,7 +82,7 @@ BinContainer compress(const BinContainer& stream, int level) //throw ZlibInterna template <class BinContainer> -BinContainer decompress(const BinContainer& stream) //throw ZlibInternalError +BinContainer decompress(const BinContainer& stream) //throw SysError { BinContainer contOut; if (!stream.empty()) //don't dereference iterator into empty container! @@ -91,30 +90,30 @@ BinContainer decompress(const BinContainer& stream) //throw ZlibInternalError //retrieve size of uncompressed data uint64_t uncompressedSize = 0; //use portable number type! if (stream.size() < sizeof(uncompressedSize)) - throw ZlibInternalError(); + 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 ZlibInternalError(); + throw SysError(L"zlib error: uncompressed size == 0"); try { contOut.resize(static_cast<size_t>(uncompressedSize)); //throw std::bad_alloc } - catch (std::bad_alloc&) //most likely due to data corruption! + catch (const std::bad_alloc& e) //most likely due to data corruption! { - throw ZlibInternalError(); + 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), &*contOut.begin(), - static_cast<size_t>(uncompressedSize)); //throw ZlibInternalError + static_cast<size_t>(uncompressedSize)); //throw SysError if (bytesWritten != static_cast<size_t>(uncompressedSize)) - throw ZlibInternalError(); + throw SysError(L"zlib error: bytes written != uncompressed size"); } return contOut; } |