diff options
-rw-r--r-- | http/http_connection.hpp | 10 | ||||
-rw-r--r-- | include/json_html_serializer.hpp | 626 | ||||
-rw-r--r-- | meson.build | 1 | ||||
-rw-r--r-- | src/json_html_serializer.cpp | 571 |
4 files changed, 578 insertions, 630 deletions
diff --git a/http/http_connection.hpp b/http/http_connection.hpp index 7ae22e9e49..cb0da2679c 100644 --- a/http/http_connection.hpp +++ b/http/http_connection.hpp @@ -33,14 +33,6 @@ namespace crow { -inline void prettyPrintJson(crow::Response& res) -{ - json_html_util::dumpHtml(res.body(), res.jsonValue); - - res.addHeader(boost::beast::http::field::content_type, - "text/html;charset=UTF-8"); -} - // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) static int connectionCount = 0; @@ -355,7 +347,7 @@ class Connection : if (prefered == ContentType::HTML) { - prettyPrintJson(res); + json_html_util::prettyPrintJson(res); } else if (prefered == ContentType::CBOR) { 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 <nlohmann/json.hpp> -#include <algorithm> +#include <string> 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<std::uint8_t, 400> 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<uint32_t>(0xff >> type) & (byte); - - state = utf8d[256U + state * 16U + type]; - return state; -} - -inline void dumpEscaped(std::string& out, const std::string& str) -{ - std::array<char, 512> 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<uint8_t>(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<uint16_t>(codePoint)); - bytes += 6; - } - else - { - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - std::snprintf( - &stringBuffer[bytes], 13, "\\u%04x\\u%04x", - static_cast<uint16_t>(0xD7C0 + - (codePoint >> 10)), - static_cast<uint16_t>(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 <typename NumberType, - std::enable_if_t<std::is_same<NumberType, uint64_t>::value or - std::is_same<NumberType, int64_t>::value, - int> = 0> -void dumpInteger(std::string& out, NumberType number) -{ - std::array<char, 64> numberbuffer{{}}; - - static constexpr std::array<std::array<char, 2>, 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<NumberType, int64_t>::value && - !(number >= 0); // see issue #755 - uint64_t absValue = 0; - - unsigned int nChars = 0; - - if (isNegative) - { - *bufferPtr = '-'; - absValue = static_cast<uint64_t>(0 - number); - - // account one more byte for the minus sign - nChars = 1 + countDigits(absValue); - } - else - { - absValue = static_cast<uint64_t>(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<unsigned>((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<unsigned>(absValue); - *bufferPtr = digitsTo99[digitsIndex][1]; - bufferPtr = std::prev(bufferPtr); - *bufferPtr = digitsTo99[digitsIndex][0]; - // assignment never used: bufferPtr = std::prev(bufferPtr); - } - else - { - *bufferPtr = static_cast<char>('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<char, 64> 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<char, 64> numberbuffer{{}}; - // get number of digits for a float -> text -> float round-trip - static constexpr auto d = std::numeric_limits<double>::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<std::size_t>(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<std::size_t>(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 <long double> == <double>. - static constexpr bool isIeeeSingleOrDouble = - (std::numeric_limits<double>::is_iec559 and - std::numeric_limits<double>::digits == 24 and - std::numeric_limits<double>::max_exponent == 128) or - (std::numeric_limits<double>::is_iec559 and - std::numeric_limits<double>::digits == 53 and - std::numeric_limits<double>::max_exponent == 1024); - - dumpfloat(out, number, - std::integral_constant<bool, isIeeeSingleOrDouble>()); -} - -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 += "<div class=tab>"; - 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 += "<a href=\""; - dumpEscaped(out, i.value()); - out += "\">"; - } - dump(out, i.value()); - if (inATag) - { - out += "</a>"; - } - i++; - if (i != val.end()) - { - out += ","; - } - out += "<br>"; - } - out += "</div>"; - out += '}'; - - return; - } - - case nlohmann::json::value_t::array: - { - if (val.empty()) - { - out += "[]"; - return; - } - - out += "["; - - out += "<div class=tab>"; - - // first n-1 elements - for (auto i = val.cbegin(); i != val.cend() - 1; ++i) - { - dump(out, *i); - out += ",<br>"; - } - - // last element - dump(out, val.back()); - - out += "</div>"; - out += ']'; - - return; - } - - case nlohmann::json::value_t::string: - { - out += '\"'; - const std::string* ptr = val.get_ptr<const std::string*>(); - dumpEscaped(out, *ptr); - out += '\"'; - return; - } - - case nlohmann::json::value_t::boolean: - { - if (*(val.get_ptr<const bool*>())) - { - out += "true"; - } - else - { - out += "false"; - } - return; - } - - case nlohmann::json::value_t::number_integer: - { - dumpInteger(out, *(val.get_ptr<const int64_t*>())); - return; - } - - case nlohmann::json::value_t::number_unsigned: - { - dumpInteger(out, *(val.get_ptr<const uint64_t*>())); - return; - } - - case nlohmann::json::value_t::number_float: - { - dumpfloat(out, *(val.get_ptr<const double*>())); - return; - } - - case nlohmann::json::value_t::discarded: - { - out += "<discarded>"; - 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 += "<html>\n" - "<head>\n" - "<title>Redfish API</title>\n" - "<link href=\"/redfish.css\" rel=\"stylesheet\">\n" - "</head>\n" - "<body>\n" - "<div class=\"container\">\n" - "<img src=\"/DMTF_Redfish_logo_2017.svg\" alt=\"redfish\" " - "height=\"406px\" " - "width=\"576px\">\n" - "<div class=\"content\">\n"; - dump(out, json); - out += "</div>\n" - "</div>\n" - "</body>\n" - "</html>\n"; -} +void dumpHtml(std::string& out, const nlohmann::json& json); +void prettyPrintJson(crow::Response& res); } // namespace json_html_util diff --git a/meson.build b/meson.build index 6cdde78193..cdfc3dd962 100644 --- a/meson.build +++ b/meson.build @@ -338,6 +338,7 @@ srcfiles_bmcweb = files( 'src/boost_beast.cpp', 'src/boost_url.cpp', 'src/dbus_singleton.cpp', + 'src/json_html_serializer.cpp', ) bmcweblib = static_library( diff --git a/src/json_html_serializer.cpp b/src/json_html_serializer.cpp new file mode 100644 index 0000000000..405061beee --- /dev/null +++ b/src/json_html_serializer.cpp @@ -0,0 +1,571 @@ +#include "json_html_serializer.hpp" + +#include "http_response.hpp" + +#include <nlohmann/json.hpp> + +#include <algorithm> +#include <array> +#include <limits> + +namespace json_html_util +{ + +static constexpr uint8_t utf8Accept = 0; +static constexpr uint8_t utf8Reject = 1; + +static uint8_t decode(uint8_t& state, uint32_t& codePoint, + const uint8_t byte) noexcept +{ + // clang-format off + static const std::array<std::uint8_t, 400> 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<uint32_t>(0xff >> type) & (byte); + + state = utf8d[256U + state * 16U + type]; + return state; +} + +static void dumpEscaped(std::string& out, const std::string& str) +{ + std::array<char, 512> 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<uint8_t>(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<uint16_t>(codePoint)); + bytes += 6; + } + else + { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + std::snprintf( + &stringBuffer[bytes], 13, "\\u%04x\\u%04x", + static_cast<uint16_t>(0xD7C0 + + (codePoint >> 10)), + static_cast<uint16_t>(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"; + } +} + +static 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 <typename NumberType, + std::enable_if_t<std::is_same<NumberType, uint64_t>::value or + std::is_same<NumberType, int64_t>::value, + int> = 0> +void dumpInteger(std::string& out, NumberType number) +{ + std::array<char, 64> numberbuffer{{}}; + + static constexpr std::array<std::array<char, 2>, 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<NumberType, int64_t>::value && + !(number >= 0); // see issue #755 + uint64_t absValue = 0; + + unsigned int nChars = 0; + + if (isNegative) + { + *bufferPtr = '-'; + absValue = static_cast<uint64_t>(0 - number); + + // account one more byte for the minus sign + nChars = 1 + countDigits(absValue); + } + else + { + absValue = static_cast<uint64_t>(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<unsigned>((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<unsigned>(absValue); + *bufferPtr = digitsTo99[digitsIndex][1]; + bufferPtr = std::prev(bufferPtr); + *bufferPtr = digitsTo99[digitsIndex][0]; + // assignment never used: bufferPtr = std::prev(bufferPtr); + } + else + { + *bufferPtr = static_cast<char>('0' + absValue); + // assignment never used: bufferPtr = std::prev(bufferPtr); + } + + out.append(numberbuffer.data(), nChars); +} + +static void dumpfloat(std::string& out, double number) +{ + // NaN / inf + if (!std::isfinite(number)) + { + out += "null"; + return; + } + std::array<char, 64> numberbuffer{{}}; + + ::nlohmann::detail::to_chars(numberbuffer.begin(), numberbuffer.end(), + number); + + out += numberbuffer.data(); +} + +static 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 += "<div class=tab>"; + 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 += "<a href=\""; + dumpEscaped(out, i.value()); + out += "\">"; + } + dump(out, i.value()); + if (inATag) + { + out += "</a>"; + } + i++; + if (i != val.end()) + { + out += ","; + } + out += "<br>"; + } + out += "</div>"; + out += '}'; + + return; + } + + case nlohmann::json::value_t::array: + { + if (val.empty()) + { + out += "[]"; + return; + } + + out += "["; + + out += "<div class=tab>"; + + // first n-1 elements + for (auto i = val.cbegin(); i != val.cend() - 1; ++i) + { + dump(out, *i); + out += ",<br>"; + } + + // last element + dump(out, val.back()); + + out += "</div>"; + out += ']'; + + return; + } + + case nlohmann::json::value_t::string: + { + out += '\"'; + const std::string* ptr = val.get_ptr<const std::string*>(); + dumpEscaped(out, *ptr); + out += '\"'; + return; + } + + case nlohmann::json::value_t::boolean: + { + if (*(val.get_ptr<const bool*>())) + { + out += "true"; + } + else + { + out += "false"; + } + return; + } + + case nlohmann::json::value_t::number_integer: + { + dumpInteger(out, *(val.get_ptr<const int64_t*>())); + return; + } + + case nlohmann::json::value_t::number_unsigned: + { + dumpInteger(out, *(val.get_ptr<const uint64_t*>())); + return; + } + + case nlohmann::json::value_t::number_float: + { + dumpfloat(out, *(val.get_ptr<const double*>())); + return; + } + + case nlohmann::json::value_t::discarded: + { + out += "<discarded>"; + return; + } + + case nlohmann::json::value_t::null: + { + out += "null"; + return; + } + case nlohmann::json::value_t::binary: + { + // Do nothing; Should never happen. + return; + } + } +} + +void dumpHtml(std::string& out, const nlohmann::json& json) +{ + out += "<html>\n" + "<head>\n" + "<title>Redfish API</title>\n" + "<link href=\"/redfish.css\" rel=\"stylesheet\">\n" + "</head>\n" + "<body>\n" + "<div class=\"container\">\n" + "<img src=\"/DMTF_Redfish_logo_2017.svg\" alt=\"redfish\" " + "height=\"406px\" " + "width=\"576px\">\n" + "<div class=\"content\">\n"; + dump(out, json); + out += "</div>\n" + "</div>\n" + "</body>\n" + "</html>\n"; +} + +void prettyPrintJson(crow::Response& res) +{ + json_html_util::dumpHtml(res.body(), res.jsonValue); + + res.addHeader(boost::beast::http::field::content_type, + "text/html;charset=UTF-8"); +} + +} // namespace json_html_util |