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

source code of qtbase/src/corelib/serialization/qcbordiagnostic.cpp