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