Coverage Report

Created: 2026-02-14 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/pds/vicarkeywordhandler.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  VICAR Driver; JPL/MIPL VICAR Format
4
 * Purpose:  Implementation of VICARKeywordHandler - a class to read
5
 *           keyword data from VICAR data products.
6
 * Author:   Sebastian Walter <sebastian dot walter at fu-berlin dot de>
7
 *
8
 * NOTE: This driver code is loosely based on the ISIS and PDS drivers.
9
 * It is not intended to diminish the contribution of the authors.
10
 ******************************************************************************
11
 * Copyright (c) 2014, Sebastian Walter <sebastian dot walter at fu-berlin dot
12
 *de>
13
 *
14
 * SPDX-License-Identifier: MIT
15
 ****************************************************************************/
16
17
#include "cpl_string.h"
18
#include "vicarkeywordhandler.h"
19
#include "vicardataset.h"
20
21
#include <algorithm>
22
#include <limits>
23
24
/************************************************************************/
25
/* ==================================================================== */
26
/*                          VICARKeywordHandler                         */
27
/* ==================================================================== */
28
/************************************************************************/
29
30
/************************************************************************/
31
/*                        VICARKeywordHandler()                         */
32
/************************************************************************/
33
34
VICARKeywordHandler::VICARKeywordHandler()
35
187
    : papszKeywordList(nullptr), pszHeaderNext(nullptr)
36
187
{
37
187
    oJSon.Deinit();
38
187
}
39
40
/************************************************************************/
41
/*                        ~VICARKeywordHandler()                        */
42
/************************************************************************/
43
44
VICARKeywordHandler::~VICARKeywordHandler()
45
46
187
{
47
187
    CSLDestroy(papszKeywordList);
48
187
}
49
50
/************************************************************************/
51
/*                               Ingest()                               */
52
/************************************************************************/
53
54
bool VICARKeywordHandler::Ingest(VSILFILE *fp, const GByte *pabyHeader)
55
56
187
{
57
    /* -------------------------------------------------------------------- */
58
    /*      Read in label at beginning of file.                             */
59
    /* -------------------------------------------------------------------- */
60
187
    if (VSIFSeekL(fp, 0, SEEK_SET) != 0)
61
0
        return false;
62
63
    // Find LBLSIZE Entry
64
187
    const char *pszLBLSIZE =
65
187
        strstr(reinterpret_cast<const char *>(pabyHeader), "LBLSIZE");
66
187
    if (!pszLBLSIZE)
67
0
        return false;
68
69
187
    const char *pch1 = strchr(pszLBLSIZE, '=');
70
187
    if (pch1 == nullptr)
71
0
        return false;
72
187
    ++pch1;
73
246
    while (isspace(static_cast<unsigned char>(*pch1)))
74
59
        ++pch1;
75
187
    const char *pch2 = strchr(pch1, ' ');
76
187
    if (pch2 == nullptr)
77
1
        return false;
78
79
186
    std::string keyval;
80
186
    keyval.assign(pch1, static_cast<size_t>(pch2 - pch1));
81
186
    int LabelSize = atoi(keyval.c_str());
82
186
    if (LabelSize <= 0 || LabelSize > 10 * 1024 * 124)
83
4
        return false;
84
85
182
    char *pszChunk = reinterpret_cast<char *>(VSIMalloc(LabelSize + 1));
86
182
    if (pszChunk == nullptr)
87
0
        return false;
88
182
    int nBytesRead = static_cast<int>(VSIFReadL(pszChunk, 1, LabelSize, fp));
89
182
    pszChunk[nBytesRead] = '\0';
90
91
182
    osHeaderText += pszChunk;
92
182
    VSIFree(pszChunk);
93
182
    pszHeaderNext = osHeaderText.c_str();
94
95
    /* -------------------------------------------------------------------- */
96
    /*      Process name/value pairs                                        */
97
    /* -------------------------------------------------------------------- */
98
182
    if (!Parse())
99
46
        return false;
100
101
    /* -------------------------------------------------------------------- */
102
    /*      Now check for the Vicar End-of-Dataset Label...                 */
103
    /* -------------------------------------------------------------------- */
104
136
    const char *pszResult = CSLFetchNameValueDef(papszKeywordList, "EOL", "0");
105
136
    if (!EQUAL(pszResult, "1"))
106
136
        return true;
107
108
    /* -------------------------------------------------------------------- */
109
    /*      There is a EOL!   e.G.  h4231_0000.nd4.06                       */
110
    /* -------------------------------------------------------------------- */
111
112
0
    uint64_t nPixelOffset;
113
0
    uint64_t nLineOffset;
114
0
    uint64_t nBandOffset;
115
0
    uint64_t nImageOffsetWithoutNBB;
116
0
    uint64_t nNBB;
117
0
    uint64_t nImageSize;
118
0
    if (!VICARDataset::GetSpacings(*this, nPixelOffset, nLineOffset,
119
0
                                   nBandOffset, nImageOffsetWithoutNBB, nNBB,
120
0
                                   nImageSize))
121
0
        return false;
122
123
    // Position of EOL in case of compressed data
124
0
    const vsi_l_offset nEOCI1 = static_cast<vsi_l_offset>(
125
0
        CPLAtoGIntBig(CSLFetchNameValueDef(papszKeywordList, "EOCI1", "0")));
126
0
    const vsi_l_offset nEOCI2 = static_cast<vsi_l_offset>(
127
0
        CPLAtoGIntBig(CSLFetchNameValueDef(papszKeywordList, "EOCI2", "0")));
128
0
    const vsi_l_offset nEOCI = (nEOCI2 << 32) | nEOCI1;
129
130
0
    if (nImageOffsetWithoutNBB >
131
0
        std::numeric_limits<uint64_t>::max() - nImageSize)
132
0
    {
133
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid label values");
134
0
        return false;
135
0
    }
136
137
0
    const vsi_l_offset nStartEOL =
138
0
        nEOCI ? nEOCI : nImageOffsetWithoutNBB + nImageSize;
139
140
0
    if (VSIFSeekL(fp, nStartEOL, SEEK_SET) != 0)
141
0
    {
142
0
        CPLError(CE_Failure, CPLE_AppDefined, "Error seeking to EOL");
143
0
        return false;
144
0
    }
145
0
    char *pszEOLHeader = static_cast<char *>(VSIMalloc(32));
146
0
    if (pszEOLHeader == nullptr)
147
0
        return false;
148
0
    nBytesRead = static_cast<int>(VSIFReadL(pszEOLHeader, 1, 31, fp));
149
0
    pszEOLHeader[nBytesRead] = '\0';
150
0
    pszLBLSIZE = strstr(pszEOLHeader, "LBLSIZE");
151
0
    if (!pszLBLSIZE)
152
0
    {
153
0
        CPLError(CE_Failure, CPLE_AppDefined,
154
0
                 "END-OF-DATASET LABEL NOT FOUND!");
155
0
        VSIFree(pszEOLHeader);
156
0
        return false;
157
0
    }
158
0
    pch1 = strchr(pszLBLSIZE, '=');
159
0
    if (pch1 == nullptr)
160
0
    {
161
0
        CPLError(CE_Failure, CPLE_AppDefined,
162
0
                 "END-OF-DATASET LABEL NOT FOUND!");
163
0
        VSIFree(pszEOLHeader);
164
0
        return false;
165
0
    }
166
0
    ++pch1;
167
0
    while (isspace(static_cast<unsigned char>(*pch1)))
168
0
        ++pch1;
169
0
    pch2 = strchr(pch1, ' ');
170
0
    if (pch2 == nullptr)
171
0
    {
172
0
        CPLError(CE_Failure, CPLE_AppDefined,
173
0
                 "END-OF-DATASET LABEL NOT FOUND!");
174
0
        VSIFree(pszEOLHeader);
175
0
        return false;
176
0
    }
177
0
    keyval.assign(pch1, static_cast<size_t>(pch2 - pch1));
178
0
    const auto nSkipEOLLBLSize = static_cast<size_t>(pch2 - pszEOLHeader);
179
0
    VSIFree(pszEOLHeader);
180
181
0
    int EOLabelSize = atoi(keyval.c_str());
182
0
    if (EOLabelSize <= 0 ||
183
0
        static_cast<size_t>(EOLabelSize) <= nSkipEOLLBLSize ||
184
0
        EOLabelSize > 100 * 1024 * 1024)
185
0
        return false;
186
0
    if (VSIFSeekL(fp, nStartEOL, SEEK_SET) != 0)
187
0
    {
188
0
        CPLError(CE_Failure, CPLE_AppDefined, "Error seeking to EOL");
189
0
        return false;
190
0
    }
191
0
    char *pszChunkEOL =
192
0
        static_cast<char *>(VSI_MALLOC_VERBOSE(EOLabelSize + 1));
193
0
    if (pszChunkEOL == nullptr)
194
0
        return false;
195
0
    nBytesRead = static_cast<int>(VSIFReadL(pszChunkEOL, 1, EOLabelSize, fp));
196
0
    pszChunkEOL[nBytesRead] = '\0';
197
0
    osHeaderText += pszChunkEOL + nSkipEOLLBLSize;
198
0
    VSIFree(pszChunkEOL);
199
0
    CSLDestroy(papszKeywordList);
200
0
    papszKeywordList = nullptr;
201
0
    pszHeaderNext = osHeaderText.c_str();
202
0
    return Parse();
203
0
}
204
205
/************************************************************************/
206
/*                               Parse()                                */
207
/************************************************************************/
208
209
136
#define SYNTHETIC_END_MARKER "__END__"
210
211
bool VICARKeywordHandler::Parse()
212
182
{
213
182
    CPLString osName, osValue, osGroupName;
214
182
    CPLJSONObject oProperties;
215
182
    CPLJSONObject oTasks;
216
182
    CPLJSONObject oCurObj;
217
182
    bool bHasProperties = false;
218
182
    bool bHasTasks = false;
219
220
182
    oJSon = CPLJSONObject();
221
7.60k
    for (; true;)
222
7.60k
    {
223
7.60k
        if (!ReadPair(osName, osValue, osGroupName.empty() ? oJSon : oCurObj))
224
46
            return false;
225
226
7.56k
        if (EQUAL(osName, SYNTHETIC_END_MARKER))
227
136
            break;
228
229
7.42k
        if (EQUAL(osName, "PROPERTY"))
230
125
        {
231
125
            osGroupName = osValue;
232
125
            oCurObj = CPLJSONObject();
233
125
            bHasProperties = true;
234
125
            oProperties.Add(osValue, oCurObj);
235
125
        }
236
7.30k
        else if (EQUAL(osName, "TASK"))
237
67
        {
238
67
            osGroupName = osValue;
239
67
            oCurObj = CPLJSONObject();
240
67
            bHasTasks = true;
241
67
            oTasks.Add(osValue, oCurObj);
242
67
        }
243
7.23k
        else
244
7.23k
        {
245
7.23k
            if (!osGroupName.empty())
246
1.88k
                osName = osGroupName + "." + osName;
247
7.23k
            papszKeywordList =
248
7.23k
                CSLSetNameValue(papszKeywordList, osName, osValue);
249
7.23k
        }
250
7.42k
    }
251
136
    if (bHasProperties)
252
34
        oJSon.Add("PROPERTY", oProperties);
253
136
    if (bHasTasks)
254
28
        oJSon.Add("TASK", oTasks);
255
136
    return true;
256
182
}
257
258
/************************************************************************/
259
/*                              ReadPair()                              */
260
/*                                                                      */
261
/*      Read a name/value pair from the input stream.  Strip off        */
262
/*      white space, ignore comments, split on '='.                     */
263
/*      Returns TRUE on success.                                        */
264
/************************************************************************/
265
266
bool VICARKeywordHandler::ReadPair(CPLString &osName, CPLString &osValue,
267
                                   CPLJSONObject &oCur)
268
7.60k
{
269
7.60k
    osName.clear();
270
7.60k
    osValue.clear();
271
272
7.60k
    if (!ReadName(osName))
273
175
    {
274
        // VICAR has no NULL string termination
275
175
        if (*pszHeaderNext == '\0')
276
136
        {
277
136
            osName = SYNTHETIC_END_MARKER;
278
136
            return true;
279
136
        }
280
39
        return false;
281
175
    }
282
283
7.43k
    bool bIsString = false;
284
7.43k
    if (*pszHeaderNext == '(')
285
527
    {
286
527
        CPLString osWord;
287
527
        pszHeaderNext++;
288
527
        CPLJSONArray oArray;
289
527
        oCur.Add(osName, oArray);
290
1.66k
        while (ReadValue(osWord, true, bIsString))
291
1.36k
        {
292
1.36k
            if (!osValue.empty())
293
994
                osValue += ',';
294
1.36k
            osValue += osWord;
295
1.36k
            if (bIsString)
296
220
            {
297
220
                oArray.Add(osWord);
298
220
            }
299
1.14k
            else if (CPLGetValueType(osWord) == CPL_VALUE_INTEGER)
300
273
            {
301
273
                oArray.Add(atoi(osWord));
302
273
            }
303
869
            else
304
869
            {
305
869
                oArray.Add(CPLAtof(osWord));
306
869
            }
307
1.36k
            if (*pszHeaderNext == ')')
308
223
            {
309
223
                pszHeaderNext++;
310
223
                break;
311
223
            }
312
1.13k
            pszHeaderNext++;
313
1.13k
        }
314
527
    }
315
6.90k
    else
316
6.90k
    {
317
6.90k
        if (!ReadValue(osValue, false, bIsString))
318
7
            return false;
319
6.89k
        if (!EQUAL(osName, "PROPERTY") && !EQUAL(osName, "TASK"))
320
6.70k
        {
321
6.70k
            if (bIsString)
322
3.41k
            {
323
3.41k
                oCur.Add(osName, osValue);
324
3.41k
            }
325
3.29k
            else if (CPLGetValueType(osValue) == CPL_VALUE_INTEGER)
326
2.59k
            {
327
2.59k
                oCur.Add(osName, atoi(osValue));
328
2.59k
            }
329
697
            else
330
697
            {
331
697
                oCur.Add(osName, CPLAtof(osValue));
332
697
            }
333
6.70k
        }
334
6.89k
    }
335
336
7.42k
    return true;
337
7.43k
}
338
339
/************************************************************************/
340
/*                              ReadName()                              */
341
/************************************************************************/
342
343
bool VICARKeywordHandler::ReadName(CPLString &osWord)
344
345
7.60k
{
346
7.60k
    osWord.clear();
347
348
7.60k
    SkipWhite();
349
350
7.60k
    if (*pszHeaderNext == '\0')
351
96
        return false;
352
353
88.3k
    while (*pszHeaderNext != '=' &&
354
81.3k
           !isspace(static_cast<unsigned char>(*pszHeaderNext)))
355
80.8k
    {
356
80.8k
        if (*pszHeaderNext == '\0')
357
39
            return false;
358
80.8k
        osWord += *pszHeaderNext;
359
80.8k
        pszHeaderNext++;
360
80.8k
    }
361
362
7.47k
    SkipWhite();
363
364
7.47k
    if (*pszHeaderNext != '=')
365
40
        return false;
366
7.43k
    pszHeaderNext++;
367
368
7.43k
    SkipWhite();
369
370
7.43k
    return true;
371
7.47k
}
372
373
/************************************************************************/
374
/*                              ReadWord()                              */
375
/************************************************************************/
376
377
bool VICARKeywordHandler::ReadValue(CPLString &osWord, bool bInList,
378
                                    bool &bIsString)
379
380
8.57k
{
381
8.57k
    osWord.clear();
382
383
8.57k
    SkipWhite();
384
385
8.57k
    if (*pszHeaderNext == '\0')
386
3
        return false;
387
388
8.56k
    if (*pszHeaderNext == '\'')
389
1.62k
    {
390
1.62k
        bIsString = true;
391
1.62k
        pszHeaderNext++;
392
16.5k
        while (true)
393
16.5k
        {
394
16.5k
            if (*pszHeaderNext == '\0')
395
6
                return false;
396
16.5k
            if (*(pszHeaderNext) == '\'')
397
1.67k
            {
398
1.67k
                if (*(pszHeaderNext + 1) == '\'')
399
63
                {
400
                    // Skip Double Quotes
401
63
                    pszHeaderNext++;
402
63
                }
403
1.61k
                else
404
1.61k
                    break;
405
1.67k
            }
406
14.8k
            osWord += *pszHeaderNext;
407
14.8k
            pszHeaderNext++;
408
14.8k
        }
409
1.61k
        pszHeaderNext++;
410
1.61k
    }
411
6.94k
    else
412
6.94k
    {
413
66.0k
        while (!isspace(static_cast<unsigned char>(*pszHeaderNext)))
414
60.4k
        {
415
60.4k
            if (*pszHeaderNext == '\0')
416
56
                return !bInList;
417
60.3k
            if (bInList && (*pszHeaderNext == ',' || *pszHeaderNext == ')'))
418
1.25k
            {
419
1.25k
                return true;
420
1.25k
            }
421
59.1k
            osWord += *pszHeaderNext;
422
59.1k
            pszHeaderNext++;
423
59.1k
        }
424
5.63k
        bIsString = CPLGetValueType(osWord) == CPL_VALUE_STRING;
425
5.63k
    }
426
427
7.24k
    SkipWhite();
428
7.24k
    if (bInList && *pszHeaderNext != ',' && *pszHeaderNext != ')')
429
286
        return false;
430
431
6.96k
    return true;
432
7.24k
}
433
434
/************************************************************************/
435
/*                             SkipWhite()                              */
436
/*  Skip white spaces                                                   */
437
/************************************************************************/
438
439
void VICARKeywordHandler::SkipWhite()
440
441
38.3k
{
442
56.1k
    for (; true;)
443
56.1k
    {
444
56.1k
        if (isspace(static_cast<unsigned char>(*pszHeaderNext)))
445
17.8k
        {
446
17.8k
            pszHeaderNext++;
447
17.8k
            continue;
448
17.8k
        }
449
450
        // not white space, return.
451
38.3k
        return;
452
56.1k
    }
453
38.3k
}
454
455
/************************************************************************/
456
/*                             GetKeyword()                             */
457
/************************************************************************/
458
459
const char *VICARKeywordHandler::GetKeyword(const char *pszPath,
460
                                            const char *pszDefault) const
461
462
3.15k
{
463
3.15k
    const char *pszResult = CSLFetchNameValue(papszKeywordList, pszPath);
464
465
3.15k
    if (pszResult == nullptr)
466
2.02k
        return pszDefault;
467
468
1.13k
    return pszResult;
469
3.15k
}