File indexing completed on 2025-01-05 04:01:15

0001 /*
0002  * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #include "cbor_write_json.hpp"
0008 #include <QCborValue>
0009 #include <QCborMap>
0010 #include <QCborArray>
0011 #include <cstdint>
0012 
0013 /******************************************************************************
0014  * These function are mostly taken from Qt code
0015  * See
0016  * https://github.com/qt/qtbase/blob/dev/src/corelib/serialization/qjsonwriter.cpp
0017  * https://github.com/qt/qtbase/blob/dev/src/corelib/text/qstringconverter_p.h
0018  ******************************************************************************/
0019 
0020 #ifndef __cpp_char8_t
0021 enum char8_t : uchar {};
0022 #endif
0023 
0024 
0025 struct QUtf8BaseTraits
0026 {
0027     static const bool isTrusted = false;
0028     static const bool allowNonCharacters = true;
0029     static const bool skipAsciiHandling = false;
0030     static const int Error = -1;
0031     static const int EndOfString = -2;
0032 
0033     static bool isValidCharacter(uint u)
0034     { return int(u) >= 0; }
0035 
0036     static void appendByte(uchar *&ptr, uchar b)
0037     { *ptr++ = b; }
0038 
0039     static void appendByte(char8_t *&ptr, char8_t b)
0040     { *ptr++ = b; }
0041 
0042     static uchar peekByte(const uchar *ptr, qsizetype n = 0)
0043     { return ptr[n]; }
0044 
0045     static uchar peekByte(const char8_t *ptr, int n = 0)
0046     { return ptr[n]; }
0047 
0048     static qptrdiff availableBytes(const uchar *ptr, const uchar *end)
0049     { return end - ptr; }
0050 
0051     static qptrdiff availableBytes(const char8_t *ptr, const char8_t *end)
0052     { return end - ptr; }
0053 
0054     static void advanceByte(const uchar *&ptr, qsizetype n = 1)
0055     { ptr += n; }
0056 
0057     static void advanceByte(const char8_t *&ptr, int n = 1)
0058     { ptr += n; }
0059 
0060     static void appendUtf16(ushort *&ptr, ushort uc)
0061     { *ptr++ = uc; }
0062 
0063     static void appendUtf16(char16_t *&ptr, ushort uc)
0064     { *ptr++ = char16_t(uc); }
0065 
0066     static void appendUcs4(ushort *&ptr, uint uc)
0067     {
0068         appendUtf16(ptr, QChar::highSurrogate(uc));
0069         appendUtf16(ptr, QChar::lowSurrogate(uc));
0070     }
0071 
0072     static void appendUcs4(char16_t *&ptr, char32_t uc)
0073     {
0074         appendUtf16(ptr, QChar::highSurrogate(uc));
0075         appendUtf16(ptr, QChar::lowSurrogate(uc));
0076     }
0077 
0078     static ushort peekUtf16(const ushort *ptr, qsizetype n = 0)
0079     { return ptr[n]; }
0080 
0081     static ushort peekUtf16(const char16_t *ptr, int n = 0)
0082     { return ptr[n]; }
0083 
0084     static qptrdiff availableUtf16(const ushort *ptr, const ushort *end)
0085     { return end - ptr; }
0086 
0087     static qptrdiff availableUtf16(const char16_t *ptr, const char16_t *end)
0088     { return end - ptr; }
0089 
0090     static void advanceUtf16(const ushort *&ptr, qsizetype n = 1)
0091     { ptr += n; }
0092 
0093     static void advanceUtf16(const char16_t *&ptr, int n = 1)
0094     { ptr += n; }
0095 
0096     // it's possible to output to UCS-4 too
0097     static void appendUtf16(uint *&ptr, ushort uc)
0098     { *ptr++ = uc; }
0099 
0100     static void appendUtf16(char32_t *&ptr, ushort uc)
0101     { *ptr++ = char32_t(uc); }
0102 
0103     static void appendUcs4(uint *&ptr, uint uc)
0104     { *ptr++ = uc; }
0105 
0106     static void appendUcs4(char32_t *&ptr, uint uc)
0107     { *ptr++ = char32_t(uc); }
0108 };
0109 
0110 
0111 namespace QUtf8Functions
0112 {
0113     /// returns 0 on success; errors can only happen if \a u is a surrogate:
0114     /// Error if \a u is a low surrogate;
0115     /// if \a u is a high surrogate, Error if the next isn't a low one,
0116     /// EndOfString if we run into the end of the string.
0117     template <typename Traits, typename OutputPtr, typename InputPtr> inline
0118     int toUtf8(ushort u, OutputPtr &dst, InputPtr &src, InputPtr end)
0119     {
0120         if (!Traits::skipAsciiHandling && u < 0x80) {
0121             // U+0000 to U+007F (US-ASCII) - one byte
0122             Traits::appendByte(dst, uchar(u));
0123             return 0;
0124         } else if (u < 0x0800) {
0125             // U+0080 to U+07FF - two bytes
0126             // first of two bytes
0127             Traits::appendByte(dst, 0xc0 | uchar(u >> 6));
0128         } else {
0129             if (!QChar::isSurrogate(u)) {
0130                 // U+0800 to U+FFFF (except U+D800-U+DFFF) - three bytes
0131                 if (!Traits::allowNonCharacters && QChar::isNonCharacter(u))
0132                     return Traits::Error;
0133 
0134                 // first of three bytes
0135                 Traits::appendByte(dst, 0xe0 | uchar(u >> 12));
0136             } else {
0137                 // U+10000 to U+10FFFF - four bytes
0138                 // need to get one extra codepoint
0139                 if (Traits::availableUtf16(src, end) == 0)
0140                     return Traits::EndOfString;
0141 
0142                 ushort low = Traits::peekUtf16(src);
0143                 if (!QChar::isHighSurrogate(u))
0144                     return Traits::Error;
0145                 if (!QChar::isLowSurrogate(low))
0146                     return Traits::Error;
0147 
0148                 Traits::advanceUtf16(src);
0149                 uint ucs4 = QChar::surrogateToUcs4(u, low);
0150 
0151                 if (!Traits::allowNonCharacters && QChar::isNonCharacter(ucs4))
0152                     return Traits::Error;
0153 
0154                 // first byte
0155                 Traits::appendByte(dst, 0xf0 | (uchar(ucs4 >> 18) & 0xf));
0156 
0157                 // second of four bytes
0158                 Traits::appendByte(dst, 0x80 | (uchar(ucs4 >> 12) & 0x3f));
0159 
0160                 // for the rest of the bytes
0161                 u = ushort(ucs4);
0162             }
0163 
0164             // second to last byte
0165             Traits::appendByte(dst, 0x80 | (uchar(u >> 6) & 0x3f));
0166         }
0167 
0168         // last byte
0169         Traits::appendByte(dst, 0x80 | (u & 0x3f));
0170         return 0;
0171     }
0172 }
0173 
0174 
0175 static void objectContentToJson(const QCborMap& p, QByteArray &json, int indent, bool compact);
0176 static void arrayContentToJson(const QCborArray& a, QByteArray &json, int indent, bool compact);
0177 
0178 static inline uchar hexdig(uint u)
0179 {
0180     return (u < 0xa ? '0' + u : 'a' + u - 0xa);
0181 }
0182 
0183 static QByteArray escapedString(const QString &s)
0184 {
0185     // give it a minimum size to ensure the resize() below always adds enough space
0186     QByteArray ba(qMax(s.length(), 16), Qt::Uninitialized);
0187 
0188     uchar *cursor = reinterpret_cast<uchar *>(const_cast<char *>(ba.constData()));
0189     const uchar *ba_end = cursor + ba.length();
0190     const ushort *src = reinterpret_cast<const ushort *>(s.constBegin());
0191     const ushort *const end = reinterpret_cast<const ushort *>(s.constEnd());
0192 
0193     while (src != end) {
0194         if (cursor >= ba_end - 6) {
0195             // ensure we have enough space
0196             int pos = cursor - (const uchar *)ba.constData();
0197             ba.resize(ba.size()*2);
0198             cursor = (uchar *)ba.data() + pos;
0199             ba_end = (const uchar *)ba.constData() + ba.length();
0200         }
0201 
0202         uint u = *src++;
0203         if (u < 0x80) {
0204             if (u < 0x20 || u == 0x22 || u == 0x5c) {
0205                 *cursor++ = '\\';
0206                 switch (u) {
0207                 case 0x22:
0208                     *cursor++ = '"';
0209                     break;
0210                 case 0x5c:
0211                     *cursor++ = '\\';
0212                     break;
0213                 case 0x8:
0214                     *cursor++ = 'b';
0215                     break;
0216                 case 0xc:
0217                     *cursor++ = 'f';
0218                     break;
0219                 case 0xa:
0220                     *cursor++ = 'n';
0221                     break;
0222                 case 0xd:
0223                     *cursor++ = 'r';
0224                     break;
0225                 case 0x9:
0226                     *cursor++ = 't';
0227                     break;
0228                 default:
0229                     *cursor++ = 'u';
0230                     *cursor++ = '0';
0231                     *cursor++ = '0';
0232                     *cursor++ = hexdig(u>>4);
0233                     *cursor++ = hexdig(u & 0xf);
0234                }
0235             } else {
0236                 *cursor++ = (uchar)u;
0237             }
0238         } else if (QUtf8Functions::toUtf8<QUtf8BaseTraits>(u, cursor, src, end) < 0) {
0239             // failed to get valid utf8 use JSON escape sequence
0240             *cursor++ = '\\';
0241             *cursor++ = 'u';
0242             *cursor++ = hexdig(u>>12 & 0x0f);
0243             *cursor++ = hexdig(u>>8 & 0x0f);
0244             *cursor++ = hexdig(u>>4 & 0x0f);
0245             *cursor++ = hexdig(u & 0x0f);
0246         }
0247     }
0248 
0249     ba.resize(cursor - (const uchar *)ba.constData());
0250     return ba;
0251 }
0252 
0253 static void valueToJson(const QCborValue &v, QByteArray &json, int indent, bool compact)
0254 {
0255     QCborValue::Type type = v.type();
0256     switch (type) {
0257     case QCborValue::True:
0258         json += "true";
0259         break;
0260     case QCborValue::False:
0261         json += "false";
0262         break;
0263     case QCborValue::Integer:
0264         json += QByteArray::number(v.toInteger());
0265         break;
0266     case QCborValue::Double: {
0267         const double d = v.toDouble();
0268         if (qIsFinite(d))
0269         {
0270             QByteArray dstr;
0271             if ( compact )
0272             {
0273                 // prec is weird with 'g' so we emulate it
0274                 QByteArray f = QByteArray::number(d, 'f', 3);
0275                 QByteArray e = QByteArray::number(d, 'e', 3);
0276                 dstr = e.size() < f.size() ? e : f;
0277             }
0278             else
0279             {
0280                 dstr = QByteArray::number(d, 'g', QLocale::FloatingPointShortest);
0281             }
0282             if ( dstr.endsWith(".000") )
0283                 dstr = dstr.left(dstr.size()-4);
0284             json += dstr;
0285         }
0286         else
0287         {
0288             json += "null"; // +INF || -INF || NaN (see RFC4627#section2.4)
0289         }
0290         break;
0291     }
0292     case QCborValue::String:
0293         json += '"';
0294         json += escapedString(v.toString());
0295         json += '"';
0296         break;
0297     case QCborValue::Array:
0298         json += compact ? "[" : "[\n";
0299         arrayContentToJson(v.toArray(), json, indent + (compact ? 0 : 1), compact);
0300         json += QByteArray(4*indent, ' ');
0301         json += ']';
0302         break;
0303     case QCborValue::Map:
0304         json += compact ? "{" : "{\n";
0305         objectContentToJson(v.toMap(), json, indent + (compact ? 0 : 1), compact);
0306         json += QByteArray(4*indent, ' ');
0307         json += '}';
0308         break;
0309     case QCborValue::Null:
0310     default:
0311         json += "null";
0312     }
0313 }
0314 
0315 static void arrayContentToJson(const QCborArray& a, QByteArray &json, int indent, bool compact)
0316 {
0317     if ( a.empty() )
0318         return;
0319 
0320     QByteArray indentString(4*indent, ' ');
0321 
0322     qsizetype i = 0;
0323     while (true) {
0324         json += indentString;
0325         valueToJson(a.at(i), json, indent, compact);
0326 
0327         if (++i == a.size()) {
0328             if (!compact)
0329                 json += '\n';
0330             break;
0331         }
0332 
0333         json += compact ? "," : ",\n";
0334     }
0335 }
0336 
0337 
0338 static void objectContentToJson(const QCborMap& o, QByteArray &json, int indent, bool compact)
0339 {
0340     if ( o.empty() )
0341         return;
0342 
0343     QByteArray indentString(4*indent, ' ');
0344 
0345     auto it = o.begin();
0346     auto end = o.end();
0347 
0348     while (true) {
0349         json += indentString;
0350         json += '"';
0351         json += escapedString(it.key().toString());
0352         json += compact ? "\":" : "\": ";
0353         valueToJson(it.value(), json, indent, compact);
0354 
0355         ++it;
0356         if ( it == end ) {
0357             if (!compact)
0358                 json += '\n';
0359             break;
0360         }
0361 
0362         json += compact ? "," : ",\n";
0363     }
0364 }
0365 
0366 QByteArray glaxnimate::io::lottie::cbor_write_json(const QCborMap &o, bool compact)
0367 {
0368     QByteArray json;
0369     json += compact ? "{" : "{\n";
0370     objectContentToJson(o, json, 0, compact);
0371     json += compact ? "}" : "}\n";
0372     return json;
0373 }