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[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[bytes], 13, "\\u%04x\\u%04x", 175 static_cast<uint16_t>(0xD7C0 + 176 (codePoint >> 10)), 177 static_cast<uint16_t>(0xDC00 + 178 (codePoint & 0x3FF))); 179 bytes += 12; 180 } 181 } 182 else 183 { 184 // copy byte to buffer (all previous bytes 185 // been copied have in default case above) 186 stringBuffer[bytes++] = str[i]; 187 } 188 break; 189 } 190 } 191 192 // write buffer and reset index; there must be 13 bytes 193 // left, as this is the maximal number of bytes to be 194 // written ("\uxxxx\uxxxx\0") for one code point 195 if (stringBuffer.size() - bytes < 13) 196 { 197 out.append(stringBuffer.data(), bytes); 198 bytes = 0; 199 } 200 201 // remember the byte position of this accept 202 bytesAfterLastAccept = bytes; 203 undumpedChars = 0; 204 break; 205 } 206 207 case utf8Reject: // decode found invalid UTF-8 byte 208 { 209 // in case we saw this character the first time, we 210 // would like to read it again, because the byte 211 // may be OK for itself, but just not OK for the 212 // previous sequence 213 if (undumpedChars > 0) 214 { 215 --i; 216 } 217 218 // reset length buffer to the last accepted index; 219 // thus removing/ignoring the invalid characters 220 bytes = bytesAfterLastAccept; 221 222 stringBuffer[bytes++] = '\\'; 223 stringBuffer[bytes++] = 'u'; 224 stringBuffer[bytes++] = 'f'; 225 stringBuffer[bytes++] = 'f'; 226 stringBuffer[bytes++] = 'f'; 227 stringBuffer[bytes++] = 'd'; 228 229 bytesAfterLastAccept = bytes; 230 231 undumpedChars = 0; 232 233 // continue processing the string 234 state = utf8Accept; 235 break; 236 } 237 238 default: // decode found yet incomplete multi-byte code point 239 { 240 ++undumpedChars; 241 break; 242 } 243 } 244 } 245 246 // we finished processing the string 247 if (state == utf8Accept) 248 { 249 // write buffer 250 if (bytes > 0) 251 { 252 out.append(stringBuffer.data(), bytes); 253 } 254 } 255 else 256 { 257 // write all accepted bytes 258 out.append(stringBuffer.data(), bytesAfterLastAccept); 259 out += "\\ufffd"; 260 } 261 } 262 263 inline unsigned int countDigits(uint64_t number) noexcept 264 { 265 unsigned int nDigits = 1; 266 for (;;) 267 { 268 if (number < 10) 269 { 270 return nDigits; 271 } 272 if (number < 100) 273 { 274 return nDigits + 1; 275 } 276 if (number < 1000) 277 { 278 return nDigits + 2; 279 } 280 if (number < 10000) 281 { 282 return nDigits + 3; 283 } 284 number = number / 10000U; 285 nDigits += 4; 286 } 287 } 288 289 template <typename NumberType, 290 std::enable_if_t<std::is_same<NumberType, uint64_t>::value or 291 std::is_same<NumberType, int64_t>::value, 292 int> = 0> 293 void dumpInteger(std::string& out, NumberType number) 294 { 295 std::array<char, 64> numberbuffer{{}}; 296 297 static constexpr std::array<std::array<char, 2>, 100> digitsTo99{{ 298 {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'}, 299 {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'}, 300 {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'}, 301 {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, 302 {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'}, 303 {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'}, 304 {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'}, 305 {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'}, 306 {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, 307 {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'}, 308 {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'}, 309 {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'}, 310 {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'}, 311 {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, 312 {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'}, 313 {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'}, 314 {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}, 315 }}; 316 317 // special case for "0" 318 if (number == 0) 319 { 320 out += '0'; 321 return; 322 } 323 324 // use a pointer to fill the buffer 325 auto* bufferPtr = numberbuffer.begin(); 326 327 const bool isNegative = std::is_same<NumberType, int64_t>::value && 328 !(number >= 0); // see issue #755 329 uint64_t absValue = 0; 330 331 unsigned int nChars = 0; 332 333 if (isNegative) 334 { 335 *bufferPtr = '-'; 336 absValue = static_cast<uint64_t>(0 - number); 337 338 // account one more byte for the minus sign 339 nChars = 1 + countDigits(absValue); 340 } 341 else 342 { 343 absValue = static_cast<uint64_t>(number); 344 nChars = countDigits(absValue); 345 } 346 347 // spare 1 byte for '\0' 348 if (nChars >= numberbuffer.size() - 1) 349 { 350 return; 351 } 352 353 // jump to the end to generate the string from backward 354 // so we later avoid reversing the result 355 std::advance(bufferPtr, nChars - 1); 356 357 // Fast int2ascii implementation inspired by "Fastware" talk by Andrei 358 // Alexandrescu See: https://www.youtube.com/watch?v=o4-CwDo2zpg 359 while (absValue >= 100) 360 { 361 const auto digitsIndex = static_cast<unsigned>((absValue % 100)); 362 absValue /= 100; 363 *bufferPtr = digitsTo99[digitsIndex][1]; 364 bufferPtr = std::prev(bufferPtr); 365 *bufferPtr = digitsTo99[digitsIndex][0]; 366 bufferPtr = std::prev(bufferPtr); 367 } 368 369 if (absValue >= 10) 370 { 371 const auto digitsIndex = static_cast<unsigned>(absValue); 372 *bufferPtr = digitsTo99[digitsIndex][1]; 373 bufferPtr = std::prev(bufferPtr); 374 *bufferPtr = digitsTo99[digitsIndex][0]; 375 // assignment never used: bufferPtr = std::prev(bufferPtr); 376 } 377 else 378 { 379 *bufferPtr = static_cast<char>('0' + absValue); 380 // assignment never used: bufferPtr = std::prev(bufferPtr); 381 } 382 383 out.append(numberbuffer.data(), nChars); 384 } 385 386 inline void dumpfloat(std::string& out, double number, 387 std::true_type /*isIeeeSingleOrDouble*/) 388 { 389 std::array<char, 64> numberbuffer{{}}; 390 391 ::nlohmann::detail::to_chars(numberbuffer.begin(), numberbuffer.end(), 392 number); 393 394 out += numberbuffer.data(); 395 } 396 397 inline void dumpfloat(std::string& out, double number, 398 std::false_type /*isIeeeSingleOrDouble*/) 399 { 400 std::array<char, 64> numberbuffer{{}}; 401 // get number of digits for a float -> text -> float round-trip 402 static constexpr auto d = std::numeric_limits<double>::max_digits10; 403 404 // the actual conversion 405 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) 406 std::ptrdiff_t len = std::snprintf(numberbuffer.data(), numberbuffer.size(), 407 "%.*g", d, number); 408 409 // negative value indicates an error 410 if (len <= 0) 411 { 412 return; 413 } 414 415 // check if buffer was large enough 416 if (numberbuffer.size() < static_cast<std::size_t>(len)) 417 { 418 return; 419 } 420 421 auto* end = numberbuffer.begin(); 422 std::advance(end, len); 423 end = std::remove(numberbuffer.begin(), end, ','); 424 std::fill(end, numberbuffer.end(), '\0'); 425 426 if (std::distance(numberbuffer.begin(), end) > len) 427 { 428 return; 429 } 430 len = std::distance(numberbuffer.begin(), end); 431 432 out.append(numberbuffer.data(), static_cast<std::size_t>(len)); 433 434 // determine if need to append ".0" 435 auto* newEnd = numberbuffer.begin(); 436 std::advance(newEnd, len + 1); 437 438 const bool valueIsIntLike = 439 std::none_of(numberbuffer.begin(), newEnd, 440 [](char c) { return (c == '.' or c == 'e'); }); 441 442 if (valueIsIntLike) 443 { 444 out += ".0"; 445 } 446 } 447 448 inline void dumpfloat(std::string& out, double number) 449 { 450 // NaN / inf 451 if (!std::isfinite(number)) 452 { 453 out += "null"; 454 return; 455 } 456 457 // If float is an IEEE-754 single or double precision number, 458 // use the Grisu2 algorithm to produce short numbers which are 459 // guaranteed to round-trip, using strtof and strtod, resp. 460 // 461 // NB: The test below works if <long double> == <double>. 462 static constexpr bool isIeeeSingleOrDouble = 463 (std::numeric_limits<double>::is_iec559 and 464 std::numeric_limits<double>::digits == 24 and 465 std::numeric_limits<double>::max_exponent == 128) or 466 (std::numeric_limits<double>::is_iec559 and 467 std::numeric_limits<double>::digits == 53 and 468 std::numeric_limits<double>::max_exponent == 1024); 469 470 dumpfloat(out, number, 471 std::integral_constant<bool, isIeeeSingleOrDouble>()); 472 } 473 474 inline void dump(std::string& out, const nlohmann::json& val) 475 { 476 switch (val.type()) 477 { 478 case nlohmann::json::value_t::object: 479 { 480 if (val.empty()) 481 { 482 out += "{}"; 483 return; 484 } 485 486 out += "{"; 487 488 out += "<div class=tab>"; 489 for (auto i = val.begin(); i != val.end();) 490 { 491 out += """; 492 dumpEscaped(out, i.key()); 493 out += "": "; 494 495 bool inATag = false; 496 if (i.key() == "@odata.id" || i.key() == "@odata.context" || 497 i.key() == "Members@odata.nextLink" || i.key() == "Uri") 498 { 499 inATag = true; 500 out += "<a href=\""; 501 dumpEscaped(out, i.value()); 502 out += "\">"; 503 } 504 dump(out, i.value()); 505 if (inATag) 506 { 507 out += "</a>"; 508 } 509 i++; 510 if (i != val.end()) 511 { 512 out += ","; 513 } 514 out += "<br>"; 515 } 516 out += "</div>"; 517 out += '}'; 518 519 return; 520 } 521 522 case nlohmann::json::value_t::array: 523 { 524 if (val.empty()) 525 { 526 out += "[]"; 527 return; 528 } 529 530 out += "["; 531 532 out += "<div class=tab>"; 533 534 // first n-1 elements 535 for (auto i = val.cbegin(); i != val.cend() - 1; ++i) 536 { 537 dump(out, *i); 538 out += ",<br>"; 539 } 540 541 // last element 542 dump(out, val.back()); 543 544 out += "</div>"; 545 out += ']'; 546 547 return; 548 } 549 550 case nlohmann::json::value_t::string: 551 { 552 out += '\"'; 553 const std::string* ptr = val.get_ptr<const std::string*>(); 554 dumpEscaped(out, *ptr); 555 out += '\"'; 556 return; 557 } 558 559 case nlohmann::json::value_t::boolean: 560 { 561 if (*(val.get_ptr<const bool*>())) 562 { 563 out += "true"; 564 } 565 else 566 { 567 out += "false"; 568 } 569 return; 570 } 571 572 case nlohmann::json::value_t::number_integer: 573 { 574 dumpInteger(out, *(val.get_ptr<const int64_t*>())); 575 return; 576 } 577 578 case nlohmann::json::value_t::number_unsigned: 579 { 580 dumpInteger(out, *(val.get_ptr<const uint64_t*>())); 581 return; 582 } 583 584 case nlohmann::json::value_t::number_float: 585 { 586 dumpfloat(out, *(val.get_ptr<const double*>())); 587 return; 588 } 589 590 case nlohmann::json::value_t::discarded: 591 { 592 out += "<discarded>"; 593 return; 594 } 595 596 case nlohmann::json::value_t::null: 597 { 598 out += "null"; 599 return; 600 } 601 case nlohmann::json::value_t::binary: 602 { 603 // Do nothing; Should never happen. 604 return; 605 } 606 } 607 } 608 609 inline void dumpHtml(std::string& out, const nlohmann::json& json) 610 { 611 out += "<html>\n" 612 "<head>\n" 613 "<title>Redfish API</title>\n" 614 "<link href=\"/redfish.css\" rel=\"stylesheet\">\n" 615 "</head>\n" 616 "<body>\n" 617 "<div class=\"container\">\n" 618 "<img src=\"/DMTF_Redfish_logo_2017.svg\" alt=\"redfish\" " 619 "height=\"406px\" " 620 "width=\"576px\">\n" 621 "<div class=\"content\">\n"; 622 dump(out, json); 623 out += "</div>\n" 624 "</div>\n" 625 "</body>\n" 626 "</html>\n"; 627 } 628 629 } // namespace json_html_util 630