• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 8872387746

29 Apr 2024 02:16AM UTC coverage: 69.076% (+0.003%) from 69.073%
8872387746

Pull #9801

github

web-flow
Bump actions/upload-artifact from 4.3.2 to 4.3.3

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.2 to 4.3.3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/1746f4ab6...65462800f)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #9801: Bump actions/upload-artifact from 4.3.2 to 4.3.3

534153 of 773282 relevant lines covered (69.08%)

205719.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.24
/port/cpl_minixml.cpp
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
 * Permission is hereby granted, free of charge, to any person obtaining a
12
 * copy of this software and associated documentation files (the "Software"),
13
 * to deal in the Software without restriction, including without limitation
14
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15
 * and/or sell copies of the Software, and to permit persons to whom the
16
 * Software is furnished to do so, subject to the following conditions:
17
 *
18
 * The above copyright notice and this permission notice shall be included
19
 * in all copies or substantial portions of the Software.
20
 *
21
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
24
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27
 * DEALINGS IN THE SOFTWARE.
28
 **********************************************************************
29
 *
30
 * Independent Security Audit 2003/04/05 Andrey Kiselev:
31
 *   Completed audit of this module. Any documents may be parsed without
32
 *   buffer overflows and stack corruptions.
33
 *
34
 * Security Audit 2003/03/28 warmerda:
35
 *   Completed security audit.  I believe that this module may be safely used
36
 *   to parse, and serialize arbitrary documents provided by a potentially
37
 *   hostile source.
38
 *
39
 */
40

41
#include "cpl_minixml.h"
42

43
#include <cctype>
44
#include <climits>
45
#include <cstddef>
46
#include <cstdio>
47
#include <cstring>
48

49
#include <algorithm>
50

51
#include "cpl_conv.h"
52
#include "cpl_error.h"
53
#include "cpl_string.h"
54
#include "cpl_vsi.h"
55

56
typedef enum
57
{
58
    TNone,
59
    TString,
60
    TOpen,
61
    TClose,
62
    TEqual,
63
    TToken,
64
    TSlashClose,
65
    TQuestionClose,
66
    TComment,
67
    TLiteral
68
} XMLTokenType;
69

70
typedef struct
71
{
72
    CPLXMLNode *psFirstNode;
73
    CPLXMLNode *psLastChild;
74
} StackContext;
75

76
typedef struct
77
{
78
    const char *pszInput;
79
    int nInputOffset;
80
    int nInputLine;
81
    bool bInElement;
82
    XMLTokenType eTokenType;
83
    char *pszToken;
84
    size_t nTokenMaxSize;
85
    size_t nTokenSize;
86

87
    int nStackMaxSize;
88
    int nStackSize;
89
    StackContext *papsStack;
90

91
    CPLXMLNode *psFirstNode;
92
    CPLXMLNode *psLastNode;
93
} ParseContext;
94

95
static CPLXMLNode *_CPLCreateXMLNode(CPLXMLNode *poParent, CPLXMLNodeType eType,
96
                                     const char *pszText);
97

98
/************************************************************************/
99
/*                              ReadChar()                              */
100
/************************************************************************/
101

102
static CPL_INLINE char ReadChar(ParseContext *psContext)
357,289,000✔
103

104
{
105
    const char chReturn = psContext->pszInput[psContext->nInputOffset++];
357,289,000✔
106

107
    if (chReturn == '\0')
357,289,000✔
108
        psContext->nInputOffset--;
224,161✔
109
    else if (chReturn == 10)
357,065,000✔
110
        psContext->nInputLine++;
4,211,680✔
111

112
    return chReturn;
357,289,000✔
113
}
114

115
/************************************************************************/
116
/*                             UnreadChar()                             */
117
/************************************************************************/
118

119
static CPL_INLINE void UnreadChar(ParseContext *psContext, char chToUnread)
14,432,800✔
120

121
{
122
    if (chToUnread == '\0')
14,432,800✔
123
        return;
36✔
124

125
    CPLAssert(chToUnread == psContext->pszInput[psContext->nInputOffset - 1]);
14,432,700✔
126

127
    psContext->nInputOffset--;
14,432,700✔
128

129
    if (chToUnread == 10)
14,432,700✔
130
        psContext->nInputLine--;
273✔
131
}
132

133
/************************************************************************/
134
/*                           ReallocToken()                             */
135
/************************************************************************/
136

137
static bool ReallocToken(ParseContext *psContext)
957,962✔
138
{
139
    if (psContext->nTokenMaxSize > INT_MAX / 2)
957,962✔
140
    {
141
        CPLError(CE_Failure, CPLE_OutOfMemory,
×
142
                 "Out of memory allocating %d*2 bytes",
143
                 static_cast<int>(psContext->nTokenMaxSize));
×
144
        VSIFree(psContext->pszToken);
×
145
        psContext->pszToken = nullptr;
×
146
        return false;
×
147
    }
148

149
    psContext->nTokenMaxSize *= 2;
957,962✔
150
    char *pszToken = static_cast<char *>(
151
        VSIRealloc(psContext->pszToken, psContext->nTokenMaxSize));
957,962✔
152
    if (pszToken == nullptr)
957,962✔
153
    {
154
        CPLError(CE_Failure, CPLE_OutOfMemory,
×
155
                 "Out of memory allocating %d bytes",
156
                 static_cast<int>(psContext->nTokenMaxSize));
×
157
        VSIFree(psContext->pszToken);
×
158
        psContext->pszToken = nullptr;
×
159
        return false;
×
160
    }
161
    psContext->pszToken = pszToken;
957,962✔
162
    return true;
957,962✔
163
}
164

165
/************************************************************************/
166
/*                             AddToToken()                             */
167
/************************************************************************/
168

169
static CPL_INLINE bool _AddToToken(ParseContext *psContext, char chNewChar)
257,956,000✔
170

171
{
172
    if (psContext->nTokenSize >= psContext->nTokenMaxSize - 2)
257,956,000✔
173
    {
174
        if (!ReallocToken(psContext))
957,962✔
175
            return false;
×
176
    }
177

178
    psContext->pszToken[psContext->nTokenSize++] = chNewChar;
257,956,000✔
179
    psContext->pszToken[psContext->nTokenSize] = '\0';
257,956,000✔
180
    return true;
257,956,000✔
181
}
182

183
// TODO(schwehr): Remove the goto.
184
#define AddToToken(psContext, chNewChar)                                       \
185
    if (!_AddToToken(psContext, chNewChar))                                    \
186
        goto fail;
187

188
/************************************************************************/
189
/*                             ReadToken()                              */
190
/************************************************************************/
191

192
static XMLTokenType ReadToken(ParseContext *psContext, CPLErr &eLastErrorType)
41,595,100✔
193

194
{
195
    psContext->nTokenSize = 0;
41,595,100✔
196
    psContext->pszToken[0] = '\0';
41,595,100✔
197

198
    char chNext = ReadChar(psContext);
41,595,100✔
199
    while (isspace(static_cast<unsigned char>(chNext)))
89,272,000✔
200
        chNext = ReadChar(psContext);
47,676,900✔
201

202
    /* -------------------------------------------------------------------- */
203
    /*      Handle comments.                                                */
204
    /* -------------------------------------------------------------------- */
205
    if (chNext == '<' &&
41,595,100✔
206
        STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset, "!--"))
5,710,800✔
207
    {
208
        psContext->eTokenType = TComment;
67,331✔
209

210
        // Skip "!--" characters.
211
        ReadChar(psContext);
67,331✔
212
        ReadChar(psContext);
67,331✔
213
        ReadChar(psContext);
67,331✔
214

215
        while (!STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset,
5,121,350✔
216
                               "-->") &&
10,310,000✔
217
               (chNext = ReadChar(psContext)) != '\0')
5,121,350✔
218
            AddToToken(psContext, chNext);
5,121,350✔
219

220
        // Skip "-->" characters.
221
        ReadChar(psContext);
67,331✔
222
        ReadChar(psContext);
67,331✔
223
        ReadChar(psContext);
67,331✔
224
    }
225
    /* -------------------------------------------------------------------- */
226
    /*      Handle DOCTYPE.                                                 */
227
    /* -------------------------------------------------------------------- */
228
    else if (chNext == '<' &&
41,527,800✔
229
             STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset,
5,643,480✔
230
                            "!DOCTYPE"))
231
    {
232
        bool bInQuotes = false;
18✔
233
        psContext->eTokenType = TLiteral;
18✔
234

235
        AddToToken(psContext, '<');
18✔
236
        do
237
        {
238
            chNext = ReadChar(psContext);
1,233✔
239
            if (chNext == '\0')
1,233✔
240
            {
241
                eLastErrorType = CE_Failure;
×
242
                CPLError(eLastErrorType, CPLE_AppDefined,
×
243
                         "Parse error in DOCTYPE on or before line %d, "
244
                         "reached end of file without '>'.",
245
                         psContext->nInputLine);
246

247
                break;
×
248
            }
249

250
            /* The markup declaration block within a DOCTYPE tag consists of:
251
             * - a left square bracket [
252
             * - a list of declarations
253
             * - a right square bracket ]
254
             * Example:
255
             * <!DOCTYPE RootElement [ ...declarations... ]>
256
             */
257
            if (chNext == '[')
1,233✔
258
            {
259
                AddToToken(psContext, chNext);
1✔
260

261
                do
98✔
262
                {
263
                    chNext = ReadChar(psContext);
99✔
264
                    if (chNext == ']')
99✔
265
                        break;
×
266
                    AddToToken(psContext, chNext);
99✔
267
                } while (chNext != '\0' &&
99✔
268
                         !STARTS_WITH_CI(psContext->pszInput +
99✔
269
                                             psContext->nInputOffset,
270
                                         "]>"));
271

272
                if (chNext == '\0')
1✔
273
                {
274
                    eLastErrorType = CE_Failure;
×
275
                    CPLError(eLastErrorType, CPLE_AppDefined,
×
276
                             "Parse error in DOCTYPE on or before line %d, "
277
                             "reached end of file without ']'.",
278
                             psContext->nInputLine);
279
                    break;
×
280
                }
281

282
                if (chNext != ']')
1✔
283
                {
284
                    chNext = ReadChar(psContext);
1✔
285
                    AddToToken(psContext, chNext);
1✔
286

287
                    // Skip ">" character, will be consumed below.
288
                    chNext = ReadChar(psContext);
1✔
289
                }
290
            }
291

292
            if (chNext == '\"')
1,233✔
293
                bInQuotes = !bInQuotes;
54✔
294

295
            if (chNext == '>' && !bInQuotes)
1,233✔
296
            {
297
                AddToToken(psContext, '>');
18✔
298
                break;
18✔
299
            }
300

301
            AddToToken(psContext, chNext);
1,215✔
302
        } while (true);
18✔
303
    }
304
    /* -------------------------------------------------------------------- */
305
    /*      Handle CDATA.                                                   */
306
    /* -------------------------------------------------------------------- */
307
    else if (chNext == '<' &&
41,527,800✔
308
             STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset,
5,643,460✔
309
                            "![CDATA["))
310
    {
311
        psContext->eTokenType = TString;
173✔
312

313
        // Skip !CDATA[
314
        ReadChar(psContext);
173✔
315
        ReadChar(psContext);
173✔
316
        ReadChar(psContext);
173✔
317
        ReadChar(psContext);
173✔
318
        ReadChar(psContext);
173✔
319
        ReadChar(psContext);
173✔
320
        ReadChar(psContext);
173✔
321
        ReadChar(psContext);
173✔
322

323
        while (!STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset,
91,580✔
324
                               "]]>") &&
183,334✔
325
               (chNext = ReadChar(psContext)) != '\0')
91,581✔
326
            AddToToken(psContext, chNext);
91,580✔
327

328
        // Skip "]]>" characters.
329
        ReadChar(psContext);
173✔
330
        ReadChar(psContext);
173✔
331
        ReadChar(psContext);
173✔
332
    }
333
    /* -------------------------------------------------------------------- */
334
    /*      Simple single tokens of interest.                               */
335
    /* -------------------------------------------------------------------- */
336
    else if (chNext == '<' && !psContext->bInElement)
41,527,600✔
337
    {
338
        psContext->eTokenType = TOpen;
5,643,280✔
339
        psContext->bInElement = true;
5,643,280✔
340
    }
341
    else if (chNext == '>' && psContext->bInElement)
35,884,300✔
342
    {
343
        psContext->eTokenType = TClose;
3,781,270✔
344
        psContext->bInElement = false;
3,781,270✔
345
    }
346
    else if (chNext == '=' && psContext->bInElement)
32,103,000✔
347
    {
348
        psContext->eTokenType = TEqual;
7,792,100✔
349
    }
350
    else if (chNext == '\0')
24,310,900✔
351
    {
352
        psContext->eTokenType = TNone;
224,120✔
353
    }
354
    /* -------------------------------------------------------------------- */
355
    /*      Handle the /> token terminator.                                 */
356
    /* -------------------------------------------------------------------- */
357
    else if (chNext == '/' && psContext->bInElement &&
24,086,800✔
358
             psContext->pszInput[psContext->nInputOffset] == '>')
3,739,480✔
359
    {
360
        chNext = ReadChar(psContext);
1,853,870✔
361
        (void)chNext;
362
        CPLAssert(chNext == '>');
1,853,870✔
363

364
        psContext->eTokenType = TSlashClose;
1,853,870✔
365
        psContext->bInElement = false;
1,853,870✔
366
    }
367
    /* -------------------------------------------------------------------- */
368
    /*      Handle the ?> token terminator.                                 */
369
    /* -------------------------------------------------------------------- */
370
    else if (chNext == '?' && psContext->bInElement &&
22,232,900✔
371
             psContext->pszInput[psContext->nInputOffset] == '>')
16,201✔
372
    {
373
        chNext = ReadChar(psContext);
8,099✔
374
        (void)chNext;
375
        CPLAssert(chNext == '>');
8,099✔
376

377
        psContext->eTokenType = TQuestionClose;
8,099✔
378
        psContext->bInElement = false;
8,099✔
379
    }
380
    /* -------------------------------------------------------------------- */
381
    /*      Collect a quoted string.                                        */
382
    /* -------------------------------------------------------------------- */
383
    else if (psContext->bInElement && chNext == '"')
22,224,800✔
384
    {
385
        psContext->eTokenType = TString;
6,706,140✔
386

387
        while ((chNext = ReadChar(psContext)) != '"' && chNext != '\0')
58,249,600✔
388
            AddToToken(psContext, chNext);
51,543,500✔
389

390
        if (chNext != '"')
6,706,140✔
391
        {
392
            psContext->eTokenType = TNone;
×
393
            eLastErrorType = CE_Failure;
×
394
            CPLError(
×
395
                eLastErrorType, CPLE_AppDefined,
396
                "Parse error on line %d, reached EOF before closing quote.",
397
                psContext->nInputLine);
398
        }
399

400
        // Do we need to unescape it?
401
        if (strchr(psContext->pszToken, '&') != nullptr)
6,706,140✔
402
        {
403
            int nLength = 0;
208✔
404
            char *pszUnescaped =
405
                CPLUnescapeString(psContext->pszToken, &nLength, CPLES_XML);
208✔
406
            strcpy(psContext->pszToken, pszUnescaped);
208✔
407
            CPLFree(pszUnescaped);
208✔
408
            psContext->nTokenSize = strlen(psContext->pszToken);
208✔
409
        }
6,706,140✔
410
    }
411
    else if (psContext->bInElement && chNext == '\'')
15,518,700✔
412
    {
413
        psContext->eTokenType = TString;
1,085,960✔
414

415
        while ((chNext = ReadChar(psContext)) != '\'' && chNext != '\0')
19,063,400✔
416
            AddToToken(psContext, chNext);
17,977,500✔
417

418
        if (chNext != '\'')
1,085,910✔
419
        {
420
            psContext->eTokenType = TNone;
1✔
421
            eLastErrorType = CE_Failure;
1✔
422
            CPLError(
1✔
423
                eLastErrorType, CPLE_AppDefined,
424
                "Parse error on line %d, reached EOF before closing quote.",
425
                psContext->nInputLine);
426
        }
427

428
        // Do we need to unescape it?
429
        if (strchr(psContext->pszToken, '&') != nullptr)
1,085,960✔
430
        {
431
            int nLength = 0;
1,552✔
432
            char *pszUnescaped =
433
                CPLUnescapeString(psContext->pszToken, &nLength, CPLES_XML);
1,552✔
434
            strcpy(psContext->pszToken, pszUnescaped);
1,552✔
435
            CPLFree(pszUnescaped);
1,552✔
436
            psContext->nTokenSize = strlen(psContext->pszToken);
1,552✔
437
        }
1,085,960✔
438
    }
439
    /* -------------------------------------------------------------------- */
440
    /*      Collect an unquoted string, terminated by a open angle          */
441
    /*      bracket.                                                        */
442
    /* -------------------------------------------------------------------- */
443
    else if (!psContext->bInElement)
14,432,700✔
444
    {
445
        psContext->eTokenType = TString;
997,379✔
446

447
        AddToToken(psContext, chNext);
997,379✔
448
        while ((chNext = ReadChar(psContext)) != '<' && chNext != '\0')
92,239,900✔
449
            AddToToken(psContext, chNext);
91,242,500✔
450
        UnreadChar(psContext, chNext);
997,379✔
451

452
        // Do we need to unescape it?
453
        if (strchr(psContext->pszToken, '&') != nullptr)
997,379✔
454
        {
455
            int nLength = 0;
19,039✔
456
            char *pszUnescaped =
457
                CPLUnescapeString(psContext->pszToken, &nLength, CPLES_XML);
19,039✔
458
            strcpy(psContext->pszToken, pszUnescaped);
19,039✔
459
            CPLFree(pszUnescaped);
19,039✔
460
            psContext->nTokenSize = strlen(psContext->pszToken);
19,039✔
461
        }
462
    }
463

464
    /* -------------------------------------------------------------------- */
465
    /*      Collect a regular token terminated by white space, or           */
466
    /*      special character(s) like an equal sign.                        */
467
    /* -------------------------------------------------------------------- */
468
    else
469
    {
470
        psContext->eTokenType = TToken;
13,435,400✔
471

472
        // Add the first character to the token regardless of what it is.
473
        AddToToken(psContext, chNext);
13,435,400✔
474

475
        for (chNext = ReadChar(psContext);
90,982,800✔
476
             (chNext >= 'A' && chNext <= 'Z') ||
90,982,800✔
477
             (chNext >= 'a' && chNext <= 'z') || chNext == '-' ||
87,575,000✔
478
             chNext == '_' || chNext == '.' || chNext == ':' ||
106,299,000✔
479
             (chNext >= '0' && chNext <= '9');
10,791,900✔
480
             chNext = ReadChar(psContext))
77,547,400✔
481
        {
482
            AddToToken(psContext, chNext);
77,547,700✔
483
        }
484

485
        UnreadChar(psContext, chNext);
13,435,100✔
486
    }
487

488
    return psContext->eTokenType;
41,595,100✔
489

490
fail:
×
491
    psContext->eTokenType = TNone;
×
492
    return TNone;
×
493
}
494

495
/************************************************************************/
496
/*                              PushNode()                              */
497
/************************************************************************/
498

499
static bool PushNode(ParseContext *psContext, CPLXMLNode *psNode,
3,757,670✔
500
                     CPLErr &eLastErrorType)
501

502
{
503
    if (psContext->nStackMaxSize <= psContext->nStackSize)
3,757,670✔
504
    {
505
        // Somewhat arbitrary number.
506
        if (psContext->nStackMaxSize >= 10000)
226,020✔
507
        {
508
            eLastErrorType = CE_Failure;
1✔
509
            CPLError(CE_Failure, CPLE_NotSupported,
1✔
510
                     "XML element depth beyond 10000. Giving up");
511
            VSIFree(psContext->papsStack);
1✔
512
            psContext->papsStack = nullptr;
1✔
513
            return false;
1✔
514
        }
515
        psContext->nStackMaxSize += 10;
226,019✔
516

517
        StackContext *papsStack = static_cast<StackContext *>(
518
            VSIRealloc(psContext->papsStack,
452,038✔
519
                       sizeof(StackContext) * psContext->nStackMaxSize));
226,019✔
520
        if (papsStack == nullptr)
226,019✔
521
        {
522
            eLastErrorType = CE_Failure;
×
523
            CPLError(CE_Failure, CPLE_OutOfMemory,
×
524
                     "Out of memory allocating %d bytes",
525
                     static_cast<int>(sizeof(StackContext)) *
526
                         psContext->nStackMaxSize);
×
527
            VSIFree(psContext->papsStack);
×
528
            psContext->papsStack = nullptr;
×
529
            return false;
×
530
        }
531
        psContext->papsStack = papsStack;
226,019✔
532
    }
533
#ifdef DEBUG
534
    // To make Coverity happy, but cannot happen.
535
    if (psContext->papsStack == nullptr)
3,757,670✔
536
        return false;
×
537
#endif
538

539
    psContext->papsStack[psContext->nStackSize].psFirstNode = psNode;
3,757,670✔
540
    psContext->papsStack[psContext->nStackSize].psLastChild = nullptr;
3,757,670✔
541
    psContext->nStackSize++;
3,757,670✔
542

543
    return true;
3,757,670✔
544
}
545

546
/************************************************************************/
547
/*                             AttachNode()                             */
548
/*                                                                      */
549
/*      Attach the passed node as a child of the current node.          */
550
/*      Special handling exists for adding siblings to psFirst if       */
551
/*      there is nothing on the stack.                                  */
552
/************************************************************************/
553

554
static void AttachNode(ParseContext *psContext, CPLXMLNode *psNode)
12,614,700✔
555

556
{
557
    if (psContext->psFirstNode == nullptr)
12,614,700✔
558
    {
559
        psContext->psFirstNode = psNode;
224,120✔
560
        psContext->psLastNode = psNode;
224,120✔
561
    }
562
    else if (psContext->nStackSize == 0)
12,390,600✔
563
    {
564
        psContext->psLastNode->psNext = psNode;
10,207✔
565
        psContext->psLastNode = psNode;
10,207✔
566
    }
567
    else
568
    {
569
        if (psContext->papsStack[psContext->nStackSize - 1]
12,380,300✔
570
                .psFirstNode->psChild == nullptr)
12,380,300✔
571
        {
572
            psContext->papsStack[psContext->nStackSize - 1]
3,735,760✔
573
                .psFirstNode->psChild = psNode;
3,735,760✔
574
        }
575
        else
576
        {
577
            psContext->papsStack[psContext->nStackSize - 1]
8,644,580✔
578
                .psLastChild->psNext = psNode;
8,644,580✔
579
        }
580
        psContext->papsStack[psContext->nStackSize - 1].psLastChild = psNode;
12,380,300✔
581
    }
582
}
12,614,700✔
583

584
/************************************************************************/
585
/*                         CPLParseXMLString()                          */
586
/************************************************************************/
587

588
/**
589
 * \brief Parse an XML string into tree form.
590
 *
591
 * The passed document is parsed into a CPLXMLNode tree representation.
592
 * If the document is not well formed XML then NULL is returned, and errors
593
 * are reported via CPLError().  No validation beyond wellformedness is
594
 * done.  The CPLParseXMLFile() convenience function can be used to parse
595
 * from a file.
596
 *
597
 * The returned document tree is owned by the caller and should be freed
598
 * with CPLDestroyXMLNode() when no longer needed.
599
 *
600
 * If the document has more than one "root level" element then those after the
601
 * first will be attached to the first as siblings (via the psNext pointers)
602
 * even though there is no common parent.  A document with no XML structure
603
 * (no angle brackets for instance) would be considered well formed, and
604
 * returned as a single CXT_Text node.
605
 *
606
 * @param pszString the document to parse.
607
 *
608
 * @return parsed tree or NULL on error.
609
 */
610

611
CPLXMLNode *CPLParseXMLString(const char *pszString)
224,145✔
612

613
{
614
    if (pszString == nullptr)
224,145✔
615
    {
616
        CPLError(CE_Failure, CPLE_AppDefined,
×
617
                 "CPLParseXMLString() called with NULL pointer.");
618
        return nullptr;
×
619
    }
620

621
    // Save back error context.
622
    const CPLErr eErrClass = CPLGetLastErrorType();
224,145✔
623
    const CPLErrorNum nErrNum = CPLGetLastErrorNo();
224,145✔
624
    const CPLString osErrMsg = CPLGetLastErrorMsg();
448,290✔
625

626
    // Reset it now.
627
    CPLErrorSetState(CE_None, CPLE_AppDefined, "");
224,145✔
628

629
    /* -------------------------------------------------------------------- */
630
    /*      Check for a UTF-8 BOM and skip if found                         */
631
    /*                                                                      */
632
    /*      TODO: BOM is variable-length parameter and depends on encoding. */
633
    /*            Add BOM detection for other encodings.                    */
634
    /* -------------------------------------------------------------------- */
635

636
    // Used to skip to actual beginning of XML data.
637
    if ((static_cast<unsigned char>(pszString[0]) == 0xEF) &&
224,145✔
638
        (static_cast<unsigned char>(pszString[1]) == 0xBB) &&
3✔
639
        (static_cast<unsigned char>(pszString[2]) == 0xBF))
3✔
640
    {
641
        pszString += 3;
3✔
642
    }
643

644
    /* -------------------------------------------------------------------- */
645
    /*      Initialize parse context.                                       */
646
    /* -------------------------------------------------------------------- */
647
    ParseContext sContext;
648
    sContext.pszInput = pszString;
224,145✔
649
    sContext.nInputOffset = 0;
224,145✔
650
    sContext.nInputLine = 0;
224,145✔
651
    sContext.bInElement = false;
224,145✔
652
    sContext.nTokenMaxSize = 10;
224,145✔
653
    sContext.pszToken = static_cast<char *>(VSIMalloc(sContext.nTokenMaxSize));
224,145✔
654
    if (sContext.pszToken == nullptr)
224,145✔
655
        return nullptr;
×
656
    sContext.nTokenSize = 0;
224,145✔
657
    sContext.eTokenType = TNone;
224,145✔
658
    sContext.nStackMaxSize = 0;
224,145✔
659
    sContext.nStackSize = 0;
224,145✔
660
    sContext.papsStack = nullptr;
224,145✔
661
    sContext.psFirstNode = nullptr;
224,145✔
662
    sContext.psLastNode = nullptr;
224,145✔
663

664
#ifdef DEBUG
665
    bool bRecoverableError = true;
224,145✔
666
#endif
667
    CPLErr eLastErrorType = CE_None;
224,145✔
668

669
    /* ==================================================================== */
670
    /*      Loop reading tokens.                                            */
671
    /* ==================================================================== */
672
    while (ReadToken(&sContext, eLastErrorType) != TNone)
18,482,000✔
673
    {
674
    loop_beginning:
18,257,900✔
675
        /* --------------------------------------------------------------------
676
         */
677
        /*      Create a new element. */
678
        /* --------------------------------------------------------------------
679
         */
680
        if (sContext.eTokenType == TOpen)
18,257,900✔
681
        {
682
            if (ReadToken(&sContext, eLastErrorType) != TToken)
5,643,280✔
683
            {
684
                eLastErrorType = CE_Failure;
2✔
685
                CPLError(eLastErrorType, CPLE_AppDefined,
2✔
686
                         "Line %d: Didn't find element token after "
687
                         "open angle bracket.",
688
                         sContext.nInputLine);
689
                break;
2✔
690
            }
691

692
            CPLXMLNode *psElement = nullptr;
5,643,280✔
693
            if (sContext.pszToken[0] != '/')
5,643,280✔
694
            {
695
                psElement =
696
                    _CPLCreateXMLNode(nullptr, CXT_Element, sContext.pszToken);
3,757,670✔
697
                if (!psElement)
3,757,670✔
698
                    break;
×
699
                AttachNode(&sContext, psElement);
3,757,670✔
700
                if (!PushNode(&sContext, psElement, eLastErrorType))
3,757,670✔
701
                    break;
1✔
702
            }
703
            else
704
            {
705
                if (sContext.nStackSize == 0 ||
1,885,610✔
706
                    !EQUAL(sContext.pszToken + 1,
1,885,610✔
707
                           sContext.papsStack[sContext.nStackSize - 1]
708
                               .psFirstNode->pszValue))
709
                {
710
#ifdef DEBUG
711
                    // Makes life of fuzzers easier if we accept somewhat
712
                    // corrupted XML like <foo> ... </not_foo>.
713
                    if (CPLTestBool(
14✔
714
                            CPLGetConfigOption("CPL_MINIXML_RELAXED", "FALSE")))
715
                    {
716
                        eLastErrorType = CE_Warning;
×
717
                        CPLError(
×
718
                            eLastErrorType, CPLE_AppDefined,
719
                            "Line %d: <%.500s> doesn't have matching <%.500s>.",
720
                            sContext.nInputLine, sContext.pszToken,
721
                            sContext.pszToken + 1);
×
722
                        if (sContext.nStackSize == 0)
×
723
                            break;
×
724
                        goto end_processing_close;
×
725
                    }
726
                    else
727
#endif
728
                    {
729
                        eLastErrorType = CE_Failure;
16✔
730
                        CPLError(
16✔
731
                            eLastErrorType, CPLE_AppDefined,
732
                            "Line %d: <%.500s> doesn't have matching <%.500s>.",
733
                            sContext.nInputLine, sContext.pszToken,
734
                            sContext.pszToken + 1);
16✔
735
                        break;
16✔
736
                    }
737
                }
738
                else
739
                {
740
                    if (strcmp(sContext.pszToken + 1,
1,885,600✔
741
                               sContext.papsStack[sContext.nStackSize - 1]
1,885,600✔
742
                                   .psFirstNode->pszValue) != 0)
1,885,600✔
743
                    {
744
                        // TODO: At some point we could just error out like any
745
                        // other sane XML parser would do.
746
                        eLastErrorType = CE_Warning;
1✔
747
                        CPLError(
1✔
748
                            eLastErrorType, CPLE_AppDefined,
749
                            "Line %d: <%.500s> matches <%.500s>, but the case "
750
                            "isn't the same.  Going on, but this is invalid "
751
                            "XML that might be rejected in future versions.",
752
                            sContext.nInputLine,
753
                            sContext.papsStack[sContext.nStackSize - 1]
1✔
754
                                .psFirstNode->pszValue,
1✔
755
                            sContext.pszToken);
756
                    }
757
#ifdef DEBUG
758
                end_processing_close:
1,885,600✔
759
#endif
760
                    if (ReadToken(&sContext, eLastErrorType) != TClose)
1,885,600✔
761
                    {
762
                        eLastErrorType = CE_Failure;
3✔
763
                        CPLError(eLastErrorType, CPLE_AppDefined,
3✔
764
                                 "Line %d: Missing close angle bracket "
765
                                 "after <%.500s.",
766
                                 sContext.nInputLine, sContext.pszToken);
767
                        break;
3✔
768
                    }
769

770
                    // Pop element off stack
771
                    sContext.nStackSize--;
1,885,590✔
772
                }
773
            }
774
        }
775

776
        /* --------------------------------------------------------------------
777
         */
778
        /*      Add an attribute to a token. */
779
        /* --------------------------------------------------------------------
780
         */
781
        else if (sContext.eTokenType == TToken)
12,614,600✔
782
        {
783
            CPLXMLNode *psAttr =
784
                _CPLCreateXMLNode(nullptr, CXT_Attribute, sContext.pszToken);
7,792,110✔
785
            if (!psAttr)
7,792,110✔
786
                break;
×
787
            AttachNode(&sContext, psAttr);
7,792,110✔
788

789
            XMLTokenType nextToken = ReadToken(&sContext, eLastErrorType);
7,792,110✔
790
            if (nextToken != TEqual)
7,792,110✔
791
            {
792
                // Parse stuff like <?valbuddy_schematron
793
                // ../wmtsSimpleGetCapabilities.sch?>
794
                if (sContext.nStackSize > 0 &&
5✔
795
                    sContext.papsStack[sContext.nStackSize - 1]
5✔
796
                            .psFirstNode->pszValue[0] == '?')
5✔
797
                {
798
                    psAttr->eType = CXT_Text;
3✔
799
                    if (nextToken == TNone)
3✔
800
                        break;
1✔
801
                    goto loop_beginning;
2✔
802
                }
803

804
                eLastErrorType = CE_Failure;
2✔
805
                CPLError(eLastErrorType, CPLE_AppDefined,
2✔
806
                         "Line %d: Didn't find expected '=' for value of "
807
                         "attribute '%.500s'.",
808
                         sContext.nInputLine, psAttr->pszValue);
809
#ifdef DEBUG
810
                // Accepting an attribute without child text
811
                // would break too much assumptions in driver code
812
                bRecoverableError = false;
2✔
813
#endif
814
                break;
2✔
815
            }
816

817
            if (ReadToken(&sContext, eLastErrorType) == TToken)
7,792,100✔
818
            {
819
                /* TODO: at some point we could just error out like any other */
820
                /* sane XML parser would do */
821
                eLastErrorType = CE_Warning;
2✔
822
                CPLError(eLastErrorType, CPLE_AppDefined,
2✔
823
                         "Line %d: Attribute value should be single or double "
824
                         "quoted.  Going on, but this is invalid XML that "
825
                         "might be rejected in future versions.",
826
                         sContext.nInputLine);
827
            }
828
            else if (sContext.eTokenType != TString)
7,792,100✔
829
            {
830
                eLastErrorType = CE_Failure;
1✔
831
                CPLError(eLastErrorType, CPLE_AppDefined,
1✔
832
                         "Line %d: Didn't find expected attribute value.",
833
                         sContext.nInputLine);
834
#ifdef DEBUG
835
                // Accepting an attribute without child text
836
                // would break too much assumptions in driver code
837
                bRecoverableError = false;
1✔
838
#endif
839
                break;
1✔
840
            }
841

842
            if (!_CPLCreateXMLNode(psAttr, CXT_Text, sContext.pszToken))
7,792,100✔
843
                break;
×
844
        }
845

846
        /* --------------------------------------------------------------------
847
         */
848
        /*      Close the start section of an element. */
849
        /* --------------------------------------------------------------------
850
         */
851
        else if (sContext.eTokenType == TClose)
4,822,540✔
852
        {
853
            if (sContext.nStackSize == 0)
1,895,670✔
854
            {
855
                eLastErrorType = CE_Failure;
×
856
                CPLError(eLastErrorType, CPLE_AppDefined,
×
857
                         "Line %d: Found unbalanced '>'.", sContext.nInputLine);
858
                break;
×
859
            }
860
        }
861

862
        /* --------------------------------------------------------------------
863
         */
864
        /*      Close the start section of an element, and pop it */
865
        /*      immediately. */
866
        /* --------------------------------------------------------------------
867
         */
868
        else if (sContext.eTokenType == TSlashClose)
2,926,870✔
869
        {
870
            if (sContext.nStackSize == 0)
1,853,870✔
871
            {
872
                eLastErrorType = CE_Failure;
×
873
                CPLError(eLastErrorType, CPLE_AppDefined,
×
874
                         "Line %d: Found unbalanced '/>'.",
875
                         sContext.nInputLine);
876
                break;
×
877
            }
878

879
            sContext.nStackSize--;
1,853,870✔
880
        }
881
        /* --------------------------------------------------------------------
882
         */
883
        /*      Close the start section of a <?...?> element, and pop it */
884
        /*      immediately. */
885
        /* --------------------------------------------------------------------
886
         */
887
        else if (sContext.eTokenType == TQuestionClose)
1,073,000✔
888
        {
889
            if (sContext.nStackSize == 0)
8,099✔
890
            {
891
                eLastErrorType = CE_Failure;
×
892
                CPLError(eLastErrorType, CPLE_AppDefined,
×
893
                         "Line %d: Found unbalanced '?>'.",
894
                         sContext.nInputLine);
895
                break;
×
896
            }
897
            else if (sContext.papsStack[sContext.nStackSize - 1]
8,099✔
898
                         .psFirstNode->pszValue[0] != '?')
8,099✔
899
            {
900
                eLastErrorType = CE_Failure;
1✔
901
                CPLError(eLastErrorType, CPLE_AppDefined,
1✔
902
                         "Line %d: Found '?>' without matching '<?'.",
903
                         sContext.nInputLine);
904
                break;
1✔
905
            }
906

907
            sContext.nStackSize--;
8,098✔
908
        }
909
        /* --------------------------------------------------------------------
910
         */
911
        /*      Handle comments.  They are returned as a whole token with the */
912
        /*      prefix and postfix omitted.  No processing of white space */
913
        /*      will be done. */
914
        /* --------------------------------------------------------------------
915
         */
916
        else if (sContext.eTokenType == TComment)
1,064,900✔
917
        {
918
            CPLXMLNode *psValue =
919
                _CPLCreateXMLNode(nullptr, CXT_Comment, sContext.pszToken);
67,331✔
920
            if (!psValue)
67,331✔
921
                break;
×
922
            AttachNode(&sContext, psValue);
67,331✔
923
        }
924
        /* --------------------------------------------------------------------
925
         */
926
        /*      Handle literals.  They are returned without processing. */
927
        /* --------------------------------------------------------------------
928
         */
929
        else if (sContext.eTokenType == TLiteral)
997,569✔
930
        {
931
            CPLXMLNode *psValue =
932
                _CPLCreateXMLNode(nullptr, CXT_Literal, sContext.pszToken);
18✔
933
            if (!psValue)
18✔
934
                break;
×
935
            AttachNode(&sContext, psValue);
18✔
936
        }
937
        /* --------------------------------------------------------------------
938
         */
939
        /*      Add a text value node as a child of the current element. */
940
        /* --------------------------------------------------------------------
941
         */
942
        else if (sContext.eTokenType == TString && !sContext.bInElement)
997,551✔
943
        {
944
            CPLXMLNode *psValue =
945
                _CPLCreateXMLNode(nullptr, CXT_Text, sContext.pszToken);
997,552✔
946
            if (!psValue)
997,552✔
947
                break;
×
948
            AttachNode(&sContext, psValue);
997,552✔
949
        }
950
        /* --------------------------------------------------------------------
951
         */
952
        /*      Anything else is an error. */
953
        /* --------------------------------------------------------------------
954
         */
955
        else
956
        {
957
            eLastErrorType = CE_Failure;
×
958
            CPLError(eLastErrorType, CPLE_AppDefined,
×
959
                     "Parse error at line %d, unexpected token:%.500s",
960
                     sContext.nInputLine, sContext.pszToken);
961
            break;
1✔
962
        }
963
    }
964

965
    /* -------------------------------------------------------------------- */
966
    /*      Did we pop all the way out of our stack?                        */
967
    /* -------------------------------------------------------------------- */
968
    if (CPLGetLastErrorType() != CE_Failure && sContext.nStackSize > 0 &&
224,196✔
969
        sContext.papsStack != nullptr)
50✔
970
    {
971
#ifdef DEBUG
972
        // Makes life of fuzzers easier if we accept somewhat corrupted XML
973
        // like <x> ...
974
        if (bRecoverableError &&
100✔
975
            CPLTestBool(CPLGetConfigOption("CPL_MINIXML_RELAXED", "FALSE")))
50✔
976
        {
977
            eLastErrorType = CE_Warning;
×
978
        }
979
        else
980
#endif
981
        {
982
            eLastErrorType = CE_Failure;
50✔
983
        }
984
        CPLError(
50✔
985
            eLastErrorType, CPLE_AppDefined,
986
            "Parse error at EOF, not all elements have been closed, "
987
            "starting with %.500s",
988
            sContext.papsStack[sContext.nStackSize - 1].psFirstNode->pszValue);
50✔
989
    }
990

991
    /* -------------------------------------------------------------------- */
992
    /*      Cleanup                                                         */
993
    /* -------------------------------------------------------------------- */
994
    CPLFree(sContext.pszToken);
224,145✔
995
    if (sContext.papsStack != nullptr)
224,145✔
996
        CPLFree(sContext.papsStack);
224,108✔
997

998
    // We do not trust CPLGetLastErrorType() as if CPLTurnFailureIntoWarning()
999
    // has been set we would never get failures
1000
    if (eLastErrorType == CE_Failure)
224,145✔
1001
    {
1002
        CPLDestroyXMLNode(sContext.psFirstNode);
78✔
1003
        sContext.psFirstNode = nullptr;
78✔
1004
        sContext.psLastNode = nullptr;
78✔
1005
    }
1006

1007
    if (eLastErrorType == CE_None)
224,145✔
1008
    {
1009
        // Restore initial error state.
1010
        CPLErrorSetState(eErrClass, nErrNum, osErrMsg);
224,065✔
1011
    }
1012

1013
    return sContext.psFirstNode;
224,145✔
1014
}
1015

1016
/************************************************************************/
1017
/*                            _GrowBuffer()                             */
1018
/************************************************************************/
1019

1020
static bool _GrowBuffer(size_t nNeeded, char **ppszText, size_t *pnMaxLength)
1,995,670✔
1021

1022
{
1023
    if (nNeeded + 1 >= *pnMaxLength)
1,995,670✔
1024
    {
1025
        *pnMaxLength = std::max(*pnMaxLength * 2, nNeeded + 1);
25,308✔
1026
        char *pszTextNew =
1027
            static_cast<char *>(VSIRealloc(*ppszText, *pnMaxLength));
25,308✔
1028
        if (pszTextNew == nullptr)
25,308✔
1029
            return false;
×
1030
        *ppszText = pszTextNew;
25,308✔
1031
    }
1032
    return true;
1,995,670✔
1033
}
1034

1035
/************************************************************************/
1036
/*                        CPLSerializeXMLNode()                         */
1037
/************************************************************************/
1038

1039
// TODO(schwehr): Rewrite this whole thing using C++ string.
1040
// CPLSerializeXMLNode has buffer overflows.
1041
static bool CPLSerializeXMLNode(const CPLXMLNode *psNode, int nIndent,
803,776✔
1042
                                char **ppszText, size_t *pnLength,
1043
                                size_t *pnMaxLength)
1044

1045
{
1046
    if (psNode == nullptr)
803,776✔
1047
        return true;
×
1048

1049
    /* -------------------------------------------------------------------- */
1050
    /*      Ensure the buffer is plenty large to hold this additional       */
1051
    /*      string.                                                         */
1052
    /* -------------------------------------------------------------------- */
1053
    *pnLength += strlen(*ppszText + *pnLength);
803,776✔
1054
    if (!_GrowBuffer(strlen(psNode->pszValue) + *pnLength + 40 + nIndent,
803,776✔
1055
                     ppszText, pnMaxLength))
1056
        return false;
×
1057

1058
    /* -------------------------------------------------------------------- */
1059
    /*      Text is just directly emitted.                                  */
1060
    /* -------------------------------------------------------------------- */
1061
    if (psNode->eType == CXT_Text)
803,776✔
1062
    {
1063
        char *pszEscaped =
1064
            CPLEscapeString(psNode->pszValue, -1, CPLES_XML_BUT_QUOTES);
150,113✔
1065

1066
        CPLAssert(psNode->psChild == nullptr);
150,113✔
1067

1068
        // Escaped text might be bigger than expected.
1069
        if (!_GrowBuffer(strlen(pszEscaped) + *pnLength, ppszText, pnMaxLength))
150,113✔
1070
        {
1071
            CPLFree(pszEscaped);
×
1072
            return false;
×
1073
        }
1074
        strcat(*ppszText + *pnLength, pszEscaped);
150,113✔
1075

1076
        CPLFree(pszEscaped);
150,113✔
1077
    }
1078

1079
    /* -------------------------------------------------------------------- */
1080
    /*      Attributes require a little formatting.                         */
1081
    /* -------------------------------------------------------------------- */
1082
    else if (psNode->eType == CXT_Attribute)
653,663✔
1083
    {
1084
        CPLAssert(psNode->psChild != nullptr &&
282,675✔
1085
                  psNode->psChild->eType == CXT_Text);
1086

1087
        snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength, " %s=\"",
282,675✔
1088
                 psNode->pszValue);
282,675✔
1089
        *pnLength += strlen(*ppszText + *pnLength);
282,675✔
1090

1091
        char *pszEscaped =
1092
            CPLEscapeString(psNode->psChild->pszValue, -1, CPLES_XML);
282,675✔
1093

1094
        if (!_GrowBuffer(strlen(pszEscaped) + *pnLength, ppszText, pnMaxLength))
282,675✔
1095
        {
1096
            CPLFree(pszEscaped);
×
1097
            return false;
×
1098
        }
1099
        strcat(*ppszText + *pnLength, pszEscaped);
282,675✔
1100

1101
        CPLFree(pszEscaped);
282,675✔
1102

1103
        *pnLength += strlen(*ppszText + *pnLength);
282,675✔
1104
        if (!_GrowBuffer(3 + *pnLength, ppszText, pnMaxLength))
282,675✔
1105
            return false;
×
1106
        strcat(*ppszText + *pnLength, "\"");
282,675✔
1107
    }
1108

1109
    /* -------------------------------------------------------------------- */
1110
    /*      Handle comment output.                                          */
1111
    /* -------------------------------------------------------------------- */
1112
    else if (psNode->eType == CXT_Comment)
370,988✔
1113
    {
1114
        CPLAssert(psNode->psChild == nullptr);
12,451✔
1115

1116
        for (int i = 0; i < nIndent; i++)
81,757✔
1117
            (*ppszText)[(*pnLength)++] = ' ';
69,306✔
1118

1119
        snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength, "<!--%s-->\n",
12,451✔
1120
                 psNode->pszValue);
12,451✔
1121
    }
1122

1123
    /* -------------------------------------------------------------------- */
1124
    /*      Handle literal output (like <!DOCTYPE...>)                      */
1125
    /* -------------------------------------------------------------------- */
1126
    else if (psNode->eType == CXT_Literal)
358,537✔
1127
    {
1128
        CPLAssert(psNode->psChild == nullptr);
4✔
1129

1130
        for (int i = 0; i < nIndent; i++)
28✔
1131
            (*ppszText)[(*pnLength)++] = ' ';
24✔
1132

1133
        strcpy(*ppszText + *pnLength, psNode->pszValue);
4✔
1134
        strcat(*ppszText + *pnLength, "\n");
4✔
1135
    }
1136

1137
    /* -------------------------------------------------------------------- */
1138
    /*      Elements actually have to deal with general children, and       */
1139
    /*      various formatting issues.                                      */
1140
    /* -------------------------------------------------------------------- */
1141
    else if (psNode->eType == CXT_Element)
358,533✔
1142
    {
1143
        if (nIndent)
358,533✔
1144
            memset(*ppszText + *pnLength, ' ', nIndent);
348,198✔
1145
        *pnLength += nIndent;
358,533✔
1146
        (*ppszText)[*pnLength] = '\0';
358,533✔
1147

1148
        snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength, "<%s",
358,533✔
1149
                 psNode->pszValue);
358,533✔
1150

1151
        if (psNode->pszValue[0] == '?')
358,533✔
1152
        {
1153
            for (const CPLXMLNode *psChild = psNode->psChild;
2,665✔
1154
                 psChild != nullptr; psChild = psChild->psNext)
8,008✔
1155
            {
1156
                if (psChild->eType == CXT_Text)
5,343✔
1157
                {
1158
                    *pnLength += strlen(*ppszText + *pnLength);
3✔
1159
                    if (!_GrowBuffer(1 + *pnLength, ppszText, pnMaxLength))
3✔
1160
                        return false;
×
1161
                    strcat(*ppszText + *pnLength, " ");
3✔
1162
                }
1163

1164
                if (!CPLSerializeXMLNode(psChild, 0, ppszText, pnLength,
5,343✔
1165
                                         pnMaxLength))
1166
                {
1167
                    return false;
×
1168
                }
1169
            }
1170
            if (!_GrowBuffer(*pnLength + 40, ppszText, pnMaxLength))
2,665✔
1171
                return false;
×
1172

1173
            strcat(*ppszText + *pnLength, "?>\n");
2,665✔
1174
        }
1175
        else
1176
        {
1177
            bool bHasNonAttributeChildren = false;
355,868✔
1178
            // Serialize *all* the attribute children, regardless of order
1179
            for (const CPLXMLNode *psChild = psNode->psChild;
355,868✔
1180
                 psChild != nullptr; psChild = psChild->psNext)
1,143,460✔
1181
            {
1182
                if (psChild->eType == CXT_Attribute)
787,593✔
1183
                {
1184
                    if (!CPLSerializeXMLNode(psChild, 0, ppszText, pnLength,
277,335✔
1185
                                             pnMaxLength))
1186
                        return false;
×
1187
                }
1188
                else
1189
                    bHasNonAttributeChildren = true;
510,258✔
1190
            }
1191

1192
            if (!bHasNonAttributeChildren)
355,868✔
1193
            {
1194
                if (!_GrowBuffer(*pnLength + 40, ppszText, pnMaxLength))
87,896✔
1195
                    return false;
×
1196

1197
                strcat(*ppszText + *pnLength, " />\n");
87,896✔
1198
            }
1199
            else
1200
            {
1201
                bool bJustText = true;
267,972✔
1202

1203
                strcat(*ppszText + *pnLength, ">");
267,972✔
1204

1205
                for (const CPLXMLNode *psChild = psNode->psChild;
267,972✔
1206
                     psChild != nullptr; psChild = psChild->psNext)
908,641✔
1207
                {
1208
                    if (psChild->eType == CXT_Attribute)
640,669✔
1209
                        continue;
130,411✔
1210

1211
                    if (psChild->eType != CXT_Text && bJustText)
510,258✔
1212
                    {
1213
                        bJustText = false;
117,895✔
1214
                        *pnLength += strlen(*ppszText + *pnLength);
117,895✔
1215
                        if (!_GrowBuffer(1 + *pnLength, ppszText, pnMaxLength))
117,895✔
1216
                            return false;
×
1217
                        strcat(*ppszText + *pnLength, "\n");
117,895✔
1218
                    }
1219

1220
                    if (!CPLSerializeXMLNode(psChild, nIndent + 2, ppszText,
510,258✔
1221
                                             pnLength, pnMaxLength))
1222
                        return false;
×
1223
                }
1224

1225
                *pnLength += strlen(*ppszText + *pnLength);
267,972✔
1226
                if (!_GrowBuffer(strlen(psNode->pszValue) + *pnLength + 40 +
267,972✔
1227
                                     nIndent,
267,972✔
1228
                                 ppszText, pnMaxLength))
1229
                    return false;
×
1230

1231
                if (!bJustText)
267,972✔
1232
                {
1233
                    if (nIndent)
117,895✔
1234
                        memset(*ppszText + *pnLength, ' ', nIndent);
110,328✔
1235
                    *pnLength += nIndent;
117,895✔
1236
                    (*ppszText)[*pnLength] = '\0';
117,895✔
1237
                }
1238

1239
                *pnLength += strlen(*ppszText + *pnLength);
267,972✔
1240
                snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength,
267,972✔
1241
                         "</%s>\n", psNode->pszValue);
267,972✔
1242
            }
1243
        }
1244
    }
1245

1246
    return true;
803,776✔
1247
}
1248

1249
/************************************************************************/
1250
/*                        CPLSerializeXMLTree()                         */
1251
/************************************************************************/
1252

1253
/**
1254
 * \brief Convert tree into string document.
1255
 *
1256
 * This function converts a CPLXMLNode tree representation of a document
1257
 * into a flat string representation.  White space indentation is used
1258
 * visually preserve the tree structure of the document.  The returned
1259
 * document becomes owned by the caller and should be freed with CPLFree()
1260
 * when no longer needed.
1261
 *
1262
 * @param psNode the node to serialize.
1263
 *
1264
 * @return the document on success or NULL on failure.
1265
 */
1266

1267
char *CPLSerializeXMLTree(const CPLXMLNode *psNode)
7,678✔
1268

1269
{
1270
    size_t nMaxLength = 100;
7,678✔
1271
    char *pszText = static_cast<char *>(CPLCalloc(nMaxLength, sizeof(char)));
7,678✔
1272
    if (pszText == nullptr)
7,678✔
1273
        return nullptr;
×
1274

1275
    size_t nLength = 0;
7,678✔
1276
    for (const CPLXMLNode *psThis = psNode; psThis != nullptr;
18,518✔
1277
         psThis = psThis->psNext)
10,840✔
1278
    {
1279
        if (!CPLSerializeXMLNode(psThis, 0, &pszText, &nLength, &nMaxLength))
10,840✔
1280
        {
1281
            VSIFree(pszText);
×
1282
            return nullptr;
×
1283
        }
1284
    }
1285

1286
    return pszText;
7,678✔
1287
}
1288

1289
/************************************************************************/
1290
/*                          CPLCreateXMLNode()                          */
1291
/************************************************************************/
1292

1293
#ifdef DEBUG
1294
static CPLXMLNode *psDummyStaticNode;
1295
#endif
1296

1297
/**
1298
 * \brief Create an document tree item.
1299
 *
1300
 * Create a single CPLXMLNode object with the desired value and type, and
1301
 * attach it as a child of the indicated parent.
1302
 *
1303
 * @param poParent the parent to which this node should be attached as a
1304
 * child.  May be NULL to keep as free standing.
1305
 * @param eType the type of the newly created node
1306
 * @param pszText the value of the newly created node
1307
 *
1308
 * @return the newly created node, now owned by the caller (or parent node).
1309
 */
1310

1311
CPLXMLNode *CPLCreateXMLNode(CPLXMLNode *poParent, CPLXMLNodeType eType,
451,333✔
1312
                             const char *pszText)
1313

1314
{
1315
    auto ret = _CPLCreateXMLNode(poParent, eType, pszText);
451,333✔
1316
    if (!ret)
451,333✔
1317
    {
1318
        CPLError(CE_Fatal, CPLE_OutOfMemory, "CPLCreateXMLNode() failed");
×
1319
    }
1320
    return ret;
451,333✔
1321
}
1322

1323
/************************************************************************/
1324
/*                         _CPLCreateXMLNode()                          */
1325
/************************************************************************/
1326

1327
/* Same as CPLCreateXMLNode() but can return NULL in case of out-of-memory */
1328
/* situation */
1329

1330
static CPLXMLNode *_CPLCreateXMLNode(CPLXMLNode *poParent, CPLXMLNodeType eType,
20,858,100✔
1331
                                     const char *pszText)
1332

1333
{
1334

1335
    /* -------------------------------------------------------------------- */
1336
    /*      Create new node.                                                */
1337
    /* -------------------------------------------------------------------- */
1338
    CPLXMLNode *psNode =
1339
        static_cast<CPLXMLNode *>(VSICalloc(sizeof(CPLXMLNode), 1));
20,858,100✔
1340
    if (psNode == nullptr)
20,858,100✔
1341
    {
1342
        CPLError(CE_Failure, CPLE_OutOfMemory, "Cannot allocate CPLXMLNode");
1✔
1343
        return nullptr;
×
1344
    }
1345

1346
    psNode->eType = eType;
20,858,100✔
1347
    psNode->pszValue = VSIStrdup(pszText ? pszText : "");
20,858,100✔
1348
    if (psNode->pszValue == nullptr)
20,858,100✔
1349
    {
1350
        CPLError(CE_Failure, CPLE_OutOfMemory,
1✔
1351
                 "Cannot allocate psNode->pszValue");
1352
        VSIFree(psNode);
×
1353
        return nullptr;
×
1354
    }
1355

1356
    /* -------------------------------------------------------------------- */
1357
    /*      Attach to parent, if provided.                                  */
1358
    /* -------------------------------------------------------------------- */
1359
    if (poParent != nullptr)
20,858,100✔
1360
    {
1361
        if (poParent->psChild == nullptr)
8,141,360✔
1362
            poParent->psChild = psNode;
8,005,590✔
1363
        else
1364
        {
1365
            CPLXMLNode *psLink = poParent->psChild;
135,766✔
1366
            if (psLink->psNext == nullptr && eType == CXT_Attribute &&
135,766✔
1367
                psLink->eType == CXT_Text)
24,636✔
1368
            {
1369
                psNode->psNext = psLink;
8,995✔
1370
                poParent->psChild = psNode;
8,995✔
1371
            }
1372
            else
1373
            {
1374
                while (psLink->psNext != nullptr)
578,899✔
1375
                {
1376
                    if (eType == CXT_Attribute &&
456,352✔
1377
                        psLink->psNext->eType == CXT_Text)
34,080✔
1378
                    {
1379
                        psNode->psNext = psLink->psNext;
4,224✔
1380
                        break;
4,224✔
1381
                    }
1382

1383
                    psLink = psLink->psNext;
452,128✔
1384
                }
1385

1386
                psLink->psNext = psNode;
126,771✔
1387
            }
1388
        }
1389
    }
1390
#ifdef DEBUG
1391
    else
1392
    {
1393
        // Coverity sometimes doesn't realize that this function is passed
1394
        // with a non NULL parent and thinks that this branch is taken, leading
1395
        // to creating object being leak by caller. This ugly hack hopefully
1396
        // makes it believe that someone will reference it.
1397
        psDummyStaticNode = psNode;
12,716,800✔
1398
    }
1399
#endif
1400

1401
    return psNode;
20,858,100✔
1402
}
1403

1404
/************************************************************************/
1405
/*                         CPLDestroyXMLNode()                          */
1406
/************************************************************************/
1407

1408
/**
1409
 * \brief Destroy a tree.
1410
 *
1411
 * This function frees resources associated with a CPLXMLNode and all its
1412
 * children nodes.
1413
 *
1414
 * @param psNode the tree to free.
1415
 */
1416

1417
void CPLDestroyXMLNode(CPLXMLNode *psNode)
21,123,500✔
1418

1419
{
1420
    while (psNode != nullptr)
21,123,500✔
1421
    {
1422
        if (psNode->pszValue != nullptr)
20,883,900✔
1423
            CPLFree(psNode->pszValue);
20,883,900✔
1424

1425
        if (psNode->psChild != nullptr)
20,883,900✔
1426
        {
1427
            CPLXMLNode *psNext = psNode->psNext;
11,796,700✔
1428
            psNode->psNext = psNode->psChild;
11,796,700✔
1429
            // Move the child and its siblings as the next
1430
            // siblings of the current node.
1431
            if (psNext != nullptr)
11,796,700✔
1432
            {
1433
                CPLXMLNode *psIter = psNode->psChild;
11,257,100✔
1434
                while (psIter->psNext != nullptr)
18,981,800✔
1435
                    psIter = psIter->psNext;
7,724,690✔
1436
                psIter->psNext = psNext;
11,257,100✔
1437
            }
1438
        }
1439

1440
        CPLXMLNode *psNext = psNode->psNext;
20,883,900✔
1441

1442
        CPLFree(psNode);
20,883,900✔
1443

1444
        psNode = psNext;
20,883,900✔
1445
    }
1446
}
239,659✔
1447

1448
/************************************************************************/
1449
/*                           CPLSearchXMLNode()                         */
1450
/************************************************************************/
1451

1452
/**
1453
 * \brief Search for a node in document.
1454
 *
1455
 * Searches the children (and potentially siblings) of the documented
1456
 * passed in for the named element or attribute.  To search following
1457
 * siblings as well as children, prefix the pszElement name with an equal
1458
 * sign.  This function does an in-order traversal of the document tree.
1459
 * So it will first match against the current node, then its first child,
1460
 * that child's first child, and so on.
1461
 *
1462
 * Use CPLGetXMLNode() to find a specific child, or along a specific
1463
 * node path.
1464
 *
1465
 * @param psRoot the subtree to search.  This should be a node of type
1466
 * CXT_Element.  NULL is safe.
1467
 *
1468
 * @param pszElement the name of the element or attribute to search for.
1469
 *
1470
 * @return The matching node or NULL on failure.
1471
 */
1472

1473
CPLXMLNode *CPLSearchXMLNode(CPLXMLNode *psRoot, const char *pszElement)
91,489✔
1474

1475
{
1476
    if (psRoot == nullptr || pszElement == nullptr)
91,489✔
1477
        return nullptr;
×
1478

1479
    bool bSideSearch = false;
91,489✔
1480

1481
    if (*pszElement == '=')
91,489✔
1482
    {
1483
        bSideSearch = true;
5,104✔
1484
        pszElement++;
5,104✔
1485
    }
1486

1487
    /* -------------------------------------------------------------------- */
1488
    /*      Does this node match?                                           */
1489
    /* -------------------------------------------------------------------- */
1490
    if ((psRoot->eType == CXT_Element || psRoot->eType == CXT_Attribute) &&
91,489✔
1491
        EQUAL(pszElement, psRoot->pszValue))
91,318✔
1492
        return psRoot;
3,859✔
1493

1494
    /* -------------------------------------------------------------------- */
1495
    /*      Search children.                                                */
1496
    /* -------------------------------------------------------------------- */
1497
    CPLXMLNode *psChild = nullptr;
87,630✔
1498
    for (psChild = psRoot->psChild; psChild != nullptr;
233,489✔
1499
         psChild = psChild->psNext)
145,859✔
1500
    {
1501
        if ((psChild->eType == CXT_Element ||
147,122✔
1502
             psChild->eType == CXT_Attribute) &&
87,983✔
1503
            EQUAL(pszElement, psChild->pszValue))
85,430✔
1504
            return psChild;
423✔
1505

1506
        if (psChild->psChild != nullptr)
146,699✔
1507
        {
1508
            CPLXMLNode *psResult = CPLSearchXMLNode(psChild, pszElement);
81,468✔
1509
            if (psResult != nullptr)
81,468✔
1510
                return psResult;
840✔
1511
        }
1512
    }
1513

1514
    /* -------------------------------------------------------------------- */
1515
    /*      Search siblings if we are in side search mode.                  */
1516
    /* -------------------------------------------------------------------- */
1517
    if (bSideSearch)
86,367✔
1518
    {
1519
        for (psRoot = psRoot->psNext; psRoot != nullptr;
5,542✔
1520
             psRoot = psRoot->psNext)
810✔
1521
        {
1522
            CPLXMLNode *psResult = CPLSearchXMLNode(psRoot, pszElement);
4,671✔
1523
            if (psResult != nullptr)
4,671✔
1524
                return psResult;
3,861✔
1525
        }
1526
    }
1527

1528
    return nullptr;
82,506✔
1529
}
1530

1531
/************************************************************************/
1532
/*                           CPLGetXMLNode()                            */
1533
/************************************************************************/
1534

1535
/**
1536
 * \brief Find node by path.
1537
 *
1538
 * Searches the document or subdocument indicated by psRoot for an element
1539
 * (or attribute) with the given path.  The path should consist of a set of
1540
 * element names separated by dots, not including the name of the root
1541
 * element (psRoot).  If the requested element is not found NULL is returned.
1542
 *
1543
 * Attribute names may only appear as the last item in the path.
1544
 *
1545
 * The search is done from the root nodes children, but all intermediate
1546
 * nodes in the path must be specified.  Searching for "name" would only find
1547
 * a name element or attribute if it is a direct child of the root, not at any
1548
 * level in the subdocument.
1549
 *
1550
 * If the pszPath is prefixed by "=" then the search will begin with the
1551
 * root node, and its siblings, instead of the root nodes children.  This
1552
 * is particularly useful when searching within a whole document which is
1553
 * often prefixed by one or more "junk" nodes like the <?xml> declaration.
1554
 *
1555
 * @param psRoot the subtree in which to search.  This should be a node of
1556
 * type CXT_Element.  NULL is safe.
1557
 *
1558
 * @param pszPath the list of element names in the path (dot separated).
1559
 *
1560
 * @return the requested element node, or NULL if not found.
1561
 */
1562

1563
CPLXMLNode *CPLGetXMLNode(CPLXMLNode *psRoot, const char *pszPath)
1,484,450✔
1564

1565
{
1566
    if (psRoot == nullptr || pszPath == nullptr)
1,484,450✔
1567
        return nullptr;
1,050✔
1568

1569
    bool bSideSearch = false;
1,483,400✔
1570

1571
    if (*pszPath == '=')
1,483,400✔
1572
    {
1573
        bSideSearch = true;
213,617✔
1574
        pszPath++;
213,617✔
1575
    }
1576

1577
    const char *const apszTokens[2] = {pszPath, nullptr};
1,483,400✔
1578

1579
    // Slight optimization: avoid using CSLTokenizeStringComplex that
1580
    // does memory allocations when it is not really necessary.
1581
    bool bFreeTokens = false;
1,483,400✔
1582
    char **papszTokensToFree = nullptr;
1,483,400✔
1583
    const char *const *papszTokens;
1584
    if (strchr(pszPath, '.'))
1,483,400✔
1585
    {
1586
        papszTokensToFree =
1587
            CSLTokenizeStringComplex(pszPath, ".", FALSE, FALSE);
227,730✔
1588
        papszTokens = papszTokensToFree;
227,730✔
1589
        bFreeTokens = true;
227,730✔
1590
    }
1591
    else
1592
    {
1593
        papszTokens = apszTokens;
1,255,670✔
1594
    }
1595

1596
    int iToken = 0;
1,483,400✔
1597
    while (papszTokens[iToken] != nullptr && psRoot != nullptr)
2,590,870✔
1598
    {
1599
        CPLXMLNode *psChild = nullptr;
1,716,120✔
1600

1601
        if (bSideSearch)
1,716,120✔
1602
        {
1603
            psChild = psRoot;
213,617✔
1604
            bSideSearch = false;
213,617✔
1605
        }
1606
        else
1607
            psChild = psRoot->psChild;
1,502,500✔
1608

1609
        for (; psChild != nullptr; psChild = psChild->psNext)
6,786,230✔
1610
        {
1611
            if (psChild->eType != CXT_Text &&
6,177,580✔
1612
                EQUAL(papszTokens[iToken], psChild->pszValue))
6,142,050✔
1613
                break;
1,107,470✔
1614
        }
1615

1616
        if (psChild == nullptr)
1,716,120✔
1617
        {
1618
            psRoot = nullptr;
608,649✔
1619
            break;
608,649✔
1620
        }
1621

1622
        psRoot = psChild;
1,107,470✔
1623
        iToken++;
1,107,470✔
1624
    }
1625

1626
    if (bFreeTokens)
1,483,400✔
1627
        CSLDestroy(papszTokensToFree);
227,730✔
1628
    return psRoot;
1,483,400✔
1629
}
1630

1631
/************************************************************************/
1632
/*                           CPLGetXMLValue()                           */
1633
/************************************************************************/
1634

1635
/**
1636
 * \brief Fetch element/attribute value.
1637
 *
1638
 * Searches the document for the element/attribute value associated with
1639
 * the path.  The corresponding node is internally found with CPLGetXMLNode()
1640
 * (see there for details on path handling).  Once found, the value is
1641
 * considered to be the first CXT_Text child of the node.
1642
 *
1643
 * If the attribute/element search fails, or if the found node has no
1644
 * value then the passed default value is returned.
1645
 *
1646
 * The returned value points to memory within the document tree, and should
1647
 * not be altered or freed.
1648
 *
1649
 * @param psRoot the subtree in which to search.  This should be a node of
1650
 * type CXT_Element.  NULL is safe.
1651
 *
1652
 * @param pszPath the list of element names in the path (dot separated).  An
1653
 * empty path means get the value of the psRoot node.
1654
 *
1655
 * @param pszDefault the value to return if a corresponding value is not
1656
 * found, may be NULL.
1657
 *
1658
 * @return the requested value or pszDefault if not found.
1659
 */
1660

1661
const char *CPLGetXMLValue(const CPLXMLNode *psRoot, const char *pszPath,
1,491,210✔
1662
                           const char *pszDefault)
1663

1664
{
1665
    const CPLXMLNode *psTarget = nullptr;
1,491,210✔
1666

1667
    if (pszPath == nullptr || *pszPath == '\0')
1,491,210✔
1668
        psTarget = psRoot;
102,006✔
1669
    else
1670
        psTarget = CPLGetXMLNode(psRoot, pszPath);
1,389,210✔
1671

1672
    if (psTarget == nullptr)
1,491,210✔
1673
        return pszDefault;
566,199✔
1674

1675
    if (psTarget->eType == CXT_Attribute)
925,013✔
1676
    {
1677
        CPLAssert(psTarget->psChild != nullptr &&
501,830✔
1678
                  psTarget->psChild->eType == CXT_Text);
1679

1680
        return psTarget->psChild->pszValue;
501,830✔
1681
    }
1682

1683
    if (psTarget->eType == CXT_Element)
423,183✔
1684
    {
1685
        // Find first non-attribute child, and verify it is a single text
1686
        // with no siblings.
1687

1688
        psTarget = psTarget->psChild;
423,159✔
1689

1690
        while (psTarget != nullptr && psTarget->eType == CXT_Attribute)
469,066✔
1691
            psTarget = psTarget->psNext;
45,907✔
1692

1693
        if (psTarget != nullptr && psTarget->eType == CXT_Text &&
423,159✔
1694
            psTarget->psNext == nullptr)
420,637✔
1695
            return psTarget->pszValue;
420,637✔
1696
    }
1697

1698
    return pszDefault;
2,546✔
1699
}
1700

1701
/************************************************************************/
1702
/*                           CPLAddXMLChild()                           */
1703
/************************************************************************/
1704

1705
/**
1706
 * \brief Add child node to parent.
1707
 *
1708
 * The passed child is added to the list of children of the indicated
1709
 * parent.  Normally the child is added at the end of the parents child
1710
 * list, but attributes (CXT_Attribute) will be inserted after any other
1711
 * attributes but before any other element type.  Ownership of the child
1712
 * node is effectively assumed by the parent node.   If the child has
1713
 * siblings (its psNext is not NULL) they will be trimmed, but if the child
1714
 * has children they are carried with it.
1715
 *
1716
 * @param psParent the node to attach the child to.  May not be NULL.
1717
 *
1718
 * @param psChild the child to add to the parent.  May not be NULL.  Should
1719
 * not be a child of any other parent.
1720
 */
1721

1722
void CPLAddXMLChild(CPLXMLNode *psParent, CPLXMLNode *psChild)
5,646✔
1723

1724
{
1725
    if (psParent->psChild == nullptr)
5,646✔
1726
    {
1727
        psParent->psChild = psChild;
2,353✔
1728
        return;
2,353✔
1729
    }
1730

1731
    // Insert at head of list if first child is not attribute.
1732
    if (psChild->eType == CXT_Attribute &&
3,293✔
1733
        psParent->psChild->eType != CXT_Attribute)
21✔
1734
    {
1735
        psChild->psNext = psParent->psChild;
×
1736
        psParent->psChild = psChild;
×
1737
        return;
×
1738
    }
1739

1740
    // Search for end of list.
1741
    CPLXMLNode *psSib = nullptr;
3,293✔
1742
    for (psSib = psParent->psChild; psSib->psNext != nullptr;
14,460✔
1743
         psSib = psSib->psNext)
11,167✔
1744
    {
1745
        // Insert attributes if the next node is not an attribute.
1746
        if (psChild->eType == CXT_Attribute && psSib->psNext != nullptr &&
11,168✔
1747
            psSib->psNext->eType != CXT_Attribute)
5✔
1748
        {
1749
            psChild->psNext = psSib->psNext;
1✔
1750
            psSib->psNext = psChild;
1✔
1751
            return;
1✔
1752
        }
1753
    }
1754

1755
    psSib->psNext = psChild;
3,292✔
1756
}
1757

1758
/************************************************************************/
1759
/*                        CPLRemoveXMLChild()                           */
1760
/************************************************************************/
1761

1762
/**
1763
 * \brief Remove child node from parent.
1764
 *
1765
 * The passed child is removed from the child list of the passed parent,
1766
 * but the child is not destroyed.  The child retains ownership of its
1767
 * own children, but is cleanly removed from the child list of the parent.
1768
 *
1769
 * @param psParent the node to the child is attached to.
1770
 *
1771
 * @param psChild the child to remove.
1772
 *
1773
 * @return TRUE on success or FALSE if the child was not found.
1774
 */
1775

1776
int CPLRemoveXMLChild(CPLXMLNode *psParent, CPLXMLNode *psChild)
2,592✔
1777

1778
{
1779
    if (psParent == nullptr)
2,592✔
1780
        return FALSE;
×
1781

1782
    CPLXMLNode *psLast = nullptr;
2,592✔
1783
    CPLXMLNode *psThis = nullptr;
2,592✔
1784
    for (psThis = psParent->psChild; psThis != nullptr; psThis = psThis->psNext)
5,685✔
1785
    {
1786
        if (psThis == psChild)
4,451✔
1787
        {
1788
            if (psLast == nullptr)
1,358✔
1789
                psParent->psChild = psThis->psNext;
872✔
1790
            else
1791
                psLast->psNext = psThis->psNext;
486✔
1792

1793
            psThis->psNext = nullptr;
1,358✔
1794
            return TRUE;
1,358✔
1795
        }
1796
        psLast = psThis;
3,093✔
1797
    }
1798

1799
    return FALSE;
1,234✔
1800
}
1801

1802
/************************************************************************/
1803
/*                          CPLAddXMLSibling()                          */
1804
/************************************************************************/
1805

1806
/**
1807
 * \brief Add new sibling.
1808
 *
1809
 * The passed psNewSibling is added to the end of siblings of the
1810
 * psOlderSibling node.  That is, it is added to the end of the psNext
1811
 * chain.  There is no special handling if psNewSibling is an attribute.
1812
 * If this is required, use CPLAddXMLChild().
1813
 *
1814
 * @param psOlderSibling the node to attach the sibling after.
1815
 *
1816
 * @param psNewSibling the node to add at the end of psOlderSiblings psNext
1817
 * chain.
1818
 */
1819

1820
void CPLAddXMLSibling(CPLXMLNode *psOlderSibling, CPLXMLNode *psNewSibling)
4,131✔
1821

1822
{
1823
    if (psOlderSibling == nullptr)
4,131✔
1824
        return;
×
1825

1826
    while (psOlderSibling->psNext != nullptr)
4,202✔
1827
        psOlderSibling = psOlderSibling->psNext;
71✔
1828

1829
    psOlderSibling->psNext = psNewSibling;
4,131✔
1830
}
1831

1832
/************************************************************************/
1833
/*                    CPLCreateXMLElementAndValue()                     */
1834
/************************************************************************/
1835

1836
/**
1837
 * \brief Create an element and text value.
1838
 *
1839
 * This is function is a convenient short form for:
1840
 *
1841
 * \code
1842
 *     CPLXMLNode *psTextNode;
1843
 *     CPLXMLNode *psElementNode;
1844
 *
1845
 *     psElementNode = CPLCreateXMLNode( psParent, CXT_Element, pszName );
1846
 *     psTextNode = CPLCreateXMLNode( psElementNode, CXT_Text, pszValue );
1847
 *
1848
 *     return psElementNode;
1849
 * \endcode
1850
 *
1851
 * It creates a CXT_Element node, with a CXT_Text child, and
1852
 * attaches the element to the passed parent.
1853
 *
1854
 * @param psParent the parent node to which the resulting node should
1855
 * be attached.  May be NULL to keep as freestanding.
1856
 *
1857
 * @param pszName the element name to create.
1858
 * @param pszValue the text to attach to the element. Must not be NULL.
1859
 *
1860
 * @return the pointer to the new element node.
1861
 */
1862

1863
CPLXMLNode *CPLCreateXMLElementAndValue(CPLXMLNode *psParent,
61,852✔
1864
                                        const char *pszName,
1865
                                        const char *pszValue)
1866

1867
{
1868
    CPLXMLNode *psElementNode =
1869
        CPLCreateXMLNode(psParent, CXT_Element, pszName);
61,852✔
1870
    CPLCreateXMLNode(psElementNode, CXT_Text, pszValue);
61,852✔
1871

1872
    return psElementNode;
61,852✔
1873
}
1874

1875
/************************************************************************/
1876
/*                    CPLCreateXMLElementAndValue()                     */
1877
/************************************************************************/
1878

1879
/**
1880
 * \brief Create an attribute and text value.
1881
 *
1882
 * This is function is a convenient short form for:
1883
 *
1884
 * \code
1885
 *   CPLXMLNode *psAttributeNode;
1886
 *
1887
 *   psAttributeNode = CPLCreateXMLNode( psParent, CXT_Attribute, pszName );
1888
 *   CPLCreateXMLNode( psAttributeNode, CXT_Text, pszValue );
1889
 * \endcode
1890
 *
1891
 * It creates a CXT_Attribute node, with a CXT_Text child, and
1892
 * attaches the element to the passed parent.
1893
 *
1894
 * @param psParent the parent node to which the resulting node should
1895
 * be attached.  Must not be NULL.
1896
 * @param pszName the attribute name to create.
1897
 * @param pszValue the text to attach to the attribute. Must not be NULL.
1898
 *
1899
 * @since GDAL 2.0
1900
 */
1901

1902
void CPLAddXMLAttributeAndValue(CPLXMLNode *psParent, const char *pszName,
27,536✔
1903
                                const char *pszValue)
1904
{
1905
    CPLAssert(psParent != nullptr);
27,536✔
1906
    CPLXMLNode *psAttributeNode =
1907
        CPLCreateXMLNode(psParent, CXT_Attribute, pszName);
27,536✔
1908
    CPLCreateXMLNode(psAttributeNode, CXT_Text, pszValue);
27,536✔
1909
}
27,536✔
1910

1911
/************************************************************************/
1912
/*                          CPLCloneXMLTree()                           */
1913
/************************************************************************/
1914

1915
/**
1916
 * \brief Copy tree.
1917
 *
1918
 * Creates a deep copy of a CPLXMLNode tree.
1919
 *
1920
 * @param psTree the tree to duplicate.
1921
 *
1922
 * @return a copy of the whole tree.
1923
 */
1924

1925
CPLXMLNode *CPLCloneXMLTree(const CPLXMLNode *psTree)
30,593✔
1926

1927
{
1928
    CPLXMLNode *psPrevious = nullptr;
30,593✔
1929
    CPLXMLNode *psReturn = nullptr;
30,593✔
1930

1931
    while (psTree != nullptr)
80,450✔
1932
    {
1933
        CPLXMLNode *psCopy =
1934
            CPLCreateXMLNode(nullptr, psTree->eType, psTree->pszValue);
49,857✔
1935
        if (psReturn == nullptr)
49,857✔
1936
            psReturn = psCopy;
30,593✔
1937
        if (psPrevious != nullptr)
49,857✔
1938
            psPrevious->psNext = psCopy;
19,264✔
1939

1940
        if (psTree->psChild != nullptr)
49,857✔
1941
            psCopy->psChild = CPLCloneXMLTree(psTree->psChild);
29,208✔
1942

1943
        psPrevious = psCopy;
49,857✔
1944
        psTree = psTree->psNext;
49,857✔
1945
    }
1946

1947
    return psReturn;
30,593✔
1948
}
1949

1950
/************************************************************************/
1951
/*                           CPLSetXMLValue()                           */
1952
/************************************************************************/
1953

1954
/**
1955
 * \brief Set element value by path.
1956
 *
1957
 * Find (or create) the target element or attribute specified in the
1958
 * path, and assign it the indicated value.
1959
 *
1960
 * Any path elements that do not already exist will be created.  The target
1961
 * nodes value (the first CXT_Text child) will be replaced with the provided
1962
 * value.
1963
 *
1964
 * If the target node is an attribute instead of an element, the name
1965
 * should be prefixed with a #.
1966
 *
1967
 * Example:
1968
 *   CPLSetXMLValue( "Citation.Id.Description", "DOQ dataset" );
1969
 *   CPLSetXMLValue( "Citation.Id.Description.#name", "doq" );
1970
 *
1971
 * @param psRoot the subdocument to be updated.
1972
 *
1973
 * @param pszPath the dot separated path to the target element/attribute.
1974
 *
1975
 * @param pszValue the text value to assign.
1976
 *
1977
 * @return TRUE on success.
1978
 */
1979

1980
int CPLSetXMLValue(CPLXMLNode *psRoot, const char *pszPath,
39,043✔
1981
                   const char *pszValue)
1982

1983
{
1984
    char **papszTokens = CSLTokenizeStringComplex(pszPath, ".", FALSE, FALSE);
39,043✔
1985
    int iToken = 0;
39,043✔
1986

1987
    while (papszTokens[iToken] != nullptr)
89,633✔
1988
    {
1989
        bool bIsAttribute = false;
50,590✔
1990
        const char *pszName = papszTokens[iToken];
50,590✔
1991

1992
        if (pszName[0] == '#')
50,590✔
1993
        {
1994
            bIsAttribute = true;
31,279✔
1995
            pszName++;
31,279✔
1996
        }
1997

1998
        if (psRoot->eType != CXT_Element)
50,590✔
1999
        {
2000
            CSLDestroy(papszTokens);
×
2001
            return FALSE;
×
2002
        }
2003

2004
        CPLXMLNode *psChild = nullptr;
50,590✔
2005
        for (psChild = psRoot->psChild; psChild != nullptr;
123,632✔
2006
             psChild = psChild->psNext)
73,042✔
2007
        {
2008
            if (psChild->eType != CXT_Text && EQUAL(pszName, psChild->pszValue))
85,788✔
2009
                break;
12,746✔
2010
        }
2011

2012
        if (psChild == nullptr)
50,590✔
2013
        {
2014
            if (bIsAttribute)
37,844✔
2015
                psChild = CPLCreateXMLNode(psRoot, CXT_Attribute, pszName);
30,964✔
2016
            else
2017
                psChild = CPLCreateXMLNode(psRoot, CXT_Element, pszName);
6,880✔
2018
        }
2019

2020
        psRoot = psChild;
50,590✔
2021
        iToken++;
50,590✔
2022
    }
2023

2024
    CSLDestroy(papszTokens);
39,043✔
2025

2026
    /* -------------------------------------------------------------------- */
2027
    /*      Find the "text" child if there is one.                          */
2028
    /* -------------------------------------------------------------------- */
2029
    CPLXMLNode *psTextChild = psRoot->psChild;
39,043✔
2030

2031
    while (psTextChild != nullptr && psTextChild->eType != CXT_Text)
39,233✔
2032
        psTextChild = psTextChild->psNext;
190✔
2033

2034
    /* -------------------------------------------------------------------- */
2035
    /*      Now set a value node under this node.                           */
2036
    /* -------------------------------------------------------------------- */
2037

2038
    if (psTextChild == nullptr)
39,043✔
2039
        CPLCreateXMLNode(psRoot, CXT_Text, pszValue);
38,472✔
2040
    else
2041
    {
2042
        CPLFree(psTextChild->pszValue);
571✔
2043
        psTextChild->pszValue = CPLStrdup(pszValue);
571✔
2044
    }
2045

2046
    return TRUE;
39,043✔
2047
}
2048

2049
/************************************************************************/
2050
/*                        CPLStripXMLNamespace()                        */
2051
/************************************************************************/
2052

2053
/**
2054
 * \brief Strip indicated namespaces.
2055
 *
2056
 * The subdocument (psRoot) is recursively examined, and any elements
2057
 * with the indicated namespace prefix will have the namespace prefix
2058
 * stripped from the element names.  If the passed namespace is NULL, then
2059
 * all namespace prefixes will be stripped.
2060
 *
2061
 * Nodes other than elements should remain unaffected.  The changes are
2062
 * made "in place", and should not alter any node locations, only the
2063
 * pszValue field of affected nodes.
2064
 *
2065
 * @param psRoot the document to operate on.
2066
 * @param pszNamespace the name space prefix (not including colon), or NULL.
2067
 * @param bRecurse TRUE to recurse over whole document, or FALSE to only
2068
 * operate on the passed node.
2069
 */
2070

2071
void CPLStripXMLNamespace(CPLXMLNode *psRoot, const char *pszNamespace,
1,509,970✔
2072
                          int bRecurse)
2073

2074
{
2075
    size_t nNameSpaceLen = (pszNamespace) ? strlen(pszNamespace) : 0;
1,509,970✔
2076

2077
    while (psRoot != nullptr)
3,881,400✔
2078
    {
2079
        if (psRoot->eType == CXT_Element || psRoot->eType == CXT_Attribute)
2,371,420✔
2080
        {
2081
            if (pszNamespace != nullptr)
1,332,700✔
2082
            {
2083
                if (EQUALN(pszNamespace, psRoot->pszValue, nNameSpaceLen) &&
597✔
2084
                    psRoot->pszValue[nNameSpaceLen] == ':')
177✔
2085
                {
2086
                    memmove(psRoot->pszValue,
177✔
2087
                            psRoot->pszValue + nNameSpaceLen + 1,
177✔
2088
                            strlen(psRoot->pszValue + nNameSpaceLen + 1) + 1);
177✔
2089
                }
2090
            }
2091
            else
2092
            {
2093
                for (const char *pszCheck = psRoot->pszValue; *pszCheck != '\0';
8,254,640✔
2094
                     pszCheck++)
2095
                {
2096
                    if (*pszCheck == ':')
7,854,370✔
2097
                    {
2098
                        memmove(psRoot->pszValue, pszCheck + 1,
931,840✔
2099
                                strlen(pszCheck + 1) + 1);
931,840✔
2100
                        break;
931,840✔
2101
                    }
2102
                }
2103
            }
2104
        }
2105

2106
        if (bRecurse)
2,371,420✔
2107
        {
2108
            if (psRoot->psChild != nullptr)
2,371,420✔
2109
                CPLStripXMLNamespace(psRoot->psChild, pszNamespace, 1);
1,318,940✔
2110

2111
            psRoot = psRoot->psNext;
2,371,420✔
2112
        }
2113
        else
2114
        {
2115
            break;
×
2116
        }
2117
    }
2118
}
1,509,970✔
2119

2120
/************************************************************************/
2121
/*                          CPLParseXMLFile()                           */
2122
/************************************************************************/
2123

2124
/**
2125
 * \brief Parse XML file into tree.
2126
 *
2127
 * The named file is opened, loaded into memory as a big string, and
2128
 * parsed with CPLParseXMLString().  Errors in reading the file or parsing
2129
 * the XML will be reported by CPLError().
2130
 *
2131
 * The "large file" API is used, so XML files can come from virtualized
2132
 * files.
2133
 *
2134
 * @param pszFilename the file to open.
2135
 *
2136
 * @return NULL on failure, or the document tree on success.
2137
 */
2138

2139
CPLXMLNode *CPLParseXMLFile(const char *pszFilename)
4,910✔
2140

2141
{
2142
    /* -------------------------------------------------------------------- */
2143
    /*      Ingest the file.                                                */
2144
    /* -------------------------------------------------------------------- */
2145
    GByte *pabyOut = nullptr;
4,910✔
2146
    if (!VSIIngestFile(nullptr, pszFilename, &pabyOut, nullptr, -1))
4,910✔
2147
        return nullptr;
60✔
2148

2149
    char *pszDoc = reinterpret_cast<char *>(pabyOut);
4,850✔
2150

2151
    /* -------------------------------------------------------------------- */
2152
    /*      Parse it.                                                       */
2153
    /* -------------------------------------------------------------------- */
2154
    CPLXMLNode *psTree = CPLParseXMLString(pszDoc);
4,850✔
2155
    CPLFree(pszDoc);
4,850✔
2156

2157
    return psTree;
4,850✔
2158
}
2159

2160
/************************************************************************/
2161
/*                     CPLSerializeXMLTreeToFile()                      */
2162
/************************************************************************/
2163

2164
/**
2165
 * \brief Write document tree to a file.
2166
 *
2167
 * The passed document tree is converted into one big string (with
2168
 * CPLSerializeXMLTree()) and then written to the named file.  Errors writing
2169
 * the file will be reported by CPLError().  The source document tree is
2170
 * not altered.  If the output file already exists it will be overwritten.
2171
 *
2172
 * @param psTree the document tree to write.
2173
 * @param pszFilename the name of the file to write to.
2174
 * @return TRUE on success, FALSE otherwise.
2175
 */
2176

2177
int CPLSerializeXMLTreeToFile(const CPLXMLNode *psTree, const char *pszFilename)
2,587✔
2178

2179
{
2180
    /* -------------------------------------------------------------------- */
2181
    /*      Serialize document.                                             */
2182
    /* -------------------------------------------------------------------- */
2183
    char *pszDoc = CPLSerializeXMLTree(psTree);
2,587✔
2184
    if (pszDoc == nullptr)
2,587✔
2185
        return FALSE;
×
2186

2187
    const vsi_l_offset nLength = strlen(pszDoc);
2,587✔
2188

2189
    /* -------------------------------------------------------------------- */
2190
    /*      Create file.                                                    */
2191
    /* -------------------------------------------------------------------- */
2192
    VSILFILE *fp = VSIFOpenL(pszFilename, "wt");
2,587✔
2193
    if (fp == nullptr)
2,587✔
2194
    {
2195
        CPLError(CE_Failure, CPLE_OpenFailed, "Failed to open %.500s to write.",
6✔
2196
                 pszFilename);
2197
        CPLFree(pszDoc);
6✔
2198
        return FALSE;
6✔
2199
    }
2200

2201
    /* -------------------------------------------------------------------- */
2202
    /*      Write file.                                                     */
2203
    /* -------------------------------------------------------------------- */
2204
    if (VSIFWriteL(pszDoc, 1, static_cast<size_t>(nLength), fp) != nLength)
2,581✔
2205
    {
2206
        CPLError(CE_Failure, CPLE_FileIO,
77✔
2207
                 "Failed to write whole XML document (%.500s).", pszFilename);
2208
        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
77✔
2209
        CPLFree(pszDoc);
77✔
2210
        return FALSE;
77✔
2211
    }
2212

2213
    /* -------------------------------------------------------------------- */
2214
    /*      Cleanup                                                         */
2215
    /* -------------------------------------------------------------------- */
2216
    const bool bRet = VSIFCloseL(fp) == 0;
2,504✔
2217
    if (!bRet)
2,504✔
2218
    {
2219
        CPLError(CE_Failure, CPLE_FileIO,
×
2220
                 "Failed to write whole XML document (%.500s).", pszFilename);
2221
    }
2222
    CPLFree(pszDoc);
2,504✔
2223

2224
    return bRet;
2,504✔
2225
}
2226

2227
/************************************************************************/
2228
/*                       CPLCleanXMLElementName()                       */
2229
/************************************************************************/
2230

2231
/**
2232
 * \brief Make string into safe XML token.
2233
 *
2234
 * Modifies a string in place to try and make it into a legal
2235
 * XML token that can be used as an element name.   This is accomplished
2236
 * by changing any characters not legal in a token into an underscore.
2237
 *
2238
 * NOTE: This function should implement the rules in section 2.3 of
2239
 * http://www.w3.org/TR/xml11/ but it doesn't yet do that properly.  We
2240
 * only do a rough approximation of that.
2241
 *
2242
 * @param pszTarget the string to be adjusted.  It is altered in place.
2243
 */
2244

2245
void CPLCleanXMLElementName(char *pszTarget)
375✔
2246
{
2247
    if (pszTarget == nullptr)
375✔
2248
        return;
×
2249

2250
    for (; *pszTarget != '\0'; pszTarget++)
3,439✔
2251
    {
2252
        if ((static_cast<unsigned char>(*pszTarget) & 0x80) ||
3,064✔
2253
            isalnum(static_cast<unsigned char>(*pszTarget)) ||
3,064✔
2254
            *pszTarget == '_' || *pszTarget == '.')
201✔
2255
        {
2256
            // Ok.
2257
        }
2258
        else
2259
        {
2260
            *pszTarget = '_';
×
2261
        }
2262
    }
2263
}
2264

2265
/************************************************************************/
2266
/*                     CPLXMLNodeGetRAMUsageEstimate()                  */
2267
/************************************************************************/
2268

2269
static size_t CPLXMLNodeGetRAMUsageEstimate(const CPLXMLNode *psNode,
47,451✔
2270
                                            bool bVisitSiblings)
2271
{
2272
    size_t nRet = sizeof(CPLXMLNode);
47,451✔
2273
    // malloc() aligns on 16-byte boundaries on 64 bit.
2274
    nRet += std::max(2 * sizeof(void *), strlen(psNode->pszValue) + 1);
47,451✔
2275
    if (bVisitSiblings)
47,451✔
2276
    {
2277
        for (const CPLXMLNode *psIter = psNode->psNext; psIter;
47,451✔
2278
             psIter = psIter->psNext)
19,410✔
2279
        {
2280
            nRet += CPLXMLNodeGetRAMUsageEstimate(psIter, false);
19,410✔
2281
        }
2282
    }
2283
    if (psNode->psChild)
47,451✔
2284
    {
2285
        nRet += CPLXMLNodeGetRAMUsageEstimate(psNode->psChild, true);
26,374✔
2286
    }
2287
    return nRet;
47,451✔
2288
}
2289

2290
/** Return a conservative estimate of the RAM usage of this node, its children
2291
 * and siblings. The returned values is in bytes.
2292
 *
2293
 * @since 3.9
2294
 */
2295
size_t CPLXMLNodeGetRAMUsageEstimate(const CPLXMLNode *psNode)
1,667✔
2296
{
2297
    return CPLXMLNodeGetRAMUsageEstimate(psNode, true);
1,667✔
2298
}
2299

2300
/************************************************************************/
2301
/*            CPLXMLTreeCloser::getDocumentElement()                    */
2302
/************************************************************************/
2303

2304
CPLXMLNode *CPLXMLTreeCloser::getDocumentElement()
72✔
2305
{
2306
    CPLXMLNode *doc = get();
72✔
2307
    // skip the Declaration and assume the next is the root element
2308
    while (doc != nullptr &&
120✔
2309
           (doc->eType != CXT_Element || doc->pszValue[0] == '?'))
120✔
2310
    {
2311
        doc = doc->psNext;
48✔
2312
    }
2313
    return doc;
72✔
2314
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc