Coverage Report

Created: 2026-08-14 09:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/port/cpl_minixml.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * Project:  CPL - Common Portability Library
4
 * Purpose:  Implementation of MiniXML Parser and handling.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 **********************************************************************
8
 * Copyright (c) 2001, Frank Warmerdam
9
 * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 **********************************************************************
13
 *
14
 * Independent Security Audit 2003/04/05 Andrey Kiselev:
15
 *   Completed audit of this module. Any documents may be parsed without
16
 *   buffer overflows and stack corruptions.
17
 *
18
 * Security Audit 2003/03/28 warmerda:
19
 *   Completed security audit.  I believe that this module may be safely used
20
 *   to parse, and serialize arbitrary documents provided by a potentially
21
 *   hostile source.
22
 *
23
 */
24
25
#include "cpl_minixml.h"
26
27
#include <cctype>
28
#include <climits>
29
#include <cstddef>
30
#include <cstdio>
31
#include <cstring>
32
33
#include <algorithm>
34
35
#include "cpl_conv.h"
36
#include "cpl_error.h"
37
#include "cpl_string.h"
38
#include "cpl_vsi.h"
39
40
typedef enum
41
{
42
    TNone,
43
    TString,
44
    TOpen,
45
    TClose,
46
    TEqual,
47
    TToken,
48
    TSlashClose,
49
    TQuestionClose,
50
    TComment,
51
    TLiteral
52
} XMLTokenType;
53
54
typedef struct
55
{
56
    CPLXMLNode *psFirstNode;
57
    CPLXMLNode *psLastChild;
58
} StackContext;
59
60
typedef struct
61
{
62
    const char *pszInput;
63
    int nInputOffset;
64
    int nInputLine;
65
    bool bInElement;
66
    XMLTokenType eTokenType;
67
    char *pszToken;
68
    size_t nTokenMaxSize;
69
    size_t nTokenSize;
70
71
    int nStackMaxSize;
72
    int nStackSize;
73
    StackContext *papsStack;
74
75
    CPLXMLNode *psFirstNode;
76
    CPLXMLNode *psLastNode;
77
} ParseContext;
78
79
static CPLXMLNode *_CPLCreateXMLNode(CPLXMLNode *poParent, CPLXMLNodeType eType,
80
                                     const char *pszText);
81
82
/************************************************************************/
83
/*                              ReadChar()                              */
84
/************************************************************************/
85
86
static CPL_INLINE char ReadChar(ParseContext *psContext)
87
88
4.91G
{
89
4.91G
    const char chReturn = psContext->pszInput[psContext->nInputOffset++];
90
91
4.91G
    if (chReturn == '\0')
92
1.06M
        psContext->nInputOffset--;
93
4.91G
    else if (chReturn == 10)
94
60.8M
        psContext->nInputLine++;
95
96
4.91G
    return chReturn;
97
4.91G
}
98
99
/************************************************************************/
100
/*                             UnreadChar()                             */
101
/************************************************************************/
102
103
static CPL_INLINE void UnreadChar(ParseContext *psContext, char chToUnread)
104
105
75.1M
{
106
75.1M
    if (chToUnread == '\0')
107
216k
        return;
108
109
74.9M
    CPLAssert(chToUnread == psContext->pszInput[psContext->nInputOffset - 1]);
110
111
74.9M
    psContext->nInputOffset--;
112
113
74.9M
    if (chToUnread == 10)
114
264k
        psContext->nInputLine--;
115
74.9M
}
116
117
/************************************************************************/
118
/*                            ReallocToken()                            */
119
/************************************************************************/
120
121
static bool ReallocToken(ParseContext *psContext)
122
4.00M
{
123
4.00M
    if (psContext->nTokenMaxSize > INT_MAX / 2)
124
0
    {
125
0
        CPLError(CE_Failure, CPLE_OutOfMemory,
126
0
                 "Out of memory allocating %d*2 bytes",
127
0
                 static_cast<int>(psContext->nTokenMaxSize));
128
0
        VSIFree(psContext->pszToken);
129
0
        psContext->pszToken = nullptr;
130
0
        return false;
131
0
    }
132
133
4.00M
    psContext->nTokenMaxSize *= 2;
134
4.00M
    char *pszToken = static_cast<char *>(
135
4.00M
        VSIRealloc(psContext->pszToken, psContext->nTokenMaxSize));
136
4.00M
    if (pszToken == nullptr)
137
0
    {
138
0
        CPLError(CE_Failure, CPLE_OutOfMemory,
139
0
                 "Out of memory allocating %d bytes",
140
0
                 static_cast<int>(psContext->nTokenMaxSize));
141
0
        VSIFree(psContext->pszToken);
142
0
        psContext->pszToken = nullptr;
143
0
        return false;
144
0
    }
145
4.00M
    psContext->pszToken = pszToken;
146
4.00M
    return true;
147
4.00M
}
148
149
/************************************************************************/
150
/*                             AddToToken()                             */
151
/************************************************************************/
152
153
static CPL_INLINE bool _AddToToken(ParseContext *psContext, char chNewChar)
154
155
4.57G
{
156
4.57G
    if (psContext->nTokenSize >= psContext->nTokenMaxSize - 2)
157
4.00M
    {
158
4.00M
        if (!ReallocToken(psContext))
159
0
            return false;
160
4.00M
    }
161
162
4.57G
    psContext->pszToken[psContext->nTokenSize++] = chNewChar;
163
4.57G
    psContext->pszToken[psContext->nTokenSize] = '\0';
164
4.57G
    return true;
165
4.57G
}
166
167
// TODO(schwehr): Remove the goto.
168
#define AddToToken(psContext, chNewChar)                                       \
169
4.57G
    if (!_AddToToken(psContext, chNewChar))                                    \
170
4.09G
        goto fail;
171
172
/************************************************************************/
173
/*                             ReadToken()                              */
174
/************************************************************************/
175
176
static XMLTokenType ReadToken(ParseContext *psContext, CPLErr &eLastErrorType)
177
178
182M
{
179
182M
    psContext->nTokenSize = 0;
180
182M
    psContext->pszToken[0] = '\0';
181
182
182M
    char chNext = ReadChar(psContext);
183
301M
    while (isspace(static_cast<unsigned char>(chNext)))
184
118M
        chNext = ReadChar(psContext);
185
186
    /* -------------------------------------------------------------------- */
187
    /*      Handle comments.                                                */
188
    /* -------------------------------------------------------------------- */
189
182M
    if (chNext == '<' &&
190
30.5M
        STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset, "!--"))
191
228k
    {
192
228k
        psContext->eTokenType = TComment;
193
194
        // Skip "!--" characters.
195
228k
        ReadChar(psContext);
196
228k
        ReadChar(psContext);
197
228k
        ReadChar(psContext);
198
199
33.0M
        while (!STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset,
200
32.8M
                               "-->") &&
201
32.8M
               (chNext = ReadChar(psContext)) != '\0')
202
32.8M
            AddToToken(psContext, chNext);
203
204
        // Skip "-->" characters.
205
228k
        ReadChar(psContext);
206
228k
        ReadChar(psContext);
207
228k
        ReadChar(psContext);
208
228k
    }
209
    /* -------------------------------------------------------------------- */
210
    /*      Handle DOCTYPE.                                                 */
211
    /* -------------------------------------------------------------------- */
212
182M
    else if (chNext == '<' &&
213
30.2M
             STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset,
214
182M
                            "!DOCTYPE"))
215
63.5k
    {
216
63.5k
        bool bInQuotes = false;
217
63.5k
        psContext->eTokenType = TLiteral;
218
219
63.5k
        AddToToken(psContext, '<');
220
63.5k
        do
221
46.2M
        {
222
46.2M
            chNext = ReadChar(psContext);
223
46.2M
            if (chNext == '\0')
224
2.77k
            {
225
2.77k
                eLastErrorType = CE_Failure;
226
2.77k
                CPLError(eLastErrorType, CPLE_AppDefined,
227
2.77k
                         "Parse error in DOCTYPE on or before line %d, "
228
2.77k
                         "reached end of file without '>'.",
229
2.77k
                         psContext->nInputLine);
230
231
2.77k
                break;
232
2.77k
            }
233
234
            /* The markup declaration block within a DOCTYPE tag consists of:
235
             * - a left square bracket [
236
             * - a list of declarations
237
             * - a right square bracket ]
238
             * Example:
239
             * <!DOCTYPE RootElement [ ...declarations... ]>
240
             */
241
46.2M
            if (chNext == '[')
242
172k
            {
243
172k
                AddToToken(psContext, chNext);
244
245
172k
                do
246
16.3M
                {
247
16.3M
                    chNext = ReadChar(psContext);
248
16.3M
                    if (chNext == ']')
249
128k
                        break;
250
16.2M
                    AddToToken(psContext, chNext);
251
16.2M
                } while (chNext != '\0' &&
252
16.2M
                         !STARTS_WITH_CI(psContext->pszInput +
253
172k
                                             psContext->nInputOffset,
254
172k
                                         "]>"));
255
256
172k
                if (chNext == '\0')
257
2.37k
                {
258
2.37k
                    eLastErrorType = CE_Failure;
259
2.37k
                    CPLError(eLastErrorType, CPLE_AppDefined,
260
2.37k
                             "Parse error in DOCTYPE on or before line %d, "
261
2.37k
                             "reached end of file without ']'.",
262
2.37k
                             psContext->nInputLine);
263
2.37k
                    break;
264
2.37k
                }
265
266
170k
                if (chNext != ']')
267
41.3k
                {
268
41.3k
                    chNext = ReadChar(psContext);
269
41.3k
                    AddToToken(psContext, chNext);
270
271
                    // Skip ">" character, will be consumed below.
272
41.3k
                    chNext = ReadChar(psContext);
273
41.3k
                }
274
170k
            }
275
276
46.2M
            if (chNext == '\"')
277
113k
                bInQuotes = !bInQuotes;
278
279
46.2M
            if (chNext == '>' && !bInQuotes)
280
58.4k
            {
281
58.4k
                AddToToken(psContext, '>');
282
58.4k
                break;
283
58.4k
            }
284
285
46.2M
            AddToToken(psContext, chNext);
286
46.2M
        } while (true);
287
63.5k
    }
288
    /* -------------------------------------------------------------------- */
289
    /*      Handle CDATA.                                                   */
290
    /* -------------------------------------------------------------------- */
291
182M
    else if (chNext == '<' &&
292
30.2M
             STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset,
293
182M
                            "![CDATA["))
294
27.0k
    {
295
27.0k
        psContext->eTokenType = TString;
296
297
        // Skip !CDATA[
298
27.0k
        ReadChar(psContext);
299
27.0k
        ReadChar(psContext);
300
27.0k
        ReadChar(psContext);
301
27.0k
        ReadChar(psContext);
302
27.0k
        ReadChar(psContext);
303
27.0k
        ReadChar(psContext);
304
27.0k
        ReadChar(psContext);
305
27.0k
        ReadChar(psContext);
306
307
45.6M
        while (!STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset,
308
45.6M
                               "]]>") &&
309
45.6M
               (chNext = ReadChar(psContext)) != '\0')
310
45.6M
            AddToToken(psContext, chNext);
311
312
        // Skip "]]>" characters.
313
27.0k
        ReadChar(psContext);
314
27.0k
        ReadChar(psContext);
315
27.0k
        ReadChar(psContext);
316
27.0k
    }
317
    /* -------------------------------------------------------------------- */
318
    /*      Simple single tokens of interest.                               */
319
    /* -------------------------------------------------------------------- */
320
182M
    else if (chNext == '<' && !psContext->bInElement)
321
28.2M
    {
322
28.2M
        psContext->eTokenType = TOpen;
323
28.2M
        psContext->bInElement = true;
324
28.2M
    }
325
153M
    else if (chNext == '>' && psContext->bInElement)
326
22.3M
    {
327
22.3M
        psContext->eTokenType = TClose;
328
22.3M
        psContext->bInElement = false;
329
22.3M
    }
330
131M
    else if (chNext == '=' && psContext->bInElement)
331
25.2M
    {
332
25.2M
        psContext->eTokenType = TEqual;
333
25.2M
    }
334
106M
    else if (chNext == '\0')
335
769k
    {
336
769k
        psContext->eTokenType = TNone;
337
769k
    }
338
    /* -------------------------------------------------------------------- */
339
    /*      Handle the /> token terminator.                                 */
340
    /* -------------------------------------------------------------------- */
341
105M
    else if (chNext == '/' && psContext->bInElement &&
342
16.6M
             psContext->pszInput[psContext->nInputOffset] == '>')
343
5.66M
    {
344
5.66M
        chNext = ReadChar(psContext);
345
5.66M
        (void)chNext;
346
5.66M
        CPLAssert(chNext == '>');
347
348
5.66M
        psContext->eTokenType = TSlashClose;
349
5.66M
        psContext->bInElement = false;
350
5.66M
    }
351
    /* -------------------------------------------------------------------- */
352
    /*      Handle the ?> token terminator.                                 */
353
    /* -------------------------------------------------------------------- */
354
99.8M
    else if (chNext == '?' && psContext->bInElement &&
355
708k
             psContext->pszInput[psContext->nInputOffset] == '>')
356
174k
    {
357
174k
        chNext = ReadChar(psContext);
358
174k
        (void)chNext;
359
174k
        CPLAssert(chNext == '>');
360
361
174k
        psContext->eTokenType = TQuestionClose;
362
174k
        psContext->bInElement = false;
363
174k
    }
364
    /* -------------------------------------------------------------------- */
365
    /*      Collect a quoted string.                                        */
366
    /* -------------------------------------------------------------------- */
367
99.6M
    else if (psContext->bInElement && chNext == '"')
368
14.0M
    {
369
14.0M
        psContext->eTokenType = TString;
370
371
2.25G
        while ((chNext = ReadChar(psContext)) != '"' && chNext != '\0')
372
2.23G
            AddToToken(psContext, chNext);
373
374
14.0M
        if (chNext != '"')
375
14.2k
        {
376
14.2k
            psContext->eTokenType = TNone;
377
14.2k
            eLastErrorType = CE_Failure;
378
14.2k
            CPLError(
379
14.2k
                eLastErrorType, CPLE_AppDefined,
380
14.2k
                "Parse error on line %d, reached EOF before closing quote.",
381
14.2k
                psContext->nInputLine);
382
14.2k
        }
383
384
        // Do we need to unescape it?
385
14.0M
        if (strchr(psContext->pszToken, '&') != nullptr)
386
110k
        {
387
110k
            int nLength = 0;
388
110k
            char *pszUnescaped =
389
110k
                CPLUnescapeString(psContext->pszToken, &nLength, CPLES_XML);
390
110k
            strcpy(psContext->pszToken, pszUnescaped);
391
110k
            CPLFree(pszUnescaped);
392
110k
            psContext->nTokenSize = strlen(psContext->pszToken);
393
110k
        }
394
14.0M
    }
395
85.6M
    else if (psContext->bInElement && chNext == '\'')
396
10.5M
    {
397
10.5M
        psContext->eTokenType = TString;
398
399
327M
        while ((chNext = ReadChar(psContext)) != '\'' && chNext != '\0')
400
317M
            AddToToken(psContext, chNext);
401
402
10.5M
        if (chNext != '\'')
403
3.25k
        {
404
3.25k
            psContext->eTokenType = TNone;
405
3.25k
            eLastErrorType = CE_Failure;
406
3.25k
            CPLError(
407
3.25k
                eLastErrorType, CPLE_AppDefined,
408
3.25k
                "Parse error on line %d, reached EOF before closing quote.",
409
3.25k
                psContext->nInputLine);
410
3.25k
        }
411
412
        // Do we need to unescape it?
413
10.5M
        if (strchr(psContext->pszToken, '&') != nullptr)
414
10.8k
        {
415
10.8k
            int nLength = 0;
416
10.8k
            char *pszUnescaped =
417
10.8k
                CPLUnescapeString(psContext->pszToken, &nLength, CPLES_XML);
418
10.8k
            strcpy(psContext->pszToken, pszUnescaped);
419
10.8k
            CPLFree(pszUnescaped);
420
10.8k
            psContext->nTokenSize = strlen(psContext->pszToken);
421
10.8k
        }
422
10.5M
    }
423
    /* -------------------------------------------------------------------- */
424
    /*      Collect an unquoted string, terminated by a open angle          */
425
    /*      bracket.                                                        */
426
    /* -------------------------------------------------------------------- */
427
75.1M
    else if (!psContext->bInElement)
428
7.91M
    {
429
7.91M
        psContext->eTokenType = TString;
430
431
7.91M
        AddToToken(psContext, chNext);
432
1.46G
        while ((chNext = ReadChar(psContext)) != '<' && chNext != '\0')
433
1.45G
            AddToToken(psContext, chNext);
434
7.91M
        UnreadChar(psContext, chNext);
435
436
        // Do we need to unescape it?
437
7.91M
        if (strchr(psContext->pszToken, '&') != nullptr)
438
719k
        {
439
719k
            int nLength = 0;
440
719k
            char *pszUnescaped =
441
719k
                CPLUnescapeString(psContext->pszToken, &nLength, CPLES_XML);
442
719k
            strcpy(psContext->pszToken, pszUnescaped);
443
719k
            CPLFree(pszUnescaped);
444
719k
            psContext->nTokenSize = strlen(psContext->pszToken);
445
719k
        }
446
7.91M
    }
447
448
    /* -------------------------------------------------------------------- */
449
    /*      Collect a regular token terminated by white space, or           */
450
    /*      special character(s) like an equal sign.                        */
451
    /* -------------------------------------------------------------------- */
452
67.2M
    else
453
67.2M
    {
454
67.2M
        psContext->eTokenType = TToken;
455
456
        // Add the first character to the token regardless of what it is.
457
67.2M
        AddToToken(psContext, chNext);
458
459
67.2M
        for (chNext = ReadChar(psContext);
460
414M
             (chNext >= 'A' && chNext <= 'Z') ||
461
371M
             (chNext >= 'a' && chNext <= 'z') || chNext == '-' ||
462
76.9M
             chNext == '_' || chNext == '.' || chNext == ':' ||
463
71.5M
             (chNext >= '0' && chNext <= '9');
464
347M
             chNext = ReadChar(psContext))
465
347M
        {
466
347M
            AddToToken(psContext, chNext);
467
347M
        }
468
469
67.2M
        UnreadChar(psContext, chNext);
470
67.2M
    }
471
472
182M
    return psContext->eTokenType;
473
474
0
fail:
475
0
    psContext->eTokenType = TNone;
476
0
    return TNone;
477
182M
}
478
479
/************************************************************************/
480
/*                              PushNode()                              */
481
/************************************************************************/
482
483
static bool PushNode(ParseContext *psContext, CPLXMLNode *psNode,
484
                     CPLErr &eLastErrorType)
485
486
17.5M
{
487
17.5M
    if (psContext->nStackMaxSize <= psContext->nStackSize)
488
882k
    {
489
        // Somewhat arbitrary number.
490
882k
        if (psContext->nStackMaxSize >= 10000)
491
30
        {
492
30
            eLastErrorType = CE_Failure;
493
30
            CPLError(CE_Failure, CPLE_NotSupported,
494
30
                     "XML element depth beyond 10000. Giving up");
495
30
            VSIFree(psContext->papsStack);
496
30
            psContext->papsStack = nullptr;
497
30
            return false;
498
30
        }
499
882k
        psContext->nStackMaxSize += 10;
500
501
882k
        StackContext *papsStack = static_cast<StackContext *>(
502
882k
            VSIRealloc(psContext->papsStack,
503
882k
                       sizeof(StackContext) * psContext->nStackMaxSize));
504
882k
        if (papsStack == nullptr)
505
0
        {
506
0
            eLastErrorType = CE_Failure;
507
0
            CPLError(CE_Failure, CPLE_OutOfMemory,
508
0
                     "Out of memory allocating %d bytes",
509
0
                     static_cast<int>(sizeof(StackContext)) *
510
0
                         psContext->nStackMaxSize);
511
0
            VSIFree(psContext->papsStack);
512
0
            psContext->papsStack = nullptr;
513
0
            return false;
514
0
        }
515
882k
        psContext->papsStack = papsStack;
516
882k
    }
517
#ifdef DEBUG
518
    // To make Coverity happy, but cannot happen.
519
    if (psContext->papsStack == nullptr)
520
        return false;
521
#endif
522
523
17.5M
    psContext->papsStack[psContext->nStackSize].psFirstNode = psNode;
524
17.5M
    psContext->papsStack[psContext->nStackSize].psLastChild = nullptr;
525
17.5M
    psContext->nStackSize++;
526
527
17.5M
    return true;
528
17.5M
}
529
530
/************************************************************************/
531
/*                             AttachNode()                             */
532
/*                                                                      */
533
/*      Attach the passed node as a child of the current node.          */
534
/*      Special handling exists for adding siblings to psFirst if       */
535
/*      there is nothing on the stack.                                  */
536
/************************************************************************/
537
538
static void AttachNode(ParseContext *psContext, CPLXMLNode *psNode)
539
540
64.1M
{
541
64.1M
    if (psContext->psFirstNode == nullptr)
542
802k
    {
543
802k
        psContext->psFirstNode = psNode;
544
802k
        psContext->psLastNode = psNode;
545
802k
    }
546
63.3M
    else if (psContext->nStackSize == 0)
547
760k
    {
548
760k
        psContext->psLastNode->psNext = psNode;
549
760k
        psContext->psLastNode = psNode;
550
760k
    }
551
62.5M
    else
552
62.5M
    {
553
62.5M
        if (psContext->papsStack[psContext->nStackSize - 1]
554
62.5M
                .psFirstNode->psChild == nullptr)
555
16.6M
        {
556
16.6M
            psContext->papsStack[psContext->nStackSize - 1]
557
16.6M
                .psFirstNode->psChild = psNode;
558
16.6M
        }
559
45.9M
        else
560
45.9M
        {
561
45.9M
            psContext->papsStack[psContext->nStackSize - 1]
562
45.9M
                .psLastChild->psNext = psNode;
563
45.9M
        }
564
62.5M
        psContext->papsStack[psContext->nStackSize - 1].psLastChild = psNode;
565
62.5M
    }
566
64.1M
}
567
568
/************************************************************************/
569
/*                         CPLParseXMLString()                          */
570
/************************************************************************/
571
572
/**
573
 * \brief Parse an XML string into tree form.
574
 *
575
 * The passed document is parsed into a CPLXMLNode tree representation.
576
 * If the document is not well formed XML then NULL is returned, and errors
577
 * are reported via CPLError().  No validation beyond wellformedness is
578
 * done.  The CPLParseXMLFile() convenience function can be used to parse
579
 * from a file.
580
 *
581
 * The returned document tree is owned by the caller and should be freed
582
 * with CPLDestroyXMLNode() when no longer needed.
583
 *
584
 * If the document has more than one "root level" element then those after the
585
 * first will be attached to the first as siblings (via the psNext pointers)
586
 * even though there is no common parent.  A document with no XML structure
587
 * (no angle brackets for instance) would be considered well formed, and
588
 * returned as a single CXT_Text node.
589
 *
590
 * @param pszString the document to parse.
591
 *
592
 * @return parsed tree or NULL on error.
593
 */
594
595
CPLXMLNode *CPLParseXMLString(const char *pszString)
596
597
820k
{
598
820k
    if (pszString == nullptr)
599
0
    {
600
0
        CPLError(CE_Failure, CPLE_AppDefined,
601
0
                 "CPLParseXMLString() called with NULL pointer.");
602
0
        return nullptr;
603
0
    }
604
605
    // Save back error context.
606
820k
    const CPLErr eErrClass = CPLGetLastErrorType();
607
820k
    const CPLErrorNum nErrNum = CPLGetLastErrorNo();
608
820k
    const CPLString osErrMsg = CPLGetLastErrorMsg();
609
610
    // Reset it now.
611
820k
    CPLErrorSetState(CE_None, CPLE_AppDefined, "");
612
613
    /* -------------------------------------------------------------------- */
614
    /*      Check for a UTF-8 BOM and skip if found                         */
615
    /*                                                                      */
616
    /*      TODO: BOM is variable-length parameter and depends on encoding. */
617
    /*            Add BOM detection for other encodings.                    */
618
    /* -------------------------------------------------------------------- */
619
620
    // Used to skip to actual beginning of XML data.
621
820k
    if ((static_cast<unsigned char>(pszString[0]) == 0xEF) &&
622
2.00k
        (static_cast<unsigned char>(pszString[1]) == 0xBB) &&
623
749
        (static_cast<unsigned char>(pszString[2]) == 0xBF))
624
439
    {
625
439
        pszString += 3;
626
439
    }
627
628
    /* -------------------------------------------------------------------- */
629
    /*      Initialize parse context.                                       */
630
    /* -------------------------------------------------------------------- */
631
820k
    ParseContext sContext;
632
820k
    sContext.pszInput = pszString;
633
820k
    sContext.nInputOffset = 0;
634
820k
    sContext.nInputLine = 0;
635
820k
    sContext.bInElement = false;
636
820k
    sContext.nTokenMaxSize = 10;
637
820k
    sContext.pszToken = static_cast<char *>(VSIMalloc(sContext.nTokenMaxSize));
638
820k
    if (sContext.pszToken == nullptr)
639
0
        return nullptr;
640
820k
    sContext.nTokenSize = 0;
641
820k
    sContext.eTokenType = TNone;
642
820k
    sContext.nStackMaxSize = 0;
643
820k
    sContext.nStackSize = 0;
644
820k
    sContext.papsStack = nullptr;
645
820k
    sContext.psFirstNode = nullptr;
646
820k
    sContext.psLastNode = nullptr;
647
648
#ifdef DEBUG
649
    bool bRecoverableError = true;
650
#endif
651
820k
    CPLErr eLastErrorType = CE_None;
652
653
    /* ==================================================================== */
654
    /*      Loop reading tokens.                                            */
655
    /* ==================================================================== */
656
80.0M
    while (ReadToken(&sContext, eLastErrorType) != TNone)
657
79.2M
    {
658
92.3M
    loop_beginning:
659
        /* --------------------------------------------------------------------
660
         */
661
        /*      Create a new element. */
662
        /* --------------------------------------------------------------------
663
         */
664
92.3M
        if (sContext.eTokenType == TOpen)
665
28.2M
        {
666
28.2M
            if (ReadToken(&sContext, eLastErrorType) != TToken)
667
3.01k
            {
668
3.01k
                eLastErrorType = CE_Failure;
669
3.01k
                CPLError(eLastErrorType, CPLE_AppDefined,
670
3.01k
                         "Line %d: Didn't find element token after "
671
3.01k
                         "open angle bracket.",
672
3.01k
                         sContext.nInputLine);
673
3.01k
                break;
674
3.01k
            }
675
676
28.2M
            CPLXMLNode *psElement = nullptr;
677
28.2M
            if (sContext.pszToken[0] != '/')
678
17.5M
            {
679
17.5M
                psElement =
680
17.5M
                    _CPLCreateXMLNode(nullptr, CXT_Element, sContext.pszToken);
681
17.5M
                if (!psElement)
682
0
                    break;
683
17.5M
                AttachNode(&sContext, psElement);
684
17.5M
                if (!PushNode(&sContext, psElement, eLastErrorType))
685
30
                    break;
686
17.5M
            }
687
10.6M
            else
688
10.6M
            {
689
10.6M
                if (sContext.nStackSize == 0 ||
690
10.6M
                    !EQUAL(sContext.pszToken + 1,
691
10.6M
                           sContext.papsStack[sContext.nStackSize - 1]
692
10.6M
                               .psFirstNode->pszValue))
693
9.68k
                {
694
#ifdef DEBUG
695
                    // Makes life of fuzzers easier if we accept somewhat
696
                    // corrupted XML like <foo> ... </not_foo>.
697
                    if (CPLTestBool(
698
                            CPLGetConfigOption("CPL_MINIXML_RELAXED", "FALSE")))
699
                    {
700
                        eLastErrorType = CE_Warning;
701
                        CPLError(
702
                            eLastErrorType, CPLE_AppDefined,
703
                            "Line %d: <%.500s> doesn't have matching <%.500s>.",
704
                            sContext.nInputLine, sContext.pszToken,
705
                            sContext.pszToken + 1);
706
                        if (sContext.nStackSize == 0)
707
                            break;
708
                        goto end_processing_close;
709
                    }
710
                    else
711
#endif
712
9.68k
                    {
713
9.68k
                        eLastErrorType = CE_Failure;
714
9.68k
                        CPLError(
715
9.68k
                            eLastErrorType, CPLE_AppDefined,
716
9.68k
                            "Line %d: <%.500s> doesn't have matching <%.500s>.",
717
9.68k
                            sContext.nInputLine, sContext.pszToken,
718
9.68k
                            sContext.pszToken + 1);
719
9.68k
                        break;
720
9.68k
                    }
721
9.68k
                }
722
10.6M
                else
723
10.6M
                {
724
10.6M
                    if (strcmp(sContext.pszToken + 1,
725
10.6M
                               sContext.papsStack[sContext.nStackSize - 1]
726
10.6M
                                   .psFirstNode->pszValue) != 0)
727
388k
                    {
728
                        // TODO: At some point we could just error out like any
729
                        // other sane XML parser would do.
730
388k
                        eLastErrorType = CE_Warning;
731
388k
                        CPLError(
732
388k
                            eLastErrorType, CPLE_AppDefined,
733
388k
                            "Line %d: <%.500s> matches <%.500s>, but the case "
734
388k
                            "isn't the same.  Going on, but this is invalid "
735
388k
                            "XML that might be rejected in future versions.",
736
388k
                            sContext.nInputLine,
737
388k
                            sContext.papsStack[sContext.nStackSize - 1]
738
388k
                                .psFirstNode->pszValue,
739
388k
                            sContext.pszToken);
740
388k
                    }
741
#ifdef DEBUG
742
                end_processing_close:
743
#endif
744
10.6M
                    if (ReadToken(&sContext, eLastErrorType) != TClose)
745
1.04k
                    {
746
1.04k
                        eLastErrorType = CE_Failure;
747
1.04k
                        CPLError(eLastErrorType, CPLE_AppDefined,
748
1.04k
                                 "Line %d: Missing close angle bracket "
749
1.04k
                                 "after <%.500s.",
750
1.04k
                                 sContext.nInputLine, sContext.pszToken);
751
1.04k
                        break;
752
1.04k
                    }
753
754
                    // Pop element off stack
755
10.6M
                    sContext.nStackSize--;
756
10.6M
                }
757
10.6M
            }
758
28.2M
        }
759
760
        /* --------------------------------------------------------------------
761
         */
762
        /*      Add an attribute to a token. */
763
        /* --------------------------------------------------------------------
764
         */
765
64.1M
        else if (sContext.eTokenType == TToken)
766
38.3M
        {
767
38.3M
            CPLXMLNode *psAttr =
768
38.3M
                _CPLCreateXMLNode(nullptr, CXT_Attribute, sContext.pszToken);
769
38.3M
            if (!psAttr)
770
0
                break;
771
38.3M
            AttachNode(&sContext, psAttr);
772
773
38.3M
            XMLTokenType nextToken = ReadToken(&sContext, eLastErrorType);
774
38.3M
            if (nextToken != TEqual)
775
13.0M
            {
776
                // Parse stuff like <?valbuddy_schematron
777
                // ../wmtsSimpleGetCapabilities.sch?>
778
13.0M
                if (sContext.nStackSize > 0 &&
779
13.0M
                    sContext.papsStack[sContext.nStackSize - 1]
780
13.0M
                            .psFirstNode->pszValue[0] == '?')
781
13.0M
                {
782
13.0M
                    psAttr->eType = CXT_Text;
783
13.0M
                    if (nextToken == TNone)
784
3.45k
                        break;
785
13.0M
                    goto loop_beginning;
786
13.0M
                }
787
788
18.9k
                eLastErrorType = CE_Failure;
789
18.9k
                CPLError(eLastErrorType, CPLE_AppDefined,
790
18.9k
                         "Line %d: Didn't find expected '=' for value of "
791
18.9k
                         "attribute '%.500s'.",
792
18.9k
                         sContext.nInputLine, psAttr->pszValue);
793
#ifdef DEBUG
794
                // Accepting an attribute without child text
795
                // would break too much assumptions in driver code
796
                bRecoverableError = false;
797
#endif
798
18.9k
                break;
799
13.0M
            }
800
801
25.2M
            if (ReadToken(&sContext, eLastErrorType) == TToken)
802
726k
            {
803
                /* TODO: at some point we could just error out like any other */
804
                /* sane XML parser would do */
805
726k
                eLastErrorType = CE_Warning;
806
726k
                CPLError(eLastErrorType, CPLE_AppDefined,
807
726k
                         "Line %d: Attribute value should be single or double "
808
726k
                         "quoted.  Going on, but this is invalid XML that "
809
726k
                         "might be rejected in future versions.",
810
726k
                         sContext.nInputLine);
811
726k
            }
812
24.4M
            else if (sContext.eTokenType != TString)
813
14.4k
            {
814
14.4k
                eLastErrorType = CE_Failure;
815
14.4k
                CPLError(eLastErrorType, CPLE_AppDefined,
816
14.4k
                         "Line %d: Didn't find expected attribute value.",
817
14.4k
                         sContext.nInputLine);
818
#ifdef DEBUG
819
                // Accepting an attribute without child text
820
                // would break too much assumptions in driver code
821
                bRecoverableError = false;
822
#endif
823
14.4k
                break;
824
14.4k
            }
825
826
25.2M
            if (!_CPLCreateXMLNode(psAttr, CXT_Text, sContext.pszToken))
827
0
                break;
828
25.2M
        }
829
830
        /* --------------------------------------------------------------------
831
         */
832
        /*      Close the start section of an element. */
833
        /* --------------------------------------------------------------------
834
         */
835
25.7M
        else if (sContext.eTokenType == TClose)
836
11.7M
        {
837
11.7M
            if (sContext.nStackSize == 0)
838
0
            {
839
0
                eLastErrorType = CE_Failure;
840
0
                CPLError(eLastErrorType, CPLE_AppDefined,
841
0
                         "Line %d: Found unbalanced '>'.", sContext.nInputLine);
842
0
                break;
843
0
            }
844
11.7M
        }
845
846
        /* --------------------------------------------------------------------
847
         */
848
        /*      Close the start section of an element, and pop it */
849
        /*      immediately. */
850
        /* --------------------------------------------------------------------
851
         */
852
14.0M
        else if (sContext.eTokenType == TSlashClose)
853
5.66M
        {
854
5.66M
            if (sContext.nStackSize == 0)
855
0
            {
856
0
                eLastErrorType = CE_Failure;
857
0
                CPLError(eLastErrorType, CPLE_AppDefined,
858
0
                         "Line %d: Found unbalanced '/>'.",
859
0
                         sContext.nInputLine);
860
0
                break;
861
0
            }
862
863
5.66M
            sContext.nStackSize--;
864
5.66M
        }
865
        /* --------------------------------------------------------------------
866
         */
867
        /*      Close the start section of a <?...?> element, and pop it */
868
        /*      immediately. */
869
        /* --------------------------------------------------------------------
870
         */
871
8.41M
        else if (sContext.eTokenType == TQuestionClose)
872
174k
        {
873
174k
            if (sContext.nStackSize == 0)
874
0
            {
875
0
                eLastErrorType = CE_Failure;
876
0
                CPLError(eLastErrorType, CPLE_AppDefined,
877
0
                         "Line %d: Found unbalanced '?>'.",
878
0
                         sContext.nInputLine);
879
0
                break;
880
0
            }
881
174k
            else if (sContext.papsStack[sContext.nStackSize - 1]
882
174k
                         .psFirstNode->pszValue[0] != '?')
883
1.61k
            {
884
1.61k
                eLastErrorType = CE_Failure;
885
1.61k
                CPLError(eLastErrorType, CPLE_AppDefined,
886
1.61k
                         "Line %d: Found '?>' without matching '<?'.",
887
1.61k
                         sContext.nInputLine);
888
1.61k
                break;
889
1.61k
            }
890
891
172k
            sContext.nStackSize--;
892
172k
        }
893
        /* --------------------------------------------------------------------
894
         */
895
        /*      Handle comments.  They are returned as a whole token with the */
896
        /*      prefix and postfix omitted.  No processing of white space */
897
        /*      will be done. */
898
        /* --------------------------------------------------------------------
899
         */
900
8.24M
        else if (sContext.eTokenType == TComment)
901
228k
        {
902
228k
            CPLXMLNode *psValue =
903
228k
                _CPLCreateXMLNode(nullptr, CXT_Comment, sContext.pszToken);
904
228k
            if (!psValue)
905
0
                break;
906
228k
            AttachNode(&sContext, psValue);
907
228k
        }
908
        /* --------------------------------------------------------------------
909
         */
910
        /*      Handle literals.  They are returned without processing. */
911
        /* --------------------------------------------------------------------
912
         */
913
8.01M
        else if (sContext.eTokenType == TLiteral)
914
63.5k
        {
915
63.5k
            CPLXMLNode *psValue =
916
63.5k
                _CPLCreateXMLNode(nullptr, CXT_Literal, sContext.pszToken);
917
63.5k
            if (!psValue)
918
0
                break;
919
63.5k
            AttachNode(&sContext, psValue);
920
63.5k
        }
921
        /* --------------------------------------------------------------------
922
         */
923
        /*      Add a text value node as a child of the current element. */
924
        /* --------------------------------------------------------------------
925
         */
926
7.95M
        else if (sContext.eTokenType == TString && !sContext.bInElement)
927
7.94M
        {
928
7.94M
            CPLXMLNode *psValue =
929
7.94M
                _CPLCreateXMLNode(nullptr, CXT_Text, sContext.pszToken);
930
7.94M
            if (!psValue)
931
0
                break;
932
7.94M
            AttachNode(&sContext, psValue);
933
7.94M
        }
934
        /* --------------------------------------------------------------------
935
         */
936
        /*      Anything else is an error. */
937
        /* --------------------------------------------------------------------
938
         */
939
4.60k
        else
940
4.60k
        {
941
4.60k
            eLastErrorType = CE_Failure;
942
4.60k
            CPLError(eLastErrorType, CPLE_AppDefined,
943
4.60k
                     "Parse error at line %d, unexpected token:%.500s",
944
4.60k
                     sContext.nInputLine, sContext.pszToken);
945
4.60k
            break;
946
4.60k
        }
947
92.3M
    }
948
949
    /* -------------------------------------------------------------------- */
950
    /*      Did we pop all the way out of our stack?                        */
951
    /* -------------------------------------------------------------------- */
952
820k
    if (CPLGetLastErrorType() != CE_Failure && sContext.nStackSize > 0 &&
953
25.0k
        sContext.papsStack != nullptr)
954
25.0k
    {
955
#ifdef DEBUG
956
        // Makes life of fuzzers easier if we accept somewhat corrupted XML
957
        // like <x> ...
958
        if (bRecoverableError &&
959
            CPLTestBool(CPLGetConfigOption("CPL_MINIXML_RELAXED", "FALSE")))
960
        {
961
            eLastErrorType = CE_Warning;
962
        }
963
        else
964
#endif
965
25.0k
        {
966
25.0k
            eLastErrorType = CE_Failure;
967
25.0k
        }
968
25.0k
        CPLError(
969
25.0k
            eLastErrorType, CPLE_AppDefined,
970
25.0k
            "Parse error at EOF, not all elements have been closed, "
971
25.0k
            "starting with %.500s",
972
25.0k
            sContext.papsStack[sContext.nStackSize - 1].psFirstNode->pszValue);
973
25.0k
    }
974
975
    /* -------------------------------------------------------------------- */
976
    /*      Cleanup                                                         */
977
    /* -------------------------------------------------------------------- */
978
820k
    CPLFree(sContext.pszToken);
979
820k
    if (sContext.papsStack != nullptr)
980
767k
        CPLFree(sContext.papsStack);
981
982
    // We do not trust CPLGetLastErrorType() as if CPLTurnFailureIntoWarning()
983
    // has been set we would never get failures
984
820k
    if (eLastErrorType == CE_Failure)
985
76.3k
    {
986
76.3k
        CPLDestroyXMLNode(sContext.psFirstNode);
987
76.3k
        sContext.psFirstNode = nullptr;
988
76.3k
        sContext.psLastNode = nullptr;
989
76.3k
    }
990
991
820k
    if (eLastErrorType == CE_None)
992
614k
    {
993
        // Restore initial error state.
994
614k
        CPLErrorSetState(eErrClass, nErrNum, osErrMsg);
995
614k
    }
996
997
820k
    return sContext.psFirstNode;
998
820k
}
999
1000
/************************************************************************/
1001
/*                            _GrowBuffer()                             */
1002
/************************************************************************/
1003
1004
static bool _GrowBuffer(size_t nNeeded, char **ppszText, size_t *pnMaxLength)
1005
1006
126M
{
1007
126M
    if (nNeeded + 1 >= *pnMaxLength)
1008
804k
    {
1009
804k
        *pnMaxLength = std::max(*pnMaxLength * 2, nNeeded + 1);
1010
804k
        char *pszTextNew =
1011
804k
            static_cast<char *>(VSIRealloc(*ppszText, *pnMaxLength));
1012
804k
        if (pszTextNew == nullptr)
1013
0
            return false;
1014
804k
        *ppszText = pszTextNew;
1015
804k
    }
1016
126M
    return true;
1017
126M
}
1018
1019
/************************************************************************/
1020
/*                        CPLSerializeXMLNode()                         */
1021
/************************************************************************/
1022
1023
// TODO(schwehr): Rewrite this whole thing using C++ string.
1024
// CPLSerializeXMLNode has buffer overflows.
1025
static bool CPLSerializeXMLNode(const CPLXMLNode *psNode, int nIndent,
1026
                                char **ppszText, size_t *pnLength,
1027
                                size_t *pnMaxLength)
1028
1029
53.7M
{
1030
53.7M
    if (psNode == nullptr)
1031
0
        return true;
1032
1033
    /* -------------------------------------------------------------------- */
1034
    /*      Ensure the buffer is plenty large to hold this additional       */
1035
    /*      string.                                                         */
1036
    /* -------------------------------------------------------------------- */
1037
53.7M
    *pnLength += strlen(*ppszText + *pnLength);
1038
53.7M
    if (!_GrowBuffer(strlen(psNode->pszValue) + *pnLength + 40 + nIndent,
1039
53.7M
                     ppszText, pnMaxLength))
1040
0
        return false;
1041
1042
    /* -------------------------------------------------------------------- */
1043
    /*      Text is just directly emitted.                                  */
1044
    /* -------------------------------------------------------------------- */
1045
53.7M
    if (psNode->eType == CXT_Text)
1046
21.4M
    {
1047
21.4M
        char *pszEscaped =
1048
21.4M
            CPLEscapeString(psNode->pszValue, -1, CPLES_XML_BUT_QUOTES);
1049
1050
21.4M
        CPLAssert(psNode->psChild == nullptr);
1051
1052
        // Escaped text might be bigger than expected.
1053
21.4M
        if (!_GrowBuffer(strlen(pszEscaped) + *pnLength, ppszText, pnMaxLength))
1054
0
        {
1055
0
            CPLFree(pszEscaped);
1056
0
            return false;
1057
0
        }
1058
21.4M
        strcat(*ppszText + *pnLength, pszEscaped);
1059
1060
21.4M
        CPLFree(pszEscaped);
1061
21.4M
    }
1062
1063
    /* -------------------------------------------------------------------- */
1064
    /*      Attributes require a little formatting.                         */
1065
    /* -------------------------------------------------------------------- */
1066
32.3M
    else if (psNode->eType == CXT_Attribute)
1067
11.3M
    {
1068
11.3M
        CPLAssert(psNode->psChild != nullptr &&
1069
11.3M
                  psNode->psChild->eType == CXT_Text);
1070
1071
11.3M
        snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength, " %s=\"",
1072
11.3M
                 psNode->pszValue);
1073
11.3M
        *pnLength += strlen(*ppszText + *pnLength);
1074
1075
11.3M
        char *pszEscaped =
1076
11.3M
            CPLEscapeString(psNode->psChild->pszValue, -1, CPLES_XML);
1077
1078
11.3M
        if (!_GrowBuffer(strlen(pszEscaped) + *pnLength, ppszText, pnMaxLength))
1079
0
        {
1080
0
            CPLFree(pszEscaped);
1081
0
            return false;
1082
0
        }
1083
11.3M
        strcat(*ppszText + *pnLength, pszEscaped);
1084
1085
11.3M
        CPLFree(pszEscaped);
1086
1087
11.3M
        *pnLength += strlen(*ppszText + *pnLength);
1088
11.3M
        if (!_GrowBuffer(3 + *pnLength, ppszText, pnMaxLength))
1089
0
            return false;
1090
11.3M
        strcat(*ppszText + *pnLength, "\"");
1091
11.3M
    }
1092
1093
    /* -------------------------------------------------------------------- */
1094
    /*      Handle comment output.                                          */
1095
    /* -------------------------------------------------------------------- */
1096
21.0M
    else if (psNode->eType == CXT_Comment)
1097
43.8k
    {
1098
43.8k
        CPLAssert(psNode->psChild == nullptr);
1099
1100
136M
        for (int i = 0; i < nIndent; i++)
1101
136M
            (*ppszText)[(*pnLength)++] = ' ';
1102
1103
43.8k
        snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength, "<!--%s-->\n",
1104
43.8k
                 psNode->pszValue);
1105
43.8k
    }
1106
1107
    /* -------------------------------------------------------------------- */
1108
    /*      Handle literal output (like <!DOCTYPE...>)                      */
1109
    /* -------------------------------------------------------------------- */
1110
20.9M
    else if (psNode->eType == CXT_Literal)
1111
25.5k
    {
1112
25.5k
        CPLAssert(psNode->psChild == nullptr);
1113
1114
46.0M
        for (int i = 0; i < nIndent; i++)
1115
46.0M
            (*ppszText)[(*pnLength)++] = ' ';
1116
1117
25.5k
        strcpy(*ppszText + *pnLength, psNode->pszValue);
1118
25.5k
        strcat(*ppszText + *pnLength, "\n");
1119
25.5k
    }
1120
1121
    /* -------------------------------------------------------------------- */
1122
    /*      Elements actually have to deal with general children, and       */
1123
    /*      various formatting issues.                                      */
1124
    /* -------------------------------------------------------------------- */
1125
20.9M
    else if (psNode->eType == CXT_Element)
1126
20.9M
    {
1127
20.9M
        if (nIndent)
1128
20.6M
            memset(*ppszText + *pnLength, ' ', nIndent);
1129
20.9M
        *pnLength += nIndent;
1130
20.9M
        (*ppszText)[*pnLength] = '\0';
1131
1132
20.9M
        snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength, "<%s",
1133
20.9M
                 psNode->pszValue);
1134
1135
20.9M
        if (psNode->pszValue[0] == '?')
1136
107k
        {
1137
107k
            for (const CPLXMLNode *psChild = psNode->psChild;
1138
5.17M
                 psChild != nullptr; psChild = psChild->psNext)
1139
5.06M
            {
1140
5.06M
                if (psChild->eType == CXT_Text)
1141
4.85M
                {
1142
4.85M
                    *pnLength += strlen(*ppszText + *pnLength);
1143
4.85M
                    if (!_GrowBuffer(1 + *pnLength, ppszText, pnMaxLength))
1144
0
                        return false;
1145
4.85M
                    strcat(*ppszText + *pnLength, " ");
1146
4.85M
                }
1147
1148
5.06M
                if (!CPLSerializeXMLNode(psChild, 0, ppszText, pnLength,
1149
5.06M
                                         pnMaxLength))
1150
0
                {
1151
0
                    return false;
1152
0
                }
1153
5.06M
            }
1154
107k
            if (!_GrowBuffer(*pnLength + 40, ppszText, pnMaxLength))
1155
0
                return false;
1156
1157
107k
            strcat(*ppszText + *pnLength, "?>\n");
1158
107k
        }
1159
20.8M
        else
1160
20.8M
        {
1161
20.8M
            bool bHasNonAttributeChildren = false;
1162
            // Serialize *all* the attribute children, regardless of order
1163
20.8M
            for (const CPLXMLNode *psChild = psNode->psChild;
1164
69.1M
                 psChild != nullptr; psChild = psChild->psNext)
1165
48.2M
            {
1166
48.2M
                if (psChild->eType == CXT_Attribute)
1167
11.1M
                {
1168
11.1M
                    if (!CPLSerializeXMLNode(psChild, 0, ppszText, pnLength,
1169
11.1M
                                             pnMaxLength))
1170
0
                        return false;
1171
11.1M
                }
1172
37.1M
                else
1173
37.1M
                    bHasNonAttributeChildren = true;
1174
48.2M
            }
1175
1176
20.8M
            if (!bHasNonAttributeChildren)
1177
1.19M
            {
1178
1.19M
                if (!_GrowBuffer(*pnLength + 40, ppszText, pnMaxLength))
1179
0
                    return false;
1180
1181
1.19M
                strcat(*ppszText + *pnLength, " />\n");
1182
1.19M
            }
1183
19.6M
            else
1184
19.6M
            {
1185
19.6M
                bool bJustText = true;
1186
1187
19.6M
                strcat(*ppszText + *pnLength, ">");
1188
1189
19.6M
                for (const CPLXMLNode *psChild = psNode->psChild;
1190
63.1M
                     psChild != nullptr; psChild = psChild->psNext)
1191
43.4M
                {
1192
43.4M
                    if (psChild->eType == CXT_Attribute)
1193
6.30M
                        continue;
1194
1195
37.1M
                    if (psChild->eType != CXT_Text && bJustText)
1196
3.22M
                    {
1197
3.22M
                        bJustText = false;
1198
3.22M
                        *pnLength += strlen(*ppszText + *pnLength);
1199
3.22M
                        if (!_GrowBuffer(1 + *pnLength, ppszText, pnMaxLength))
1200
0
                            return false;
1201
3.22M
                        strcat(*ppszText + *pnLength, "\n");
1202
3.22M
                    }
1203
1204
37.1M
                    if (!CPLSerializeXMLNode(psChild, nIndent + 2, ppszText,
1205
37.1M
                                             pnLength, pnMaxLength))
1206
0
                        return false;
1207
37.1M
                }
1208
1209
19.6M
                *pnLength += strlen(*ppszText + *pnLength);
1210
19.6M
                if (!_GrowBuffer(strlen(psNode->pszValue) + *pnLength + 40 +
1211
19.6M
                                     nIndent,
1212
19.6M
                                 ppszText, pnMaxLength))
1213
0
                    return false;
1214
1215
19.6M
                if (!bJustText)
1216
3.22M
                {
1217
3.22M
                    if (nIndent)
1218
3.04M
                        memset(*ppszText + *pnLength, ' ', nIndent);
1219
3.22M
                    *pnLength += nIndent;
1220
3.22M
                    (*ppszText)[*pnLength] = '\0';
1221
3.22M
                }
1222
1223
19.6M
                *pnLength += strlen(*ppszText + *pnLength);
1224
19.6M
                snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength,
1225
19.6M
                         "</%s>\n", psNode->pszValue);
1226
19.6M
            }
1227
20.8M
        }
1228
20.9M
    }
1229
1230
53.7M
    return true;
1231
53.7M
}
1232
1233
/************************************************************************/
1234
/*                        CPLSerializeXMLTree()                         */
1235
/************************************************************************/
1236
1237
/**
1238
 * \brief Convert tree into string document.
1239
 *
1240
 * This function converts a CPLXMLNode tree representation of a document
1241
 * into a flat string representation.  White space indentation is used
1242
 * visually preserve the tree structure of the document.  The returned
1243
 * document becomes owned by the caller and should be freed with CPLFree()
1244
 * when no longer needed.
1245
 *
1246
 * @param psNode the node to serialize.
1247
 *
1248
 * @return the document on success or NULL on failure.
1249
 */
1250
1251
char *CPLSerializeXMLTree(const CPLXMLNode *psNode)
1252
1253
177k
{
1254
177k
    size_t nMaxLength = 100;
1255
177k
    char *pszText = static_cast<char *>(CPLCalloc(nMaxLength, sizeof(char)));
1256
177k
    if (pszText == nullptr)
1257
0
        return nullptr;
1258
1259
177k
    size_t nLength = 0;
1260
628k
    for (const CPLXMLNode *psThis = psNode; psThis != nullptr;
1261
451k
         psThis = psThis->psNext)
1262
451k
    {
1263
451k
        if (!CPLSerializeXMLNode(psThis, 0, &pszText, &nLength, &nMaxLength))
1264
0
        {
1265
0
            VSIFree(pszText);
1266
0
            return nullptr;
1267
0
        }
1268
451k
    }
1269
1270
177k
    return pszText;
1271
177k
}
1272
1273
/************************************************************************/
1274
/*                          CPLCreateXMLNode()                          */
1275
/************************************************************************/
1276
1277
#ifdef DEBUG
1278
static CPLXMLNode *psDummyStaticNode;
1279
#endif
1280
1281
/**
1282
 * \brief Create an document tree item.
1283
 *
1284
 * Create a single CPLXMLNode object with the desired value and type, and
1285
 * attach it as a child of the indicated parent.
1286
 *
1287
 * @param poParent the parent to which this node should be attached as a
1288
 * child.  May be NULL to keep as free standing.
1289
 * @param eType the type of the newly created node
1290
 * @param pszText the value of the newly created node
1291
 *
1292
 * @return the newly created node, now owned by the caller (or parent node).
1293
 */
1294
1295
CPLXMLNode *CPLCreateXMLNode(CPLXMLNode *poParent, CPLXMLNodeType eType,
1296
                             const char *pszText)
1297
1298
154M
{
1299
154M
    auto ret = _CPLCreateXMLNode(poParent, eType, pszText);
1300
154M
    if (!ret)
1301
0
    {
1302
0
        CPLError(CE_Fatal, CPLE_OutOfMemory, "CPLCreateXMLNode() failed");
1303
0
    }
1304
154M
    return ret;
1305
154M
}
1306
1307
/************************************************************************/
1308
/*                         _CPLCreateXMLNode()                          */
1309
/************************************************************************/
1310
1311
/* Same as CPLCreateXMLNode() but can return NULL in case of out-of-memory */
1312
/* situation */
1313
1314
static CPLXMLNode *_CPLCreateXMLNode(CPLXMLNode *poParent, CPLXMLNodeType eType,
1315
                                     const char *pszText)
1316
1317
243M
{
1318
1319
    /* -------------------------------------------------------------------- */
1320
    /*      Create new node.                                                */
1321
    /* -------------------------------------------------------------------- */
1322
243M
    CPLXMLNode *psNode =
1323
243M
        static_cast<CPLXMLNode *>(VSICalloc(sizeof(CPLXMLNode), 1));
1324
243M
    if (psNode == nullptr)
1325
0
    {
1326
0
        CPLError(CE_Failure, CPLE_OutOfMemory, "Cannot allocate CPLXMLNode");
1327
0
        return nullptr;
1328
0
    }
1329
1330
243M
    psNode->eType = eType;
1331
243M
    psNode->pszValue = VSIStrdup(pszText ? pszText : "");
1332
243M
    if (psNode->pszValue == nullptr)
1333
0
    {
1334
0
        CPLError(CE_Failure, CPLE_OutOfMemory,
1335
0
                 "Cannot allocate psNode->pszValue");
1336
0
        VSIFree(psNode);
1337
0
        return nullptr;
1338
0
    }
1339
1340
    /* -------------------------------------------------------------------- */
1341
    /*      Attach to parent, if provided.                                  */
1342
    /* -------------------------------------------------------------------- */
1343
243M
    if (poParent != nullptr)
1344
159M
    {
1345
159M
        if (poParent->psChild == nullptr)
1346
105M
            poParent->psChild = psNode;
1347
53.9M
        else
1348
53.9M
        {
1349
53.9M
            CPLXMLNode *psLink = poParent->psChild;
1350
53.9M
            if (psLink->psNext == nullptr && eType == CXT_Attribute &&
1351
16.9M
                psLink->eType == CXT_Text)
1352
14.3M
            {
1353
14.3M
                psNode->psNext = psLink;
1354
14.3M
                poParent->psChild = psNode;
1355
14.3M
            }
1356
39.6M
            else
1357
39.6M
            {
1358
104M
                while (psLink->psNext != nullptr)
1359
81.1M
                {
1360
81.1M
                    if (eType == CXT_Attribute &&
1361
26.5M
                        psLink->psNext->eType == CXT_Text)
1362
15.9M
                    {
1363
15.9M
                        psNode->psNext = psLink->psNext;
1364
15.9M
                        break;
1365
15.9M
                    }
1366
1367
65.2M
                    psLink = psLink->psNext;
1368
65.2M
                }
1369
1370
39.6M
                psLink->psNext = psNode;
1371
39.6M
            }
1372
53.9M
        }
1373
159M
    }
1374
#ifdef DEBUG
1375
    else
1376
    {
1377
        // Coverity sometimes doesn't realize that this function is passed
1378
        // with a non NULL parent and thinks that this branch is taken, leading
1379
        // to creating object being leak by caller. This ugly hack hopefully
1380
        // makes it believe that someone will reference it.
1381
        psDummyStaticNode = psNode;
1382
    }
1383
#endif
1384
1385
243M
    return psNode;
1386
243M
}
1387
1388
/************************************************************************/
1389
/*                         CPLDestroyXMLNode()                          */
1390
/************************************************************************/
1391
1392
/**
1393
 * \brief Destroy a tree.
1394
 *
1395
 * This function frees resources associated with a CPLXMLNode and all its
1396
 * children nodes.
1397
 *
1398
 * @param psNode the tree to free.
1399
 */
1400
1401
void CPLDestroyXMLNode(CPLXMLNode *psNode)
1402
1403
1.11M
{
1404
244M
    while (psNode != nullptr)
1405
243M
    {
1406
243M
        if (psNode->pszValue != nullptr)
1407
243M
            CPLFree(psNode->pszValue);
1408
1409
243M
        if (psNode->psChild != nullptr)
1410
122M
        {
1411
122M
            CPLXMLNode *psNext = psNode->psNext;
1412
122M
            psNode->psNext = psNode->psChild;
1413
            // Move the child and its siblings as the next
1414
            // siblings of the current node.
1415
122M
            if (psNext != nullptr)
1416
118M
            {
1417
118M
                CPLXMLNode *psIter = psNode->psChild;
1418
221M
                while (psIter->psNext != nullptr)
1419
102M
                    psIter = psIter->psNext;
1420
118M
                psIter->psNext = psNext;
1421
118M
            }
1422
122M
        }
1423
1424
243M
        CPLXMLNode *psNext = psNode->psNext;
1425
1426
243M
        CPLFree(psNode);
1427
1428
243M
        psNode = psNext;
1429
243M
    }
1430
1.11M
}
1431
1432
/************************************************************************/
1433
/*                          CPLSearchXMLNode()                          */
1434
/************************************************************************/
1435
1436
/**
1437
 * \brief Search for a node in document.
1438
 *
1439
 * Searches the children (and potentially siblings) of the documented
1440
 * passed in for the named element or attribute.  To search following
1441
 * siblings as well as children, prefix the pszElement name with an equal
1442
 * sign.  This function does an in-order traversal of the document tree.
1443
 * So it will first match against the current node, then its first child,
1444
 * that child's first child, and so on.
1445
 *
1446
 * Use CPLGetXMLNode() to find a specific child, or along a specific
1447
 * node path.
1448
 *
1449
 * @param psRoot the subtree to search.  This should be a node of type
1450
 * CXT_Element.  NULL is safe.
1451
 *
1452
 * @param pszElement the name of the element or attribute to search for.
1453
 *
1454
 * @return The matching node or NULL on failure.
1455
 */
1456
1457
CPLXMLNode *CPLSearchXMLNode(CPLXMLNode *psRoot, const char *pszElement)
1458
1459
258k
{
1460
258k
    if (psRoot == nullptr || pszElement == nullptr)
1461
0
        return nullptr;
1462
1463
258k
    bool bSideSearch = false;
1464
1465
258k
    if (*pszElement == '=')
1466
10.7k
    {
1467
10.7k
        bSideSearch = true;
1468
10.7k
        pszElement++;
1469
10.7k
    }
1470
1471
    /* -------------------------------------------------------------------- */
1472
    /*      Does this node match?                                           */
1473
    /* -------------------------------------------------------------------- */
1474
258k
    if ((psRoot->eType == CXT_Element || psRoot->eType == CXT_Attribute) &&
1475
251k
        EQUAL(pszElement, psRoot->pszValue))
1476
8.06k
        return psRoot;
1477
1478
    /* -------------------------------------------------------------------- */
1479
    /*      Search children.                                                */
1480
    /* -------------------------------------------------------------------- */
1481
250k
    CPLXMLNode *psChild = nullptr;
1482
1.02M
    for (psChild = psRoot->psChild; psChild != nullptr;
1483
771k
         psChild = psChild->psNext)
1484
771k
    {
1485
771k
        if ((psChild->eType == CXT_Element ||
1486
612k
             psChild->eType == CXT_Attribute) &&
1487
225k
            EQUAL(pszElement, psChild->pszValue))
1488
53
            return psChild;
1489
1490
771k
        if (psChild->psChild != nullptr)
1491
223k
        {
1492
223k
            CPLXMLNode *psResult = CPLSearchXMLNode(psChild, pszElement);
1493
223k
            if (psResult != nullptr)
1494
152
                return psResult;
1495
223k
        }
1496
771k
    }
1497
1498
    /* -------------------------------------------------------------------- */
1499
    /*      Search siblings if we are in side search mode.                  */
1500
    /* -------------------------------------------------------------------- */
1501
249k
    if (bSideSearch)
1502
10.4k
    {
1503
26.6k
        for (psRoot = psRoot->psNext; psRoot != nullptr;
1504
16.1k
             psRoot = psRoot->psNext)
1505
23.9k
        {
1506
23.9k
            CPLXMLNode *psResult = CPLSearchXMLNode(psRoot, pszElement);
1507
23.9k
            if (psResult != nullptr)
1508
7.77k
                return psResult;
1509
23.9k
        }
1510
10.4k
    }
1511
1512
242k
    return nullptr;
1513
249k
}
1514
1515
/************************************************************************/
1516
/*                           CPLGetXMLNode()                            */
1517
/************************************************************************/
1518
1519
/**
1520
 * \brief Find node by path.
1521
 *
1522
 * Searches the document or subdocument indicated by psRoot for an element
1523
 * (or attribute) with the given path.  The path should consist of a set of
1524
 * element names separated by dots, not including the name of the root
1525
 * element (psRoot).  If the requested element is not found NULL is returned.
1526
 *
1527
 * Attribute names may only appear as the last item in the path.
1528
 *
1529
 * The search is done from the root nodes children, but all intermediate
1530
 * nodes in the path must be specified.  Searching for "name" would only find
1531
 * a name element or attribute if it is a direct child of the root, not at any
1532
 * level in the subdocument.
1533
 *
1534
 * If the pszPath is prefixed by "=" then the search will begin with the
1535
 * root node, and its siblings, instead of the root nodes children.  This
1536
 * is particularly useful when searching within a whole document which is
1537
 * often prefixed by one or more "junk" nodes like the <?xml> declaration.
1538
 *
1539
 * @param psRoot the subtree in which to search.  This should be a node of
1540
 * type CXT_Element.  NULL is safe.
1541
 *
1542
 * @param pszPath the list of element names in the path (dot separated).
1543
 *
1544
 * @return the requested element node, or NULL if not found.
1545
 */
1546
1547
CPLXMLNode *CPLGetXMLNode(CPLXMLNode *psRoot, const char *pszPath)
1548
1549
22.4M
{
1550
22.4M
    if (psRoot == nullptr || pszPath == nullptr)
1551
19.1k
        return nullptr;
1552
1553
22.4M
    bool bSideSearch = false;
1554
1555
22.4M
    if (*pszPath == '=')
1556
334k
    {
1557
334k
        bSideSearch = true;
1558
334k
        pszPath++;
1559
334k
    }
1560
1561
22.4M
    const char *const apszTokens[2] = {pszPath, nullptr};
1562
1563
    // Slight optimization: avoid using CSLTokenizeStringComplex that
1564
    // does memory allocations when it is not really necessary.
1565
22.4M
    bool bFreeTokens = false;
1566
22.4M
    char **papszTokensToFree = nullptr;
1567
22.4M
    const char *const *papszTokens;
1568
22.4M
    if (strchr(pszPath, '.'))
1569
529k
    {
1570
529k
        papszTokensToFree =
1571
529k
            CSLTokenizeStringComplex(pszPath, ".", FALSE, FALSE);
1572
529k
        papszTokens = papszTokensToFree;
1573
529k
        bFreeTokens = true;
1574
529k
    }
1575
21.8M
    else
1576
21.8M
    {
1577
21.8M
        papszTokens = apszTokens;
1578
21.8M
    }
1579
1580
22.4M
    int iToken = 0;
1581
31.6M
    while (papszTokens[iToken] != nullptr && psRoot != nullptr)
1582
22.8M
    {
1583
22.8M
        CPLXMLNode *psChild = nullptr;
1584
1585
22.8M
        if (bSideSearch)
1586
334k
        {
1587
334k
            psChild = psRoot;
1588
334k
            bSideSearch = false;
1589
334k
        }
1590
22.5M
        else
1591
22.5M
            psChild = psRoot->psChild;
1592
1593
90.6M
        for (; psChild != nullptr; psChild = psChild->psNext)
1594
77.0M
        {
1595
77.0M
            if (psChild->eType != CXT_Text &&
1596
64.2M
                EQUAL(papszTokens[iToken], psChild->pszValue))
1597
9.28M
                break;
1598
77.0M
        }
1599
1600
22.8M
        if (psChild == nullptr)
1601
13.5M
        {
1602
13.5M
            psRoot = nullptr;
1603
13.5M
            break;
1604
13.5M
        }
1605
1606
9.28M
        psRoot = psChild;
1607
9.28M
        iToken++;
1608
9.28M
    }
1609
1610
22.4M
    if (bFreeTokens)
1611
529k
        CSLDestroy(papszTokensToFree);
1612
22.4M
    return psRoot;
1613
22.4M
}
1614
1615
/************************************************************************/
1616
/*                           CPLGetXMLValue()                           */
1617
/************************************************************************/
1618
1619
/**
1620
 * \brief Fetch element/attribute value.
1621
 *
1622
 * Searches the document for the element/attribute value associated with
1623
 * the path.  The corresponding node is internally found with CPLGetXMLNode()
1624
 * (see there for details on path handling).  Once found, the value is
1625
 * considered to be the first CXT_Text child of the node.
1626
 *
1627
 * If the attribute/element search fails, or if the found node has no
1628
 * value then the passed default value is returned.
1629
 *
1630
 * The returned value points to memory within the document tree, and should
1631
 * not be altered or freed.
1632
 *
1633
 * @param psRoot the subtree in which to search.  This should be a node of
1634
 * type CXT_Element.  NULL is safe.
1635
 *
1636
 * @param pszPath the list of element names in the path (dot separated).  An
1637
 * empty path means get the value of the psRoot node.
1638
 *
1639
 * @param pszDefault the value to return if a corresponding value is not
1640
 * found, may be NULL.
1641
 *
1642
 * @return the requested value or pszDefault if not found.
1643
 */
1644
1645
const char *CPLGetXMLValue(const CPLXMLNode *psRoot, const char *pszPath,
1646
                           const char *pszDefault)
1647
1648
21.4M
{
1649
21.4M
    const CPLXMLNode *psTarget = nullptr;
1650
1651
21.4M
    if (pszPath == nullptr || *pszPath == '\0')
1652
816k
        psTarget = psRoot;
1653
20.5M
    else
1654
20.5M
        psTarget = CPLGetXMLNode(psRoot, pszPath);
1655
1656
21.4M
    if (psTarget == nullptr)
1657
12.2M
        return pszDefault;
1658
1659
9.11M
    if (psTarget->eType == CXT_Attribute)
1660
7.38M
    {
1661
7.38M
        CPLAssert(psTarget->psChild != nullptr &&
1662
7.38M
                  psTarget->psChild->eType == CXT_Text);
1663
1664
7.38M
        return psTarget->psChild->pszValue;
1665
7.38M
    }
1666
1667
1.72M
    if (psTarget->eType == CXT_Element)
1668
1.72M
    {
1669
        // Find first non-attribute child, and verify it is a single text
1670
        // with no siblings.
1671
1672
1.72M
        psTarget = psTarget->psChild;
1673
1674
2.39M
        while (psTarget != nullptr && psTarget->eType == CXT_Attribute)
1675
668k
            psTarget = psTarget->psNext;
1676
1677
1.72M
        if (psTarget != nullptr && psTarget->eType == CXT_Text &&
1678
1.35M
            psTarget->psNext == nullptr)
1679
1.35M
            return psTarget->pszValue;
1680
1.72M
    }
1681
1682
376k
    return pszDefault;
1683
1.72M
}
1684
1685
/************************************************************************/
1686
/*                           CPLAddXMLChild()                           */
1687
/************************************************************************/
1688
1689
/**
1690
 * \brief Add child node to parent.
1691
 *
1692
 * The passed child is added to the list of children of the indicated
1693
 * parent.  Normally the child is added at the end of the parents child
1694
 * list, but attributes (CXT_Attribute) will be inserted after any other
1695
 * attributes but before any other element type.  Ownership of the child
1696
 * node is effectively assumed by the parent node.   If the child has
1697
 * siblings (its psNext is not NULL) they will be trimmed, but if the child
1698
 * has children they are carried with it.
1699
 *
1700
 * @param psParent the node to attach the child to.  May not be NULL.
1701
 *
1702
 * @param psChild the child to add to the parent.  May not be NULL.  Should
1703
 * not be a child of any other parent.
1704
 */
1705
1706
void CPLAddXMLChild(CPLXMLNode *psParent, CPLXMLNode *psChild)
1707
1708
2.24M
{
1709
2.24M
    if (psParent->psChild == nullptr)
1710
22.4k
    {
1711
22.4k
        psParent->psChild = psChild;
1712
22.4k
        return;
1713
22.4k
    }
1714
1715
    // Insert at head of list if first child is not attribute.
1716
2.22M
    if (psChild->eType == CXT_Attribute &&
1717
0
        psParent->psChild->eType != CXT_Attribute)
1718
0
    {
1719
0
        psChild->psNext = psParent->psChild;
1720
0
        psParent->psChild = psChild;
1721
0
        return;
1722
0
    }
1723
1724
    // Search for end of list.
1725
2.22M
    CPLXMLNode *psSib = nullptr;
1726
133M
    for (psSib = psParent->psChild; psSib->psNext != nullptr;
1727
131M
         psSib = psSib->psNext)
1728
131M
    {
1729
        // Insert attributes if the next node is not an attribute.
1730
131M
        if (psChild->eType == CXT_Attribute && psSib->psNext != nullptr &&
1731
0
            psSib->psNext->eType != CXT_Attribute)
1732
0
        {
1733
0
            psChild->psNext = psSib->psNext;
1734
0
            psSib->psNext = psChild;
1735
0
            return;
1736
0
        }
1737
131M
    }
1738
1739
2.22M
    psSib->psNext = psChild;
1740
2.22M
}
1741
1742
/************************************************************************/
1743
/*                         CPLRemoveXMLChild()                          */
1744
/************************************************************************/
1745
1746
/**
1747
 * \brief Remove child node from parent.
1748
 *
1749
 * The passed child is removed from the child list of the passed parent,
1750
 * but the child is not destroyed.  The child retains ownership of its
1751
 * own children, but is cleanly removed from the child list of the parent.
1752
 *
1753
 * @param psParent the node to the child is attached to.
1754
 *
1755
 * @param psChild the child to remove.
1756
 *
1757
 * @return TRUE on success or FALSE if the child was not found.
1758
 */
1759
1760
int CPLRemoveXMLChild(CPLXMLNode *psParent, CPLXMLNode *psChild)
1761
1762
678
{
1763
678
    if (psParent == nullptr)
1764
0
        return FALSE;
1765
1766
678
    CPLXMLNode *psLast = nullptr;
1767
678
    CPLXMLNode *psThis = nullptr;
1768
1.76k
    for (psThis = psParent->psChild; psThis != nullptr; psThis = psThis->psNext)
1769
1.76k
    {
1770
1.76k
        if (psThis == psChild)
1771
678
        {
1772
678
            if (psLast == nullptr)
1773
407
                psParent->psChild = psThis->psNext;
1774
271
            else
1775
271
                psLast->psNext = psThis->psNext;
1776
1777
678
            psThis->psNext = nullptr;
1778
678
            return TRUE;
1779
678
        }
1780
1.08k
        psLast = psThis;
1781
1.08k
    }
1782
1783
0
    return FALSE;
1784
678
}
1785
1786
/************************************************************************/
1787
/*                          CPLAddXMLSibling()                          */
1788
/************************************************************************/
1789
1790
/**
1791
 * \brief Add new sibling.
1792
 *
1793
 * The passed psNewSibling is added to the end of siblings of the
1794
 * psOlderSibling node.  That is, it is added to the end of the psNext
1795
 * chain.  There is no special handling if psNewSibling is an attribute.
1796
 * If this is required, use CPLAddXMLChild().
1797
 *
1798
 * @param psOlderSibling the node to attach the sibling after.
1799
 *
1800
 * @param psNewSibling the node to add at the end of psOlderSiblings psNext
1801
 * chain.
1802
 */
1803
1804
void CPLAddXMLSibling(CPLXMLNode *psOlderSibling, CPLXMLNode *psNewSibling)
1805
1806
342k
{
1807
342k
    if (psOlderSibling == nullptr)
1808
0
        return;
1809
1810
342k
    while (psOlderSibling->psNext != nullptr)
1811
17
        psOlderSibling = psOlderSibling->psNext;
1812
1813
342k
    psOlderSibling->psNext = psNewSibling;
1814
342k
}
1815
1816
/************************************************************************/
1817
/*                    CPLCreateXMLElementAndValue()                     */
1818
/************************************************************************/
1819
1820
/**
1821
 * \brief Create an element and text value.
1822
 *
1823
 * This is function is a convenient short form for:
1824
 *
1825
 * \code
1826
 *     CPLXMLNode *psTextNode;
1827
 *     CPLXMLNode *psElementNode;
1828
 *
1829
 *     psElementNode = CPLCreateXMLNode( psParent, CXT_Element, pszName );
1830
 *     psTextNode = CPLCreateXMLNode( psElementNode, CXT_Text, pszValue );
1831
 *
1832
 *     return psElementNode;
1833
 * \endcode
1834
 *
1835
 * It creates a CXT_Element node, with a CXT_Text child, and
1836
 * attaches the element to the passed parent.
1837
 *
1838
 * @param psParent the parent node to which the resulting node should
1839
 * be attached.  May be NULL to keep as freestanding.
1840
 *
1841
 * @param pszName the element name to create.
1842
 * @param pszValue the text to attach to the element. Must not be NULL.
1843
 *
1844
 * @return the pointer to the new element node.
1845
 */
1846
1847
CPLXMLNode *CPLCreateXMLElementAndValue(CPLXMLNode *psParent,
1848
                                        const char *pszName,
1849
                                        const char *pszValue)
1850
1851
27.2M
{
1852
27.2M
    CPLXMLNode *psElementNode =
1853
27.2M
        CPLCreateXMLNode(psParent, CXT_Element, pszName);
1854
27.2M
    CPLCreateXMLNode(psElementNode, CXT_Text, pszValue);
1855
1856
27.2M
    return psElementNode;
1857
27.2M
}
1858
1859
/************************************************************************/
1860
/*                    CPLCreateXMLElementAndValue()                     */
1861
/************************************************************************/
1862
1863
/**
1864
 * \brief Create an attribute and text value.
1865
 *
1866
 * This is function is a convenient short form for:
1867
 *
1868
 * \code
1869
 *   CPLXMLNode *psAttributeNode;
1870
 *
1871
 *   psAttributeNode = CPLCreateXMLNode( psParent, CXT_Attribute, pszName );
1872
 *   CPLCreateXMLNode( psAttributeNode, CXT_Text, pszValue );
1873
 * \endcode
1874
 *
1875
 * It creates a CXT_Attribute node, with a CXT_Text child, and
1876
 * attaches the element to the passed parent.
1877
 *
1878
 * @param psParent the parent node to which the resulting node should
1879
 * be attached.  Must not be NULL.
1880
 * @param pszName the attribute name to create.
1881
 * @param pszValue the text to attach to the attribute. Must not be NULL.
1882
 *
1883
 */
1884
1885
void CPLAddXMLAttributeAndValue(CPLXMLNode *psParent, const char *pszName,
1886
                                const char *pszValue)
1887
38.8M
{
1888
38.8M
    CPLAssert(psParent != nullptr);
1889
38.8M
    CPLXMLNode *psAttributeNode =
1890
38.8M
        CPLCreateXMLNode(psParent, CXT_Attribute, pszName);
1891
38.8M
    CPLCreateXMLNode(psAttributeNode, CXT_Text, pszValue);
1892
38.8M
}
1893
1894
/************************************************************************/
1895
/*                          CPLCloneXMLTree()                           */
1896
/************************************************************************/
1897
1898
/**
1899
 * \brief Copy tree.
1900
 *
1901
 * Creates a deep copy of a CPLXMLNode tree.
1902
 *
1903
 * @param psTree the tree to duplicate.
1904
 *
1905
 * @return a copy of the whole tree.
1906
 */
1907
1908
CPLXMLNode *CPLCloneXMLTree(const CPLXMLNode *psTree)
1909
1910
133k
{
1911
133k
    CPLXMLNode *psPrevious = nullptr;
1912
133k
    CPLXMLNode *psReturn = nullptr;
1913
1914
940k
    while (psTree != nullptr)
1915
807k
    {
1916
807k
        CPLXMLNode *psCopy =
1917
807k
            CPLCreateXMLNode(nullptr, psTree->eType, psTree->pszValue);
1918
807k
        if (psReturn == nullptr)
1919
133k
            psReturn = psCopy;
1920
807k
        if (psPrevious != nullptr)
1921
674k
            psPrevious->psNext = psCopy;
1922
1923
807k
        if (psTree->psChild != nullptr)
1924
116k
            psCopy->psChild = CPLCloneXMLTree(psTree->psChild);
1925
1926
807k
        psPrevious = psCopy;
1927
807k
        psTree = psTree->psNext;
1928
807k
    }
1929
1930
133k
    return psReturn;
1931
133k
}
1932
1933
/************************************************************************/
1934
/*                           CPLSetXMLValue()                           */
1935
/************************************************************************/
1936
1937
/**
1938
 * \brief Set element value by path.
1939
 *
1940
 * Find (or create) the target element or attribute specified in the
1941
 * path, and assign it the indicated value.
1942
 *
1943
 * Any path elements that do not already exist will be created.  The target
1944
 * nodes value (the first CXT_Text child) will be replaced with the provided
1945
 * value.
1946
 *
1947
 * If the target node is an attribute instead of an element, the name
1948
 * should be prefixed with a #.
1949
 *
1950
 * Example:
1951
 *   CPLSetXMLValue( "Citation.Id.Description", "DOQ dataset" );
1952
 *   CPLSetXMLValue( "Citation.Id.Description.#name", "doq" );
1953
 *
1954
 * @param psRoot the subdocument to be updated.
1955
 *
1956
 * @param pszPath the dot separated path to the target element/attribute.
1957
 *
1958
 * @param pszValue the text value to assign.
1959
 *
1960
 * @return TRUE on success.
1961
 */
1962
1963
int CPLSetXMLValue(CPLXMLNode *psRoot, const char *pszPath,
1964
                   const char *pszValue)
1965
1966
5.89M
{
1967
5.89M
    char **papszTokens = CSLTokenizeStringComplex(pszPath, ".", FALSE, FALSE);
1968
5.89M
    int iToken = 0;
1969
1970
12.7M
    while (papszTokens[iToken] != nullptr)
1971
6.81M
    {
1972
6.81M
        bool bIsAttribute = false;
1973
6.81M
        const char *pszName = papszTokens[iToken];
1974
1975
6.81M
        if (pszName[0] == '#')
1976
5.54M
        {
1977
5.54M
            bIsAttribute = true;
1978
5.54M
            pszName++;
1979
5.54M
        }
1980
1981
6.81M
        if (psRoot->eType != CXT_Element)
1982
0
        {
1983
0
            CSLDestroy(papszTokens);
1984
0
            return FALSE;
1985
0
        }
1986
1987
6.81M
        CPLXMLNode *psChild = nullptr;
1988
18.1M
        for (psChild = psRoot->psChild; psChild != nullptr;
1989
11.2M
             psChild = psChild->psNext)
1990
12.0M
        {
1991
12.0M
            if (psChild->eType != CXT_Text && EQUAL(pszName, psChild->pszValue))
1992
710k
                break;
1993
12.0M
        }
1994
1995
6.81M
        if (psChild == nullptr)
1996
6.10M
        {
1997
6.10M
            if (bIsAttribute)
1998
5.54M
                psChild = CPLCreateXMLNode(psRoot, CXT_Attribute, pszName);
1999
560k
            else
2000
560k
                psChild = CPLCreateXMLNode(psRoot, CXT_Element, pszName);
2001
6.10M
        }
2002
2003
6.81M
        psRoot = psChild;
2004
6.81M
        iToken++;
2005
6.81M
    }
2006
2007
5.89M
    CSLDestroy(papszTokens);
2008
2009
    /* -------------------------------------------------------------------- */
2010
    /*      Find the "text" child if there is one.                          */
2011
    /* -------------------------------------------------------------------- */
2012
5.89M
    CPLXMLNode *psTextChild = psRoot->psChild;
2013
2014
5.89M
    while (psTextChild != nullptr && psTextChild->eType != CXT_Text)
2015
238
        psTextChild = psTextChild->psNext;
2016
2017
    /* -------------------------------------------------------------------- */
2018
    /*      Now set a value node under this node.                           */
2019
    /* -------------------------------------------------------------------- */
2020
2021
5.89M
    if (psTextChild == nullptr)
2022
5.89M
        CPLCreateXMLNode(psRoot, CXT_Text, pszValue);
2023
238
    else
2024
238
    {
2025
238
        CPLFree(psTextChild->pszValue);
2026
238
        psTextChild->pszValue = CPLStrdup(pszValue);
2027
238
    }
2028
2029
5.89M
    return TRUE;
2030
5.89M
}
2031
2032
/************************************************************************/
2033
/*                        CPLStripXMLNamespace()                        */
2034
/************************************************************************/
2035
2036
/**
2037
 * \brief Strip indicated namespaces.
2038
 *
2039
 * The subdocument (psRoot) is recursively examined, and any elements
2040
 * with the indicated namespace prefix will have the namespace prefix
2041
 * stripped from the element names.  If the passed namespace is NULL, then
2042
 * all namespace prefixes will be stripped.
2043
 *
2044
 * Nodes other than elements should remain unaffected.  The changes are
2045
 * made "in place", and should not alter any node locations, only the
2046
 * pszValue field of affected nodes.
2047
 *
2048
 * @param psRoot the document to operate on.
2049
 * @param pszNamespace the name space prefix (not including colon), or NULL.
2050
 * @param bRecurse TRUE to recurse over whole document, or FALSE to only
2051
 * operate on the passed node.
2052
 */
2053
2054
void CPLStripXMLNamespace(CPLXMLNode *psRoot, const char *pszNamespace,
2055
                          int bRecurse)
2056
2057
1.98M
{
2058
1.98M
    size_t nNameSpaceLen = (pszNamespace) ? strlen(pszNamespace) : 0;
2059
2060
6.34M
    while (psRoot != nullptr)
2061
4.35M
    {
2062
4.35M
        if (psRoot->eType == CXT_Element || psRoot->eType == CXT_Attribute)
2063
2.03M
        {
2064
2.03M
            if (pszNamespace != nullptr)
2065
102k
            {
2066
102k
                if (EQUALN(pszNamespace, psRoot->pszValue, nNameSpaceLen) &&
2067
58.1k
                    psRoot->pszValue[nNameSpaceLen] == ':')
2068
58.1k
                {
2069
58.1k
                    memmove(psRoot->pszValue,
2070
58.1k
                            psRoot->pszValue + nNameSpaceLen + 1,
2071
58.1k
                            strlen(psRoot->pszValue + nNameSpaceLen + 1) + 1);
2072
58.1k
                }
2073
102k
            }
2074
1.93M
            else
2075
1.93M
            {
2076
15.5M
                for (const char *pszCheck = psRoot->pszValue; *pszCheck != '\0';
2077
13.6M
                     pszCheck++)
2078
14.0M
                {
2079
14.0M
                    if (*pszCheck == ':')
2080
439k
                    {
2081
439k
                        memmove(psRoot->pszValue, pszCheck + 1,
2082
439k
                                strlen(pszCheck + 1) + 1);
2083
439k
                        break;
2084
439k
                    }
2085
14.0M
                }
2086
1.93M
            }
2087
2.03M
        }
2088
2089
4.35M
        if (bRecurse)
2090
4.35M
        {
2091
4.35M
            if (psRoot->psChild != nullptr)
2092
1.97M
                CPLStripXMLNamespace(psRoot->psChild, pszNamespace, 1);
2093
2094
4.35M
            psRoot = psRoot->psNext;
2095
4.35M
        }
2096
0
        else
2097
0
        {
2098
0
            break;
2099
0
        }
2100
4.35M
    }
2101
1.98M
}
2102
2103
/************************************************************************/
2104
/*                          CPLParseXMLFile()                           */
2105
/************************************************************************/
2106
2107
/**
2108
 * \brief Parse XML file into tree.
2109
 *
2110
 * The named file is opened, loaded into memory as a big string, and
2111
 * parsed with CPLParseXMLString().  Errors in reading the file or parsing
2112
 * the XML will be reported by CPLError().
2113
 *
2114
 * The "large file" API is used, so XML files can come from virtualized
2115
 * files.
2116
 *
2117
 * @param pszFilename the file to open.
2118
 *
2119
 * @return NULL on failure, or the document tree on success.
2120
 */
2121
2122
CPLXMLNode *CPLParseXMLFile(const char *pszFilename)
2123
2124
312k
{
2125
    /* -------------------------------------------------------------------- */
2126
    /*      Ingest the file.                                                */
2127
    /* -------------------------------------------------------------------- */
2128
312k
    GByte *pabyOut = nullptr;
2129
312k
    if (!VSIIngestFile(nullptr, pszFilename, &pabyOut, nullptr, -1))
2130
2.39k
        return nullptr;
2131
2132
310k
    char *pszDoc = reinterpret_cast<char *>(pabyOut);
2133
2134
    /* -------------------------------------------------------------------- */
2135
    /*      Parse it.                                                       */
2136
    /* -------------------------------------------------------------------- */
2137
310k
    CPLXMLNode *psTree = CPLParseXMLString(pszDoc);
2138
310k
    CPLFree(pszDoc);
2139
2140
310k
    return psTree;
2141
312k
}
2142
2143
/************************************************************************/
2144
/*                     CPLSerializeXMLTreeToFile()                      */
2145
/************************************************************************/
2146
2147
/**
2148
 * \brief Write document tree to a file.
2149
 *
2150
 * The passed document tree is converted into one big string (with
2151
 * CPLSerializeXMLTree()) and then written to the named file.  Errors writing
2152
 * the file will be reported by CPLError().  The source document tree is
2153
 * not altered.  If the output file already exists it will be overwritten.
2154
 *
2155
 * @param psTree the document tree to write.
2156
 * @param pszFilename the name of the file to write to.
2157
 * @return TRUE on success, FALSE otherwise.
2158
 */
2159
2160
int CPLSerializeXMLTreeToFile(const CPLXMLNode *psTree, const char *pszFilename)
2161
2162
90.0k
{
2163
    /* -------------------------------------------------------------------- */
2164
    /*      Serialize document.                                             */
2165
    /* -------------------------------------------------------------------- */
2166
90.0k
    char *pszDoc = CPLSerializeXMLTree(psTree);
2167
90.0k
    if (pszDoc == nullptr)
2168
0
        return FALSE;
2169
2170
90.0k
    const vsi_l_offset nLength = strlen(pszDoc);
2171
2172
    /* -------------------------------------------------------------------- */
2173
    /*      Create file.                                                    */
2174
    /* -------------------------------------------------------------------- */
2175
90.0k
    VSILFILE *fp = VSIFOpenL(pszFilename, "wt");
2176
90.0k
    if (fp == nullptr)
2177
1.60k
    {
2178
1.60k
        CPLError(CE_Failure, CPLE_OpenFailed, "Failed to open %.500s to write.",
2179
1.60k
                 pszFilename);
2180
1.60k
        CPLFree(pszDoc);
2181
1.60k
        return FALSE;
2182
1.60k
    }
2183
2184
    /* -------------------------------------------------------------------- */
2185
    /*      Write file.                                                     */
2186
    /* -------------------------------------------------------------------- */
2187
88.4k
    if (VSIFWriteL(pszDoc, 1, static_cast<size_t>(nLength), fp) != nLength)
2188
0
    {
2189
0
        CPLError(CE_Failure, CPLE_FileIO,
2190
0
                 "Failed to write whole XML document (%.500s).", pszFilename);
2191
0
        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
2192
0
        CPLFree(pszDoc);
2193
0
        return FALSE;
2194
0
    }
2195
2196
    /* -------------------------------------------------------------------- */
2197
    /*      Cleanup                                                         */
2198
    /* -------------------------------------------------------------------- */
2199
88.4k
    const bool bRet = VSIFCloseL(fp) == 0;
2200
88.4k
    if (!bRet)
2201
0
    {
2202
0
        CPLError(CE_Failure, CPLE_FileIO,
2203
0
                 "Failed to write whole XML document (%.500s).", pszFilename);
2204
0
    }
2205
88.4k
    CPLFree(pszDoc);
2206
2207
88.4k
    return bRet;
2208
88.4k
}
2209
2210
/************************************************************************/
2211
/*                       CPLCleanXMLElementName()                       */
2212
/************************************************************************/
2213
2214
/**
2215
 * \brief Make string into safe XML token.
2216
 *
2217
 * Modifies a string in place to try and make it into a legal
2218
 * XML token that can be used as an element name.   This is accomplished
2219
 * by changing any characters not legal in a token into an underscore.
2220
 *
2221
 * NOTE: This function should implement the rules in section 2.3 of
2222
 * http://www.w3.org/TR/xml11/ but it doesn't yet do that properly.  We
2223
 * only do a rough approximation of that.
2224
 *
2225
 * @param pszTarget the string to be adjusted.  It is altered in place.
2226
 */
2227
2228
void CPLCleanXMLElementName(char *pszTarget)
2229
43.9k
{
2230
43.9k
    if (pszTarget == nullptr)
2231
0
        return;
2232
2233
1.20M
    for (; *pszTarget != '\0'; pszTarget++)
2234
1.15M
    {
2235
1.15M
        if ((static_cast<unsigned char>(*pszTarget) & 0x80) ||
2236
1.05M
            isalnum(static_cast<unsigned char>(*pszTarget)) ||
2237
169k
            *pszTarget == '_' || *pszTarget == '.')
2238
1.05M
        {
2239
            // Ok.
2240
1.05M
        }
2241
104k
        else
2242
104k
        {
2243
104k
            *pszTarget = '_';
2244
104k
        }
2245
1.15M
    }
2246
43.9k
}
2247
2248
/************************************************************************/
2249
/*                   CPLXMLNodeGetRAMUsageEstimate()                    */
2250
/************************************************************************/
2251
2252
static size_t CPLXMLNodeGetRAMUsageEstimate(const CPLXMLNode *psNode,
2253
                                            bool bVisitSiblings)
2254
5.59M
{
2255
5.59M
    size_t nRet = sizeof(CPLXMLNode);
2256
    // malloc() aligns on 16-byte boundaries on 64 bit.
2257
5.59M
    nRet += std::max(2 * sizeof(void *), strlen(psNode->pszValue) + 1);
2258
5.59M
    if (bVisitSiblings)
2259
3.27M
    {
2260
5.59M
        for (const CPLXMLNode *psIter = psNode->psNext; psIter;
2261
3.27M
             psIter = psIter->psNext)
2262
2.32M
        {
2263
2.32M
            nRet += CPLXMLNodeGetRAMUsageEstimate(psIter, false);
2264
2.32M
        }
2265
3.27M
    }
2266
5.59M
    if (psNode->psChild)
2267
3.13M
    {
2268
3.13M
        nRet += CPLXMLNodeGetRAMUsageEstimate(psNode->psChild, true);
2269
3.13M
    }
2270
5.59M
    return nRet;
2271
5.59M
}
2272
2273
/** Return a conservative estimate of the RAM usage of this node, its children
2274
 * and siblings. The returned values is in bytes.
2275
 *
2276
 * @since 3.9
2277
 */
2278
size_t CPLXMLNodeGetRAMUsageEstimate(const CPLXMLNode *psNode)
2279
141k
{
2280
141k
    return CPLXMLNodeGetRAMUsageEstimate(psNode, true);
2281
141k
}
2282
2283
/************************************************************************/
2284
/*                CPLXMLTreeCloser::getDocumentElement()                */
2285
/************************************************************************/
2286
2287
CPLXMLNode *CPLXMLTreeCloser::getDocumentElement()
2288
0
{
2289
0
    CPLXMLNode *doc = get();
2290
    // skip the Declaration and assume the next is the root element
2291
0
    while (doc != nullptr &&
2292
0
           (doc->eType != CXT_Element || doc->pszValue[0] == '?'))
2293
0
    {
2294
0
        doc = doc->psNext;
2295
0
    }
2296
0
    return doc;
2297
0
}