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(u'.') && !s.contains(u'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 const auto toHexUpper = [](char32_t value) -> QChar {
175 // QtMiscUtils::toHexUpper() returns char, we need QChar, so wrap
176 return char16_t(QtMiscUtils::toHexUpper(value));
177 };
178 if (ptr->isHighSurrogate() && (ptr + 1) != end && ptr[1].isLowSurrogate()) {
179 // properly-paired surrogates
180 ++ptr;
181 char32_t ucs4 = QChar::surrogateToUcs4(uc, ptr->unicode());
182 buf[1] = u'U';
183 buf[2] = u'0'; // toHexUpper(ucs4 >> 28);
184 buf[3] = u'0'; // toHexUpper(ucs4 >> 24);
185 buf[4] = toHexUpper(ucs4 >> 20);
186 buf[5] = toHexUpper(ucs4 >> 16);
187 buf[6] = toHexUpper(ucs4 >> 12);
188 buf[7] = toHexUpper(ucs4 >> 8);
189 buf[8] = toHexUpper(ucs4 >> 4);
190 buf[9] = toHexUpper(ucs4);
191 buflen = 10;
192 } else {
193 buf[1] = u'u';
194 buf[2] = toHexUpper(uc >> 12);
195 buf[3] = toHexUpper(uc >> 8);
196 buf[4] = toHexUpper(uc >> 4);
197 buf[5] = toHexUpper(uc);
198 buflen = 6;
199 }
200 }
201
202 result.append(buf, buflen);
203 begin = ptr + 1;
204 }
205
206 result += QLatin1Char('"');
207}
208
209void DiagnosticNotation::appendArray(const QCborArray &a)
210{
211 result += QLatin1Char('[');
212
213 // length 2 (including the space) when not line wrapping
214 QLatin1String commaValue(", ", opts & QCborValue::LineWrapped ? 1 : 2);
215 {
216 Nest n(this);
217 QLatin1String comma;
218 for (auto v : a) {
219 result += comma + separator;
220 comma = commaValue;
221 appendValue(v);
222 }
223 }
224
225 result += separator + QLatin1Char(']');
226}
227
228void DiagnosticNotation::appendMap(const QCborMap &m)
229{
230 result += QLatin1Char('{');
231
232 // length 2 (including the space) when not line wrapping
233 QLatin1String commaValue(", ", opts & QCborValue::LineWrapped ? 1 : 2);
234 {
235 Nest n(this);
236 QLatin1String comma;
237 for (auto v : m) {
238 result += comma + separator;
239 comma = commaValue;
240 appendValue(v.first);
241 result += QLatin1String(": ");
242 appendValue(v.second);
243 }
244 }
245
246 result += separator + QLatin1Char('}');
247};
248
249void DiagnosticNotation::appendValue(const QCborValue &v)
250{
251 switch (v.type()) {
252 case QCborValue::Integer:
253 result += QString::number(v.toInteger());
254 return;
255 case QCborValue::ByteArray:
256 switch (byteArrayFormatStack.top()) {
257 case int(QCborKnownTags::ExpectedBase16):
258 result += QString::fromLatin1("h'" +
259 v.toByteArray().toHex(opts & QCborValue::ExtendedFormat ? ' ' : '\0') +
260 '\'');
261 return;
262 case int(QCborKnownTags::ExpectedBase64):
263 result += QString::fromLatin1("b64'" + v.toByteArray().toBase64() + '\'');
264 return;
265 default:
266 case int(QCborKnownTags::ExpectedBase64url):
267 result += QString::fromLatin1("b64'" +
268 v.toByteArray().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) +
269 '\'');
270 return;
271 }
272 case QCborValue::String:
273 return appendString(v.toString());
274 case QCborValue::Array:
275 return appendArray(v.toArray());
276 case QCborValue::Map:
277 return appendMap(v.toMap());
278 case QCborValue::False:
279 result += QLatin1String("false");
280 return;
281 case QCborValue::True:
282 result += QLatin1String("true");
283 return;
284 case QCborValue::Null:
285 result += QLatin1String("null");
286 return;
287 case QCborValue::Undefined:
288 result += QLatin1String("undefined");
289 return;
290 case QCborValue::Double:
291 result += makeFpString(v.toDouble());
292 return;
293 case QCborValue::Invalid:
294 result += QStringLiteral("<invalid>");
295 return;
296
297 default:
298 // Only tags, extended types, and simple types remain; see below.
299 break;
300 }
301
302 if (v.isTag()) {
303 // We handle all extended types as regular tags, so it won't matter
304 // whether we understand that tag or not.
305 bool byteArrayFormat = opts & QCborValue::ExtendedFormat && isByteArrayEncodingTag(v.tag());
306 if (byteArrayFormat)
307 byteArrayFormatStack.push(int(v.tag()));
308 result += QString::number(quint64(v.tag())) + QLatin1Char('(');
309 appendValue(v.taggedValue());
310 result += QLatin1Char(')');
311 if (byteArrayFormat)
312 byteArrayFormatStack.pop();
313 } else {
314 // must be a simple type
315 result += QString::fromLatin1("simple(%1)").arg(quint8(v.toSimpleType()));
316 }
317}
318
319/*!
320 Creates the diagnostic notation equivalent of this CBOR object and returns
321 it. The \a opts parameter controls the dialect of the notation. Diagnostic
322 notation is useful in debugging, to aid the developer in understanding what
323 value is stored in the QCborValue or in a CBOR stream. For that reason, the
324 Qt API provides no support for parsing the diagnostic back into the
325 in-memory format or CBOR stream, though the representation is unique and it
326 would be possible.
327
328 CBOR diagnostic notation is specified by
329 \l{https://tools.ietf.org/html/rfc7049#section-6}{section 6} of RFC 7049.
330 It is a text representation of the CBOR stream and it is very similar to
331 JSON, but it supports the CBOR types not found in JSON. The extended format
332 enabled by the \l{DiagnosticNotationOption}{ExtendedFormat} flag is
333 currently in some IETF drafts and its format is subject to change.
334
335 This function produces the equivalent representation of the stream that
336 toCbor() would produce, without any transformation option provided there.
337 This also implies this function may not produce a representation of the
338 stream that was used to create the object, if it was created using
339 fromCbor(), as that function may have applied transformations. For a
340 high-fidelity notation of a stream, without transformation, see the \c
341 cbordump example.
342
343 \sa toCbor(), QJsonDocument::toJson()
344 */
345QString QCborValue::toDiagnosticNotation(DiagnosticNotationOptions opts) const
346{
347 return DiagnosticNotation::create(*this, opts);
348}
349
350QT_END_NAMESPACE
351