summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--http/http_connection.hpp10
-rw-r--r--include/json_html_serializer.hpp626
-rw-r--r--meson.build1
-rw-r--r--src/json_html_serializer.cpp571
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 += "&quot";
- dumpEscaped(out, i.key());
- out += "&quot: ";
-
- 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 += "&quot";
+ dumpEscaped(out, i.key());
+ out += "&quot: ";
+
+ 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