1 /****************************************************************************
2 **
3 ** Copyright (C) 2018 Intel Corporation.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtCore module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qcborvalue.h"
41 #include "qcborvalue_p.h"
42 
43 #include "qcborarray.h"
44 #include "qcbormap.h"
45 
46 #include <private/qnumeric_p.h>
47 #include <qstack.h>
48 #include <private/qtools_p.h>
49 
50 QT_BEGIN_NAMESPACE
51 
52 namespace  {
53 class DiagnosticNotation
54 {
55 public:
create(const QCborValue & v,QCborValue::DiagnosticNotationOptions opts)56     static QString create(const QCborValue &v, QCborValue::DiagnosticNotationOptions opts)
57     {
58         DiagnosticNotation dn(opts);
59         dn.appendValue(v);
60         return dn.result;
61     }
62 
63 private:
64     QStack<int> byteArrayFormatStack;
65     QString separator;
66     QString result;
67     QCborValue::DiagnosticNotationOptions opts;
68     int nestingLevel = 0;
69 
70     struct Nest {
71         enum { IndentationWidth = 4 };
72         DiagnosticNotation *dn;
Nest__anon71ad67380111::DiagnosticNotation::Nest73         Nest(DiagnosticNotation *that) : dn(that)
74         {
75             ++dn->nestingLevel;
76             static const char indent[IndentationWidth + 1] = "    ";
77             if (dn->opts & QCborValue::LineWrapped)
78                 dn->separator += QLatin1String(indent, IndentationWidth);
79         }
~Nest__anon71ad67380111::DiagnosticNotation::Nest80         ~Nest()
81         {
82             --dn->nestingLevel;
83             if (dn->opts & QCborValue::LineWrapped)
84                 dn->separator.chop(IndentationWidth);
85         }
86     };
87 
DiagnosticNotation(QCborValue::DiagnosticNotationOptions opts_)88     DiagnosticNotation(QCborValue::DiagnosticNotationOptions opts_)
89         : separator(QLatin1String(opts_ & QCborValue::LineWrapped ? "\n" : "")), opts(opts_)
90     {
91         byteArrayFormatStack.push(int(QCborKnownTags::ExpectedBase16));
92     }
93 
94     void appendString(const QString &s);
95     void appendArray(const QCborArray &a);
96     void appendMap(const QCborMap &m);
97     void appendValue(const QCborValue &v);
98 };
99 }
100 
makeFpString(double d)101 static QString makeFpString(double d)
102 {
103     QString s;
104     quint64 v;
105     if (qt_is_inf(d)) {
106         s = (d < 0) ? QStringLiteral("-inf") : QStringLiteral("inf");
107     } else if (qt_is_nan(d)) {
108         s = QStringLiteral("nan");
109     } else if (convertDoubleTo(d, &v)) {
110         s = QString::fromLatin1("%1.0").arg(v);
111         if (d < 0)
112             s.prepend(QLatin1Char('-'));
113     } else {
114         s = QString::number(d, 'g', QLocale::FloatingPointShortest);
115         if (!s.contains(QLatin1Char('.')) && !s.contains('e'))
116             s += QLatin1Char('.');
117     }
118     return s;
119 }
120 
isByteArrayEncodingTag(QCborTag tag)121 static bool isByteArrayEncodingTag(QCborTag tag)
122 {
123     switch (quint64(tag)) {
124     case quint64(QCborKnownTags::ExpectedBase16):
125     case quint64(QCborKnownTags::ExpectedBase64):
126     case quint64(QCborKnownTags::ExpectedBase64url):
127         return true;
128     }
129     return false;
130 }
131 
appendString(const QString & s)132 void DiagnosticNotation::appendString(const QString &s)
133 {
134     result += QLatin1Char('"');
135 
136     const QChar *begin = s.begin();
137     const QChar *end = s.end();
138     while (begin < end) {
139         // find the longest span comprising only non-escaped characters
140         const QChar *ptr = begin;
141         for ( ; ptr < end; ++ptr) {
142             ushort uc = ptr->unicode();
143             if (uc == '\\' || uc == '"' || uc < ' ' || uc >= 0x7f)
144                 break;
145         }
146 
147         if (ptr != begin)
148             result.append(begin, ptr - begin);
149 
150         if (ptr == end)
151             break;
152 
153         // there's an escaped character
154         static const char escapeMap[16] = {
155             // The C escape characters \a \b \t \n \v \f and \r indexed by
156             // their ASCII values
157             0, 0, 0, 0,
158             0, 0, 0, 'a',
159             'b', 't', 'n', 'v',
160             'f', 'r', 0, 0
161         };
162         int buflen = 2;
163         QChar buf[10];
164         buf[0] = QLatin1Char('\\');
165         buf[1] = QChar::Null;
166         char16_t uc = ptr->unicode();
167 
168         if (uc < sizeof(escapeMap))
169             buf[1] = QLatin1Char(escapeMap[uc]);
170         else if (uc == '"' || uc == '\\')
171             buf[1] = QChar(uc);
172 
173         if (buf[1] == QChar::Null) {
174             using QtMiscUtils::toHexUpper;
175             if (ptr->isHighSurrogate() && (ptr + 1) != end && ptr[1].isLowSurrogate()) {
176                 // properly-paired surrogates
177                 ++ptr;
178                 char32_t ucs4 = QChar::surrogateToUcs4(uc, ptr->unicode());
179                 buf[1] = 'U';
180                 buf[2] = '0'; // toHexUpper(ucs4 >> 28);
181                 buf[3] = '0'; // toHexUpper(ucs4 >> 24);
182                 buf[4] = toHexUpper(ucs4 >> 20);
183                 buf[5] = toHexUpper(ucs4 >> 16);
184                 buf[6] = toHexUpper(ucs4 >> 12);
185                 buf[7] = toHexUpper(ucs4 >> 8);
186                 buf[8] = toHexUpper(ucs4 >> 4);
187                 buf[9] = toHexUpper(ucs4);
188                 buflen = 10;
189             } else {
190                 buf[1] = 'u';
191                 buf[2] = toHexUpper(uc >> 12);
192                 buf[3] = toHexUpper(uc >> 8);
193                 buf[4] = toHexUpper(uc >> 4);
194                 buf[5] = toHexUpper(uc);
195                 buflen = 6;
196             }
197         }
198 
199         result.append(buf, buflen);
200         begin = ptr + 1;
201     }
202 
203     result += QLatin1Char('"');
204 }
205 
appendArray(const QCborArray & a)206 void DiagnosticNotation::appendArray(const QCborArray &a)
207 {
208     result += QLatin1Char('[');
209 
210     // length 2 (including the space) when not line wrapping
211     QLatin1String commaValue(", ", opts & QCborValue::LineWrapped ? 1 : 2);
212     {
213         Nest n(this);
214         QLatin1String comma;
215         for (auto v : a) {
216             result += comma + separator;
217             comma = commaValue;
218             appendValue(v);
219         }
220     }
221 
222     result += separator + QLatin1Char(']');
223 }
224 
appendMap(const QCborMap & m)225 void DiagnosticNotation::appendMap(const QCborMap &m)
226 {
227     result += QLatin1Char('{');
228 
229     // length 2 (including the space) when not line wrapping
230     QLatin1String commaValue(", ", opts & QCborValue::LineWrapped ? 1 : 2);
231     {
232         Nest n(this);
233         QLatin1String comma;
234         for (auto v : m) {
235             result += comma + separator;
236             comma = commaValue;
237             appendValue(v.first);
238             result += QLatin1String(": ");
239             appendValue(v.second);
240         }
241     }
242 
243     result += separator + QLatin1Char('}');
244 };
245 
appendValue(const QCborValue & v)246 void DiagnosticNotation::appendValue(const QCborValue &v)
247 {
248     switch (v.type()) {
249     case QCborValue::Integer:
250         result += QString::number(v.toInteger());
251         return;
252     case QCborValue::ByteArray:
253         switch (byteArrayFormatStack.top()) {
254         case int(QCborKnownTags::ExpectedBase16):
255             result += QString::fromLatin1("h'" +
256                                           v.toByteArray().toHex(opts & QCborValue::ExtendedFormat ? ' ' : '\0') +
257                                           '\'');
258             return;
259         case int(QCborKnownTags::ExpectedBase64):
260             result += QString::fromLatin1("b64'" + v.toByteArray().toBase64() + '\'');
261             return;
262         default:
263         case int(QCborKnownTags::ExpectedBase64url):
264             result += QString::fromLatin1("b64'" +
265                                           v.toByteArray().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) +
266                                           '\'');
267             return;
268         }
269     case QCborValue::String:
270         return appendString(v.toString());
271     case QCborValue::Array:
272         return appendArray(v.toArray());
273     case QCborValue::Map:
274         return appendMap(v.toMap());
275     case QCborValue::False:
276         result += QLatin1String("false");
277         return;
278     case QCborValue::True:
279         result += QLatin1String("true");
280         return;
281     case QCborValue::Null:
282         result += QLatin1String("null");
283         return;
284     case QCborValue::Undefined:
285         result += QLatin1String("undefined");
286         return;
287     case QCborValue::Double:
288         result += makeFpString(v.toDouble());
289         return;
290     case QCborValue::Invalid:
291         result += QStringLiteral("<invalid>");
292         return;
293 
294     default:
295         // Only tags, extended types, and simple types remain; see below.
296         break;
297     }
298 
299     if (v.isTag()) {
300         // We handle all extended types as regular tags, so it won't matter
301         // whether we understand that tag or not.
302         bool byteArrayFormat = opts & QCborValue::ExtendedFormat && isByteArrayEncodingTag(v.tag());
303         if (byteArrayFormat)
304             byteArrayFormatStack.push(int(v.tag()));
305         result += QString::number(quint64(v.tag())) + QLatin1Char('(');
306         appendValue(v.taggedValue());
307         result += QLatin1Char(')');
308         if (byteArrayFormat)
309             byteArrayFormatStack.pop();
310     } else {
311         // must be a simple type
312         result += QString::fromLatin1("simple(%1)").arg(quint8(v.toSimpleType()));
313     }
314 }
315 
316 /*!
317     Creates the diagnostic notation equivalent of this CBOR object and returns
318     it. The \a opts parameter controls the dialect of the notation. Diagnostic
319     notation is useful in debugging, to aid the developer in understanding what
320     value is stored in the QCborValue or in a CBOR stream. For that reason, the
321     Qt API provides no support for parsing the diagnostic back into the
322     in-memory format or CBOR stream, though the representation is unique and it
323     would be possible.
324 
325     CBOR diagnostic notation is specified by
326     \l{https://tools.ietf.org/html/rfc7049#section-6}{section 6} of RFC 7049.
327     It is a text representation of the CBOR stream and it is very similar to
328     JSON, but it supports the CBOR types not found in JSON. The extended format
329     enabled by the \l{DiagnosticNotationOption}{ExtendedFormat} flag is
330     currently in some IETF drafts and its format is subject to change.
331 
332     This function produces the equivalent representation of the stream that
333     toCbor() would produce, without any transformation option provided there.
334     This also implies this function may not produce a representation of the
335     stream that was used to create the object, if it was created using
336     fromCbor(), as that function may have applied transformations. For a
337     high-fidelity notation of a stream, without transformation, see the \c
338     cbordump example.
339 
340     \sa toCbor(), QJsonDocument::toJson()
341  */
toDiagnosticNotation(DiagnosticNotationOptions opts) const342 QString QCborValue::toDiagnosticNotation(DiagnosticNotationOptions opts) const
343 {
344     return DiagnosticNotation::create(*this, opts);
345 }
346 
347 QT_END_NAMESPACE
348