blob: 174b0869f597fcf3af6d24d11fcf3325f8b0c4b0 [file] [log] [blame]
Ed Tanous57fce802019-05-21 13:00:34 -07001#include <nlohmann/json.hpp>
2
3#include <algorithm>
4
5namespace json_html_util
6{
7
8static constexpr uint8_t utf8Accept = 0;
9static constexpr uint8_t utf8Reject = 1;
10
11inline 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
51inline 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 bytes_after_last_accept = 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 bytes_after_last_accept = 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 = bytes_after_last_accept;
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 bytes_after_last_accept = 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(), bytes_after_last_accept);
260 out += "\\ufffd";
261 }
262}
263
264inline unsigned int countDigits(uint64_t number) noexcept
265{
266 unsigned int n_digits = 1;
267 for (;;)
268 {
269 if (number < 10)
270 {
271 return n_digits;
272 }
273 if (number < 100)
274 {
275 return n_digits + 1;
276 }
277 if (number < 1000)
278 {
279 return n_digits + 2;
280 }
281 if (number < 10000)
282 {
283 return n_digits + 3;
284 }
285 number = number / 10000u;
286 n_digits += 4;
287 }
288}
289
290template <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>
294void dumpInteger(std::string& out, NumberType number)
295{
296 std::array<char, 64> numberbuffer{{}};
297
298 static constexpr std::array<std::array<char, 2>, 100> digits_to_99{{
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) = digits_to_99[digitsIndex][1];
365 *(--bufferPtr) = digits_to_99[digitsIndex][0];
366 }
367
368 if (absValue >= 10)
369 {
370 const auto digitsIndex = static_cast<unsigned>(absValue);
371 *(--bufferPtr) = digits_to_99[digitsIndex][1];
372 *(--bufferPtr) = digits_to_99[digitsIndex][0];
373 }
374 else
375 {
376 *(--bufferPtr) = static_cast<char>('0' + absValue);
377 }
378
379 out.append(numberbuffer.data(), nChars);
380}
381
382inline 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
392inline 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
438inline 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
464inline 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 += "&quot";
482 dumpEscaped(out, i.key());
483 out += "&quot: ";
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 }
Ed Tanous3e4c7792020-09-25 08:12:41 -0700591 case nlohmann::json::value_t::binary:
592 {
593 // Do nothing; Should never happen.
594 return;
595 }
Ed Tanous57fce802019-05-21 13:00:34 -0700596 }
597}
598
599inline 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