Coverage Report

Created: 2026-09-26 08:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/port/cplkeywordparser.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  Common Portability Library
4
 * Purpose:  Implementation of CPLKeywordParser - a class for parsing
5
 *           the keyword format used for files like QuickBird .RPB files.
6
 *           This is a slight variation on the NASAKeywordParser used for
7
 *           the PDS/ISIS2/ISIS3 formats.
8
 * Author:   Frank Warmerdam <warmerdam@pobox.com
9
 *
10
 ******************************************************************************
11
 * Copyright (c) 2008, Frank Warmerdam <warmerdam@pobox.com>
12
 * Copyright (c) 2009-2010, Even Rouault <even dot rouault at spatialys.com>
13
 *
14
 * SPDX-License-Identifier: MIT
15
 ****************************************************************************/
16
17
//! @cond Doxygen_Suppress
18
19
#include "cpl_port.h"
20
#include "cplkeywordparser.h"
21
22
#include <cctype>
23
#include <cstring>
24
#include <string>
25
26
#include "cpl_string.h"
27
#include "cpl_vsi.h"
28
29
/************************************************************************/
30
/* ==================================================================== */
31
/*                          CPLKeywordParser                           */
32
/* ==================================================================== */
33
/************************************************************************/
34
35
/************************************************************************/
36
/*                          CPLKeywordParser()                          */
37
/************************************************************************/
38
39
785
CPLKeywordParser::CPLKeywordParser() = default;
40
41
/************************************************************************/
42
/*                         ~CPLKeywordParser()                          */
43
/************************************************************************/
44
45
CPLKeywordParser::~CPLKeywordParser()
46
47
785
{
48
785
    CSLDestroy(papszKeywordList);
49
785
    papszKeywordList = nullptr;
50
785
}
51
52
/************************************************************************/
53
/*                               Ingest()                               */
54
/************************************************************************/
55
56
int CPLKeywordParser::Ingest(VSILFILE *fp)
57
58
785
{
59
    /* -------------------------------------------------------------------- */
60
    /*      Read in buffer till we find END all on its own line.            */
61
    /* -------------------------------------------------------------------- */
62
40.6k
    for (; true;)
63
40.6k
    {
64
40.6k
        char szChunk[513] = {};
65
40.6k
        const size_t nBytesRead = VSIFReadL(szChunk, 1, 512, fp);
66
67
40.6k
        szChunk[nBytesRead] = '\0';
68
40.6k
        osHeaderText += szChunk;
69
70
40.6k
        if (nBytesRead < 512)
71
750
            break;
72
73
39.9k
        const char *pszCheck = nullptr;
74
39.9k
        if (osHeaderText.size() > 520)
75
36.7k
            pszCheck = osHeaderText.c_str() + (osHeaderText.size() - 520);
76
3.14k
        else
77
3.14k
            pszCheck = szChunk;
78
79
39.9k
        if (strstr(pszCheck, "\r\nEND;\r\n") != nullptr ||
80
39.8k
            strstr(pszCheck, "\nEND;\n") != nullptr)
81
35
            break;
82
39.9k
    }
83
84
785
    pszHeaderNext = osHeaderText.c_str();
85
86
    /* -------------------------------------------------------------------- */
87
    /*      Process name/value pairs, keeping track of a "path stack".      */
88
    /* -------------------------------------------------------------------- */
89
785
    return ReadGroup("", 0);
90
785
}
91
92
/************************************************************************/
93
/*                             ReadGroup()                              */
94
/************************************************************************/
95
96
bool CPLKeywordParser::ReadGroup(const char *pszPathPrefix, int nRecLevel)
97
98
4.85k
{
99
4.85k
    CPLString osName;
100
4.85k
    CPLString osValue;
101
102
    // Arbitrary threshold to avoid stack overflow
103
4.85k
    if (nRecLevel == 100)
104
3
        return false;
105
106
60.9k
    for (; true;)
107
60.9k
    {
108
60.9k
        if (!ReadPair(osName, osValue))
109
772
            return false;
110
111
60.1k
        if (EQUAL(osName, "BEGIN_GROUP") || EQUAL(osName, "GROUP"))
112
4.06k
        {
113
4.06k
            if (!ReadGroup((CPLString(pszPathPrefix) + osValue + ".").c_str(),
114
4.06k
                           nRecLevel + 1))
115
3.07k
                return false;
116
4.06k
        }
117
56.0k
        else if (STARTS_WITH_CI(osName, "END"))
118
1.00k
        {
119
1.00k
            return true;
120
1.00k
        }
121
55.0k
        else
122
55.0k
        {
123
55.0k
            osName = pszPathPrefix + osName;
124
55.0k
            papszKeywordList =
125
55.0k
                CSLSetNameValue(papszKeywordList, osName, osValue);
126
55.0k
        }
127
60.1k
    }
128
4.84k
}
129
130
/************************************************************************/
131
/*                              ReadPair()                              */
132
/*                                                                      */
133
/*      Read a name/value pair from the input stream.  Strip off        */
134
/*      white space, ignore comments, split on '='.                     */
135
/************************************************************************/
136
137
bool CPLKeywordParser::ReadPair(CPLString &osName, CPLString &osValue)
138
139
60.9k
{
140
60.9k
    osName = "";
141
60.9k
    osValue = "";
142
143
60.9k
    if (!ReadWord(osName))
144
408
        return false;
145
146
60.4k
    SkipWhite();
147
148
60.4k
    if (EQUAL(osName, "END"))
149
300
        return TRUE;
150
151
60.1k
    if (*pszHeaderNext != '=')
152
293
    {
153
        // ISIS3 does not have anything after the end group/object keyword.
154
293
        return EQUAL(osName, "End_Group") || EQUAL(osName, "End_Object");
155
293
    }
156
157
59.9k
    pszHeaderNext++;
158
159
59.9k
    SkipWhite();
160
161
59.9k
    osValue = "";
162
163
    // Handle value lists like:     Name   = (Red, Red)
164
    // or list of lists like: TLCList = ( (0, 0.000000), (8299, 4.811014) );
165
59.9k
    if (*pszHeaderNext == '(')
166
304
    {
167
304
        CPLString osWord;
168
304
        int nDepth = 0;
169
304
        const char *pszLastPos = pszHeaderNext;
170
171
15.8k
        while (ReadWord(osWord) && pszLastPos != pszHeaderNext)
172
15.7k
        {
173
15.7k
            SkipWhite();
174
15.7k
            pszLastPos = pszHeaderNext;
175
176
15.7k
            osValue += osWord;
177
15.7k
            const char *pszIter = osWord.c_str();
178
15.7k
            bool bInQuote = false;
179
493k
            while (*pszIter != '\0')
180
478k
            {
181
478k
                if (*pszIter == '"')
182
2.86k
                    bInQuote = !bInQuote;
183
475k
                else if (!bInQuote)
184
373k
                {
185
373k
                    if (*pszIter == '(')
186
4.02k
                        nDepth++;
187
369k
                    else if (*pszIter == ')')
188
3.60k
                    {
189
3.60k
                        nDepth--;
190
3.60k
                        if (nDepth == 0)
191
160
                            break;
192
3.60k
                    }
193
373k
                }
194
478k
                pszIter++;
195
478k
            }
196
15.7k
            if (*pszIter == ')' && nDepth == 0)
197
160
                break;
198
15.7k
        }
199
304
    }
200
201
59.6k
    else  // Handle more normal "single word" values.
202
59.6k
    {
203
        // Special case to handle non-conformant IMD files generated by
204
        // previous GDAL version where we omit to surround values that have
205
        // spaces with double quotes.
206
        // So we use a heuristics to handle things like:
207
        //       key = value with spaces without single or double quotes at
208
        //       beginning of value;[\r]\n
209
59.6k
        const char *pszNextLF = strchr(pszHeaderNext, '\n');
210
59.6k
        if (pszNextLF)
211
59.1k
        {
212
59.1k
            std::string osTxt(pszHeaderNext, pszNextLF - pszHeaderNext);
213
59.1k
            const auto nCRPos = osTxt.find('\r');
214
59.1k
            const auto nSemiColonPos = osTxt.find(';');
215
59.1k
            const auto nQuotePos = osTxt.find('\'');
216
59.1k
            const auto nDoubleQuotePos = osTxt.find('"');
217
59.1k
            const auto nLTPos = osTxt.find('<');
218
59.1k
            if (nSemiColonPos != std::string::npos &&
219
56.9k
                (nCRPos == std::string::npos || (nCRPos + 1 == osTxt.size())) &&
220
56.5k
                ((nCRPos != std::string::npos &&
221
3.66k
                  (nSemiColonPos + 1 == nCRPos)) ||
222
53.1k
                 (nCRPos == std::string::npos &&
223
52.8k
                  (nSemiColonPos + 1 == osTxt.size()))) &&
224
4.49k
                (nQuotePos == std::string::npos || nQuotePos != 0) &&
225
4.49k
                (nDoubleQuotePos == std::string::npos ||
226
2.29k
                 nDoubleQuotePos != 0) &&
227
3.81k
                (nLTPos == std::string::npos ||
228
735
                 osTxt.find('>') == std::string::npos))
229
3.68k
            {
230
3.68k
                pszHeaderNext = pszNextLF;
231
3.68k
                osTxt.resize(nSemiColonPos);
232
3.68k
                osValue = osTxt;
233
5.73k
                while (!osValue.empty() && osValue.back() == ' ')
234
2.05k
                    osValue.pop_back();
235
3.68k
                return true;
236
3.68k
            }
237
59.1k
        }
238
239
55.9k
        if (!ReadWord(osValue))
240
71
            return false;
241
55.9k
    }
242
243
56.1k
    SkipWhite();
244
245
    // No units keyword?
246
56.1k
    if (*pszHeaderNext != '<')
247
55.8k
        return true;
248
249
    // Append units keyword.  For lines that like like this:
250
    //  MAP_RESOLUTION               = 4.0 <PIXEL/DEGREE>
251
252
339
    CPLString osWord;
253
254
339
    osValue += " ";
255
256
5.62k
    while (ReadWord(osWord))
257
5.53k
    {
258
5.53k
        SkipWhite();
259
260
5.53k
        osValue += osWord;
261
5.53k
        if (osWord.back() == '>')
262
254
            break;
263
5.53k
    }
264
265
339
    return true;
266
56.1k
}
267
268
/************************************************************************/
269
/*                              ReadWord()                              */
270
/************************************************************************/
271
272
bool CPLKeywordParser::ReadWord(CPLString &osWord)
273
274
138k
{
275
138k
    osWord = "";
276
277
138k
    SkipWhite();
278
279
138k
    if (*pszHeaderNext == '\0' || *pszHeaderNext == '=')
280
626
        return false;
281
282
5.68M
    while (*pszHeaderNext != '\0' && *pszHeaderNext != '=' &&
283
5.66M
           *pszHeaderNext != ';' &&
284
5.60M
           !isspace(static_cast<unsigned char>(*pszHeaderNext)))
285
5.55M
    {
286
5.55M
        if (*pszHeaderNext == '"')
287
16.9k
        {
288
16.9k
            osWord += *(pszHeaderNext++);
289
312k
            while (*pszHeaderNext != '"')
290
295k
            {
291
295k
                if (*pszHeaderNext == '\0')
292
52
                    return false;
293
294
295k
                osWord += *(pszHeaderNext++);
295
295k
            }
296
16.8k
            osWord += *(pszHeaderNext++);
297
16.8k
        }
298
5.53M
        else if (*pszHeaderNext == '\'')
299
6.16k
        {
300
6.16k
            osWord += *(pszHeaderNext++);
301
234k
            while (*pszHeaderNext != '\'')
302
228k
            {
303
228k
                if (*pszHeaderNext == '\0')
304
30
                    return false;
305
306
228k
                osWord += *(pszHeaderNext++);
307
228k
            }
308
6.13k
            osWord += *(pszHeaderNext++);
309
6.13k
        }
310
5.52M
        else
311
5.52M
        {
312
5.52M
            osWord += *pszHeaderNext;
313
5.52M
            pszHeaderNext++;
314
5.52M
        }
315
5.55M
    }
316
317
137k
    if (*pszHeaderNext == ';')
318
61.0k
        pszHeaderNext++;
319
320
137k
    return true;
321
137k
}
322
323
/************************************************************************/
324
/*                             SkipWhite()                              */
325
/************************************************************************/
326
327
void CPLKeywordParser::SkipWhite()
328
329
336k
{
330
424k
    for (; true;)
331
424k
    {
332
        // Skip white space (newline, space, tab, etc )
333
424k
        if (isspace(static_cast<unsigned char>(*pszHeaderNext)))
334
77.7k
        {
335
77.7k
            pszHeaderNext++;
336
77.7k
            continue;
337
77.7k
        }
338
339
        // Skip C style comments
340
347k
        if (*pszHeaderNext == '/' && pszHeaderNext[1] == '*')
341
2.88k
        {
342
2.88k
            pszHeaderNext += 2;
343
344
647k
            while (*pszHeaderNext != '\0' &&
345
647k
                   (*pszHeaderNext != '*' || pszHeaderNext[1] != '/'))
346
644k
            {
347
644k
                pszHeaderNext++;
348
644k
            }
349
2.88k
            if (*pszHeaderNext == '\0')
350
89
                break;
351
352
2.79k
            pszHeaderNext += 2;
353
2.79k
            continue;
354
2.88k
        }
355
356
        // Skip # style comments
357
344k
        if (*pszHeaderNext == '#')
358
8.09k
        {
359
8.09k
            pszHeaderNext += 1;
360
361
            // consume till end of line.
362
491k
            while (*pszHeaderNext != '\0' && *pszHeaderNext != 10 &&
363
485k
                   *pszHeaderNext != 13)
364
483k
            {
365
483k
                pszHeaderNext++;
366
483k
            }
367
8.09k
            continue;
368
8.09k
        }
369
370
        // not white space, return.
371
336k
        return;
372
344k
    }
373
336k
}
374
375
/************************************************************************/
376
/*                             GetKeyword()                             */
377
/************************************************************************/
378
379
const char *CPLKeywordParser::GetKeyword(const char *pszPath,
380
                                         const char *pszDefault)
381
382
30
{
383
30
    const char *pszResult = CSLFetchNameValue(papszKeywordList, pszPath);
384
30
    if (pszResult == nullptr)
385
30
        return pszDefault;
386
387
0
    return pszResult;
388
30
}
389
390
//! @endcond