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
50QT_BEGIN_NAMESPACE
51
52namespace {
53class DiagnosticNotation
54{
55public:
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
63private:
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;
73 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 }
80 ~Nest()
81 {
82 --dn->nestingLevel;
83 if (dn->opts & QCborValue::LineWrapped)
84 dn->separator.chop(IndentationWidth);
85 }
86 };
87
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
101static 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
121static 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
132void 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
206void 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
225void 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
246void 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 */
342QString QCborValue::toDiagnosticNotation(DiagnosticNotationOptions opts) const
343{
344 return DiagnosticNotation::create(*this, opts);
345}
346
347QT_END_NAMESPACE
348