From 61e349acc34787ad80aebf7809ab73c4f03c7520 Mon Sep 17 00:00:00 2001 From: Ed Tanous Date: Wed, 31 May 2023 11:57:43 -0700 Subject: Break out serializer into its own cpp file This commit is entirely just moving code, such that not all compile units need to pull in the full html serializer. Tested: Unit tests pass. Pretty good coverage. Redfish service validator passes. Signed-off-by: Ed Tanous Change-Id: Ifaebe9534c0693dc678fd994517563b89aca0cc5 --- include/json_html_serializer.hpp | 626 +-------------------------------------- 1 file changed, 5 insertions(+), 621 deletions(-) (limited to 'include/json_html_serializer.hpp') diff --git a/include/json_html_serializer.hpp b/include/json_html_serializer.hpp index e4166834b3..ede2c89b37 100644 --- a/include/json_html_serializer.hpp +++ b/include/json_html_serializer.hpp @@ -1,629 +1,13 @@ +#include "http_response.hpp" + #include -#include +#include namespace json_html_util { -static constexpr uint8_t utf8Accept = 0; -static constexpr uint8_t utf8Reject = 1; - -inline uint8_t decode(uint8_t& state, uint32_t& codePoint, - const uint8_t byte) noexcept -{ - // clang-format off - static const std::array utf8d = - { - { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF - 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF - 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF - 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF - 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 - 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 - 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 - 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 - } - }; - // clang-format on - - if (state > 0x8) - { - return state; - } - - const uint8_t type = utf8d[byte]; - - codePoint = (state != utf8Accept) - ? (byte & 0x3fU) | (codePoint << 6) - : static_cast(0xff >> type) & (byte); - - state = utf8d[256U + state * 16U + type]; - return state; -} - -inline void dumpEscaped(std::string& out, const std::string& str) -{ - std::array stringBuffer{{}}; - uint32_t codePoint = 0; - uint8_t state = utf8Accept; - std::size_t bytes = 0; // number of bytes written to string_buffer - - // number of bytes written at the point of the last valid byte - std::size_t bytesAfterLastAccept = 0; - std::size_t undumpedChars = 0; - - for (std::size_t i = 0; i < str.size(); ++i) - { - const uint8_t byte = static_cast(str[i]); - - switch (decode(state, codePoint, byte)) - { - case utf8Accept: // decode found a new code point - { - switch (codePoint) - { - case 0x08: // backspace - { - stringBuffer[bytes++] = '\\'; - stringBuffer[bytes++] = 'b'; - break; - } - - case 0x09: // horizontal tab - { - stringBuffer[bytes++] = '\\'; - stringBuffer[bytes++] = 't'; - break; - } - - case 0x0A: // newline - { - stringBuffer[bytes++] = '\\'; - stringBuffer[bytes++] = 'n'; - break; - } - - case 0x0C: // formfeed - { - stringBuffer[bytes++] = '\\'; - stringBuffer[bytes++] = 'f'; - break; - } - - case 0x0D: // carriage return - { - stringBuffer[bytes++] = '\\'; - stringBuffer[bytes++] = 'r'; - break; - } - - case 0x22: // quotation mark - { - stringBuffer[bytes++] = '&'; - stringBuffer[bytes++] = 'q'; - stringBuffer[bytes++] = 'u'; - stringBuffer[bytes++] = 'o'; - stringBuffer[bytes++] = 't'; - stringBuffer[bytes++] = ';'; - break; - } - - case 0x27: // apostrophe - { - stringBuffer[bytes++] = '&'; - stringBuffer[bytes++] = 'a'; - stringBuffer[bytes++] = 'p'; - stringBuffer[bytes++] = 'o'; - stringBuffer[bytes++] = 's'; - stringBuffer[bytes++] = ';'; - break; - } - - case 0x26: // ampersand - { - stringBuffer[bytes++] = '&'; - stringBuffer[bytes++] = 'a'; - stringBuffer[bytes++] = 'm'; - stringBuffer[bytes++] = 'p'; - stringBuffer[bytes++] = ';'; - break; - } - - case 0x3C: // less than - { - stringBuffer[bytes++] = '\\'; - stringBuffer[bytes++] = 'l'; - stringBuffer[bytes++] = 't'; - stringBuffer[bytes++] = ';'; - break; - } - - case 0x3E: // greater than - { - stringBuffer[bytes++] = '\\'; - stringBuffer[bytes++] = 'g'; - stringBuffer[bytes++] = 't'; - stringBuffer[bytes++] = ';'; - break; - } - - default: - { - // escape control characters (0x00..0x1F) - if ((codePoint <= 0x1F) or (codePoint >= 0x7F)) - { - if (codePoint <= 0xFFFF) - { - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - std::snprintf(&stringBuffer[bytes], 7, - "\\u%04x", - static_cast(codePoint)); - bytes += 6; - } - else - { - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - std::snprintf( - &stringBuffer[bytes], 13, "\\u%04x\\u%04x", - static_cast(0xD7C0 + - (codePoint >> 10)), - static_cast(0xDC00 + - (codePoint & 0x3FF))); - bytes += 12; - } - } - else - { - // copy byte to buffer (all previous bytes - // been copied have in default case above) - stringBuffer[bytes++] = str[i]; - } - break; - } - } - - // write buffer and reset index; there must be 13 bytes - // left, as this is the maximal number of bytes to be - // written ("\uxxxx\uxxxx\0") for one code point - if (stringBuffer.size() - bytes < 13) - { - out.append(stringBuffer.data(), bytes); - bytes = 0; - } - - // remember the byte position of this accept - bytesAfterLastAccept = bytes; - undumpedChars = 0; - break; - } - - case utf8Reject: // decode found invalid UTF-8 byte - { - // in case we saw this character the first time, we - // would like to read it again, because the byte - // may be OK for itself, but just not OK for the - // previous sequence - if (undumpedChars > 0) - { - --i; - } - - // reset length buffer to the last accepted index; - // thus removing/ignoring the invalid characters - bytes = bytesAfterLastAccept; - - stringBuffer[bytes++] = '\\'; - stringBuffer[bytes++] = 'u'; - stringBuffer[bytes++] = 'f'; - stringBuffer[bytes++] = 'f'; - stringBuffer[bytes++] = 'f'; - stringBuffer[bytes++] = 'd'; - - bytesAfterLastAccept = bytes; - - undumpedChars = 0; - - // continue processing the string - state = utf8Accept; - break; - } - - default: // decode found yet incomplete multi-byte code point - { - ++undumpedChars; - break; - } - } - } - - // we finished processing the string - if (state == utf8Accept) - { - // write buffer - if (bytes > 0) - { - out.append(stringBuffer.data(), bytes); - } - } - else - { - // write all accepted bytes - out.append(stringBuffer.data(), bytesAfterLastAccept); - out += "\\ufffd"; - } -} - -inline unsigned int countDigits(uint64_t number) noexcept -{ - unsigned int nDigits = 1; - for (;;) - { - if (number < 10) - { - return nDigits; - } - if (number < 100) - { - return nDigits + 1; - } - if (number < 1000) - { - return nDigits + 2; - } - if (number < 10000) - { - return nDigits + 3; - } - number = number / 10000U; - nDigits += 4; - } -} - -template ::value or - std::is_same::value, - int> = 0> -void dumpInteger(std::string& out, NumberType number) -{ - std::array numberbuffer{{}}; - - static constexpr std::array, 100> digitsTo99{{ - {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'}, - {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'}, - {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'}, - {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, - {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'}, - {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'}, - {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'}, - {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'}, - {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, - {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'}, - {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'}, - {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'}, - {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'}, - {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, - {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'}, - {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'}, - {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}, - }}; - - // special case for "0" - if (number == 0) - { - out += '0'; - return; - } - - // use a pointer to fill the buffer - auto* bufferPtr = numberbuffer.begin(); - - const bool isNegative = std::is_same::value && - !(number >= 0); // see issue #755 - uint64_t absValue = 0; - - unsigned int nChars = 0; - - if (isNegative) - { - *bufferPtr = '-'; - absValue = static_cast(0 - number); - - // account one more byte for the minus sign - nChars = 1 + countDigits(absValue); - } - else - { - absValue = static_cast(number); - nChars = countDigits(absValue); - } - - // spare 1 byte for '\0' - if (nChars >= numberbuffer.size() - 1) - { - return; - } - - // jump to the end to generate the string from backward - // so we later avoid reversing the result - std::advance(bufferPtr, nChars - 1); - - // Fast int2ascii implementation inspired by "Fastware" talk by Andrei - // Alexandrescu See: https://www.youtube.com/watch?v=o4-CwDo2zpg - while (absValue >= 100) - { - const auto digitsIndex = static_cast((absValue % 100)); - absValue /= 100; - *bufferPtr = digitsTo99[digitsIndex][1]; - bufferPtr = std::prev(bufferPtr); - *bufferPtr = digitsTo99[digitsIndex][0]; - bufferPtr = std::prev(bufferPtr); - } - - if (absValue >= 10) - { - const auto digitsIndex = static_cast(absValue); - *bufferPtr = digitsTo99[digitsIndex][1]; - bufferPtr = std::prev(bufferPtr); - *bufferPtr = digitsTo99[digitsIndex][0]; - // assignment never used: bufferPtr = std::prev(bufferPtr); - } - else - { - *bufferPtr = static_cast('0' + absValue); - // assignment never used: bufferPtr = std::prev(bufferPtr); - } - - out.append(numberbuffer.data(), nChars); -} - -inline void dumpfloat(std::string& out, double number, - std::true_type /*isIeeeSingleOrDouble*/) -{ - std::array numberbuffer{{}}; - - ::nlohmann::detail::to_chars(numberbuffer.begin(), numberbuffer.end(), - number); - - out += numberbuffer.data(); -} - -inline void dumpfloat(std::string& out, double number, - std::false_type /*isIeeeSingleOrDouble*/) -{ - std::array numberbuffer{{}}; - // get number of digits for a float -> text -> float round-trip - static constexpr auto d = std::numeric_limits::max_digits10; - - // the actual conversion - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - std::ptrdiff_t len = std::snprintf(numberbuffer.data(), numberbuffer.size(), - "%.*g", d, number); - - // negative value indicates an error - if (len <= 0) - { - return; - } - - // check if buffer was large enough - if (numberbuffer.size() < static_cast(len)) - { - return; - } - - auto* end = numberbuffer.begin(); - std::advance(end, len); - end = std::remove(numberbuffer.begin(), end, ','); - std::fill(end, numberbuffer.end(), '\0'); - - if (std::distance(numberbuffer.begin(), end) > len) - { - return; - } - len = std::distance(numberbuffer.begin(), end); - - out.append(numberbuffer.data(), static_cast(len)); - - // determine if need to append ".0" - auto* newEnd = numberbuffer.begin(); - std::advance(newEnd, len + 1); - - const bool valueIsIntLike = - std::none_of(numberbuffer.begin(), newEnd, - [](char c) { return (c == '.' or c == 'e'); }); - - if (valueIsIntLike) - { - out += ".0"; - } -} - -inline void dumpfloat(std::string& out, double number) -{ - // NaN / inf - if (!std::isfinite(number)) - { - out += "null"; - return; - } - - // If float is an IEEE-754 single or double precision number, - // use the Grisu2 algorithm to produce short numbers which are - // guaranteed to round-trip, using strtof and strtod, resp. - // - // NB: The test below works if == . - static constexpr bool isIeeeSingleOrDouble = - (std::numeric_limits::is_iec559 and - std::numeric_limits::digits == 24 and - std::numeric_limits::max_exponent == 128) or - (std::numeric_limits::is_iec559 and - std::numeric_limits::digits == 53 and - std::numeric_limits::max_exponent == 1024); - - dumpfloat(out, number, - std::integral_constant()); -} - -inline void dump(std::string& out, const nlohmann::json& val) -{ - switch (val.type()) - { - case nlohmann::json::value_t::object: - { - if (val.empty()) - { - out += "{}"; - return; - } - - out += "{"; - - out += "
"; - for (auto i = val.begin(); i != val.end();) - { - out += """; - dumpEscaped(out, i.key()); - out += "": "; - - bool inATag = false; - if (i.key() == "@odata.id" || i.key() == "@odata.context" || - i.key() == "Members@odata.nextLink" || i.key() == "Uri") - { - inATag = true; - out += ""; - } - dump(out, i.value()); - if (inATag) - { - out += ""; - } - i++; - if (i != val.end()) - { - out += ","; - } - out += "
"; - } - out += "
"; - out += '}'; - - return; - } - - case nlohmann::json::value_t::array: - { - if (val.empty()) - { - out += "[]"; - return; - } - - out += "["; - - out += "
"; - - // first n-1 elements - for (auto i = val.cbegin(); i != val.cend() - 1; ++i) - { - dump(out, *i); - out += ",
"; - } - - // last element - dump(out, val.back()); - - out += "
"; - out += ']'; - - return; - } - - case nlohmann::json::value_t::string: - { - out += '\"'; - const std::string* ptr = val.get_ptr(); - dumpEscaped(out, *ptr); - out += '\"'; - return; - } - - case nlohmann::json::value_t::boolean: - { - if (*(val.get_ptr())) - { - out += "true"; - } - else - { - out += "false"; - } - return; - } - - case nlohmann::json::value_t::number_integer: - { - dumpInteger(out, *(val.get_ptr())); - return; - } - - case nlohmann::json::value_t::number_unsigned: - { - dumpInteger(out, *(val.get_ptr())); - return; - } - - case nlohmann::json::value_t::number_float: - { - dumpfloat(out, *(val.get_ptr())); - return; - } - - case nlohmann::json::value_t::discarded: - { - out += ""; - return; - } - - case nlohmann::json::value_t::null: - { - out += "null"; - return; - } - case nlohmann::json::value_t::binary: - { - // Do nothing; Should never happen. - return; - } - } -} - -inline void dumpHtml(std::string& out, const nlohmann::json& json) -{ - out += "\n" - "\n" - "Redfish API\n" - "\n" - "\n" - "\n" - "
\n" - "\"redfish\"\n" - "
\n"; - dump(out, json); - out += "
\n" - "
\n" - "\n" - "\n"; -} +void dumpHtml(std::string& out, const nlohmann::json& json); +void prettyPrintJson(crow::Response& res); } // namespace json_html_util -- cgit v1.2.3