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 += "&quot";
493                 dumpEscaped(out, i.key());
494                 out += "&quot: ";
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