1 #include "json_html_serializer.hpp" 2 3 #include "http_response.hpp" 4 5 #include <boost/beast/http/field.hpp> 6 #include <nlohmann/json.hpp> 7 8 #include <array> 9 #include <cmath> 10 #include <cstddef> 11 #include <cstdint> 12 #include <cstdio> 13 #include <iterator> 14 #include <string> 15 #include <type_traits> 16 #include <utility> 17 18 namespace json_html_util 19 { 20 21 static constexpr uint8_t utf8Accept = 0; 22 static constexpr uint8_t utf8Reject = 1; 23 24 static uint8_t decode(uint8_t& state, uint32_t& codePoint, 25 const uint8_t byte) noexcept 26 { 27 // clang-format off 28 static const std::array<std::uint8_t, 400> utf8d = 29 { 30 { 31 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 32 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 33 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 34 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 35 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 36 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 37 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 38 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF 39 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF 40 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 41 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 42 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 43 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 44 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 45 } 46 }; 47 // clang-format on 48 49 if (state > 0x8) 50 { 51 return state; 52 } 53 54 const uint8_t type = utf8d[byte]; 55 56 codePoint = (state != utf8Accept) 57 ? (byte & 0x3fU) | (codePoint << 6) 58 : static_cast<uint32_t>(0xff >> type) & (byte); 59 60 state = utf8d[256U + state * 16U + type]; 61 return state; 62 } 63 64 static void dumpEscaped(std::string& out, const std::string& str) 65 { 66 std::array<char, 512> stringBuffer{{}}; 67 uint32_t codePoint = 0; 68 uint8_t state = utf8Accept; 69 std::size_t bytes = 0; // number of bytes written to string_buffer 70 71 // number of bytes written at the point of the last valid byte 72 std::size_t bytesAfterLastAccept = 0; 73 std::size_t undumpedChars = 0; 74 75 for (std::size_t i = 0; i < str.size(); ++i) 76 { 77 const uint8_t byte = static_cast<uint8_t>(str[i]); 78 79 switch (decode(state, codePoint, byte)) 80 { 81 case utf8Accept: // decode found a new code point 82 { 83 switch (codePoint) 84 { 85 case 0x08: // backspace 86 { 87 stringBuffer[bytes++] = '\\'; 88 stringBuffer[bytes++] = 'b'; 89 break; 90 } 91 92 case 0x09: // horizontal tab 93 { 94 stringBuffer[bytes++] = '\\'; 95 stringBuffer[bytes++] = 't'; 96 break; 97 } 98 99 case 0x0A: // newline 100 { 101 stringBuffer[bytes++] = '\\'; 102 stringBuffer[bytes++] = 'n'; 103 break; 104 } 105 106 case 0x0C: // formfeed 107 { 108 stringBuffer[bytes++] = '\\'; 109 stringBuffer[bytes++] = 'f'; 110 break; 111 } 112 113 case 0x0D: // carriage return 114 { 115 stringBuffer[bytes++] = '\\'; 116 stringBuffer[bytes++] = 'r'; 117 break; 118 } 119 120 case 0x22: // quotation mark 121 { 122 stringBuffer[bytes++] = '&'; 123 stringBuffer[bytes++] = 'q'; 124 stringBuffer[bytes++] = 'u'; 125 stringBuffer[bytes++] = 'o'; 126 stringBuffer[bytes++] = 't'; 127 stringBuffer[bytes++] = ';'; 128 break; 129 } 130 131 case 0x27: // apostrophe 132 { 133 stringBuffer[bytes++] = '&'; 134 stringBuffer[bytes++] = 'a'; 135 stringBuffer[bytes++] = 'p'; 136 stringBuffer[bytes++] = 'o'; 137 stringBuffer[bytes++] = 's'; 138 stringBuffer[bytes++] = ';'; 139 break; 140 } 141 142 case 0x26: // ampersand 143 { 144 stringBuffer[bytes++] = '&'; 145 stringBuffer[bytes++] = 'a'; 146 stringBuffer[bytes++] = 'm'; 147 stringBuffer[bytes++] = 'p'; 148 stringBuffer[bytes++] = ';'; 149 break; 150 } 151 152 case 0x3C: // less than 153 { 154 stringBuffer[bytes++] = '\\'; 155 stringBuffer[bytes++] = 'l'; 156 stringBuffer[bytes++] = 't'; 157 stringBuffer[bytes++] = ';'; 158 break; 159 } 160 161 case 0x3E: // greater than 162 { 163 stringBuffer[bytes++] = '\\'; 164 stringBuffer[bytes++] = 'g'; 165 stringBuffer[bytes++] = 't'; 166 stringBuffer[bytes++] = ';'; 167 break; 168 } 169 170 default: 171 { 172 // escape control characters (0x00..0x1F) 173 if ((codePoint <= 0x1F) or (codePoint >= 0x7F)) 174 { 175 if (codePoint <= 0xFFFF) 176 { 177 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) 178 std::snprintf(&stringBuffer[bytes], 7, 179 "\\u%04x", 180 static_cast<uint16_t>(codePoint)); 181 bytes += 6; 182 } 183 else 184 { 185 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) 186 std::snprintf( 187 &stringBuffer[bytes], 13, "\\u%04x\\u%04x", 188 static_cast<uint16_t>( 189 0xD7C0 + (codePoint >> 10)), 190 static_cast<uint16_t>( 191 0xDC00 + (codePoint & 0x3FF))); 192 bytes += 12; 193 } 194 } 195 else 196 { 197 // copy byte to buffer (all previous bytes 198 // been copied have in default case above) 199 stringBuffer[bytes++] = str[i]; 200 } 201 break; 202 } 203 } 204 205 // write buffer and reset index; there must be 13 bytes 206 // left, as this is the maximal number of bytes to be 207 // written ("\uxxxx\uxxxx\0") for one code point 208 if (stringBuffer.size() - bytes < 13) 209 { 210 out.append(stringBuffer.data(), bytes); 211 bytes = 0; 212 } 213 214 // remember the byte position of this accept 215 bytesAfterLastAccept = bytes; 216 undumpedChars = 0; 217 break; 218 } 219 220 case utf8Reject: // decode found invalid UTF-8 byte 221 { 222 // in case we saw this character the first time, we 223 // would like to read it again, because the byte 224 // may be OK for itself, but just not OK for the 225 // previous sequence 226 if (undumpedChars > 0) 227 { 228 --i; 229 } 230 231 // reset length buffer to the last accepted index; 232 // thus removing/ignoring the invalid characters 233 bytes = bytesAfterLastAccept; 234 235 stringBuffer[bytes++] = '\\'; 236 stringBuffer[bytes++] = 'u'; 237 stringBuffer[bytes++] = 'f'; 238 stringBuffer[bytes++] = 'f'; 239 stringBuffer[bytes++] = 'f'; 240 stringBuffer[bytes++] = 'd'; 241 242 bytesAfterLastAccept = bytes; 243 244 undumpedChars = 0; 245 246 // continue processing the string 247 state = utf8Accept; 248 break; 249 } 250 251 default: // decode found yet incomplete multi-byte code point 252 { 253 ++undumpedChars; 254 break; 255 } 256 } 257 } 258 259 // we finished processing the string 260 if (state == utf8Accept) 261 { 262 // write buffer 263 if (bytes > 0) 264 { 265 out.append(stringBuffer.data(), bytes); 266 } 267 } 268 else 269 { 270 // write all accepted bytes 271 out.append(stringBuffer.data(), bytesAfterLastAccept); 272 out += "\\ufffd"; 273 } 274 } 275 276 static unsigned int countDigits(uint64_t number) noexcept 277 { 278 unsigned int nDigits = 1; 279 for (;;) 280 { 281 if (number < 10) 282 { 283 return nDigits; 284 } 285 if (number < 100) 286 { 287 return nDigits + 1; 288 } 289 if (number < 1000) 290 { 291 return nDigits + 2; 292 } 293 if (number < 10000) 294 { 295 return nDigits + 3; 296 } 297 number = number / 10000U; 298 nDigits += 4; 299 } 300 } 301 302 template <typename NumberType, 303 std::enable_if_t<std::is_same<NumberType, uint64_t>::value or 304 std::is_same<NumberType, int64_t>::value, 305 int> = 0> 306 void dumpInteger(std::string& out, NumberType number) 307 { 308 std::array<char, 64> numberbuffer{{}}; 309 310 static constexpr std::array<std::array<char, 2>, 100> digitsTo99{{ 311 {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'}, 312 {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'}, 313 {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'}, 314 {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, 315 {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'}, 316 {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'}, 317 {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'}, 318 {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'}, 319 {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, 320 {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'}, 321 {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'}, 322 {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'}, 323 {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'}, 324 {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, 325 {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'}, 326 {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'}, 327 {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}, 328 }}; 329 330 // special case for "0" 331 if (number == 0) 332 { 333 out += '0'; 334 return; 335 } 336 337 // use a pointer to fill the buffer 338 auto* bufferPtr = numberbuffer.begin(); 339 340 const bool isNegative = std::is_same<NumberType, int64_t>::value && 341 !(number >= 0); // see issue #755 342 uint64_t absValue = 0; 343 344 unsigned int nChars = 0; 345 346 if (isNegative) 347 { 348 *bufferPtr = '-'; 349 absValue = static_cast<uint64_t>(0 - number); 350 351 // account one more byte for the minus sign 352 nChars = 1 + countDigits(absValue); 353 } 354 else 355 { 356 absValue = static_cast<uint64_t>(number); 357 nChars = countDigits(absValue); 358 } 359 360 // spare 1 byte for '\0' 361 if (nChars >= numberbuffer.size() - 1) 362 { 363 return; 364 } 365 366 // jump to the end to generate the string from backward 367 // so we later avoid reversing the result 368 std::advance(bufferPtr, nChars - 1); 369 370 // Fast int2ascii implementation inspired by "Fastware" talk by Andrei 371 // Alexandrescu See: https://www.youtube.com/watch?v=o4-CwDo2zpg 372 while (absValue >= 100) 373 { 374 const auto digitsIndex = static_cast<unsigned>((absValue % 100)); 375 absValue /= 100; 376 *bufferPtr = digitsTo99[digitsIndex][1]; 377 bufferPtr = std::prev(bufferPtr); 378 *bufferPtr = digitsTo99[digitsIndex][0]; 379 bufferPtr = std::prev(bufferPtr); 380 } 381 382 if (absValue >= 10) 383 { 384 const auto digitsIndex = static_cast<unsigned>(absValue); 385 *bufferPtr = digitsTo99[digitsIndex][1]; 386 bufferPtr = std::prev(bufferPtr); 387 *bufferPtr = digitsTo99[digitsIndex][0]; 388 // assignment never used: bufferPtr = std::prev(bufferPtr); 389 } 390 else 391 { 392 *bufferPtr = static_cast<char>('0' + absValue); 393 // assignment never used: bufferPtr = std::prev(bufferPtr); 394 } 395 396 out.append(numberbuffer.data(), nChars); 397 } 398 399 static void dumpfloat(std::string& out, double number) 400 { 401 // NaN / inf 402 if (!std::isfinite(number)) 403 { 404 out += "null"; 405 return; 406 } 407 std::array<char, 64> numberbuffer{{}}; 408 409 ::nlohmann::detail::to_chars(numberbuffer.begin(), numberbuffer.end(), 410 number); 411 412 out += numberbuffer.data(); 413 } 414 415 static void dump(std::string& out, const nlohmann::json& val) 416 { 417 switch (val.type()) 418 { 419 case nlohmann::json::value_t::object: 420 { 421 if (val.empty()) 422 { 423 out += "{}"; 424 return; 425 } 426 427 out += "{"; 428 429 out += "<div class=tab>"; 430 for (auto i = val.begin(); i != val.end();) 431 { 432 out += """; 433 dumpEscaped(out, i.key()); 434 out += "": "; 435 436 bool inATag = false; 437 if (i.key() == "@odata.id" || i.key() == "@odata.context" || 438 i.key() == "Members@odata.nextLink" || i.key() == "Uri") 439 { 440 inATag = true; 441 out += "<a href=\""; 442 dumpEscaped(out, i.value()); 443 out += "\">"; 444 } 445 dump(out, i.value()); 446 if (inATag) 447 { 448 out += "</a>"; 449 } 450 i++; 451 if (i != val.end()) 452 { 453 out += ","; 454 } 455 out += "<br>"; 456 } 457 out += "</div>"; 458 out += '}'; 459 460 return; 461 } 462 463 case nlohmann::json::value_t::array: 464 { 465 if (val.empty()) 466 { 467 out += "[]"; 468 return; 469 } 470 471 out += "["; 472 473 out += "<div class=tab>"; 474 475 // first n-1 elements 476 for (auto i = val.cbegin(); i != val.cend() - 1; ++i) 477 { 478 dump(out, *i); 479 out += ",<br>"; 480 } 481 482 // last element 483 dump(out, val.back()); 484 485 out += "</div>"; 486 out += ']'; 487 488 return; 489 } 490 491 case nlohmann::json::value_t::string: 492 { 493 out += '\"'; 494 const std::string* ptr = val.get_ptr<const std::string*>(); 495 if (ptr == nullptr) 496 { 497 return; 498 } 499 dumpEscaped(out, *ptr); 500 out += '\"'; 501 return; 502 } 503 504 case nlohmann::json::value_t::boolean: 505 { 506 if (*(val.get_ptr<const bool*>())) 507 { 508 out += "true"; 509 } 510 else 511 { 512 out += "false"; 513 } 514 return; 515 } 516 517 case nlohmann::json::value_t::number_integer: 518 { 519 dumpInteger(out, *(val.get_ptr<const int64_t*>())); 520 return; 521 } 522 523 case nlohmann::json::value_t::number_unsigned: 524 { 525 dumpInteger(out, *(val.get_ptr<const uint64_t*>())); 526 return; 527 } 528 529 case nlohmann::json::value_t::number_float: 530 { 531 dumpfloat(out, *(val.get_ptr<const double*>())); 532 return; 533 } 534 535 case nlohmann::json::value_t::discarded: 536 { 537 out += "<discarded>"; 538 return; 539 } 540 541 case nlohmann::json::value_t::null: 542 { 543 out += "null"; 544 return; 545 } 546 default: 547 { 548 // Do nothing; Should never happen. 549 return; 550 } 551 } 552 } 553 554 void dumpHtml(std::string& out, const nlohmann::json& json) 555 { 556 out += "<html>\n" 557 "<head>\n" 558 "<title>Redfish API</title>\n" 559 "<link href=\"/styles/redfish.css\" rel=\"stylesheet\">\n" 560 "</head>\n" 561 "<body>\n" 562 "<div class=\"container\">\n" 563 "<img src=\"/images/DMTF_Redfish_logo_2017.svg\" alt=\"redfish\" " 564 "height=\"406px\" " 565 "width=\"576px\">\n" 566 "<div class=\"content\">\n"; 567 dump(out, json); 568 out += "</div>\n" 569 "</div>\n" 570 "</body>\n" 571 "</html>\n"; 572 } 573 574 void prettyPrintJson(crow::Response& res) 575 { 576 std::string html; 577 json_html_util::dumpHtml(html, res.jsonValue); 578 579 res.write(std::move(html)); 580 res.addHeader(boost::beast::http::field::content_type, 581 "text/html;charset=UTF-8"); 582 } 583 584 } // namespace json_html_util 585