| #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) |
| { |
| 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 |