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