Coverage Report

Created: 2026-11-06 11:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/tools/source/misc/json_writer.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 */
9
10
#include <tools/json_writer.hxx>
11
#include <o3tl/string_view.hxx>
12
#include <stdio.h>
13
#include <cstddef>
14
#include <cstring>
15
#include <rtl/math.hxx>
16
17
namespace tools
18
{
19
/** These buffers are short-lived, so rather waste some space and avoid the cost of
20
 * repeated calls into the allocator */
21
constexpr int DEFAULT_BUFFER_SIZE = 2048;
22
23
JsonWriter::JsonWriter()
24
0
    : mpBuffer(rtl_string_alloc(DEFAULT_BUFFER_SIZE))
25
0
    , mPos(mpBuffer->buffer)
26
0
    , mSpaceAllocated(DEFAULT_BUFFER_SIZE)
27
0
    , mStartNodeCount(0)
28
0
    , mbFirstFieldInNode(true)
29
0
    , mbClosed(false)
30
0
{
31
0
    *mPos = '{';
32
0
    ++mPos;
33
0
    *mPos = ' ';
34
0
    ++mPos;
35
36
0
    addValidationMark();
37
0
}
38
39
JsonWriter::~JsonWriter()
40
0
{
41
0
    assert(mbClosed && "forgot to extract data?");
42
0
    rtl_string_release(mpBuffer);
43
0
}
44
45
JsonWriter::ScopedJsonWriterNode<'}'> JsonWriter::startNode(std::string_view pNodeName)
46
0
{
47
0
    putLiteral(pNodeName, "{ ");
48
49
0
    mStartNodeCount++;
50
0
    mbFirstFieldInNode = true;
51
52
0
    return { *this };
53
0
}
54
55
void JsonWriter::endNode(char closing)
56
0
{
57
0
    assert(mStartNodeCount && "mismatched StartNode/EndNode somewhere");
58
0
    --mStartNodeCount;
59
0
    ensureSpace(1);
60
0
    *mPos = closing;
61
0
    ++mPos;
62
0
    mbFirstFieldInNode = false;
63
64
0
    validate();
65
0
}
66
67
JsonWriter::ScopedJsonWriterNode<']'> JsonWriter::startArray(std::string_view pNodeName)
68
0
{
69
0
    putLiteral(pNodeName, "[ ");
70
71
0
    mStartNodeCount++;
72
0
    mbFirstFieldInNode = true;
73
74
0
    return { *this };
75
0
}
76
77
JsonWriter::ScopedJsonWriterNode<']'> JsonWriter::startAnonArray()
78
0
{
79
0
    putRaw("[ ");
80
81
0
    mStartNodeCount++;
82
0
    mbFirstFieldInNode = true;
83
84
0
    return { *this };
85
0
}
86
87
JsonWriter::ScopedJsonWriterNode<'}'> JsonWriter::startStruct()
88
0
{
89
0
    putRaw("{ ");
90
91
0
    mStartNodeCount++;
92
0
    mbFirstFieldInNode = true;
93
94
0
    return { *this };
95
0
}
96
97
static char getEscapementChar(char ch)
98
0
{
99
0
    switch (ch)
100
0
    {
101
0
        case '\b':
102
0
            return 'b';
103
0
        case '\t':
104
0
            return 't';
105
0
        case '\n':
106
0
            return 'n';
107
0
        case '\f':
108
0
            return 'f';
109
0
        case '\r':
110
0
            return 'r';
111
0
        default:
112
0
            return ch;
113
0
    }
114
0
}
115
116
static bool writeEscapedSequence(sal_uInt32 ch, char*& pos)
117
0
{
118
0
    switch (ch)
119
0
    {
120
0
        case '\b':
121
0
        case '\t':
122
0
        case '\n':
123
0
        case '\f':
124
0
        case '\r':
125
0
        case '"':
126
0
        case '/':
127
0
        case '\\':
128
0
            *pos++ = '\\';
129
0
            *pos++ = getEscapementChar(ch);
130
0
            return true;
131
0
        default:
132
0
            if (ch <= 0x1f || ch == 0x2028 || ch == 0x2029)
133
0
            {
134
                // control characters, plus special processing of U+2028 and U+2029, which are valid
135
                // JSON, but invalid JavaScript. Write them in escaped '\u2028' or '\u2029' form
136
0
                int written = snprintf(pos, 7, "\\u%.4x", static_cast<unsigned int>(ch));
137
0
                if (written > 0)
138
0
                    pos += written;
139
0
                return true;
140
0
            }
141
0
            return false;
142
0
    }
143
0
}
144
145
void JsonWriter::writeEscapedOUString(std::u16string_view rPropVal)
146
0
{
147
0
    *mPos = '"';
148
0
    ++mPos;
149
150
    // Convert from UTF-16 to UTF-8 and perform escaping
151
0
    std::size_t i = 0;
152
0
    while (i < rPropVal.size())
153
0
    {
154
0
        sal_uInt32 ch = o3tl::iterateCodePoints(rPropVal, &i);
155
0
        if (writeEscapedSequence(ch, mPos))
156
0
            continue;
157
0
        if (ch <= 0x7F)
158
0
        {
159
0
            *mPos = static_cast<char>(ch);
160
0
            ++mPos;
161
0
        }
162
0
        else if (ch <= 0x7FF)
163
0
        {
164
0
            *mPos = 0xC0 | (ch >> 6); /* 110xxxxx */
165
0
            ++mPos;
166
0
            *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */
167
0
            ++mPos;
168
0
        }
169
0
        else if (ch <= 0xFFFF)
170
0
        {
171
0
            *mPos = 0xE0 | (ch >> 12); /* 1110xxxx */
172
0
            ++mPos;
173
0
            *mPos = 0x80 | ((ch >> 6) & 0x3F); /* 10xxxxxx */
174
0
            ++mPos;
175
0
            *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */
176
0
            ++mPos;
177
0
        }
178
0
        else
179
0
        {
180
0
            *mPos = 0xF0 | (ch >> 18); /* 11110xxx */
181
0
            ++mPos;
182
0
            *mPos = 0x80 | ((ch >> 12) & 0x3F); /* 10xxxxxx */
183
0
            ++mPos;
184
0
            *mPos = 0x80 | ((ch >> 6) & 0x3F); /* 10xxxxxx */
185
0
            ++mPos;
186
0
            *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */
187
0
            ++mPos;
188
0
        }
189
0
    }
190
191
0
    *mPos = '"';
192
0
    ++mPos;
193
194
0
    validate();
195
0
}
196
197
void JsonWriter::put(std::u16string_view pPropName, std::u16string_view rPropVal)
198
0
{
199
0
    auto nPropNameLength = pPropName.length();
200
    // But values can be any UTF-8,
201
    // if the string only contains of 0x2028, it will be expanded 6 times (see writeEscapedSequence)
202
0
    auto nWorstCasePropValLength = rPropVal.size() * 6;
203
0
    ensureSpace(nPropNameLength + nWorstCasePropValLength + 8);
204
205
0
    addCommaBeforeField();
206
207
0
    writeEscapedOUString(pPropName);
208
209
0
    memcpy(mPos, ": ", 2);
210
0
    mPos += 2;
211
212
0
    writeEscapedOUString(rPropVal);
213
214
0
    validate();
215
0
}
216
217
void JsonWriter::put(std::string_view pPropName, const OUString& rPropVal)
218
0
{
219
    // Values can be any UTF-8,
220
    // if the string only contains of 0x2028, it will be expanded 6 times (see writeEscapedSequence)
221
0
    auto nWorstCasePropValLength = rPropVal.getLength() * 6 + 2;
222
0
    ensureSpaceAndWriteNameColon(pPropName, nWorstCasePropValLength);
223
224
0
    writeEscapedOUString(rPropVal);
225
0
}
226
227
void JsonWriter::put(std::string_view pPropName, std::string_view rPropVal)
228
0
{
229
    // escaping can double the length, plus quotes
230
0
    auto nWorstCasePropValLength = rPropVal.size() * 2 + 2;
231
0
    ensureSpaceAndWriteNameColon(pPropName, nWorstCasePropValLength);
232
233
0
    *mPos = '"';
234
0
    ++mPos;
235
236
    // copy and perform escaping
237
0
    for (size_t i = 0; i < rPropVal.size(); ++i)
238
0
    {
239
0
        char ch = rPropVal[i];
240
0
        if (ch == 0)
241
0
            break;
242
        // Special processing of U+2028 and U+2029
243
0
        if (ch == '\xE2' && i + 2 < rPropVal.size() && rPropVal[i + 1] == '\x80'
244
0
            && (rPropVal[i + 2] == '\xA8' || rPropVal[i + 2] == '\xA9'))
245
0
        {
246
0
            writeEscapedSequence(rPropVal[i + 2] == '\xA8' ? 0x2028 : 0x2029, mPos);
247
0
            i += 2;
248
0
        }
249
0
        else if (!writeEscapedSequence(static_cast<sal_uInt32>(ch), mPos))
250
0
        {
251
0
            *mPos = ch;
252
0
            ++mPos;
253
0
        }
254
0
    }
255
256
0
    *mPos = '"';
257
0
    ++mPos;
258
259
0
    validate();
260
0
}
261
262
void JsonWriter::put(std::string_view pPropName, bool nPropVal)
263
0
{
264
0
    putLiteral(pPropName, nPropVal ? std::string_view("true") : std::string_view("false"));
265
0
}
266
267
void JsonWriter::putSimpleValue(std::u16string_view rPropVal)
268
0
{
269
0
    auto nWorstCasePropValLength = rPropVal.size() * 6;
270
0
    ensureSpace(nWorstCasePropValLength + 4);
271
272
0
    addCommaBeforeField();
273
274
0
    writeEscapedOUString(rPropVal);
275
0
}
276
277
void JsonWriter::putRaw(std::string_view rRawBuf)
278
0
{
279
0
    ensureSpace(rRawBuf.size() + 2);
280
281
0
    addCommaBeforeField();
282
283
0
    memcpy(mPos, rRawBuf.data(), rRawBuf.size());
284
0
    mPos += rRawBuf.size();
285
286
0
    validate();
287
0
}
288
289
void JsonWriter::addCommaBeforeField()
290
0
{
291
0
    if (mbFirstFieldInNode)
292
0
        mbFirstFieldInNode = false;
293
0
    else
294
0
    {
295
0
        *mPos = ',';
296
0
        ++mPos;
297
0
        *mPos = ' ';
298
0
        ++mPos;
299
0
    }
300
0
}
301
302
void JsonWriter::ensureSpace(int noMoreBytesRequired)
303
0
{
304
0
    assert(!mbClosed && "already extracted data");
305
0
    int currentUsed = mPos - mpBuffer->buffer;
306
0
    if (currentUsed + noMoreBytesRequired >= mSpaceAllocated)
307
0
    {
308
0
        auto newSize = (currentUsed + noMoreBytesRequired) * 2;
309
0
        rtl_String* pNewBuffer = rtl_string_alloc(newSize);
310
0
        memcpy(pNewBuffer->buffer, mpBuffer->buffer, currentUsed);
311
0
        rtl_string_release(mpBuffer);
312
0
        mpBuffer = pNewBuffer;
313
0
        mPos = mpBuffer->buffer + currentUsed;
314
0
        mSpaceAllocated = newSize;
315
316
0
        addValidationMark();
317
0
    }
318
0
}
319
320
void JsonWriter::ensureSpaceAndWriteNameColon(std::string_view name, int valSize)
321
0
{
322
    // we assume property names are ascii
323
0
    ensureSpace(name.size() + valSize + 6);
324
325
0
    addCommaBeforeField();
326
327
0
    *mPos = '"';
328
0
    ++mPos;
329
0
    memcpy(mPos, name.data(), name.size());
330
0
    mPos += name.size();
331
0
    memcpy(mPos, "\": ", 3);
332
0
    mPos += 3;
333
0
}
334
335
void JsonWriter::putLiteral(std::string_view propName, std::string_view propValue)
336
0
{
337
0
    ensureSpaceAndWriteNameColon(propName, propValue.size());
338
0
    memcpy(mPos, propValue.data(), propValue.size());
339
0
    mPos += propValue.size();
340
341
0
    validate();
342
0
}
343
344
OString JsonWriter::finishAndGetAsOString()
345
0
{
346
0
    assert(mStartNodeCount == 0 && "did not close all nodes");
347
0
    assert(!mbClosed && "data already extracted");
348
0
    ensureSpace(2);
349
    // add closing brace
350
0
    *mPos = '}';
351
0
    ++mPos;
352
    // null-terminate
353
0
    *mPos = 0;
354
0
    mbClosed = true;
355
356
0
    mpBuffer->length = mPos - mpBuffer->buffer;
357
0
    return mpBuffer;
358
0
}
359
360
} // namespace tools
361
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */