/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 | } |