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