#include "json_html_serializer.hpp" #include "http_response.hpp" #include #include #include #include #include #include #include #include #include #include #include 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 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; } static 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"; } } 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 ::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); } static void dumpfloat(std::string& out, double number) { // NaN / inf if (!std::isfinite(number)) { out += "null"; return; } std::array 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 += "
"; 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(); if (ptr == nullptr) { return; } 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; } default: { // Do nothing; Should never happen. return; } } } 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 prettyPrintJson(crow::Response& res) { std::string html; json_html_util::dumpHtml(html, res.jsonValue); res.write(std::move(html)); res.addHeader(boost::beast::http::field::content_type, "text/html;charset=UTF-8"); } } // namespace json_html_util