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