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