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