Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OpenGIS Simple Features Reference Implementation
4 : * Purpose: Implements reading of FileGDB tables
5 : * Author: Even Rouault, <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_port.h"
14 : #include "filegdbtable.h"
15 :
16 : #include <algorithm>
17 : #include <cassert>
18 : #include <cinttypes>
19 : #include <cmath>
20 : #include <errno.h>
21 : #include <limits.h>
22 : #include <stddef.h>
23 : #include <stdio.h>
24 : #include <string.h>
25 : #include <time.h>
26 : #include <limits>
27 : #include <string>
28 : #include <vector>
29 :
30 : #include "cpl_conv.h"
31 : #include "cpl_error.h"
32 : #include "cpl_string.h"
33 : #include "cpl_time.h"
34 : #include "cpl_vsi.h"
35 : #include "filegdbtable_priv.h"
36 : #include "ogr_api.h"
37 : #include "ogr_core.h"
38 : #include "ogr_geometry.h"
39 : #include "ogrpgeogeometry.h"
40 : #include "ogr_spatialref.h"
41 :
42 : #define UUID_SIZE_IN_BYTES 16
43 :
44 : #define IS_VALID_LAYER_GEOM_TYPE(byVal) \
45 : ((byVal) <= FGTGT_POLYGON || (byVal) == FGTGT_MULTIPATCH)
46 :
47 : /* Reserve one extra byte in case the last field is a string */
48 : /* or 2 for 2 ReadVarIntAndAddNoCheck() in a row */
49 : /* or 4 for SkipVarUInt() with nIter = 4 */
50 : /* or for 4 ReadVarUInt64NoCheck */
51 : #define ZEROES_AFTER_END_OF_BUFFER 4
52 :
53 : constexpr GUInt32 EXT_SHAPE_Z_FLAG = 0x80000000U;
54 : constexpr GUInt32 EXT_SHAPE_M_FLAG = 0x40000000U;
55 : constexpr GUInt32 EXT_SHAPE_CURVE_FLAG = 0x20000000U;
56 :
57 : constexpr GUInt32 EXT_SHAPE_SEGMENT_ARC = 1;
58 : constexpr GUInt32 EXT_SHAPE_SEGMENT_BEZIER = 4;
59 : constexpr GUInt32 EXT_SHAPE_SEGMENT_ELLIPSE = 5;
60 :
61 : namespace OpenFileGDB
62 : {
63 :
64 : #ifdef _MSC_VER
65 : #pragma warning(push)
66 : #pragma warning(disable : 4800) /* implicit conversion from "type" to bool */
67 : #endif
68 :
69 : FileGDBGeomField::~FileGDBGeomField() = default;
70 :
71 : FileGDBRasterField::~FileGDBRasterField() = default;
72 :
73 : /************************************************************************/
74 : /* SanitizeScale() */
75 : /************************************************************************/
76 :
77 3983 : static double SanitizeScale(double dfVal)
78 : {
79 3983 : if (dfVal == 0.0)
80 0 : return std::numeric_limits<double>::min(); // to prevent divide by zero
81 3983 : return dfVal;
82 : }
83 :
84 : /************************************************************************/
85 : /* FileGDBTablePrintError() */
86 : /************************************************************************/
87 :
88 125 : void FileGDBTablePrintError(const char *pszFile, int nLineNumber)
89 : {
90 125 : CPLError(CE_Failure, CPLE_AppDefined, "Error occurred in %s at line %d",
91 : pszFile, nLineNumber);
92 125 : }
93 :
94 : /************************************************************************/
95 : /* FileGDBTable() */
96 : /************************************************************************/
97 :
98 7384 : FileGDBTable::FileGDBTable()
99 : {
100 7384 : memset(&m_sCurField, 0, sizeof(m_sCurField));
101 7384 : }
102 :
103 : /************************************************************************/
104 : /* ~FileGDBTable() */
105 : /************************************************************************/
106 :
107 7384 : FileGDBTable::~FileGDBTable()
108 : {
109 7384 : Close();
110 7384 : }
111 :
112 : /************************************************************************/
113 : /* Close() */
114 : /************************************************************************/
115 :
116 8259 : void FileGDBTable::Close()
117 : {
118 8259 : Sync();
119 :
120 8259 : if (m_fpTable)
121 7371 : VSIFCloseL(m_fpTable);
122 8259 : m_fpTable = nullptr;
123 :
124 8259 : if (m_fpTableX)
125 7254 : VSIFCloseL(m_fpTableX);
126 8259 : m_fpTableX = nullptr;
127 8259 : }
128 :
129 : /************************************************************************/
130 : /* GetFieldIdx() */
131 : /************************************************************************/
132 :
133 43296 : int FileGDBTable::GetFieldIdx(const std::string &osName) const
134 : {
135 244600 : for (size_t i = 0; i < m_apoFields.size(); i++)
136 : {
137 230092 : if (m_apoFields[i]->GetName() == osName)
138 28788 : return static_cast<int>(i);
139 : }
140 14508 : return -1;
141 : }
142 :
143 : /************************************************************************/
144 : /* ReadVarUInt() */
145 : /************************************************************************/
146 :
147 : template <class OutType, class ControlType>
148 291609 : static int ReadVarUInt(GByte *&pabyIter, GByte *pabyEnd, OutType &nOutVal)
149 : {
150 291609 : const int errorRetValue = FALSE;
151 : if (!(ControlType::check_bounds))
152 : {
153 : /* nothing */
154 : }
155 : else if (ControlType::verbose_error)
156 : {
157 218582 : returnErrorIf(pabyIter >= pabyEnd);
158 : }
159 : else
160 : {
161 913 : if (pabyIter >= pabyEnd)
162 0 : return FALSE;
163 : }
164 291609 : OutType b = *pabyIter;
165 291609 : if ((b & 0x80) == 0)
166 : {
167 232682 : pabyIter++;
168 232682 : nOutVal = b;
169 232682 : return TRUE;
170 : }
171 58927 : GByte *pabyLocalIter = pabyIter + 1;
172 58927 : int nShift = 7;
173 58927 : OutType nVal = (b & 0x7F);
174 : while (true)
175 : {
176 191531 : if (!(ControlType::check_bounds))
177 : {
178 : /* nothing */
179 : }
180 67 : else if (ControlType::verbose_error)
181 : {
182 18501 : returnErrorIf(pabyLocalIter >= pabyEnd);
183 : }
184 : else
185 : {
186 91 : if (pabyLocalIter >= pabyEnd)
187 0 : return FALSE;
188 : }
189 250525 : b = *pabyLocalIter;
190 250525 : pabyLocalIter++;
191 250525 : nVal |= (b & 0x7F) << nShift;
192 250525 : if ((b & 0x80) == 0)
193 : {
194 58927 : pabyIter = pabyLocalIter;
195 58927 : nOutVal = nVal;
196 58927 : return TRUE;
197 : }
198 191598 : nShift += 7;
199 : // To avoid undefined behavior later when doing << nShift
200 191598 : if (nShift >= static_cast<int>(sizeof(OutType)) * 8)
201 : {
202 0 : pabyIter = pabyLocalIter;
203 0 : nOutVal = nVal;
204 0 : returnError();
205 : }
206 : }
207 : }
208 :
209 : struct ControlTypeVerboseErrorTrue
210 : {
211 : // cppcheck-suppress unusedStructMember
212 : static const bool check_bounds = true;
213 : // cppcheck-suppress unusedStructMember
214 : static const bool verbose_error = true;
215 : };
216 :
217 : struct ControlTypeVerboseErrorFalse
218 : {
219 : // cppcheck-suppress unusedStructMember
220 : static const bool check_bounds = true;
221 : // cppcheck-suppress unusedStructMember
222 : static const bool verbose_error = false;
223 : };
224 :
225 : struct ControlTypeNone
226 : {
227 : // cppcheck-suppress unusedStructMember
228 : static const bool check_bounds = false;
229 : // cppcheck-suppress unusedStructMember
230 : static const bool verbose_error = false;
231 : };
232 :
233 218582 : static int ReadVarUInt32(GByte *&pabyIter, GByte *pabyEnd, GUInt32 &nOutVal)
234 : {
235 218582 : return ReadVarUInt<GUInt32, ControlTypeVerboseErrorTrue>(pabyIter, pabyEnd,
236 218582 : nOutVal);
237 : }
238 :
239 32046 : static void ReadVarUInt32NoCheck(GByte *&pabyIter, GUInt32 &nOutVal)
240 : {
241 32046 : GByte *pabyEnd = nullptr;
242 32046 : ReadVarUInt<GUInt32, ControlTypeNone>(pabyIter, pabyEnd, nOutVal);
243 32046 : }
244 :
245 913 : static int ReadVarUInt32Silent(GByte *&pabyIter, GByte *pabyEnd,
246 : GUInt32 &nOutVal)
247 : {
248 913 : return ReadVarUInt<GUInt32, ControlTypeVerboseErrorFalse>(pabyIter, pabyEnd,
249 913 : nOutVal);
250 : }
251 :
252 40068 : static void ReadVarUInt64NoCheck(GByte *&pabyIter, GUIntBig &nOutVal)
253 : {
254 40068 : GByte *pabyEnd = nullptr;
255 40068 : ReadVarUInt<GUIntBig, ControlTypeNone>(pabyIter, pabyEnd, nOutVal);
256 40068 : }
257 :
258 : /************************************************************************/
259 : /* IsLikelyFeatureAtOffset() */
260 : /************************************************************************/
261 :
262 3438 : int FileGDBTable::IsLikelyFeatureAtOffset(vsi_l_offset nOffset, GUInt32 *pnSize,
263 : int *pbDeletedRecord)
264 : {
265 3438 : VSIFSeekL(m_fpTable, nOffset, SEEK_SET);
266 : GByte abyBuffer[4];
267 3438 : if (VSIFReadL(abyBuffer, 4, 1, m_fpTable) != 1)
268 3 : return FALSE;
269 :
270 3435 : m_nRowBlobLength = GetUInt32(abyBuffer, 0);
271 3435 : if (m_nRowBlobLength < static_cast<GUInt32>(m_nNullableFieldsSizeInBytes) ||
272 3286 : m_nRowBlobLength > m_nFileSize - nOffset ||
273 631 : m_nRowBlobLength > INT_MAX - ZEROES_AFTER_END_OF_BUFFER ||
274 631 : m_nRowBlobLength > 10 * (m_nFileSize / m_nValidRecordCount))
275 : {
276 : /* Is it a deleted record ? */
277 2828 : if ((m_nRowBlobLength >> (8 * sizeof(m_nRowBlobLength) - 1)) != 0 &&
278 210 : m_nRowBlobLength != 0x80000000U)
279 : {
280 210 : m_nRowBlobLength =
281 210 : static_cast<GUInt32>(-static_cast<int>(m_nRowBlobLength));
282 210 : if (m_nRowBlobLength <
283 210 : static_cast<GUInt32>(m_nNullableFieldsSizeInBytes) ||
284 210 : m_nRowBlobLength > m_nFileSize - nOffset ||
285 53 : m_nRowBlobLength > INT_MAX - ZEROES_AFTER_END_OF_BUFFER ||
286 53 : m_nRowBlobLength > 10 * (m_nFileSize / m_nValidRecordCount))
287 157 : return FALSE;
288 : else
289 53 : *pbDeletedRecord = TRUE;
290 : }
291 : else
292 2618 : return FALSE;
293 : }
294 : else
295 607 : *pbDeletedRecord = FALSE;
296 :
297 660 : m_nRowBufferMaxSize = std::max(m_nRowBlobLength, m_nRowBufferMaxSize);
298 660 : if (m_abyBuffer.size() < m_nRowBlobLength + ZEROES_AFTER_END_OF_BUFFER)
299 : {
300 : try
301 : {
302 4 : m_abyBuffer.resize(m_nRowBlobLength + ZEROES_AFTER_END_OF_BUFFER);
303 : }
304 0 : catch (const std::exception &e)
305 : {
306 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
307 0 : return FALSE;
308 : }
309 : }
310 660 : if (m_nCountNullableFields > 0)
311 : {
312 610 : if (VSIFReadL(m_abyBuffer.data(), m_nNullableFieldsSizeInBytes, 1,
313 610 : m_fpTable) != 1)
314 0 : return FALSE;
315 : }
316 660 : m_iAccNullable = 0;
317 660 : int bExactSizeKnown = TRUE;
318 660 : GUInt32 nRequiredLength = m_nNullableFieldsSizeInBytes;
319 5007 : for (int i = 0; i < static_cast<int>(m_apoFields.size()); i++)
320 : {
321 4365 : if (m_apoFields[i]->m_bNullable)
322 : {
323 3404 : int bIsNull = TEST_BIT(m_abyBuffer.data(), m_iAccNullable);
324 3404 : m_iAccNullable++;
325 3404 : if (bIsNull)
326 805 : continue;
327 : }
328 :
329 3560 : switch (m_apoFields[i]->m_eType)
330 : {
331 0 : case FGFT_UNDEFINED:
332 0 : CPLAssert(false);
333 : break;
334 :
335 660 : case FGFT_OBJECTID:
336 660 : break;
337 :
338 1196 : case FGFT_STRING:
339 : case FGFT_XML:
340 : case FGFT_GEOMETRY:
341 : case FGFT_BINARY:
342 : {
343 1196 : nRequiredLength += 1; /* varuint32 so at least one byte */
344 1196 : bExactSizeKnown = FALSE;
345 1196 : break;
346 : }
347 :
348 0 : case FGFT_RASTER:
349 : {
350 : const FileGDBRasterField *rasterField =
351 0 : cpl::down_cast<const FileGDBRasterField *>(
352 0 : m_apoFields[i].get());
353 0 : if (rasterField->GetRasterType() ==
354 : FileGDBRasterField::Type::MANAGED)
355 0 : nRequiredLength += sizeof(GInt32);
356 : else
357 0 : nRequiredLength += 1; /* varuint32 so at least one byte */
358 0 : break;
359 : }
360 :
361 95 : case FGFT_INT16:
362 95 : nRequiredLength += sizeof(GInt16);
363 95 : break;
364 677 : case FGFT_INT32:
365 677 : nRequiredLength += sizeof(GInt32);
366 677 : break;
367 95 : case FGFT_FLOAT32:
368 95 : nRequiredLength += sizeof(float);
369 95 : break;
370 436 : case FGFT_FLOAT64:
371 436 : nRequiredLength += sizeof(double);
372 436 : break;
373 95 : case FGFT_DATETIME:
374 : case FGFT_DATE:
375 : case FGFT_TIME:
376 95 : nRequiredLength += sizeof(double);
377 95 : break;
378 306 : case FGFT_GUID:
379 : case FGFT_GLOBALID:
380 306 : nRequiredLength += UUID_SIZE_IN_BYTES;
381 306 : break;
382 0 : case FGFT_INT64:
383 0 : nRequiredLength += sizeof(int64_t);
384 0 : break;
385 0 : case FGFT_DATETIME_WITH_OFFSET:
386 0 : nRequiredLength += sizeof(double) + sizeof(int16_t);
387 0 : break;
388 : }
389 3560 : if (m_nRowBlobLength < nRequiredLength)
390 18 : return FALSE;
391 : }
392 642 : if (!bExactSizeKnown)
393 : {
394 289 : if (VSIFReadL(m_abyBuffer.data() + m_nNullableFieldsSizeInBytes,
395 289 : m_nRowBlobLength - m_nNullableFieldsSizeInBytes, 1,
396 289 : m_fpTable) != 1)
397 0 : return FALSE;
398 :
399 289 : m_iAccNullable = 0;
400 289 : nRequiredLength = m_nNullableFieldsSizeInBytes;
401 3132 : for (int i = 0; i < static_cast<int>(m_apoFields.size()); i++)
402 : {
403 2905 : if (m_apoFields[i]->m_bNullable)
404 : {
405 2348 : int bIsNull = TEST_BIT(m_abyBuffer.data(), m_iAccNullable);
406 2348 : m_iAccNullable++;
407 2348 : if (bIsNull)
408 639 : continue;
409 : }
410 :
411 2266 : switch (m_apoFields[i]->m_eType)
412 : {
413 0 : case FGFT_UNDEFINED:
414 0 : CPLAssert(false);
415 : break;
416 :
417 279 : case FGFT_OBJECTID:
418 279 : break;
419 :
420 603 : case FGFT_STRING:
421 : case FGFT_XML:
422 : {
423 603 : GByte *pabyIter = m_abyBuffer.data() + nRequiredLength;
424 : GUInt32 nLength;
425 1809 : if (!ReadVarUInt32Silent(
426 603 : pabyIter, m_abyBuffer.data() + m_nRowBlobLength,
427 1206 : nLength) ||
428 603 : pabyIter - (m_abyBuffer.data() + nRequiredLength) > 5)
429 50 : return FALSE;
430 603 : nRequiredLength =
431 603 : static_cast<GUInt32>(pabyIter - m_abyBuffer.data());
432 603 : if (nLength > m_nRowBlobLength - nRequiredLength)
433 22 : return FALSE;
434 157797 : for (GUInt32 j = 0; j < nLength; j++)
435 : {
436 157240 : if (pabyIter[j] == 0)
437 24 : return FALSE;
438 : }
439 557 : if (!CPLIsUTF8(reinterpret_cast<const char *>(pabyIter),
440 : nLength))
441 4 : return FALSE;
442 553 : nRequiredLength += nLength;
443 553 : break;
444 : }
445 :
446 310 : case FGFT_GEOMETRY:
447 : case FGFT_BINARY:
448 : {
449 310 : GByte *pabyIter = m_abyBuffer.data() + nRequiredLength;
450 : GUInt32 nLength;
451 930 : if (!ReadVarUInt32Silent(
452 310 : pabyIter, m_abyBuffer.data() + m_nRowBlobLength,
453 620 : nLength) ||
454 310 : pabyIter - (m_abyBuffer.data() + nRequiredLength) > 5)
455 12 : return FALSE;
456 310 : nRequiredLength =
457 310 : static_cast<GUInt32>(pabyIter - m_abyBuffer.data());
458 310 : if (nLength > m_nRowBlobLength - nRequiredLength)
459 12 : return FALSE;
460 298 : nRequiredLength += nLength;
461 298 : break;
462 : }
463 :
464 0 : case FGFT_RASTER:
465 : {
466 : const FileGDBRasterField *rasterField =
467 0 : cpl::down_cast<const FileGDBRasterField *>(
468 0 : m_apoFields[i].get());
469 0 : if (rasterField->GetRasterType() ==
470 : FileGDBRasterField::Type::MANAGED)
471 0 : nRequiredLength += sizeof(GInt32);
472 : else
473 : {
474 0 : GByte *pabyIter = m_abyBuffer.data() + nRequiredLength;
475 : GUInt32 nLength;
476 0 : if (!ReadVarUInt32Silent(
477 0 : pabyIter, m_abyBuffer.data() + m_nRowBlobLength,
478 0 : nLength) ||
479 0 : pabyIter - (m_abyBuffer.data() + nRequiredLength) >
480 : 5)
481 0 : return FALSE;
482 0 : nRequiredLength =
483 0 : static_cast<GUInt32>(pabyIter - m_abyBuffer.data());
484 0 : if (nLength > m_nRowBlobLength - nRequiredLength)
485 0 : return FALSE;
486 0 : nRequiredLength += nLength;
487 : }
488 0 : break;
489 : }
490 :
491 95 : case FGFT_INT16:
492 95 : nRequiredLength += sizeof(GInt16);
493 95 : break;
494 411 : case FGFT_INT32:
495 411 : nRequiredLength += sizeof(GInt32);
496 411 : break;
497 95 : case FGFT_FLOAT32:
498 95 : nRequiredLength += sizeof(float);
499 95 : break;
500 95 : case FGFT_FLOAT64:
501 95 : nRequiredLength += sizeof(double);
502 95 : break;
503 95 : case FGFT_DATETIME:
504 : case FGFT_DATE:
505 : case FGFT_TIME:
506 95 : nRequiredLength += sizeof(double);
507 95 : break;
508 283 : case FGFT_GUID:
509 : case FGFT_GLOBALID:
510 283 : nRequiredLength += UUID_SIZE_IN_BYTES;
511 283 : break;
512 0 : case FGFT_INT64:
513 0 : nRequiredLength += sizeof(int64_t);
514 0 : break;
515 0 : case FGFT_DATETIME_WITH_OFFSET:
516 0 : nRequiredLength += sizeof(double) + sizeof(int16_t);
517 0 : break;
518 : }
519 2204 : if (nRequiredLength > m_nRowBlobLength)
520 0 : return FALSE;
521 : }
522 : }
523 :
524 580 : *pnSize = 4 + nRequiredLength;
525 580 : return nRequiredLength == m_nRowBlobLength;
526 : }
527 :
528 : /************************************************************************/
529 : /* GuessFeatureLocations() */
530 : /************************************************************************/
531 :
532 : #define MARK_DELETED(x) ((x) | (static_cast<GUIntBig>(1) << 63))
533 : #define IS_DELETED(x) (((x) & (static_cast<GUIntBig>(1) << 63)) != 0)
534 : #define GET_OFFSET(x) ((x) & ~(static_cast<GUIntBig>(1) << 63))
535 :
536 38 : bool FileGDBTable::GuessFeatureLocations()
537 : {
538 38 : VSIFSeekL(m_fpTable, 0, SEEK_END);
539 38 : m_nFileSize = VSIFTellL(m_fpTable);
540 :
541 38 : int bReportDeletedFeatures = CPLTestBool(
542 38 : CPLGetConfigOption("OPENFILEGDB_REPORT_DELETED_FEATURES", "NO"));
543 :
544 38 : vsi_l_offset nOffset = 40 + m_nFieldDescLength;
545 :
546 38 : if (m_nOffsetFieldDesc != 40)
547 : {
548 : /* Check if there is a deleted field description at offset 40 */
549 : GByte abyBuffer[14];
550 1 : VSIFSeekL(m_fpTable, 40, SEEK_SET);
551 1 : if (VSIFReadL(abyBuffer, 14, 1, m_fpTable) != 1)
552 0 : return FALSE;
553 1 : int nSize = GetInt32(abyBuffer, 0);
554 1 : int nVersion = GetInt32(abyBuffer + 4, 0);
555 1 : if (nSize < 0 && nSize > -1024 * 1024 &&
556 0 : (nVersion == 3 || nVersion == 4) &&
557 0 : IS_VALID_LAYER_GEOM_TYPE(abyBuffer[8]) && abyBuffer[9] == 3 &&
558 0 : abyBuffer[10] == 0 && abyBuffer[11] == 0)
559 : {
560 0 : nOffset = 40 + (-nSize);
561 : }
562 : else
563 : {
564 1 : nOffset = 40;
565 : }
566 : }
567 :
568 38 : int64_t nInvalidRecords = 0;
569 : try
570 : {
571 3476 : while (nOffset < m_nFileSize)
572 : {
573 : GUInt32 nSize;
574 : int bDeletedRecord;
575 3438 : if (!IsLikelyFeatureAtOffset(nOffset, &nSize, &bDeletedRecord))
576 : {
577 2869 : nOffset++;
578 : }
579 : else
580 : {
581 : /*CPLDebug("OpenFileGDB", "Feature found at offset %d (size = %d)",
582 : nOffset, nSize);*/
583 569 : if (bDeletedRecord)
584 : {
585 9 : if (bReportDeletedFeatures)
586 : {
587 0 : m_bHasDeletedFeaturesListed = TRUE;
588 0 : m_anFeatureOffsets.push_back(MARK_DELETED(nOffset));
589 : }
590 : else
591 : {
592 9 : nInvalidRecords++;
593 9 : m_anFeatureOffsets.push_back(0);
594 : }
595 : }
596 : else
597 560 : m_anFeatureOffsets.push_back(nOffset);
598 569 : nOffset += nSize;
599 : }
600 : }
601 : }
602 0 : catch (const std::bad_alloc &)
603 : {
604 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
605 : "Out of memory in FileGDBTable::GuessFeatureLocations()");
606 0 : return false;
607 : }
608 38 : m_nTotalRecordCount = static_cast<int64_t>(m_anFeatureOffsets.size());
609 38 : if (m_nTotalRecordCount - nInvalidRecords > m_nValidRecordCount)
610 : {
611 0 : if (!m_bHasDeletedFeaturesListed)
612 : {
613 0 : CPLError(CE_Warning, CPLE_AppDefined,
614 : "More features found (%" PRId64
615 : ") than declared number of valid "
616 : "features ((%" PRId64 "). "
617 : "So deleted features will likely be reported.",
618 0 : m_nTotalRecordCount - nInvalidRecords,
619 : m_nValidRecordCount);
620 : }
621 0 : m_nValidRecordCount = m_nTotalRecordCount - nInvalidRecords;
622 : }
623 :
624 38 : return m_nTotalRecordCount > 0;
625 : }
626 :
627 : /************************************************************************/
628 : /* ReadTableXHeaderV3() */
629 : /************************************************************************/
630 :
631 5217 : bool FileGDBTable::ReadTableXHeaderV3()
632 : {
633 5217 : const bool errorRetValue = false;
634 : GByte abyHeader[16];
635 :
636 : // Read .gdbtablx file header
637 5217 : returnErrorIf(VSIFReadL(abyHeader, 16, 1, m_fpTableX) != 1);
638 :
639 5217 : const int nGDBTablxVersion = GetUInt32(abyHeader, 0);
640 5217 : if (nGDBTablxVersion != static_cast<int>(m_eGDBTableVersion))
641 : {
642 0 : CPLError(CE_Failure, CPLE_AppDefined,
643 : ".gdbtablx version is %d whereas it should be %d",
644 0 : nGDBTablxVersion, static_cast<int>(m_eGDBTableVersion));
645 0 : return false;
646 : }
647 :
648 5217 : m_n1024BlocksPresent = GetUInt32(abyHeader + 4, 0);
649 :
650 5217 : m_nTotalRecordCount = GetInt32(abyHeader + 8, 0);
651 5217 : if (m_n1024BlocksPresent == 0)
652 498 : returnErrorIf(m_nTotalRecordCount != 0);
653 : else
654 4719 : returnErrorIf(m_nTotalRecordCount < 0);
655 :
656 5216 : m_nTablxOffsetSize = GetUInt32(abyHeader + 12, 0);
657 5216 : returnErrorIf(m_nTablxOffsetSize < 4 || m_nTablxOffsetSize > 6);
658 :
659 5215 : m_nOffsetTableXTrailer =
660 5215 : 16 + m_nTablxOffsetSize * 1024 *
661 5215 : static_cast<vsi_l_offset>(m_n1024BlocksPresent);
662 5215 : if (m_n1024BlocksPresent != 0)
663 : {
664 : GByte abyTrailer[16];
665 :
666 4717 : VSIFSeekL(m_fpTableX, m_nOffsetTableXTrailer, SEEK_SET);
667 4722 : returnErrorIf(VSIFReadL(abyTrailer, 16, 1, m_fpTableX) != 1);
668 :
669 4716 : GUInt32 nBitmapInt32Words = GetUInt32(abyTrailer, 0);
670 :
671 4716 : GUInt32 nBitsForBlockMap = GetUInt32(abyTrailer + 4, 0);
672 4716 : returnErrorIf(nBitsForBlockMap > 1 + INT_MAX / 1024);
673 :
674 4715 : GUInt32 n1024BlocksBis = GetUInt32(abyTrailer + 8, 0);
675 4715 : returnErrorIf(n1024BlocksBis != m_n1024BlocksPresent);
676 :
677 : /* GUInt32 nLeadingNonZero32BitWords = GetUInt32(abyTrailer + 12, 0); */
678 :
679 4713 : if (nBitmapInt32Words == 0)
680 : {
681 4689 : returnErrorIf(nBitsForBlockMap != m_n1024BlocksPresent);
682 : /* returnErrorIf(nLeadingNonZero32BitWords != 0 ); */
683 : }
684 : else
685 : {
686 24 : returnErrorIf(static_cast<GUInt32>(m_nTotalRecordCount) >
687 : nBitsForBlockMap * 1024);
688 : #ifdef DEBUG_VERBOSE
689 : CPLDebug("OpenFileGDB", "%s .gdbtablx has block map array",
690 : m_osFilename.c_str());
691 : #endif
692 :
693 : // Allocate a bit mask array for blocks of 1024 features.
694 24 : uint32_t nSizeInBytes = BIT_ARRAY_SIZE_IN_BYTES(nBitsForBlockMap);
695 : try
696 : {
697 24 : m_abyTablXBlockMap.resize(nSizeInBytes);
698 : }
699 0 : catch (const std::exception &e)
700 : {
701 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
702 0 : "Cannot allocate m_abyTablXBlockMap: %s", e.what());
703 0 : return false;
704 : }
705 24 : returnErrorIf(VSIFReadL(m_abyTablXBlockMap.data(), nSizeInBytes, 1,
706 : m_fpTableX) != 1);
707 : /* returnErrorIf(nMagic2 == 0 ); */
708 :
709 : // Check that the map is consistent with m_n1024BlocksPresent
710 23 : GUInt32 nCountBlocks = 0;
711 4204160 : for (GUInt32 i = 0; i < nBitsForBlockMap; i++)
712 4204140 : nCountBlocks += TEST_BIT(m_abyTablXBlockMap.data(), i) != 0;
713 23 : returnErrorIf(nCountBlocks != m_n1024BlocksPresent);
714 : }
715 : }
716 5209 : return true;
717 : }
718 :
719 : /************************************************************************/
720 : /* ReadTableXHeaderV4() */
721 : /************************************************************************/
722 :
723 17 : bool FileGDBTable::ReadTableXHeaderV4()
724 : {
725 17 : const bool errorRetValue = false;
726 : GByte abyHeader[16];
727 :
728 : // Read .gdbtablx file header
729 17 : returnErrorIf(VSIFReadL(abyHeader, 16, 1, m_fpTableX) != 1);
730 :
731 17 : const int nGDBTablxVersion = GetUInt32(abyHeader, 0);
732 17 : if (nGDBTablxVersion != static_cast<int>(m_eGDBTableVersion))
733 : {
734 0 : CPLError(CE_Failure, CPLE_AppDefined,
735 : ".gdbtablx version is %d whereas it should be %d",
736 0 : nGDBTablxVersion, static_cast<int>(m_eGDBTableVersion));
737 0 : return false;
738 : }
739 :
740 17 : m_n1024BlocksPresent = GetUInt64(abyHeader + 4, 0);
741 :
742 17 : m_nTablxOffsetSize = GetUInt32(abyHeader + 12, 0);
743 17 : returnErrorIf(m_nTablxOffsetSize < 4 || m_nTablxOffsetSize > 6);
744 :
745 17 : returnErrorIf(m_n1024BlocksPresent >
746 : (std::numeric_limits<vsi_l_offset>::max() - 16) /
747 : (m_nTablxOffsetSize * 1024));
748 :
749 17 : m_nOffsetTableXTrailer =
750 17 : 16 + m_nTablxOffsetSize * 1024 *
751 17 : static_cast<vsi_l_offset>(m_n1024BlocksPresent);
752 17 : if (m_n1024BlocksPresent != 0)
753 : {
754 : GByte abyTrailer[12];
755 :
756 17 : VSIFSeekL(m_fpTableX, m_nOffsetTableXTrailer, SEEK_SET);
757 17 : returnErrorIf(VSIFReadL(abyTrailer, 12, 1, m_fpTableX) != 1);
758 :
759 17 : m_nTotalRecordCount = GetUInt64(abyTrailer, 0);
760 :
761 : // Cf https://github.com/rouault/dump_gdbtable/wiki/FGDB-Spec#trailing-section-16-bytes--variable-number-
762 : // for all below magic numbers and byte sequences
763 17 : GUInt32 nSizeBitmapSection = GetUInt32(abyTrailer + 8, 0);
764 17 : if (nSizeBitmapSection == 0)
765 : {
766 : // no bitmap. Fine
767 : }
768 14 : else if (nSizeBitmapSection == 22 + 32768 + 52 &&
769 12 : m_nTotalRecordCount <= 32768 * 1024 * 8)
770 : {
771 : try
772 : {
773 10 : std::vector<GByte> abyBitmapSection(nSizeBitmapSection);
774 10 : returnErrorIf(VSIFReadL(abyBitmapSection.data(),
775 : abyBitmapSection.size(), 1,
776 : m_fpTableX) != 1);
777 10 : if (memcmp(abyBitmapSection.data(), "\x01\x00\x01\x00\x00\x00",
778 20 : 6) == 0 &&
779 10 : memcmp(abyBitmapSection.data() + 22 + 32768,
780 : "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
781 : 12) == 0)
782 : {
783 : m_abyTablXBlockMap.insert(
784 0 : m_abyTablXBlockMap.end(), abyBitmapSection.data() + 22,
785 6 : abyBitmapSection.data() + 22 + 32768);
786 : }
787 : else
788 : {
789 4 : m_bReliableObjectID = false;
790 : }
791 : }
792 0 : catch (const std::exception &e)
793 : {
794 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
795 0 : "Cannot allocate m_abyTablXBlockMap: %s", e.what());
796 0 : return false;
797 10 : }
798 : }
799 : else
800 : {
801 4 : m_bReliableObjectID = false;
802 : }
803 17 : if (!m_bReliableObjectID)
804 : {
805 8 : m_nTotalRecordCount = 1024 * m_n1024BlocksPresent;
806 8 : CPLError(CE_Warning, CPLE_AppDefined,
807 : "Due to partial reverse engineering of the format, "
808 : "ObjectIDs will not be accurate and attribute and spatial "
809 : "indices cannot be used on %s",
810 : m_osFilenameWithLayerName.c_str());
811 : }
812 : }
813 17 : return true;
814 : }
815 :
816 : /************************************************************************/
817 : /* Open() */
818 : /************************************************************************/
819 :
820 5353 : bool FileGDBTable::Open(const char *pszFilename, bool bUpdate,
821 : const char *pszLayerName)
822 : {
823 5353 : const bool errorRetValue = false;
824 5353 : CPLAssert(m_fpTable == nullptr);
825 :
826 5353 : m_bUpdate = bUpdate;
827 :
828 5353 : m_osFilename = pszFilename;
829 5353 : m_osFilenameWithLayerName = m_osFilename;
830 5353 : if (pszLayerName)
831 855 : m_osFilenameWithLayerName += CPLSPrintf(" (layer %s)", pszLayerName);
832 :
833 5353 : m_fpTable = VSIFOpenL(pszFilename, m_bUpdate ? "r+b" : "rb");
834 5353 : if (m_fpTable == nullptr)
835 : {
836 2 : CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open %s: %s",
837 2 : m_osFilenameWithLayerName.c_str(), VSIStrerror(errno));
838 2 : return false;
839 : }
840 :
841 : // Read .gdbtable file header
842 : GByte abyHeader[40];
843 5351 : returnErrorIf(VSIFReadL(abyHeader, 40, 1, m_fpTable) != 1);
844 :
845 5351 : int nGDBTableVersion = GetInt32(abyHeader, 0);
846 5351 : if (nGDBTableVersion == 3)
847 : {
848 5333 : m_eGDBTableVersion = GDBTableVersion::V3;
849 : }
850 18 : else if (nGDBTableVersion == 4)
851 : {
852 18 : m_eGDBTableVersion = GDBTableVersion::V4;
853 18 : if (m_bUpdate)
854 : {
855 1 : CPLError(CE_Failure, CPLE_NotSupported,
856 : "Version 4 of the FileGeodatabase format is not supported "
857 : "for update.");
858 1 : return false;
859 : }
860 : }
861 : else
862 : {
863 0 : CPLError(CE_Failure, CPLE_NotSupported,
864 : "Version %u of the FileGeodatabase format is not supported.",
865 : nGDBTableVersion);
866 0 : return false;
867 : }
868 :
869 5350 : if (m_eGDBTableVersion == GDBTableVersion::V3)
870 : {
871 5333 : m_nValidRecordCount = GetInt32(abyHeader + 4, 0);
872 5333 : returnErrorIf(m_nValidRecordCount < 0);
873 : }
874 : else
875 : {
876 17 : m_nValidRecordCount = GetInt64(abyHeader + 16, 0);
877 17 : returnErrorIf(m_nValidRecordCount < 0);
878 : }
879 :
880 5349 : m_nHeaderBufferMaxSize = GetInt32(abyHeader + 8, 0);
881 :
882 10698 : std::string osTableXName;
883 8817 : if (m_bUpdate || (m_nValidRecordCount > 0 &&
884 3468 : !CPLTestBool(CPLGetConfigOption(
885 : "OPENFILEGDB_IGNORE_GDBTABLX", "false"))))
886 : {
887 15702 : osTableXName = CPLFormFilenameSafe(
888 10468 : CPLGetPathSafe(pszFilename).c_str(),
889 15702 : CPLGetBasenameSafe(pszFilename).c_str(), "gdbtablx");
890 5234 : m_fpTableX = VSIFOpenL(osTableXName.c_str(), m_bUpdate ? "r+b" : "rb");
891 5234 : if (m_fpTableX == nullptr)
892 : {
893 0 : if (m_bUpdate)
894 : {
895 0 : CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open %s: %s",
896 0 : osTableXName.c_str(), VSIStrerror(errno));
897 0 : return false;
898 : }
899 0 : const char *pszIgnoreGDBTablXAbsence = CPLGetConfigOption(
900 : "OPENFILEGDB_IGNORE_GDBTABLX_ABSENCE", nullptr);
901 0 : if (pszIgnoreGDBTablXAbsence == nullptr)
902 : {
903 0 : CPLError(
904 : CE_Warning, CPLE_AppDefined,
905 : "%s could not be found. "
906 : "Trying to guess feature locations, but this might fail or "
907 : "return incorrect results",
908 : osTableXName.c_str());
909 : }
910 0 : else if (!CPLTestBool(pszIgnoreGDBTablXAbsence))
911 : {
912 0 : returnErrorIf(m_fpTableX == nullptr);
913 : }
914 : }
915 10451 : else if (m_eGDBTableVersion == GDBTableVersion::V3 &&
916 5217 : !ReadTableXHeaderV3())
917 8 : return false;
918 5243 : else if (m_eGDBTableVersion == GDBTableVersion::V4 &&
919 17 : !ReadTableXHeaderV4())
920 0 : return false;
921 : }
922 :
923 5341 : if (m_fpTableX != nullptr)
924 : {
925 5226 : if (m_nValidRecordCount > m_nTotalRecordCount)
926 : {
927 6 : if (CPLTestBool(CPLGetConfigOption(
928 : "OPENFILEGDB_USE_GDBTABLE_RECORD_COUNT", "false")))
929 : {
930 : /* Potentially unsafe. See #5842 */
931 0 : CPLDebug("OpenFileGDB",
932 : "%s: nTotalRecordCount (was %" PRId64 ") forced to "
933 : "nValidRecordCount=%" PRId64,
934 : m_osFilenameWithLayerName.c_str(), m_nTotalRecordCount,
935 : m_nValidRecordCount);
936 0 : m_nTotalRecordCount = m_nValidRecordCount;
937 : }
938 : else
939 : {
940 : /* By default err on the safe side */
941 6 : CPLError(
942 : CE_Warning, CPLE_AppDefined,
943 : "File %s declares %" PRId64
944 : " valid records, but %s declares "
945 : "only %" PRId64
946 : " total records. Using that later value for safety "
947 : "(this possibly ignoring features). "
948 : "You can also try setting OPENFILEGDB_IGNORE_GDBTABLX=YES "
949 : "to "
950 : "completely ignore the .gdbtablx file (but possibly "
951 : "retrieving "
952 : "deleted features), or set "
953 : "OPENFILEGDB_USE_GDBTABLE_RECORD_COUNT=YES "
954 : "(but that setting can potentially cause crashes)",
955 : m_osFilenameWithLayerName.c_str(), m_nValidRecordCount,
956 : osTableXName.c_str(), m_nTotalRecordCount);
957 6 : m_nValidRecordCount = m_nTotalRecordCount;
958 : }
959 : }
960 :
961 : #ifdef DEBUG_VERBOSE
962 : else if (m_nTotalRecordCount != m_nValidRecordCount)
963 : {
964 : CPLDebug("OpenFileGDB",
965 : "%s: nTotalRecordCount=%" PRId64
966 : " nValidRecordCount=%" PRId64,
967 : pszFilename, m_nTotalRecordCount, m_nValidRecordCount);
968 : }
969 : #endif
970 : }
971 :
972 5341 : m_nOffsetFieldDesc = GetUInt64(abyHeader + 32, 0);
973 :
974 : #ifdef DEBUG_VERBOSE
975 : if (m_nOffsetFieldDesc != 40)
976 : {
977 : CPLDebug("OpenFileGDB", "%s: nOffsetFieldDesc=" CPL_FRMT_GUIB,
978 : pszFilename, m_nOffsetFieldDesc);
979 : }
980 : #endif
981 :
982 5341 : if (m_bUpdate)
983 : {
984 1804 : VSIFSeekL(m_fpTable, 0, SEEK_END);
985 1804 : m_nFileSize = VSIFTellL(m_fpTable);
986 : }
987 :
988 : // Skip to field description section
989 5341 : VSIFSeekL(m_fpTable, m_nOffsetFieldDesc, SEEK_SET);
990 5341 : returnErrorIf(VSIFReadL(abyHeader, 14, 1, m_fpTable) != 1);
991 5340 : m_nFieldDescLength = GetUInt32(abyHeader, 0);
992 :
993 5340 : const auto nSecondaryHeaderVersion = GetUInt32(abyHeader + 4, 0);
994 : // nSecondaryHeaderVersion == 6 is used in table arcgis_pro_32_types.gdb/a0000000b.gdbtable (big_int)
995 : // Not sure why...
996 5340 : if (m_bUpdate && nSecondaryHeaderVersion != 4 &&
997 : nSecondaryHeaderVersion != 6) // FileGDB v10
998 : {
999 0 : CPLError(CE_Failure, CPLE_NotSupported,
1000 : "Version %u of the secondary header of the FileGeodatabase "
1001 : "format is not supported for update.",
1002 : nSecondaryHeaderVersion);
1003 0 : return false;
1004 : }
1005 5340 : m_bIsV9 = (nSecondaryHeaderVersion == 3);
1006 :
1007 5340 : returnErrorIf(m_nOffsetFieldDesc >
1008 : std::numeric_limits<GUIntBig>::max() - m_nFieldDescLength);
1009 :
1010 5340 : returnErrorIf(m_nFieldDescLength > 10 * 1024 * 1024 ||
1011 : m_nFieldDescLength < 10);
1012 5338 : GByte byTableGeomType = abyHeader[8];
1013 5338 : if (IS_VALID_LAYER_GEOM_TYPE(byTableGeomType))
1014 5338 : m_eTableGeomType =
1015 5338 : static_cast<FileGDBTableGeometryType>(byTableGeomType);
1016 : else
1017 0 : CPLDebug("OpenFileGDB", "Unknown table geometry type: %d",
1018 : byTableGeomType);
1019 5338 : m_bStringsAreUTF8 = (abyHeader[9] & 0x1) != 0;
1020 5338 : const GByte byTableGeomTypeFlags = abyHeader[11];
1021 5338 : m_bGeomTypeHasM = (byTableGeomTypeFlags & (1 << 6)) != 0;
1022 5338 : m_bGeomTypeHasZ = (byTableGeomTypeFlags & (1 << 7)) != 0;
1023 :
1024 : GUInt16 iField, nFields;
1025 5338 : nFields = GetUInt16(abyHeader + 12, 0);
1026 :
1027 : /* No interest in guessing a trivial file */
1028 5338 : returnErrorIf(m_fpTableX == nullptr && nFields == 0);
1029 :
1030 5338 : GUInt32 nRemaining = m_nFieldDescLength - 10;
1031 5338 : m_nRowBufferMaxSize = nRemaining;
1032 : try
1033 : {
1034 5338 : m_abyBuffer.resize(m_nRowBufferMaxSize + ZEROES_AFTER_END_OF_BUFFER);
1035 : }
1036 0 : catch (const std::exception &e)
1037 : {
1038 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
1039 0 : returnError();
1040 : }
1041 5338 : returnErrorIf(VSIFReadL(m_abyBuffer.data(), nRemaining, 1, m_fpTable) != 1);
1042 :
1043 5337 : GByte *pabyIter = m_abyBuffer.data();
1044 63302 : for (iField = 0; iField < nFields; iField++)
1045 : {
1046 57989 : returnErrorIf(nRemaining < 1);
1047 57977 : GByte nCarCount = pabyIter[0];
1048 :
1049 57977 : pabyIter++;
1050 57977 : nRemaining--;
1051 57977 : returnErrorIf(nCarCount > nRemaining / 2);
1052 57974 : std::string osName(ReadUTF16String(pabyIter, nCarCount));
1053 57974 : pabyIter += 2 * nCarCount;
1054 57974 : nRemaining -= 2 * nCarCount;
1055 :
1056 57974 : returnErrorIf(nRemaining < 1);
1057 57974 : nCarCount = pabyIter[0];
1058 57974 : pabyIter++;
1059 57974 : nRemaining--;
1060 57974 : returnErrorIf(nCarCount > nRemaining / 2);
1061 57971 : std::string osAlias(ReadUTF16String(pabyIter, nCarCount));
1062 57971 : pabyIter += 2 * nCarCount;
1063 57971 : nRemaining -= 2 * nCarCount;
1064 :
1065 57971 : returnErrorIf(nRemaining < 1);
1066 57971 : GByte byFieldType = pabyIter[0];
1067 57971 : pabyIter++;
1068 57971 : nRemaining--;
1069 :
1070 57971 : if (byFieldType > FGFT_DATETIME_WITH_OFFSET)
1071 : {
1072 3 : CPLDebug("OpenFileGDB", "Unhandled field type : %d", byFieldType);
1073 3 : returnError();
1074 : }
1075 :
1076 57968 : FileGDBFieldType eType = static_cast<FileGDBFieldType>(byFieldType);
1077 57968 : if (eType != FGFT_GEOMETRY && eType != FGFT_RASTER)
1078 : {
1079 55274 : GByte flags = 0;
1080 55274 : int nMaxWidth = 0;
1081 55274 : GUInt32 defaultValueLength = 0;
1082 :
1083 55274 : switch (eType)
1084 : {
1085 15637 : case FGFT_STRING:
1086 : {
1087 15640 : returnErrorIf(nRemaining < 6);
1088 15637 : nMaxWidth = GetInt32(pabyIter, 0);
1089 15637 : returnErrorIf(nMaxWidth < 0);
1090 15636 : flags = pabyIter[4];
1091 15636 : pabyIter += 5;
1092 15636 : nRemaining -= 5;
1093 15636 : GByte *pabyIterBefore = pabyIter;
1094 15636 : returnErrorIf(!ReadVarUInt32(
1095 : pabyIter, pabyIter + nRemaining, defaultValueLength));
1096 15636 : nRemaining -=
1097 15636 : static_cast<GUInt32>(pabyIter - pabyIterBefore);
1098 15636 : break;
1099 : }
1100 :
1101 21544 : case FGFT_OBJECTID:
1102 : case FGFT_BINARY:
1103 : case FGFT_GUID:
1104 : case FGFT_GLOBALID:
1105 : case FGFT_XML:
1106 21544 : returnErrorIf(nRemaining < 2);
1107 21544 : flags = pabyIter[1];
1108 21544 : pabyIter += 2;
1109 21544 : nRemaining -= 2;
1110 21544 : break;
1111 :
1112 18093 : default:
1113 18093 : returnErrorIf(nRemaining < 3);
1114 18093 : flags = pabyIter[1];
1115 18093 : defaultValueLength = pabyIter[2];
1116 18093 : pabyIter += 3;
1117 18093 : nRemaining -= 3;
1118 18093 : break;
1119 : }
1120 :
1121 : OGRField sDefault;
1122 55273 : OGR_RawField_SetUnset(&sDefault);
1123 55273 : if (flags & FileGDBField::MASK_EDITABLE)
1124 : {
1125 : /* Default value */
1126 : /* Found on PreNIS.gdb/a0000000d.gdbtable */
1127 47308 : returnErrorIf(nRemaining < defaultValueLength);
1128 47306 : if (defaultValueLength)
1129 : {
1130 92 : if (eType == FGFT_STRING)
1131 : {
1132 14 : if (m_bStringsAreUTF8)
1133 : {
1134 12 : sDefault.String = static_cast<char *>(
1135 12 : CPLMalloc(defaultValueLength + 1));
1136 12 : memcpy(sDefault.String, pabyIter,
1137 : defaultValueLength);
1138 12 : sDefault.String[defaultValueLength] = 0;
1139 : }
1140 : else
1141 : {
1142 2 : m_osTempString = ReadUTF16String(
1143 2 : pabyIter, defaultValueLength / 2);
1144 2 : sDefault.String = CPLStrdup(m_osTempString.c_str());
1145 : }
1146 : }
1147 78 : else if (eType == FGFT_INT16 && defaultValueLength == 2)
1148 : {
1149 7 : sDefault.Integer = GetInt16(pabyIter, 0);
1150 7 : sDefault.Set.nMarker2 = 0;
1151 7 : sDefault.Set.nMarker3 = 0;
1152 : }
1153 71 : else if (eType == FGFT_INT32 && defaultValueLength == 4)
1154 : {
1155 18 : sDefault.Integer = GetInt32(pabyIter, 0);
1156 18 : sDefault.Set.nMarker2 = 0;
1157 18 : sDefault.Set.nMarker3 = 0;
1158 : }
1159 53 : else if (eType == FGFT_FLOAT32 && defaultValueLength == 4)
1160 : {
1161 7 : sDefault.Real = GetFloat32(pabyIter, 0);
1162 : }
1163 46 : else if (eType == FGFT_FLOAT64 && defaultValueLength == 8)
1164 : {
1165 11 : sDefault.Real = GetFloat64(pabyIter, 0);
1166 : }
1167 35 : else if ((eType == FGFT_DATETIME || eType == FGFT_DATE) &&
1168 18 : defaultValueLength == 8)
1169 : {
1170 18 : const double dfVal = GetFloat64(pabyIter, 0);
1171 18 : FileGDBDoubleDateToOGRDate(dfVal, true, &sDefault);
1172 : }
1173 17 : else if (eType == FGFT_TIME && defaultValueLength == 8)
1174 : {
1175 6 : const double dfVal = GetFloat64(pabyIter, 0);
1176 6 : FileGDBDoubleTimeToOGRTime(dfVal, &sDefault);
1177 : }
1178 11 : else if (eType == FGFT_INT64 && defaultValueLength == 8)
1179 : {
1180 4 : sDefault.Integer64 = GetInt64(pabyIter, 0);
1181 4 : sDefault.Set.nMarker3 = 0;
1182 : }
1183 7 : else if (eType == FGFT_DATETIME_WITH_OFFSET &&
1184 7 : defaultValueLength ==
1185 : sizeof(double) + sizeof(int16_t))
1186 : {
1187 7 : const double dfVal = GetFloat64(pabyIter, 0);
1188 : const int16_t nUTCOffset =
1189 7 : GetInt16(pabyIter + sizeof(double), 0);
1190 7 : FileGDBDateTimeWithOffsetToOGRDate(dfVal, nUTCOffset,
1191 : &sDefault);
1192 : }
1193 : }
1194 :
1195 47306 : pabyIter += defaultValueLength;
1196 47306 : nRemaining -= defaultValueLength;
1197 : }
1198 :
1199 55271 : if (eType == FGFT_OBJECTID)
1200 : {
1201 5312 : returnErrorIf(flags != FileGDBField::MASK_REQUIRED);
1202 5312 : returnErrorIf(m_iObjectIdField >= 0);
1203 5312 : m_iObjectIdField = static_cast<int>(m_apoFields.size());
1204 : }
1205 :
1206 110542 : auto poField = std::make_unique<FileGDBField>(this);
1207 55271 : poField->m_osName = std::move(osName);
1208 55271 : poField->m_osAlias = std::move(osAlias);
1209 55271 : poField->m_eType = eType;
1210 55271 : poField->m_bNullable = (flags & FileGDBField::MASK_NULLABLE) != 0;
1211 55271 : poField->m_bRequired = (flags & FileGDBField::MASK_REQUIRED) != 0;
1212 55271 : poField->m_bEditable = (flags & FileGDBField::MASK_EDITABLE) != 0;
1213 55271 : poField->m_nMaxWidth = nMaxWidth;
1214 55271 : poField->m_sDefault = sDefault;
1215 110542 : m_apoFields.emplace_back(std::move(poField));
1216 : }
1217 : else
1218 : {
1219 :
1220 2694 : FileGDBRasterField *poRasterField = nullptr;
1221 : FileGDBGeomField *poField;
1222 2694 : if (eType == FGFT_GEOMETRY)
1223 : {
1224 2691 : returnErrorIf(m_iGeomField >= 0);
1225 2691 : poField = new FileGDBGeomField(this);
1226 : }
1227 : else
1228 : {
1229 3 : poRasterField = new FileGDBRasterField(this);
1230 3 : poField = poRasterField;
1231 : }
1232 :
1233 2694 : poField->m_osName = std::move(osName);
1234 2694 : poField->m_osAlias = std::move(osAlias);
1235 2694 : poField->m_eType = eType;
1236 2694 : if (eType == FGFT_GEOMETRY)
1237 2691 : m_iGeomField = static_cast<int>(m_apoFields.size());
1238 : m_apoFields.emplace_back(
1239 2694 : std::unique_ptr<FileGDBGeomField>(poField));
1240 :
1241 2694 : returnErrorIf(nRemaining < 2);
1242 2694 : GByte flags = pabyIter[1];
1243 2694 : poField->m_bNullable = (flags & 1) != 0;
1244 2694 : pabyIter += 2;
1245 2694 : nRemaining -= 2;
1246 :
1247 2694 : if (eType == FGFT_RASTER)
1248 : {
1249 3 : returnErrorIf(nRemaining < 1);
1250 3 : nCarCount = pabyIter[0];
1251 3 : pabyIter++;
1252 3 : nRemaining--;
1253 3 : returnErrorIf(nRemaining <
1254 : static_cast<GUInt32>(2 * nCarCount + 1));
1255 : poRasterField->m_osRasterColumnName =
1256 3 : ReadUTF16String(pabyIter, nCarCount);
1257 3 : pabyIter += 2 * nCarCount;
1258 3 : nRemaining -= 2 * nCarCount;
1259 : }
1260 :
1261 2694 : returnErrorIf(nRemaining < 2);
1262 2694 : GUInt16 nLengthWKT = GetUInt16(pabyIter, 0);
1263 2694 : pabyIter += sizeof(nLengthWKT);
1264 2694 : nRemaining -= sizeof(nLengthWKT);
1265 :
1266 2694 : returnErrorIf(nRemaining < static_cast<GUInt32>(1 + nLengthWKT));
1267 2694 : poField->m_osWKT = ReadUTF16String(pabyIter, nLengthWKT / 2);
1268 2694 : pabyIter += nLengthWKT;
1269 2694 : nRemaining -= nLengthWKT;
1270 :
1271 2694 : GByte abyGeomFlags = pabyIter[0];
1272 2694 : pabyIter++;
1273 2694 : nRemaining--;
1274 2694 : poField->m_bHasMOriginScaleTolerance = (abyGeomFlags & 2) != 0;
1275 2694 : poField->m_bHasZOriginScaleTolerance = (abyGeomFlags & 4) != 0;
1276 :
1277 2694 : if (eType == FGFT_GEOMETRY || abyGeomFlags > 0)
1278 : {
1279 2693 : returnErrorIf(nRemaining <
1280 : static_cast<GUInt32>(
1281 : sizeof(double) *
1282 : (4 + ((eType == FGFT_GEOMETRY) ? 4 : 0) +
1283 : (poField->m_bHasMOriginScaleTolerance +
1284 : poField->m_bHasZOriginScaleTolerance) *
1285 : 3)));
1286 :
1287 : #define READ_DOUBLE(field) \
1288 : do \
1289 : { \
1290 : field = GetFloat64(pabyIter, 0); \
1291 : pabyIter += sizeof(double); \
1292 : nRemaining -= sizeof(double); \
1293 : } while (false)
1294 :
1295 2693 : READ_DOUBLE(poField->m_dfXOrigin);
1296 2693 : READ_DOUBLE(poField->m_dfYOrigin);
1297 2693 : READ_DOUBLE(poField->m_dfXYScale);
1298 2693 : returnErrorIf(poField->m_dfXYScale == 0);
1299 :
1300 2693 : if (poField->m_bHasMOriginScaleTolerance)
1301 : {
1302 2683 : READ_DOUBLE(poField->m_dfMOrigin);
1303 2683 : READ_DOUBLE(poField->m_dfMScale);
1304 : }
1305 :
1306 2693 : if (poField->m_bHasZOriginScaleTolerance)
1307 : {
1308 2690 : READ_DOUBLE(poField->m_dfZOrigin);
1309 2690 : READ_DOUBLE(poField->m_dfZScale);
1310 : }
1311 :
1312 2693 : READ_DOUBLE(poField->m_dfXYTolerance);
1313 :
1314 2693 : if (poField->m_bHasMOriginScaleTolerance)
1315 : {
1316 2683 : READ_DOUBLE(poField->m_dfMTolerance);
1317 : #ifdef DEBUG_VERBOSE
1318 : CPLDebug("OpenFileGDB",
1319 : "MOrigin = %g, MScale = %g, MTolerance = %g",
1320 : poField->m_dfMOrigin, poField->m_dfMScale,
1321 : poField->m_dfMTolerance);
1322 : #endif
1323 : }
1324 :
1325 2693 : if (poField->m_bHasZOriginScaleTolerance)
1326 : {
1327 2690 : READ_DOUBLE(poField->m_dfZTolerance);
1328 : }
1329 : }
1330 :
1331 2694 : if (eType == FGFT_RASTER)
1332 : {
1333 3 : returnErrorIf(nRemaining < 1);
1334 3 : if (*pabyIter == 0)
1335 0 : poRasterField->m_eRasterType =
1336 : FileGDBRasterField::Type::EXTERNAL;
1337 3 : else if (*pabyIter == 1)
1338 3 : poRasterField->m_eRasterType =
1339 : FileGDBRasterField::Type::MANAGED;
1340 0 : else if (*pabyIter == 2)
1341 0 : poRasterField->m_eRasterType =
1342 : FileGDBRasterField::Type::INLINE;
1343 : else
1344 : {
1345 0 : CPLError(CE_Warning, CPLE_NotSupported,
1346 0 : "Unknown raster field type %d", *pabyIter);
1347 : }
1348 3 : pabyIter += 1;
1349 3 : nRemaining -= 1;
1350 : }
1351 : else
1352 : {
1353 2691 : returnErrorIf(nRemaining < 4 * sizeof(double));
1354 2691 : m_nGeomFieldBBoxSubOffset =
1355 2691 : static_cast<uint32_t>(pabyIter - m_abyBuffer.data()) + 14;
1356 2691 : READ_DOUBLE(poField->m_dfXMin);
1357 2691 : READ_DOUBLE(poField->m_dfYMin);
1358 2691 : READ_DOUBLE(poField->m_dfXMax);
1359 2691 : READ_DOUBLE(poField->m_dfYMax);
1360 :
1361 : #ifdef PARANOID_CHECK
1362 : const auto nCurrentPos = VSIFTellL(m_fpTable);
1363 : VSIFSeekL(m_fpTable,
1364 : m_nOffsetFieldDesc + m_nGeomFieldBBoxSubOffset,
1365 : SEEK_SET);
1366 : double dfXMinFromFile;
1367 : VSIFReadL(&dfXMinFromFile, 1, sizeof(dfXMinFromFile),
1368 : m_fpTable);
1369 : fprintf(stderr, "%f %f\n", dfXMinFromFile, /*ok*/
1370 : poField->m_dfXMin);
1371 : double dfYMinFromFile;
1372 : VSIFReadL(&dfYMinFromFile, 1, sizeof(dfYMinFromFile),
1373 : m_fpTable);
1374 : fprintf(stderr, "%f %f\n", dfYMinFromFile, /*ok*/
1375 : poField->m_dfYMin);
1376 : double dfXMaxFromFile;
1377 : VSIFReadL(&dfXMaxFromFile, 1, sizeof(dfXMaxFromFile),
1378 : m_fpTable);
1379 : fprintf(stderr, "%f %f\n", dfXMaxFromFile, /*ok*/
1380 : poField->m_dfXMax);
1381 : double dfYMaxFromFile;
1382 : VSIFReadL(&dfYMaxFromFile, 1, sizeof(dfYMaxFromFile),
1383 : m_fpTable);
1384 : fprintf(stderr, "%f %f\n", dfYMaxFromFile, /*ok*/
1385 : poField->m_dfYMax);
1386 : VSIFSeekL(m_fpTable, nCurrentPos, SEEK_SET);
1387 : #endif
1388 2691 : if (m_bGeomTypeHasZ)
1389 : {
1390 204 : returnErrorIf(nRemaining < 2 * sizeof(double));
1391 204 : READ_DOUBLE(poField->m_dfZMin);
1392 204 : READ_DOUBLE(poField->m_dfZMax);
1393 : }
1394 :
1395 2691 : if (m_bGeomTypeHasM)
1396 : {
1397 85 : returnErrorIf(nRemaining < 2 * sizeof(double));
1398 85 : READ_DOUBLE(poField->m_dfMMin);
1399 85 : READ_DOUBLE(poField->m_dfMMax);
1400 : }
1401 :
1402 2691 : returnErrorIf(nRemaining < 5);
1403 : // Skip byte at zero
1404 2691 : pabyIter += 1;
1405 2691 : nRemaining -= 1;
1406 :
1407 2691 : GUInt32 nGridSizeCount = GetUInt32(pabyIter, 0);
1408 2691 : pabyIter += sizeof(nGridSizeCount);
1409 2691 : nRemaining -= sizeof(nGridSizeCount);
1410 2691 : returnErrorIf(nGridSizeCount == 0 || nGridSizeCount > 3);
1411 2691 : returnErrorIf(nRemaining < nGridSizeCount * sizeof(double));
1412 2691 : m_nGeomFieldSpatialIndexGridResSubOffset =
1413 2691 : static_cast<uint32_t>(pabyIter - m_abyBuffer.data()) + 14;
1414 9704 : for (GUInt32 i = 0; i < nGridSizeCount; i++)
1415 : {
1416 : double dfGridResolution;
1417 7013 : READ_DOUBLE(dfGridResolution);
1418 7013 : m_adfSpatialIndexGridResolution.push_back(dfGridResolution);
1419 : }
1420 : poField->m_adfSpatialIndexGridResolution =
1421 2691 : m_adfSpatialIndexGridResolution;
1422 : }
1423 : }
1424 :
1425 57965 : m_nCountNullableFields +=
1426 57965 : static_cast<int>(m_apoFields.back()->m_bNullable);
1427 : }
1428 5325 : m_nNullableFieldsSizeInBytes =
1429 5325 : BIT_ARRAY_SIZE_IN_BYTES(m_nCountNullableFields);
1430 :
1431 : #ifdef DEBUG_VERBOSE
1432 : if (nRemaining > 0)
1433 : {
1434 : CPLDebug("OpenFileGDB",
1435 : "%u remaining (ignored) bytes in field header section",
1436 : nRemaining);
1437 : }
1438 : #endif
1439 :
1440 5325 : if (m_nValidRecordCount > 0 && m_fpTableX == nullptr)
1441 38 : return GuessFeatureLocations();
1442 :
1443 5287 : return true;
1444 : }
1445 :
1446 : /************************************************************************/
1447 : /* SkipVarUInt() */
1448 : /************************************************************************/
1449 :
1450 : /* Bound check only valid if nIter <= 4 */
1451 7836 : static int SkipVarUInt(GByte *&pabyIter, GByte *pabyEnd, int nIter = 1)
1452 : {
1453 7836 : const int errorRetValue = FALSE;
1454 7836 : GByte *pabyLocalIter = pabyIter;
1455 7836 : returnErrorIf(pabyLocalIter /*+ nIter - 1*/ >= pabyEnd);
1456 35242 : while (nIter-- > 0)
1457 : {
1458 : while (true)
1459 : {
1460 142071 : GByte b = *pabyLocalIter;
1461 142071 : pabyLocalIter++;
1462 142071 : if ((b & 0x80) == 0)
1463 27406 : break;
1464 114665 : }
1465 : }
1466 7836 : pabyIter = pabyLocalIter;
1467 7836 : return TRUE;
1468 : }
1469 :
1470 : /************************************************************************/
1471 : /* ReadVarIntAndAddNoCheck() */
1472 : /************************************************************************/
1473 :
1474 : CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW
1475 115694 : static void ReadVarIntAndAddNoCheck(GByte *&pabyIter, GIntBig &nOutVal)
1476 : {
1477 : GUInt32 b;
1478 :
1479 115694 : b = *pabyIter;
1480 115694 : GUIntBig nVal = (b & 0x3F);
1481 115694 : bool bNegative = (b & 0x40) != 0;
1482 115694 : if ((b & 0x80) == 0)
1483 : {
1484 38317 : pabyIter++;
1485 38317 : if (bNegative)
1486 10 : nOutVal -= nVal;
1487 : else
1488 38307 : nOutVal += nVal;
1489 38317 : return;
1490 : }
1491 :
1492 77377 : GByte *pabyLocalIter = pabyIter + 1;
1493 77377 : int nShift = 6;
1494 : while (true)
1495 : {
1496 275873 : GUIntBig b64 = *pabyLocalIter;
1497 275873 : pabyLocalIter++;
1498 275873 : nVal |= (b64 & 0x7F) << nShift;
1499 275873 : if ((b64 & 0x80) == 0)
1500 : {
1501 77377 : pabyIter = pabyLocalIter;
1502 77377 : if (bNegative)
1503 26035 : nOutVal -= nVal;
1504 : else
1505 51342 : nOutVal += nVal;
1506 77377 : return;
1507 : }
1508 198496 : nShift += 7;
1509 : // To avoid undefined behavior later when doing << nShift
1510 198496 : if (nShift >= static_cast<int>(sizeof(GIntBig)) * 8)
1511 : {
1512 0 : pabyIter = pabyLocalIter;
1513 0 : nOutVal = nVal;
1514 0 : return;
1515 : }
1516 198496 : }
1517 : }
1518 :
1519 : /************************************************************************/
1520 : /* GetOffsetInTableForRow() */
1521 : /************************************************************************/
1522 :
1523 : vsi_l_offset
1524 1877540 : FileGDBTable::GetOffsetInTableForRow(int64_t iRow,
1525 : vsi_l_offset *pnOffsetInTableX)
1526 : {
1527 1877540 : const int errorRetValue = 0;
1528 1877540 : if (pnOffsetInTableX)
1529 2707 : *pnOffsetInTableX = 0;
1530 1877540 : returnErrorIf(iRow < 0 || iRow >= m_nTotalRecordCount);
1531 :
1532 1877540 : m_bIsDeleted = false;
1533 1877540 : if (m_fpTableX == nullptr)
1534 : {
1535 147 : m_bIsDeleted =
1536 147 : IS_DELETED(m_anFeatureOffsets[static_cast<size_t>(iRow)]);
1537 147 : return GET_OFFSET(m_anFeatureOffsets[static_cast<size_t>(iRow)]);
1538 : }
1539 :
1540 : vsi_l_offset nOffsetInTableX;
1541 1877390 : if (!m_abyTablXBlockMap.empty())
1542 : {
1543 1564230 : GUInt32 nCountBlocksBefore = 0;
1544 1564230 : const int iBlock = static_cast<int>(iRow / 1024);
1545 :
1546 : // Check if the block is not empty
1547 1564230 : if (TEST_BIT(m_abyTablXBlockMap.data(), iBlock) == 0)
1548 1479690 : return 0;
1549 :
1550 : // In case of sequential reading, optimization to avoid recomputing
1551 : // the number of blocks since the beginning of the map
1552 84538 : if (iBlock >= m_nCountBlocksBeforeIBlockIdx)
1553 : {
1554 84531 : nCountBlocksBefore = m_nCountBlocksBeforeIBlockValue;
1555 4291420 : for (int i = m_nCountBlocksBeforeIBlockIdx; i < iBlock; i++)
1556 4206890 : nCountBlocksBefore +=
1557 4206890 : TEST_BIT(m_abyTablXBlockMap.data(), i) != 0;
1558 : }
1559 : else
1560 : {
1561 7 : nCountBlocksBefore = 0;
1562 28 : for (int i = 0; i < iBlock; i++)
1563 21 : nCountBlocksBefore +=
1564 21 : TEST_BIT(m_abyTablXBlockMap.data(), i) != 0;
1565 : }
1566 84538 : m_nCountBlocksBeforeIBlockIdx = iBlock;
1567 84538 : m_nCountBlocksBeforeIBlockValue = nCountBlocksBefore;
1568 84538 : const int64_t iCorrectedRow =
1569 84538 : static_cast<int64_t>(nCountBlocksBefore) * 1024 + (iRow % 1024);
1570 84538 : nOffsetInTableX =
1571 84538 : 16 + static_cast<vsi_l_offset>(m_nTablxOffsetSize) * iCorrectedRow;
1572 : }
1573 : else
1574 : {
1575 313164 : nOffsetInTableX =
1576 313164 : 16 + static_cast<vsi_l_offset>(m_nTablxOffsetSize) * iRow;
1577 : }
1578 :
1579 397702 : if (pnOffsetInTableX)
1580 2707 : *pnOffsetInTableX = nOffsetInTableX;
1581 397702 : VSIFSeekL(m_fpTableX, nOffsetInTableX, SEEK_SET);
1582 :
1583 : GByte abyBuffer[6];
1584 397702 : m_bError = VSIFReadL(abyBuffer, m_nTablxOffsetSize, 1, m_fpTableX) != 1;
1585 397702 : returnErrorIf(m_bError);
1586 :
1587 397702 : const vsi_l_offset nOffset = ReadFeatureOffset(abyBuffer);
1588 :
1589 : #ifdef DEBUG_VERBOSE
1590 : const auto nOffsetHeaderEnd = m_nOffsetFieldDesc + m_nFieldDescLength;
1591 : if (iRow == 0 && nOffset != 0 && nOffset != nOffsetHeaderEnd &&
1592 : nOffset != nOffsetHeaderEnd + 4)
1593 : CPLDebug("OpenFileGDB",
1594 : "%s: first feature offset = " CPL_FRMT_GUIB
1595 : ". Expected " CPL_FRMT_GUIB,
1596 : m_osFilename.c_str(), nOffset, nOffsetHeaderEnd);
1597 : #endif
1598 :
1599 397702 : return nOffset;
1600 : }
1601 :
1602 : /************************************************************************/
1603 : /* ReadFeatureOffset() */
1604 : /************************************************************************/
1605 :
1606 447979 : uint64_t FileGDBTable::ReadFeatureOffset(const GByte *pabyBuffer)
1607 : {
1608 447979 : uint64_t nOffset = 0;
1609 447979 : memcpy(&nOffset, pabyBuffer, m_nTablxOffsetSize);
1610 447979 : CPL_LSBPTR64(&nOffset);
1611 447979 : return nOffset;
1612 : }
1613 :
1614 : /************************************************************************/
1615 : /* GetAndSelectNextNonEmptyRow() */
1616 : /************************************************************************/
1617 :
1618 34938 : int64_t FileGDBTable::GetAndSelectNextNonEmptyRow(int64_t iRow)
1619 : {
1620 34938 : const int64_t errorRetValue = -1;
1621 34938 : returnErrorAndCleanupIf(iRow < 0 || iRow >= m_nTotalRecordCount,
1622 : m_nCurRow = -1);
1623 :
1624 330775 : while (iRow < m_nTotalRecordCount)
1625 : {
1626 330696 : if (!m_abyTablXBlockMap.empty() && (iRow % 1024) == 0)
1627 : {
1628 138 : int iBlock = static_cast<int>(iRow / 1024);
1629 138 : if (TEST_BIT(m_abyTablXBlockMap.data(), iBlock) == 0)
1630 : {
1631 111 : int nBlocks =
1632 111 : static_cast<int>(DIV_ROUND_UP(m_nTotalRecordCount, 1024));
1633 4247210 : do
1634 : {
1635 4247320 : iBlock++;
1636 8494650 : } while (iBlock < nBlocks &&
1637 4247320 : TEST_BIT(m_abyTablXBlockMap.data(), iBlock) == 0);
1638 :
1639 111 : iRow = static_cast<int64_t>(iBlock) * 1024;
1640 111 : if (iRow >= m_nTotalRecordCount)
1641 0 : return -1;
1642 : }
1643 : }
1644 :
1645 330696 : if (SelectRow(iRow))
1646 34859 : return iRow;
1647 295837 : if (HasGotError())
1648 0 : return -1;
1649 295837 : iRow++;
1650 : }
1651 :
1652 79 : return -1;
1653 : }
1654 :
1655 : /************************************************************************/
1656 : /* SelectRow() */
1657 : /************************************************************************/
1658 :
1659 1876160 : bool FileGDBTable::SelectRow(int64_t iRow, bool bWarnOnlyOnDeletedRows)
1660 : {
1661 1876160 : const int errorRetValue = FALSE;
1662 1876160 : returnErrorAndCleanupIf(iRow < 0 || iRow >= m_nTotalRecordCount,
1663 : m_nCurRow = -1);
1664 :
1665 1876160 : if (m_nCurRow != iRow)
1666 : {
1667 1874730 : vsi_l_offset nOffsetTable = GetOffsetInTableForRow(iRow);
1668 1874730 : if (nOffsetTable == 0)
1669 : {
1670 1792690 : m_nCurRow = -1;
1671 1792690 : return FALSE;
1672 : }
1673 :
1674 82044 : VSIFSeekL(m_fpTable, nOffsetTable, SEEK_SET);
1675 : GByte abyBuffer[4];
1676 82044 : returnErrorAndCleanupIf(VSIFReadL(abyBuffer, 4, 1, m_fpTable) != 1,
1677 : m_nCurRow = -1);
1678 :
1679 82044 : m_nRowBlobLength = GetUInt32(abyBuffer, 0);
1680 82044 : if (m_bIsDeleted)
1681 : {
1682 0 : m_nRowBlobLength =
1683 0 : static_cast<GUInt32>(-static_cast<int>(m_nRowBlobLength));
1684 : }
1685 :
1686 82044 : if (m_nRowBlobLength > 0)
1687 : {
1688 : #ifdef DEBUG_VERBOSE
1689 : CPLDebug("OpenFileGDB",
1690 : "%s: iRow = %" PRId64 ", m_nRowBlobLength = %u",
1691 : m_osFilename.c_str(), iRow, m_nRowBlobLength);
1692 : #endif
1693 81888 : if ((m_nRowBlobLength >> 31) != 0)
1694 : {
1695 0 : CPLError(
1696 : bWarnOnlyOnDeletedRows ? CE_Warning : CE_Failure,
1697 : CPLE_AppDefined,
1698 : "Feature %" PRId64
1699 : " of %s appears to be deleted, but index is out of sync",
1700 : iRow + 1, m_osFilename.c_str());
1701 0 : m_nCurRow = -1;
1702 0 : return false;
1703 : }
1704 81888 : returnErrorAndCleanupIf(
1705 : m_nRowBlobLength <
1706 : static_cast<GUInt32>(m_nNullableFieldsSizeInBytes) ||
1707 : m_nRowBlobLength > INT_MAX - ZEROES_AFTER_END_OF_BUFFER,
1708 : m_nCurRow = -1);
1709 :
1710 81888 : if (m_nRowBlobLength > m_nHeaderBufferMaxSize)
1711 : {
1712 10 : if (CPLTestBool(CPLGetConfigOption(
1713 : "OGR_OPENFILEGDB_ERROR_ON_INCONSISTENT_BUFFER_MAX_SIZE",
1714 : "NO")))
1715 : {
1716 0 : CPLError(CE_Failure, CPLE_AppDefined,
1717 : "Invalid row length (%u) on feature %" PRId64
1718 : " compared "
1719 : "to the maximum size in the header (%u)",
1720 : m_nRowBlobLength, iRow + 1,
1721 : m_nHeaderBufferMaxSize);
1722 0 : m_nCurRow = -1;
1723 0 : return errorRetValue;
1724 : }
1725 : else
1726 : {
1727 : // Versions of the driver before commit
1728 : // fdf39012788b1110b3bf0ae6b8422a528f0ae8b6 didn't
1729 : // properly update the m_nHeaderBufferMaxSize field
1730 : // when updating an existing feature when the new version
1731 : // takes more space than the previous version.
1732 : // OpenFileGDB doesn't care but Esri software (FileGDB SDK
1733 : // or ArcMap/ArcGis) do, leading to issues such as
1734 : // https://github.com/qgis/QGIS/issues/57536
1735 :
1736 10 : CPLDebug("OpenFileGDB",
1737 : "Invalid row length (%u) on feature %" PRId64
1738 : " compared "
1739 : "to the maximum size in the header (%u)",
1740 : m_nRowBlobLength, iRow + 1,
1741 : m_nHeaderBufferMaxSize);
1742 :
1743 10 : if (m_bUpdate)
1744 : {
1745 3 : if (!m_bHasWarnedAboutHeaderRepair)
1746 : {
1747 1 : m_bHasWarnedAboutHeaderRepair = true;
1748 1 : CPLError(CE_Warning, CPLE_AppDefined,
1749 : "A corruption in the header of %s has "
1750 : "been detected. It is going to be "
1751 : "repaired to be properly read by other "
1752 : "software.",
1753 : m_osFilename.c_str());
1754 :
1755 1 : m_bDirtyHeader = true;
1756 :
1757 : // Invalidate existing indices, as the corrupted
1758 : // m_nHeaderBufferMaxSize value may have cause
1759 : // Esri software to generate corrupted indices.
1760 1 : m_bDirtyIndices = true;
1761 :
1762 : // Compute file size
1763 1 : VSIFSeekL(m_fpTable, 0, SEEK_END);
1764 1 : m_nFileSize = VSIFTellL(m_fpTable);
1765 1 : VSIFSeekL(m_fpTable, nOffsetTable + 4, SEEK_SET);
1766 : }
1767 : }
1768 : else
1769 : {
1770 7 : if (!m_bHasWarnedAboutHeaderRepair)
1771 : {
1772 5 : m_bHasWarnedAboutHeaderRepair = true;
1773 5 : CPLError(CE_Warning, CPLE_AppDefined,
1774 : "A corruption in the header of %s has "
1775 : "been detected. It would need to be "
1776 : "repaired to be properly read by other "
1777 : "software, either by using ogr2ogr to "
1778 : "generate a new dataset, or by opening "
1779 : "this dataset in update mode and reading "
1780 : "all its records.",
1781 : m_osFilename.c_str());
1782 : }
1783 : }
1784 :
1785 10 : m_nHeaderBufferMaxSize = m_nRowBlobLength;
1786 : }
1787 : }
1788 :
1789 81888 : if (m_nRowBlobLength > m_nRowBufferMaxSize)
1790 : {
1791 : /* For suspicious row blob length, check if we don't go beyond
1792 : * file size */
1793 1511 : if (m_nRowBlobLength > 100 * 1024 * 1024)
1794 : {
1795 1 : if (m_nFileSize == 0)
1796 : {
1797 1 : VSIFSeekL(m_fpTable, 0, SEEK_END);
1798 1 : m_nFileSize = VSIFTellL(m_fpTable);
1799 1 : VSIFSeekL(m_fpTable, nOffsetTable + 4, SEEK_SET);
1800 : }
1801 1 : if (nOffsetTable + 4 + m_nRowBlobLength > m_nFileSize)
1802 : {
1803 1 : CPLError(CE_Failure, CPLE_AppDefined,
1804 : "Invalid row length (%u) on feature %" PRId64,
1805 : m_nRowBlobLength, iRow + 1);
1806 1 : m_nCurRow = -1;
1807 1 : return errorRetValue;
1808 : }
1809 : }
1810 1510 : m_nRowBufferMaxSize = m_nRowBlobLength;
1811 : }
1812 :
1813 81887 : if (m_abyBuffer.size() <
1814 81887 : m_nRowBlobLength + ZEROES_AFTER_END_OF_BUFFER)
1815 : {
1816 : try
1817 : {
1818 3890 : m_abyBuffer.resize(m_nRowBlobLength +
1819 : ZEROES_AFTER_END_OF_BUFFER);
1820 : }
1821 0 : catch (const std::exception &e)
1822 : {
1823 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
1824 0 : returnErrorAndCleanupIf(true, m_nCurRow = -1);
1825 : }
1826 : }
1827 :
1828 81887 : returnErrorAndCleanupIf(VSIFReadL(m_abyBuffer.data(),
1829 : m_nRowBlobLength, 1,
1830 : m_fpTable) != 1,
1831 : m_nCurRow = -1);
1832 : /* Protection for 4 ReadVarUInt64NoCheck */
1833 81884 : CPL_STATIC_ASSERT(ZEROES_AFTER_END_OF_BUFFER == 4);
1834 81884 : m_abyBuffer[m_nRowBlobLength] = 0;
1835 81884 : m_abyBuffer[m_nRowBlobLength + 1] = 0;
1836 81884 : m_abyBuffer[m_nRowBlobLength + 2] = 0;
1837 81884 : m_abyBuffer[m_nRowBlobLength + 3] = 0;
1838 : }
1839 :
1840 82040 : m_nCurRow = iRow;
1841 82040 : m_nLastCol = -1;
1842 82040 : m_pabyIterVals = m_abyBuffer.data() + m_nNullableFieldsSizeInBytes;
1843 82040 : m_iAccNullable = 0;
1844 82040 : m_bError = FALSE;
1845 82040 : m_nChSaved = -1;
1846 : }
1847 :
1848 83465 : return TRUE;
1849 : }
1850 :
1851 : /************************************************************************/
1852 : /* FileGDBDoubleDateToOGRDate() */
1853 : /************************************************************************/
1854 :
1855 7747 : int FileGDBDoubleDateToOGRDate(double dfVal, bool bHighPrecision,
1856 : OGRField *psField)
1857 : {
1858 : // 25569: Number of days between 1899/12/30 00:00:00 and 1970/01/01 00:00:00
1859 7747 : double dfSeconds = (dfVal - 25569.0) * 3600.0 * 24.0;
1860 7747 : if (std::isnan(dfSeconds) ||
1861 : dfSeconds <
1862 15494 : static_cast<double>(std::numeric_limits<GIntBig>::min()) + 1000 ||
1863 : dfSeconds >
1864 7747 : static_cast<double>(std::numeric_limits<GIntBig>::max()) - 1000)
1865 : {
1866 0 : CPLError(CE_Failure, CPLE_NotSupported,
1867 : "FileGDBDoubleDateToOGRDate: Invalid days: %lf", dfVal);
1868 0 : dfSeconds = 0.0;
1869 : }
1870 7747 : if (!bHighPrecision)
1871 7693 : dfSeconds = std::floor(dfSeconds + 0.5);
1872 54 : else if (fmod(dfSeconds, 1.0) > 1 - 1e-4)
1873 18 : dfSeconds = std::floor(dfSeconds + 0.5);
1874 :
1875 : struct tm brokendowntime;
1876 7747 : CPLUnixTimeToYMDHMS(static_cast<GIntBig>(dfSeconds), &brokendowntime);
1877 :
1878 7747 : psField->Date.Year = static_cast<GInt16>(brokendowntime.tm_year + 1900);
1879 7747 : psField->Date.Month = static_cast<GByte>(brokendowntime.tm_mon + 1);
1880 7747 : psField->Date.Day = static_cast<GByte>(brokendowntime.tm_mday);
1881 7747 : psField->Date.Hour = static_cast<GByte>(brokendowntime.tm_hour);
1882 7747 : psField->Date.Minute = static_cast<GByte>(brokendowntime.tm_min);
1883 7747 : double dfSec = brokendowntime.tm_sec;
1884 7747 : if (bHighPrecision)
1885 : {
1886 54 : dfSec += fmod(dfSeconds, 1.0);
1887 : }
1888 7747 : psField->Date.Second = static_cast<float>(dfSec);
1889 7747 : psField->Date.TZFlag = 0;
1890 7747 : psField->Date.Reserved = 0;
1891 :
1892 7747 : return TRUE;
1893 : }
1894 :
1895 : /************************************************************************/
1896 : /* FileGDBDoubleTimeToOGRTime() */
1897 : /************************************************************************/
1898 :
1899 28 : int FileGDBDoubleTimeToOGRTime(double dfVal, OGRField *psField)
1900 : {
1901 28 : double dfSeconds = dfVal * 3600.0 * 24.0;
1902 28 : if (std::isnan(dfSeconds) || dfSeconds < 0 || dfSeconds > 86400)
1903 : {
1904 0 : CPLError(CE_Failure, CPLE_NotSupported,
1905 : "FileGDBDoubleTimeToOGRTime: Invalid time: %lf", dfVal);
1906 0 : dfSeconds = 0.0;
1907 : }
1908 :
1909 28 : psField->Date.Year = 0;
1910 28 : psField->Date.Month = 0;
1911 28 : psField->Date.Day = 0;
1912 28 : psField->Date.Hour = static_cast<GByte>(dfSeconds / 3600);
1913 28 : psField->Date.Minute =
1914 28 : static_cast<GByte>((static_cast<int>(dfSeconds) % 3600) / 60);
1915 28 : psField->Date.Second = static_cast<float>(fmod(dfSeconds, 60));
1916 28 : psField->Date.TZFlag = 0;
1917 28 : psField->Date.Reserved = 0;
1918 :
1919 28 : return TRUE;
1920 : }
1921 :
1922 : /************************************************************************/
1923 : /* FileGDBDateTimeWithOffsetToOGRDate() */
1924 : /************************************************************************/
1925 :
1926 31 : int FileGDBDateTimeWithOffsetToOGRDate(double dfVal, int16_t nUTCOffset,
1927 : OGRField *psField)
1928 : {
1929 31 : int ret = FileGDBDoubleDateToOGRDate(dfVal, true, psField);
1930 31 : if (nUTCOffset >= -14 * 60 && nUTCOffset <= 14 * 60)
1931 : {
1932 31 : psField->Date.TZFlag = static_cast<GByte>(100 + nUTCOffset / 15);
1933 : }
1934 : else
1935 0 : ret = FALSE;
1936 31 : return ret;
1937 : }
1938 :
1939 : /************************************************************************/
1940 : /* GetAllFieldValues() */
1941 : /************************************************************************/
1942 :
1943 69 : std::vector<OGRField> FileGDBTable::GetAllFieldValues()
1944 : {
1945 : std::vector<OGRField> asFields(m_apoFields.size(),
1946 69 : FileGDBField::UNSET_FIELD);
1947 869 : for (int i = 0; i < static_cast<int>(m_apoFields.size()); ++i)
1948 : {
1949 800 : const OGRField *psField = GetFieldValue(i);
1950 490 : if (psField && !OGR_RawField_IsNull(psField) &&
1951 2047 : !OGR_RawField_IsUnset(psField) &&
1952 757 : (m_apoFields[i]->GetType() == FGFT_STRING ||
1953 491 : m_apoFields[i]->GetType() == FGFT_XML ||
1954 409 : m_apoFields[i]->GetType() == FGFT_GLOBALID ||
1955 185 : m_apoFields[i]->GetType() == FGFT_GUID))
1956 : {
1957 344 : asFields[i].String = CPLStrdup(psField->String);
1958 : }
1959 146 : else if (psField && !OGR_RawField_IsNull(psField) &&
1960 877 : !OGR_RawField_IsUnset(psField) &&
1961 275 : (m_apoFields[i]->GetType() == FGFT_BINARY ||
1962 129 : m_apoFields[i]->GetType() == FGFT_GEOMETRY))
1963 : {
1964 34 : asFields[i].Binary.paData =
1965 17 : static_cast<GByte *>(CPLMalloc(psField->Binary.nCount));
1966 17 : asFields[i].Binary.nCount = psField->Binary.nCount;
1967 17 : memcpy(asFields[i].Binary.paData, psField->Binary.paData,
1968 17 : asFields[i].Binary.nCount);
1969 : }
1970 439 : else if (psField && !(m_apoFields[i]->GetType() == FGFT_RASTER))
1971 : {
1972 129 : asFields[i] = *psField;
1973 : }
1974 : }
1975 69 : return asFields;
1976 : }
1977 :
1978 : /************************************************************************/
1979 : /* FreeAllFieldValues() */
1980 : /************************************************************************/
1981 :
1982 69 : void FileGDBTable::FreeAllFieldValues(std::vector<OGRField> &asFields)
1983 : {
1984 869 : for (int i = 0; i < static_cast<int>(m_apoFields.size()); ++i)
1985 : {
1986 800 : if (!OGR_RawField_IsNull(&asFields[i]) &&
1987 1557 : !OGR_RawField_IsUnset(&asFields[i]) &&
1988 757 : (m_apoFields[i]->GetType() == FGFT_STRING ||
1989 491 : m_apoFields[i]->GetType() == FGFT_XML ||
1990 409 : m_apoFields[i]->GetType() == FGFT_GLOBALID ||
1991 185 : m_apoFields[i]->GetType() == FGFT_GUID))
1992 : {
1993 344 : CPLFree(asFields[i].String);
1994 344 : asFields[i].String = nullptr;
1995 : }
1996 456 : else if (!OGR_RawField_IsNull(&asFields[i]) &&
1997 731 : !OGR_RawField_IsUnset(&asFields[i]) &&
1998 275 : (m_apoFields[i]->GetType() == FGFT_BINARY ||
1999 129 : m_apoFields[i]->GetType() == FGFT_GEOMETRY))
2000 : {
2001 17 : CPLFree(asFields[i].Binary.paData);
2002 17 : asFields[i].Binary.paData = nullptr;
2003 : }
2004 : }
2005 69 : }
2006 :
2007 : /************************************************************************/
2008 : /* GetFieldValue() */
2009 : /************************************************************************/
2010 :
2011 230646 : const OGRField *FileGDBTable::GetFieldValue(int iCol)
2012 : {
2013 230646 : OGRField *errorRetValue = nullptr;
2014 :
2015 230646 : returnErrorIf(m_nCurRow < 0);
2016 230646 : returnErrorIf(static_cast<GUInt32>(iCol) >= m_apoFields.size());
2017 230646 : returnErrorIf(m_bError);
2018 :
2019 230646 : GByte *pabyEnd = m_abyBuffer.data() + m_nRowBlobLength;
2020 :
2021 : /* In case a string was previously read */
2022 230646 : if (m_nChSaved >= 0)
2023 : {
2024 57201 : *m_pabyIterVals = static_cast<GByte>(m_nChSaved);
2025 57201 : m_nChSaved = -1;
2026 : }
2027 :
2028 230646 : if (iCol <= m_nLastCol)
2029 : {
2030 10355 : m_nLastCol = -1;
2031 10355 : m_pabyIterVals = m_abyBuffer.data() + m_nNullableFieldsSizeInBytes;
2032 10355 : m_iAccNullable = 0;
2033 : }
2034 :
2035 : // Skip previous fields
2036 460791 : for (int j = m_nLastCol + 1; j < iCol; j++)
2037 : {
2038 230145 : if (m_apoFields[j]->m_bNullable)
2039 : {
2040 126886 : int bIsNull = TEST_BIT(m_abyBuffer.data(), m_iAccNullable);
2041 126886 : m_iAccNullable++;
2042 126886 : if (bIsNull)
2043 44084 : continue;
2044 : }
2045 :
2046 186061 : GUInt32 nLength = 0;
2047 186061 : CPL_IGNORE_RET_VAL(nLength);
2048 186061 : switch (m_apoFields[j]->m_eType)
2049 : {
2050 0 : case FGFT_UNDEFINED:
2051 0 : CPLAssert(false);
2052 : break;
2053 :
2054 63554 : case FGFT_OBJECTID:
2055 63554 : break;
2056 :
2057 69017 : case FGFT_STRING:
2058 : case FGFT_XML:
2059 : case FGFT_GEOMETRY:
2060 : case FGFT_BINARY:
2061 : {
2062 69017 : if (!ReadVarUInt32(m_pabyIterVals, pabyEnd, nLength))
2063 : {
2064 0 : m_bError = TRUE;
2065 0 : returnError();
2066 : }
2067 69017 : break;
2068 : }
2069 :
2070 0 : case FGFT_RASTER:
2071 : {
2072 : const FileGDBRasterField *rasterField =
2073 0 : cpl::down_cast<const FileGDBRasterField *>(
2074 0 : m_apoFields[j].get());
2075 0 : if (rasterField->GetRasterType() ==
2076 : FileGDBRasterField::Type::MANAGED)
2077 0 : nLength = sizeof(GInt32);
2078 : else
2079 : {
2080 0 : if (!ReadVarUInt32(m_pabyIterVals, pabyEnd, nLength))
2081 : {
2082 0 : m_bError = TRUE;
2083 0 : returnError();
2084 : }
2085 : }
2086 0 : break;
2087 : }
2088 :
2089 12 : case FGFT_INT16:
2090 12 : nLength = sizeof(GInt16);
2091 12 : break;
2092 14182 : case FGFT_INT32:
2093 14182 : nLength = sizeof(GInt32);
2094 14182 : break;
2095 8 : case FGFT_FLOAT32:
2096 8 : nLength = sizeof(float);
2097 8 : break;
2098 211 : case FGFT_FLOAT64:
2099 211 : nLength = sizeof(double);
2100 211 : break;
2101 27 : case FGFT_DATETIME:
2102 : case FGFT_DATE:
2103 : case FGFT_TIME:
2104 27 : nLength = sizeof(double);
2105 27 : break;
2106 39041 : case FGFT_GUID:
2107 : case FGFT_GLOBALID:
2108 39041 : nLength = UUID_SIZE_IN_BYTES;
2109 39041 : break;
2110 0 : case FGFT_INT64:
2111 0 : nLength = sizeof(int64_t);
2112 0 : break;
2113 9 : case FGFT_DATETIME_WITH_OFFSET:
2114 9 : nLength += sizeof(double) + sizeof(int16_t);
2115 9 : break;
2116 : }
2117 :
2118 186061 : if (nLength > static_cast<GUInt32>(pabyEnd - m_pabyIterVals))
2119 : {
2120 0 : m_bError = TRUE;
2121 0 : returnError();
2122 : }
2123 186061 : m_pabyIterVals += nLength;
2124 : }
2125 :
2126 230646 : m_nLastCol = iCol;
2127 :
2128 230646 : if (m_apoFields[iCol]->m_bNullable)
2129 : {
2130 203169 : int bIsNull = TEST_BIT(m_abyBuffer.data(), m_iAccNullable);
2131 203169 : m_iAccNullable++;
2132 203169 : if (bIsNull)
2133 : {
2134 27166 : return nullptr;
2135 : }
2136 : }
2137 :
2138 203480 : switch (m_apoFields[iCol]->m_eType)
2139 : {
2140 0 : case FGFT_UNDEFINED:
2141 0 : CPLAssert(false);
2142 : break;
2143 :
2144 69 : case FGFT_OBJECTID:
2145 69 : return nullptr;
2146 :
2147 64114 : case FGFT_STRING:
2148 : case FGFT_XML:
2149 : {
2150 : GUInt32 nLength;
2151 64114 : if (!ReadVarUInt32(m_pabyIterVals, pabyEnd, nLength))
2152 : {
2153 0 : m_bError = TRUE;
2154 68 : returnError();
2155 : }
2156 64114 : if (nLength > static_cast<GUInt32>(pabyEnd - m_pabyIterVals))
2157 : {
2158 68 : m_bError = TRUE;
2159 68 : returnError();
2160 : }
2161 :
2162 64046 : if (m_bStringsAreUTF8 || m_apoFields[iCol]->m_eType != FGFT_STRING)
2163 : {
2164 : /* eCurFieldType = OFTString; */
2165 64044 : m_sCurField.String = reinterpret_cast<char *>(m_pabyIterVals);
2166 64044 : m_pabyIterVals += nLength;
2167 :
2168 : /* This is a trick to avoid a alloc()+copy(). We null-terminate
2169 : */
2170 : /* after the string, and save the pointer and value to restore
2171 : */
2172 64044 : m_nChSaved = *m_pabyIterVals;
2173 64044 : *m_pabyIterVals = '\0';
2174 : }
2175 : else
2176 : {
2177 2 : m_osTempString = ReadUTF16String(m_pabyIterVals, nLength / 2);
2178 2 : m_sCurField.String = &m_osTempString[0];
2179 2 : m_pabyIterVals += nLength;
2180 : }
2181 :
2182 : /* CPLDebug("OpenFileGDB", "Field %d, row %d: %s", iCol, nCurRow,
2183 : * sCurField.String); */
2184 :
2185 64046 : break;
2186 : }
2187 :
2188 7655 : case FGFT_INT16:
2189 : {
2190 7655 : if (m_pabyIterVals + sizeof(GInt16) > pabyEnd)
2191 : {
2192 0 : m_bError = TRUE;
2193 0 : returnError();
2194 : }
2195 :
2196 : /* eCurFieldType = OFTInteger; */
2197 7655 : m_sCurField.Integer = GetInt16(m_pabyIterVals, 0);
2198 :
2199 7655 : m_pabyIterVals += sizeof(GInt16);
2200 : /* CPLDebug("OpenFileGDB", "Field %d, row %d: %d", iCol, nCurRow,
2201 : * sCurField.Integer); */
2202 :
2203 7655 : break;
2204 : }
2205 :
2206 19764 : case FGFT_INT32:
2207 : {
2208 19764 : if (m_pabyIterVals + sizeof(GInt32) > pabyEnd)
2209 : {
2210 0 : m_bError = TRUE;
2211 0 : returnError();
2212 : }
2213 :
2214 : /* eCurFieldType = OFTInteger; */
2215 19764 : m_sCurField.Integer = GetInt32(m_pabyIterVals, 0);
2216 :
2217 19764 : m_pabyIterVals += sizeof(GInt32);
2218 : /* CPLDebug("OpenFileGDB", "Field %d, row %d: %d", iCol, nCurRow,
2219 : * sCurField.Integer); */
2220 :
2221 19764 : break;
2222 : }
2223 :
2224 7655 : case FGFT_FLOAT32:
2225 : {
2226 7655 : if (m_pabyIterVals + sizeof(float) > pabyEnd)
2227 : {
2228 0 : m_bError = TRUE;
2229 0 : returnError();
2230 : }
2231 :
2232 : /* eCurFieldType = OFTReal; */
2233 7655 : m_sCurField.Real = GetFloat32(m_pabyIterVals, 0);
2234 :
2235 7655 : m_pabyIterVals += sizeof(float);
2236 : /* CPLDebug("OpenFileGDB", "Field %d, row %d: %f", iCol, nCurRow,
2237 : * sCurField.Real); */
2238 :
2239 7655 : break;
2240 : }
2241 :
2242 27664 : case FGFT_FLOAT64:
2243 : {
2244 27664 : if (m_pabyIterVals + sizeof(double) > pabyEnd)
2245 : {
2246 0 : m_bError = TRUE;
2247 0 : returnError();
2248 : }
2249 :
2250 : /* eCurFieldType = OFTReal; */
2251 27664 : m_sCurField.Real = GetFloat64(m_pabyIterVals, 0);
2252 :
2253 27664 : m_pabyIterVals += sizeof(double);
2254 : /* CPLDebug("OpenFileGDB", "Field %d, row %d: %f", iCol, nCurRow,
2255 : * sCurField.Real); */
2256 :
2257 27664 : break;
2258 : }
2259 :
2260 7695 : case FGFT_DATETIME:
2261 : case FGFT_DATE:
2262 : {
2263 7695 : if (m_pabyIterVals + sizeof(double) > pabyEnd)
2264 : {
2265 0 : m_bError = TRUE;
2266 0 : returnError();
2267 : }
2268 :
2269 : /* Number of days since 1899/12/30 00:00:00 */
2270 7695 : const double dfVal = GetFloat64(m_pabyIterVals, 0);
2271 :
2272 7695 : if (m_apoFields[iCol]->m_bReadAsDouble)
2273 : {
2274 5 : m_sCurField.Real = dfVal;
2275 : }
2276 : else
2277 : {
2278 7690 : FileGDBDoubleDateToOGRDate(
2279 7690 : dfVal, m_apoFields[iCol]->IsHighPrecision(), &m_sCurField);
2280 : /* eCurFieldType = OFTDateTime; */
2281 : }
2282 :
2283 7695 : m_pabyIterVals += sizeof(double);
2284 :
2285 7695 : break;
2286 : }
2287 :
2288 51003 : case FGFT_GEOMETRY:
2289 : case FGFT_BINARY:
2290 : {
2291 : GUInt32 nLength;
2292 51003 : if (!ReadVarUInt32(m_pabyIterVals, pabyEnd, nLength))
2293 : {
2294 0 : m_bError = TRUE;
2295 0 : returnError();
2296 : }
2297 51003 : if (nLength > static_cast<GUInt32>(pabyEnd - m_pabyIterVals))
2298 : {
2299 0 : m_bError = TRUE;
2300 0 : returnError();
2301 : }
2302 :
2303 : /* eCurFieldType = OFTBinary; */
2304 51003 : m_sCurField.Binary.nCount = nLength;
2305 51003 : m_sCurField.Binary.paData = const_cast<GByte *>(m_pabyIterVals);
2306 :
2307 51003 : m_pabyIterVals += nLength;
2308 :
2309 : /* Null terminate binary in case it is used as a string */
2310 51003 : m_nChSaved = *m_pabyIterVals;
2311 51003 : *m_pabyIterVals = '\0';
2312 :
2313 : /* CPLDebug("OpenFileGDB", "Field %d, row %d: %d bytes", iCol,
2314 : * nCurRow, snLength); */
2315 :
2316 51003 : break;
2317 : }
2318 :
2319 0 : case FGFT_RASTER:
2320 : {
2321 : const FileGDBRasterField *rasterField =
2322 0 : cpl::down_cast<const FileGDBRasterField *>(
2323 0 : m_apoFields[iCol].get());
2324 0 : if (rasterField->GetRasterType() ==
2325 : FileGDBRasterField::Type::MANAGED)
2326 : {
2327 0 : if (m_pabyIterVals + sizeof(GInt32) > pabyEnd)
2328 : {
2329 0 : m_bError = TRUE;
2330 0 : returnError();
2331 : }
2332 :
2333 0 : const GInt32 nVal = GetInt32(m_pabyIterVals, 0);
2334 :
2335 : /* eCurFieldType = OFTIntger; */
2336 0 : m_sCurField.Integer = nVal;
2337 :
2338 0 : m_pabyIterVals += sizeof(GInt32);
2339 : }
2340 : else
2341 : {
2342 : GUInt32 nLength;
2343 0 : if (!ReadVarUInt32(m_pabyIterVals, pabyEnd, nLength))
2344 : {
2345 0 : m_bError = TRUE;
2346 0 : returnError();
2347 : }
2348 0 : if (nLength > static_cast<GUInt32>(pabyEnd - m_pabyIterVals))
2349 : {
2350 0 : m_bError = TRUE;
2351 0 : returnError();
2352 : }
2353 :
2354 0 : if (rasterField->GetRasterType() ==
2355 : FileGDBRasterField::Type::EXTERNAL)
2356 : {
2357 : // coverity[tainted_data,tainted_data_argument]
2358 : m_osCacheRasterFieldPath =
2359 0 : ReadUTF16String(m_pabyIterVals, nLength / 2);
2360 0 : m_sCurField.String = &m_osCacheRasterFieldPath[0];
2361 0 : m_pabyIterVals += nLength;
2362 : }
2363 : else
2364 : {
2365 : /* eCurFieldType = OFTBinary; */
2366 0 : m_sCurField.Binary.nCount = nLength;
2367 0 : m_sCurField.Binary.paData =
2368 0 : const_cast<GByte *>(m_pabyIterVals);
2369 :
2370 0 : m_pabyIterVals += nLength;
2371 :
2372 : /* Null terminate binary in case it is used as a string */
2373 0 : m_nChSaved = *m_pabyIterVals;
2374 0 : *m_pabyIterVals = '\0';
2375 : }
2376 : }
2377 0 : break;
2378 : }
2379 :
2380 17802 : case FGFT_GUID:
2381 : case FGFT_GLOBALID:
2382 : {
2383 17802 : if (m_pabyIterVals + UUID_SIZE_IN_BYTES > pabyEnd)
2384 : {
2385 0 : m_bError = TRUE;
2386 0 : returnError();
2387 : }
2388 :
2389 : /* eCurFieldType = OFTString; */
2390 17802 : m_sCurField.String = m_achGUIDBuffer;
2391 : /*78563412BC9AF0DE1234567890ABCDEF -->
2392 : * {12345678-9ABC-DEF0-1234-567890ABCDEF} */
2393 17802 : snprintf(m_achGUIDBuffer, sizeof(m_achGUIDBuffer),
2394 : "{%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%"
2395 : "02X%02X%02X%02X}",
2396 17802 : m_pabyIterVals[3], m_pabyIterVals[2], m_pabyIterVals[1],
2397 17802 : m_pabyIterVals[0], m_pabyIterVals[5], m_pabyIterVals[4],
2398 17802 : m_pabyIterVals[7], m_pabyIterVals[6], m_pabyIterVals[8],
2399 17802 : m_pabyIterVals[9], m_pabyIterVals[10], m_pabyIterVals[11],
2400 17802 : m_pabyIterVals[12], m_pabyIterVals[13], m_pabyIterVals[14],
2401 17802 : m_pabyIterVals[15]);
2402 :
2403 17802 : m_pabyIterVals += UUID_SIZE_IN_BYTES;
2404 : /* CPLDebug("OpenFileGDB", "Field %d, row %d: %s", iCol, nCurRow,
2405 : * sCurField.String); */
2406 :
2407 17802 : break;
2408 : }
2409 :
2410 9 : case FGFT_INT64:
2411 : {
2412 9 : if (m_pabyIterVals + sizeof(int64_t) > pabyEnd)
2413 : {
2414 0 : m_bError = TRUE;
2415 0 : returnError();
2416 : }
2417 :
2418 : /* eCurFieldType = OFTInteger; */
2419 9 : m_sCurField.Integer64 = GetInt64(m_pabyIterVals, 0);
2420 :
2421 9 : m_pabyIterVals += sizeof(int64_t);
2422 : /* CPLDebug("OpenFileGDB", "Field %d, row %d: " CPL_FRMT_GIB, iCol, nCurRow,
2423 : * sCurField.Integer64); */
2424 :
2425 9 : break;
2426 : }
2427 :
2428 23 : case FGFT_TIME:
2429 : {
2430 23 : if (m_pabyIterVals + sizeof(double) > pabyEnd)
2431 : {
2432 0 : m_bError = TRUE;
2433 0 : returnError();
2434 : }
2435 :
2436 : /* Fraction of day */
2437 23 : const double dfVal = GetFloat64(m_pabyIterVals, 0);
2438 :
2439 23 : if (m_apoFields[iCol]->m_bReadAsDouble)
2440 : {
2441 3 : m_sCurField.Real = dfVal;
2442 : }
2443 : else
2444 : {
2445 20 : FileGDBDoubleTimeToOGRTime(dfVal, &m_sCurField);
2446 : /* eCurFieldType = OFTTime; */
2447 : }
2448 :
2449 23 : m_pabyIterVals += sizeof(double);
2450 :
2451 23 : break;
2452 : }
2453 :
2454 27 : case FGFT_DATETIME_WITH_OFFSET:
2455 : {
2456 27 : if (m_pabyIterVals + sizeof(double) + sizeof(int16_t) > pabyEnd)
2457 : {
2458 0 : m_bError = TRUE;
2459 0 : returnError();
2460 : }
2461 :
2462 : /* Number of days since 1899/12/30 00:00:00 */
2463 27 : const double dfVal = GetFloat64(m_pabyIterVals, 0);
2464 27 : m_pabyIterVals += sizeof(double);
2465 27 : const int16_t nUTCOffset = GetInt16(m_pabyIterVals, 0);
2466 27 : m_pabyIterVals += sizeof(int16_t);
2467 :
2468 27 : if (m_apoFields[iCol]->m_bReadAsDouble)
2469 : {
2470 3 : m_sCurField.Real = dfVal - nUTCOffset * 60.0 / 86400.0;
2471 : }
2472 : else
2473 : {
2474 24 : FileGDBDateTimeWithOffsetToOGRDate(dfVal, nUTCOffset,
2475 : &m_sCurField);
2476 : /* eCurFieldType = OFTDateTime; */
2477 : }
2478 :
2479 27 : break;
2480 : }
2481 : }
2482 :
2483 226055 : if (iCol == static_cast<int>(m_apoFields.size()) - 1 &&
2484 22712 : m_pabyIterVals < pabyEnd)
2485 : {
2486 1 : CPLDebug("OpenFileGDB", "%d bytes remaining at end of record %" PRId64,
2487 1 : static_cast<int>(pabyEnd - m_pabyIterVals), m_nCurRow);
2488 : }
2489 :
2490 203343 : return &m_sCurField;
2491 : }
2492 :
2493 : /************************************************************************/
2494 : /* GetIndexCount() */
2495 : /************************************************************************/
2496 :
2497 5708 : int FileGDBTable::GetIndexCount()
2498 : {
2499 5708 : const int errorRetValue = 0;
2500 5708 : if (m_bHasReadGDBIndexes)
2501 3910 : return static_cast<int>(m_apoIndexes.size());
2502 :
2503 1798 : m_bHasReadGDBIndexes = TRUE;
2504 :
2505 : const std::string osIndexesName = CPLFormFilenameSafe(
2506 3596 : CPLGetPathSafe(m_osFilename.c_str()).c_str(),
2507 5394 : CPLGetBasenameSafe(m_osFilename.c_str()).c_str(), "gdbindexes");
2508 1798 : VSILFILE *fpIndexes = VSIFOpenL(osIndexesName.c_str(), "rb");
2509 : VSIStatBufL sStat;
2510 1798 : if (fpIndexes == nullptr)
2511 : {
2512 1560 : if (VSIStatExL(osIndexesName.c_str(), &sStat, VSI_STAT_EXISTS_FLAG) ==
2513 : 0)
2514 0 : returnError();
2515 : else
2516 1560 : return 0;
2517 : }
2518 :
2519 238 : VSIFSeekL(fpIndexes, 0, SEEK_END);
2520 238 : vsi_l_offset nFileSize = VSIFTellL(fpIndexes);
2521 238 : returnErrorAndCleanupIf(nFileSize > 1024 * 1024, VSIFCloseL(fpIndexes));
2522 :
2523 : GByte *pabyIdx = static_cast<GByte *>(
2524 238 : VSI_MALLOC_VERBOSE(static_cast<size_t>(nFileSize)));
2525 238 : returnErrorAndCleanupIf(pabyIdx == nullptr, VSIFCloseL(fpIndexes));
2526 :
2527 238 : VSIFSeekL(fpIndexes, 0, SEEK_SET);
2528 : int nRead = static_cast<int>(
2529 238 : VSIFReadL(pabyIdx, static_cast<size_t>(nFileSize), 1, fpIndexes));
2530 238 : VSIFCloseL(fpIndexes);
2531 238 : returnErrorAndCleanupIf(nRead != 1, VSIFree(pabyIdx));
2532 :
2533 238 : GByte *pabyCur = pabyIdx;
2534 238 : GByte *pabyEnd = pabyIdx + nFileSize;
2535 238 : returnErrorAndCleanupIf(pabyEnd - pabyCur < 4, VSIFree(pabyIdx));
2536 238 : GUInt32 nIndexCount = GetUInt32(pabyCur, 0);
2537 238 : pabyCur += 4;
2538 :
2539 : // FileGDB v9 indexes structure not handled yet. Start with 13 98 85 03
2540 238 : if (nIndexCount == 0x03859813)
2541 : {
2542 44 : VSIFree(pabyIdx);
2543 :
2544 : // Hard code detection of blk_key_index on raster layers
2545 44 : const int iBlockKeyFieldIdx = GetFieldIdx("block_key");
2546 44 : if (iBlockKeyFieldIdx >= 0)
2547 : {
2548 : const std::string osAtxFilename = CPLResetExtensionSafe(
2549 2 : m_osFilename.c_str(), "blk_key_index.atx");
2550 2 : if (VSIStatExL(osAtxFilename.c_str(), &sStat,
2551 2 : VSI_STAT_EXISTS_FLAG) == 0)
2552 : {
2553 2 : auto poIndex = std::make_unique<FileGDBIndex>();
2554 2 : poIndex->m_osIndexName = "blk_key_index";
2555 2 : poIndex->m_osExpression = "block_key";
2556 2 : m_apoFields[iBlockKeyFieldIdx]->m_poIndex = poIndex.get();
2557 2 : m_apoIndexes.push_back(std::move(poIndex));
2558 2 : return 1;
2559 : }
2560 : }
2561 :
2562 42 : CPLDebug("OpenFileGDB", ".gdbindexes v9 not handled yet");
2563 42 : return 0;
2564 : }
2565 :
2566 194 : returnErrorAndCleanupIf(nIndexCount >=
2567 : static_cast<size_t>(GetFieldCount() + 1) * 10,
2568 : VSIFree(pabyIdx));
2569 :
2570 : GUInt32 i;
2571 866 : for (i = 0; i < nIndexCount; i++)
2572 : {
2573 713 : returnErrorAndCleanupIf(static_cast<size_t>(pabyEnd - pabyCur) <
2574 : sizeof(GUInt32),
2575 : VSIFree(pabyIdx));
2576 693 : const GUInt32 nIdxNameCharCount = GetUInt32(pabyCur, 0);
2577 693 : pabyCur += sizeof(GUInt32);
2578 693 : returnErrorAndCleanupIf(nIdxNameCharCount > 1024, VSIFree(pabyIdx));
2579 688 : returnErrorAndCleanupIf(static_cast<size_t>(pabyEnd - pabyCur) <
2580 : 2 * nIdxNameCharCount,
2581 : VSIFree(pabyIdx));
2582 683 : std::string osIndexName(ReadUTF16String(pabyCur, nIdxNameCharCount));
2583 683 : pabyCur += 2 * nIdxNameCharCount;
2584 :
2585 : // 4 "magic fields"
2586 683 : returnErrorAndCleanupIf(static_cast<size_t>(pabyEnd - pabyCur) <
2587 : sizeof(GUInt16) + sizeof(GUInt32) +
2588 : sizeof(GUInt16) + sizeof(GUInt32),
2589 : VSIFree(pabyIdx));
2590 : // const GUInt16 nMagic1 = GetUInt16(pabyCur, 0);
2591 683 : const GUInt32 nMagic2 = GetUInt32(pabyCur + sizeof(GUInt16), 0);
2592 : const GUInt16 nMagic3 =
2593 683 : GetUInt16(pabyCur + sizeof(GUInt16) + sizeof(GUInt32), 0);
2594 683 : if (!((nMagic2 == 2 && nMagic3 == 0) ||
2595 94 : (nMagic2 == 4 && nMagic3 == 0) ||
2596 191 : (nMagic2 == 16 && nMagic3 == 65535)))
2597 : {
2598 : // Cf files a00000029.gdbindexes, a000000ea.gdbindexes, a000000ed.gdbindexes,
2599 : // a000000f8.gdbindexes, a000000fb.gdbindexes, a00000103.gdbindexes
2600 : // from https://github.com/OSGeo/gdal/issues/11295#issuecomment-2491158506
2601 3 : CPLDebug("OpenFileGDB", "Reading %s", osIndexesName.c_str());
2602 3 : CPLDebug(
2603 : "OpenFileGDB",
2604 : "Strange (deleted?) index descriptor at index %u of name %s", i,
2605 : osIndexName.c_str());
2606 :
2607 : // Skip magic fields
2608 3 : pabyCur += sizeof(GUInt16);
2609 :
2610 3 : const GUInt32 nColNameCharCount = nMagic2;
2611 3 : pabyCur += sizeof(GUInt32);
2612 3 : returnErrorAndCleanupIf(nColNameCharCount > 1024, VSIFree(pabyIdx));
2613 3 : returnErrorAndCleanupIf(static_cast<size_t>(pabyEnd - pabyCur) <
2614 : 2 * nColNameCharCount,
2615 : VSIFree(pabyIdx));
2616 3 : pabyCur += 2 * nColNameCharCount;
2617 :
2618 : // Skip magic field
2619 3 : returnErrorAndCleanupIf(static_cast<size_t>(pabyEnd - pabyCur) <
2620 : sizeof(GUInt16),
2621 : VSIFree(pabyIdx));
2622 3 : pabyCur += sizeof(GUInt16);
2623 :
2624 3 : continue;
2625 : }
2626 :
2627 : // Skip magic fields
2628 680 : pabyCur += sizeof(GUInt16) + sizeof(GUInt32) + sizeof(GUInt16) +
2629 : sizeof(GUInt32);
2630 :
2631 680 : returnErrorAndCleanupIf(static_cast<size_t>(pabyEnd - pabyCur) <
2632 : sizeof(GUInt32),
2633 : VSIFree(pabyIdx));
2634 680 : const GUInt32 nColNameCharCount = GetUInt32(pabyCur, 0);
2635 680 : pabyCur += sizeof(GUInt32);
2636 680 : returnErrorAndCleanupIf(nColNameCharCount > 1024, VSIFree(pabyIdx));
2637 675 : returnErrorAndCleanupIf(static_cast<size_t>(pabyEnd - pabyCur) <
2638 : 2 * nColNameCharCount,
2639 : VSIFree(pabyIdx));
2640 : const std::string osExpression(
2641 670 : ReadUTF16String(pabyCur, nColNameCharCount));
2642 670 : pabyCur += 2 * nColNameCharCount;
2643 :
2644 : // Skip magic field
2645 670 : returnErrorAndCleanupIf(static_cast<size_t>(pabyEnd - pabyCur) <
2646 : sizeof(GUInt16),
2647 : VSIFree(pabyIdx));
2648 670 : pabyCur += sizeof(GUInt16);
2649 :
2650 1340 : auto poIndex = std::make_unique<FileGDBIndex>();
2651 670 : poIndex->m_osIndexName = std::move(osIndexName);
2652 670 : poIndex->m_osExpression = osExpression;
2653 :
2654 1340 : if (m_iObjectIdField < 0 ||
2655 670 : osExpression != m_apoFields[m_iObjectIdField]->GetName())
2656 : {
2657 962 : const auto osFieldName = poIndex->GetFieldName();
2658 481 : int nFieldIdx = GetFieldIdx(osFieldName);
2659 481 : if (nFieldIdx < 0)
2660 : {
2661 2 : CPLDebug("OpenFileGDB",
2662 : "Index defined for field %s that does not exist",
2663 : osFieldName.c_str());
2664 : }
2665 : else
2666 : {
2667 479 : if (m_apoFields[nFieldIdx]->m_poIndex != nullptr)
2668 : {
2669 0 : CPLDebug("OpenFileGDB",
2670 : "There is already one index defined for field %s",
2671 : osFieldName.c_str());
2672 : }
2673 : else
2674 : {
2675 479 : m_apoFields[nFieldIdx]->m_poIndex = poIndex.get();
2676 : }
2677 : }
2678 : }
2679 :
2680 670 : m_apoIndexes.push_back(std::move(poIndex));
2681 : }
2682 :
2683 173 : VSIFree(pabyIdx);
2684 :
2685 173 : return static_cast<int>(m_apoIndexes.size());
2686 : }
2687 :
2688 : /************************************************************************/
2689 : /* HasSpatialIndex() */
2690 : /************************************************************************/
2691 :
2692 905 : bool FileGDBTable::HasSpatialIndex()
2693 : {
2694 905 : if (m_nHasSpatialIndex < 0)
2695 : {
2696 : const std::string osSpxName = CPLFormFilenameSafe(
2697 1012 : CPLGetPathSafe(m_osFilename.c_str()).c_str(),
2698 1012 : CPLGetBasenameSafe(m_osFilename.c_str()).c_str(), "spx");
2699 : VSIStatBufL sStat;
2700 506 : m_nHasSpatialIndex =
2701 506 : (VSIStatExL(osSpxName.c_str(), &sStat, VSI_STAT_EXISTS_FLAG) == 0);
2702 : }
2703 905 : return m_nHasSpatialIndex != FALSE;
2704 : }
2705 :
2706 : /************************************************************************/
2707 : /* InstallFilterEnvelope() */
2708 : /************************************************************************/
2709 :
2710 : #define MAX_GUINTBIG std::numeric_limits<GUIntBig>::max()
2711 :
2712 3068 : void FileGDBTable::InstallFilterEnvelope(const OGREnvelope *psFilterEnvelope)
2713 : {
2714 3068 : if (psFilterEnvelope != nullptr)
2715 : {
2716 1470 : CPLAssert(m_iGeomField >= 0);
2717 : FileGDBGeomField *poGeomField =
2718 1470 : cpl::down_cast<FileGDBGeomField *>(GetField(m_iGeomField));
2719 :
2720 : /* We store the bounding box as unscaled coordinates, so that BBOX */
2721 : /* intersection is done with integer comparisons */
2722 1470 : if (psFilterEnvelope->MinX >= poGeomField->m_dfXOrigin)
2723 1470 : m_nFilterXMin = static_cast<GUIntBig>(
2724 1470 : 0.5 + (psFilterEnvelope->MinX - poGeomField->m_dfXOrigin) *
2725 1470 : poGeomField->m_dfXYScale);
2726 : else
2727 0 : m_nFilterXMin = 0;
2728 2940 : if (psFilterEnvelope->MaxX - poGeomField->m_dfXOrigin <
2729 1470 : static_cast<double>(MAX_GUINTBIG) / poGeomField->m_dfXYScale)
2730 1470 : m_nFilterXMax = static_cast<GUIntBig>(
2731 1470 : 0.5 + (psFilterEnvelope->MaxX - poGeomField->m_dfXOrigin) *
2732 1470 : poGeomField->m_dfXYScale);
2733 : else
2734 0 : m_nFilterXMax = MAX_GUINTBIG;
2735 1470 : if (psFilterEnvelope->MinY >= poGeomField->m_dfYOrigin)
2736 1470 : m_nFilterYMin = static_cast<GUIntBig>(
2737 1470 : 0.5 + (psFilterEnvelope->MinY - poGeomField->m_dfYOrigin) *
2738 1470 : poGeomField->m_dfXYScale);
2739 : else
2740 0 : m_nFilterYMin = 0;
2741 2940 : if (psFilterEnvelope->MaxY - poGeomField->m_dfYOrigin <
2742 1470 : static_cast<double>(MAX_GUINTBIG) / poGeomField->m_dfXYScale)
2743 1470 : m_nFilterYMax = static_cast<GUIntBig>(
2744 1470 : 0.5 + (psFilterEnvelope->MaxY - poGeomField->m_dfYOrigin) *
2745 1470 : poGeomField->m_dfXYScale);
2746 : else
2747 0 : m_nFilterYMax = MAX_GUINTBIG;
2748 : }
2749 : else
2750 : {
2751 1598 : m_nFilterXMin = 0;
2752 1598 : m_nFilterXMax = 0;
2753 1598 : m_nFilterYMin = 0;
2754 1598 : m_nFilterYMax = 0;
2755 : }
2756 3068 : }
2757 :
2758 : /************************************************************************/
2759 : /* GetMinMaxProjYForSpatialIndex() */
2760 : /************************************************************************/
2761 :
2762 : // ESRI software seems to have an extremely weird behavior regarding spatial
2763 : // indexing of geometries.
2764 : // When a projected CRS is associated with a layer, the northing of geometries
2765 : // is clamped, using the returned (dfYMin,dfYMax) values of this method.
2766 : // When creating the .spx file, if the maximum Y of a geometry is > dfYMax, then
2767 : // the geometry must be shifted along the Y axis so that its maximum value is
2768 : // dfYMax
2769 489 : void FileGDBTable::GetMinMaxProjYForSpatialIndex(double &dfYMin,
2770 : double &dfYMax) const
2771 : {
2772 489 : dfYMin = -std::numeric_limits<double>::max();
2773 489 : dfYMax = std::numeric_limits<double>::max();
2774 489 : const auto poGeomField = GetGeomField();
2775 489 : if (poGeomField == nullptr)
2776 478 : return;
2777 489 : const auto &osWKT = poGeomField->GetWKT();
2778 489 : OGRSpatialReference oSRS;
2779 661 : if (osWKT.empty() || osWKT[0] == '{' ||
2780 172 : oSRS.importFromWkt(osWKT.c_str()) != OGRERR_NONE)
2781 317 : return;
2782 172 : if (!oSRS.IsProjected())
2783 161 : return;
2784 11 : const char *pszProjection = oSRS.GetAttrValue("PROJECTION");
2785 11 : if (pszProjection == nullptr)
2786 0 : return;
2787 : double dfMinLat;
2788 : double dfMaxLat;
2789 :
2790 : // Determined through experimentation, e.g with the `find_srs_latitude_limits.py` script.
2791 11 : if (EQUAL(pszProjection, SRS_PT_TRANSVERSE_MERCATOR))
2792 : {
2793 11 : dfMinLat = -90;
2794 11 : dfMaxLat = 90;
2795 : }
2796 0 : else if (EQUAL(pszProjection, SRS_PT_MERCATOR_2SP) ||
2797 0 : EQUAL(pszProjection, SRS_PT_MERCATOR_1SP))
2798 : {
2799 0 : dfMinLat = -89.9;
2800 0 : dfMaxLat = 89.9;
2801 : }
2802 : else
2803 : {
2804 : // TODO? add other projection methods
2805 0 : return;
2806 : }
2807 :
2808 : auto poSRSLongLat =
2809 11 : std::unique_ptr<OGRSpatialReference>(oSRS.CloneGeogCS());
2810 : auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
2811 11 : OGRCreateCoordinateTransformation(poSRSLongLat.get(), &oSRS));
2812 11 : if (!poCT)
2813 0 : return;
2814 : {
2815 11 : double x = 0;
2816 11 : double y = dfMinLat;
2817 11 : if (poCT->Transform(1, &x, &y))
2818 11 : dfYMin = y;
2819 : }
2820 : {
2821 11 : double x = 0;
2822 11 : double y = dfMaxLat;
2823 11 : if (poCT->Transform(1, &x, &y))
2824 11 : dfYMax = y;
2825 : }
2826 : }
2827 :
2828 : /************************************************************************/
2829 : /* GetFeatureExtent() */
2830 : /************************************************************************/
2831 :
2832 326 : int FileGDBTable::GetFeatureExtent(const OGRField *psField,
2833 : OGREnvelope *psOutFeatureEnvelope)
2834 : {
2835 326 : const int errorRetValue = FALSE;
2836 326 : GByte *pabyCur = psField->Binary.paData;
2837 326 : GByte *pabyEnd = pabyCur + psField->Binary.nCount;
2838 : GUInt32 nGeomType;
2839 326 : int nToSkip = 0;
2840 :
2841 326 : CPLAssert(m_iGeomField >= 0);
2842 : FileGDBGeomField *poGeomField =
2843 326 : cpl::down_cast<FileGDBGeomField *>(GetField(m_iGeomField));
2844 :
2845 326 : ReadVarUInt32NoCheck(pabyCur, nGeomType);
2846 :
2847 326 : switch ((nGeomType & 0xff))
2848 : {
2849 0 : case SHPT_NULL:
2850 0 : return FALSE;
2851 :
2852 33 : case SHPT_POINTZ:
2853 : case SHPT_POINTZM:
2854 : case SHPT_POINT:
2855 : case SHPT_POINTM:
2856 : case SHPT_GENERALPOINT:
2857 : {
2858 : GUIntBig x, y;
2859 33 : ReadVarUInt64NoCheck(pabyCur, x);
2860 33 : x = CPLUnsanitizedAdd<GUIntBig>(x, -1);
2861 33 : ReadVarUInt64NoCheck(pabyCur, y);
2862 33 : y = CPLUnsanitizedAdd<GUIntBig>(y, -1);
2863 33 : psOutFeatureEnvelope->MinX =
2864 33 : x / poGeomField->m_dfXYScale + poGeomField->m_dfXOrigin;
2865 33 : psOutFeatureEnvelope->MinY =
2866 33 : y / poGeomField->m_dfXYScale + poGeomField->m_dfYOrigin;
2867 33 : psOutFeatureEnvelope->MaxX = psOutFeatureEnvelope->MinX;
2868 33 : psOutFeatureEnvelope->MaxY = psOutFeatureEnvelope->MinY;
2869 33 : return TRUE;
2870 : }
2871 :
2872 1 : case SHPT_MULTIPOINTZM:
2873 : case SHPT_MULTIPOINTZ:
2874 : case SHPT_MULTIPOINT:
2875 : case SHPT_MULTIPOINTM:
2876 : {
2877 1 : break;
2878 : }
2879 :
2880 279 : case SHPT_ARC:
2881 : case SHPT_ARCZ:
2882 : case SHPT_ARCZM:
2883 : case SHPT_ARCM:
2884 : case SHPT_POLYGON:
2885 : case SHPT_POLYGONZ:
2886 : case SHPT_POLYGONZM:
2887 : case SHPT_POLYGONM:
2888 : {
2889 279 : nToSkip = 1;
2890 279 : break;
2891 : }
2892 12 : case SHPT_GENERALPOLYLINE:
2893 : case SHPT_GENERALPOLYGON:
2894 : {
2895 12 : nToSkip = 1 + ((nGeomType & EXT_SHAPE_CURVE_FLAG) ? 1 : 0);
2896 12 : break;
2897 : }
2898 :
2899 1 : case SHPT_GENERALMULTIPATCH:
2900 : case SHPT_MULTIPATCHM:
2901 : case SHPT_MULTIPATCH:
2902 : {
2903 1 : nToSkip = 2;
2904 1 : break;
2905 : }
2906 :
2907 0 : default:
2908 0 : return FALSE;
2909 : }
2910 :
2911 : GUInt32 nPoints;
2912 293 : ReadVarUInt32NoCheck(pabyCur, nPoints);
2913 293 : if (nPoints == 0)
2914 16 : return TRUE;
2915 277 : returnErrorIf(!SkipVarUInt(pabyCur, pabyEnd, nToSkip));
2916 :
2917 : GUIntBig vxmin, vymin, vdx, vdy;
2918 :
2919 277 : returnErrorIf(pabyCur >= pabyEnd);
2920 277 : ReadVarUInt64NoCheck(pabyCur, vxmin);
2921 277 : ReadVarUInt64NoCheck(pabyCur, vymin);
2922 277 : ReadVarUInt64NoCheck(pabyCur, vdx);
2923 277 : ReadVarUInt64NoCheck(pabyCur, vdy);
2924 :
2925 277 : psOutFeatureEnvelope->MinX =
2926 277 : vxmin / poGeomField->m_dfXYScale + poGeomField->m_dfXOrigin;
2927 277 : psOutFeatureEnvelope->MinY =
2928 277 : vymin / poGeomField->m_dfXYScale + poGeomField->m_dfYOrigin;
2929 277 : psOutFeatureEnvelope->MaxX =
2930 277 : CPLUnsanitizedAdd<GUIntBig>(vxmin, vdx) / poGeomField->m_dfXYScale +
2931 277 : poGeomField->m_dfXOrigin;
2932 277 : psOutFeatureEnvelope->MaxY =
2933 277 : CPLUnsanitizedAdd<GUIntBig>(vymin, vdy) / poGeomField->m_dfXYScale +
2934 277 : poGeomField->m_dfYOrigin;
2935 :
2936 277 : return TRUE;
2937 : }
2938 :
2939 : /************************************************************************/
2940 : /* DoesGeometryIntersectsFilterEnvelope() */
2941 : /************************************************************************/
2942 :
2943 15199 : int FileGDBTable::DoesGeometryIntersectsFilterEnvelope(const OGRField *psField)
2944 : {
2945 15199 : const int errorRetValue = TRUE;
2946 15199 : GByte *pabyCur = psField->Binary.paData;
2947 15199 : GByte *pabyEnd = pabyCur + psField->Binary.nCount;
2948 : GUInt32 nGeomType;
2949 15199 : int nToSkip = 0;
2950 :
2951 15199 : ReadVarUInt32NoCheck(pabyCur, nGeomType);
2952 :
2953 15199 : switch ((nGeomType & 0xff))
2954 : {
2955 0 : case SHPT_NULL:
2956 0 : return TRUE;
2957 :
2958 14721 : case SHPT_POINTZ:
2959 : case SHPT_POINTZM:
2960 : case SHPT_POINT:
2961 : case SHPT_POINTM:
2962 : case SHPT_GENERALPOINT:
2963 : {
2964 : GUIntBig x, y;
2965 14721 : ReadVarUInt64NoCheck(pabyCur, x);
2966 14721 : if (x == 0) // POINT EMPTY
2967 0 : return FALSE;
2968 14721 : x--;
2969 14721 : if (x < m_nFilterXMin || x > m_nFilterXMax)
2970 11044 : return FALSE;
2971 3677 : ReadVarUInt64NoCheck(pabyCur, y);
2972 3677 : y--;
2973 3677 : return y >= m_nFilterYMin && y <= m_nFilterYMax;
2974 : }
2975 :
2976 8 : case SHPT_MULTIPOINTZM:
2977 : case SHPT_MULTIPOINTZ:
2978 : case SHPT_MULTIPOINT:
2979 : case SHPT_MULTIPOINTM:
2980 : {
2981 8 : break;
2982 : }
2983 :
2984 455 : case SHPT_ARC:
2985 : case SHPT_ARCZ:
2986 : case SHPT_ARCZM:
2987 : case SHPT_ARCM:
2988 : case SHPT_POLYGON:
2989 : case SHPT_POLYGONZ:
2990 : case SHPT_POLYGONZM:
2991 : case SHPT_POLYGONM:
2992 : {
2993 455 : nToSkip = 1;
2994 455 : break;
2995 : }
2996 :
2997 0 : case SHPT_GENERALPOLYLINE:
2998 : case SHPT_GENERALPOLYGON:
2999 : {
3000 0 : nToSkip = 1 + ((nGeomType & EXT_SHAPE_CURVE_FLAG) ? 1 : 0);
3001 0 : break;
3002 : }
3003 :
3004 15 : case SHPT_GENERALMULTIPATCH:
3005 : case SHPT_MULTIPATCHM:
3006 : case SHPT_MULTIPATCH:
3007 : {
3008 15 : nToSkip = 2;
3009 15 : break;
3010 : }
3011 :
3012 0 : default:
3013 0 : return TRUE;
3014 : }
3015 :
3016 : GUInt32 nPoints;
3017 478 : ReadVarUInt32NoCheck(pabyCur, nPoints);
3018 478 : if (nPoints == 0)
3019 0 : return TRUE;
3020 478 : returnErrorIf(!SkipVarUInt(pabyCur, pabyEnd, nToSkip));
3021 :
3022 : GUIntBig vxmin, vymin, vdx, vdy;
3023 :
3024 478 : returnErrorIf(pabyCur >= pabyEnd);
3025 478 : ReadVarUInt64NoCheck(pabyCur, vxmin);
3026 478 : if (vxmin > m_nFilterXMax)
3027 98 : return FALSE;
3028 380 : ReadVarUInt64NoCheck(pabyCur, vymin);
3029 380 : if (vymin > m_nFilterYMax)
3030 42 : return FALSE;
3031 338 : ReadVarUInt64NoCheck(pabyCur, vdx);
3032 338 : if (CPLUnsanitizedAdd<GUIntBig>(vxmin, vdx) < m_nFilterXMin)
3033 0 : return FALSE;
3034 338 : ReadVarUInt64NoCheck(pabyCur, vdy);
3035 338 : return CPLUnsanitizedAdd<GUIntBig>(vymin, vdy) >= m_nFilterYMin;
3036 : }
3037 :
3038 : /************************************************************************/
3039 : /* FileGDBField::UNSET_FIELD */
3040 : /************************************************************************/
3041 :
3042 1804 : static OGRField GetUnsetField()
3043 : {
3044 : OGRField sUnsetField;
3045 1804 : OGR_RawField_SetUnset(&sUnsetField);
3046 1804 : return sUnsetField;
3047 : }
3048 :
3049 : const OGRField FileGDBField::UNSET_FIELD = GetUnsetField();
3050 :
3051 : /************************************************************************/
3052 : /* FileGDBField() */
3053 : /************************************************************************/
3054 :
3055 57965 : FileGDBField::FileGDBField(FileGDBTable *poParentIn) : m_poParent(poParentIn)
3056 : {
3057 57965 : OGR_RawField_SetUnset(&m_sDefault);
3058 57965 : }
3059 :
3060 : /************************************************************************/
3061 : /* FileGDBField() */
3062 : /************************************************************************/
3063 :
3064 14464 : FileGDBField::FileGDBField(const std::string &osName,
3065 : const std::string &osAlias, FileGDBFieldType eType,
3066 : bool bNullable, bool bRequired, bool bEditable,
3067 14464 : int nMaxWidth, const OGRField &sDefault)
3068 : : m_osName(osName), m_osAlias(osAlias), m_eType(eType),
3069 : m_bNullable(bNullable), m_bRequired(bRequired), m_bEditable(bEditable),
3070 14464 : m_nMaxWidth(nMaxWidth)
3071 : {
3072 14464 : if (m_eType == FGFT_OBJECTID || m_eType == FGFT_GLOBALID)
3073 : {
3074 2263 : CPLAssert(!m_bNullable);
3075 2263 : CPLAssert(m_bRequired);
3076 2263 : CPLAssert(!m_bEditable);
3077 : }
3078 :
3079 14480 : if (m_eType == FGFT_STRING && !OGR_RawField_IsUnset(&sDefault) &&
3080 16 : !OGR_RawField_IsNull(&sDefault))
3081 : {
3082 16 : m_sDefault.String = CPLStrdup(sDefault.String);
3083 : }
3084 : else
3085 : {
3086 14448 : m_sDefault = sDefault;
3087 : }
3088 14464 : }
3089 :
3090 : /************************************************************************/
3091 : /* ~FileGDBField() */
3092 : /************************************************************************/
3093 :
3094 141684 : FileGDBField::~FileGDBField()
3095 : {
3096 72459 : if (m_eType == FGFT_STRING && !OGR_RawField_IsUnset(&m_sDefault) &&
3097 30 : !OGR_RawField_IsNull(&m_sDefault))
3098 30 : CPLFree(m_sDefault.String);
3099 141684 : }
3100 :
3101 : /************************************************************************/
3102 : /* HasIndex() */
3103 : /************************************************************************/
3104 :
3105 1296 : int FileGDBField::HasIndex()
3106 : {
3107 1296 : m_poParent->GetIndexCount();
3108 1296 : return m_poIndex != nullptr;
3109 : }
3110 :
3111 : /************************************************************************/
3112 : /* GetIndex() */
3113 : /************************************************************************/
3114 :
3115 716 : FileGDBIndex *FileGDBField::GetIndex()
3116 : {
3117 716 : m_poParent->GetIndexCount();
3118 716 : return m_poIndex;
3119 : }
3120 :
3121 : /************************************************************************/
3122 : /* getESRI_NAN() */
3123 : /************************************************************************/
3124 :
3125 1804 : static double getESRI_NAN()
3126 : {
3127 : // Use exact same quiet NaN value as generated by the ESRI SDK, just
3128 : // for the purpose of ensuring binary identical output for some tests.
3129 : // I doubt this matter much which NaN is generated for usage.
3130 : // The reason is that std::numeric_limits<double>::quiet_NaN() on my
3131 : // platform has not the least significant bit set.
3132 1804 : constexpr uint64_t nNAN = (static_cast<uint64_t>(0x7FF80000U) << 32) | 1;
3133 : double v;
3134 1804 : memcpy(&v, &nNAN, sizeof(v));
3135 1804 : return v;
3136 : }
3137 :
3138 : const double FileGDBGeomField::ESRI_NAN = getESRI_NAN();
3139 :
3140 : /************************************************************************/
3141 : /* FileGDBGeomField() */
3142 : /************************************************************************/
3143 :
3144 2694 : FileGDBGeomField::FileGDBGeomField(FileGDBTable *poParentIn)
3145 2694 : : FileGDBField(poParentIn)
3146 : {
3147 2694 : }
3148 :
3149 : /************************************************************************/
3150 : /* FileGDBGeomField() */
3151 : /************************************************************************/
3152 :
3153 480 : FileGDBGeomField::FileGDBGeomField(
3154 : const std::string &osName, const std::string &osAlias, bool bNullable,
3155 : const std::string &osWKT, double dfXOrigin, double dfYOrigin,
3156 : double dfXYScale, double dfXYTolerance,
3157 480 : const std::vector<double> &adfSpatialIndexGridResolution)
3158 : : FileGDBField(osName, osAlias, FGFT_GEOMETRY, bNullable,
3159 : /* bRequired = */ true, /* bEditable = */ true, 0,
3160 : FileGDBField::UNSET_FIELD),
3161 : m_osWKT(osWKT), m_dfXOrigin(dfXOrigin), m_dfYOrigin(dfYOrigin),
3162 : m_dfXYScale(dfXYScale), m_dfXYTolerance(dfXYTolerance),
3163 480 : m_adfSpatialIndexGridResolution(adfSpatialIndexGridResolution)
3164 : {
3165 480 : }
3166 :
3167 : /************************************************************************/
3168 : /* SetXYMinMax() */
3169 : /************************************************************************/
3170 :
3171 4082 : void FileGDBGeomField::SetXYMinMax(double dfXMin, double dfYMin, double dfXMax,
3172 : double dfYMax)
3173 : {
3174 4082 : m_dfXMin = dfXMin;
3175 4082 : m_dfYMin = dfYMin;
3176 4082 : m_dfXMax = dfXMax;
3177 4082 : m_dfYMax = dfYMax;
3178 4082 : }
3179 :
3180 : /************************************************************************/
3181 : /* SetZMinMax() */
3182 : /************************************************************************/
3183 :
3184 4080 : void FileGDBGeomField::SetZMinMax(double dfZMin, double dfZMax)
3185 : {
3186 4080 : m_dfZMin = dfZMin;
3187 4080 : m_dfZMax = dfZMax;
3188 4080 : }
3189 :
3190 : /************************************************************************/
3191 : /* SetMMinMax() */
3192 : /************************************************************************/
3193 :
3194 0 : void FileGDBGeomField::SetMMinMax(double dfMMin, double dfMMax)
3195 : {
3196 0 : m_dfMMin = dfMMin;
3197 0 : m_dfMMax = dfMMax;
3198 0 : }
3199 :
3200 : /************************************************************************/
3201 : /* SetZOriginScaleTolerance() */
3202 : /************************************************************************/
3203 :
3204 480 : void FileGDBGeomField::SetZOriginScaleTolerance(double dfZOrigin,
3205 : double dfZScale,
3206 : double dfZTolerance)
3207 : {
3208 480 : m_bHasZOriginScaleTolerance = TRUE;
3209 480 : m_dfZOrigin = dfZOrigin;
3210 480 : m_dfZScale = dfZScale;
3211 480 : m_dfZTolerance = dfZTolerance;
3212 480 : }
3213 :
3214 : /************************************************************************/
3215 : /* SetMOriginScaleTolerance() */
3216 : /************************************************************************/
3217 :
3218 480 : void FileGDBGeomField::SetMOriginScaleTolerance(double dfMOrigin,
3219 : double dfMScale,
3220 : double dfMTolerance)
3221 : {
3222 480 : m_bHasMOriginScaleTolerance = TRUE;
3223 480 : m_dfMOrigin = dfMOrigin;
3224 480 : m_dfMScale = dfMScale;
3225 480 : m_dfMTolerance = dfMTolerance;
3226 480 : }
3227 :
3228 : FileGDBOGRGeometryConverter::~FileGDBOGRGeometryConverter() = default;
3229 :
3230 : /************************************************************************/
3231 : /* FileGDBOGRGeometryConverterImpl */
3232 : /************************************************************************/
3233 :
3234 : class FileGDBOGRGeometryConverterImpl final : public FileGDBOGRGeometryConverter
3235 : {
3236 : const FileGDBGeomField *poGeomField;
3237 : GUInt32 *panPointCount = nullptr;
3238 : GUInt32 nPointCountMax = 0;
3239 : #ifdef ASSUME_INNER_RINGS_IMMEDIATELY_AFTER_OUTER_RING
3240 : int bUseOrganize = 0;
3241 : #endif
3242 :
3243 : bool ReadPartDefs(GByte *&pabyCur, GByte *pabyEnd, GUInt32 &nPoints,
3244 : GUInt32 &nParts, GUInt32 &nCurves, bool bHasCurveDesc,
3245 : bool bIsMultiPatch);
3246 : template <class XYSetter>
3247 : int ReadXYArray(XYSetter &setter, GByte *&pabyCur, GByte *pabyEnd,
3248 : GUInt32 nPoints, GIntBig &dx, GIntBig &dy);
3249 : template <class ZSetter>
3250 : int ReadZArray(ZSetter &setter, GByte *&pabyCur, GByte *pabyEnd,
3251 : GUInt32 nPoints, GIntBig &dz);
3252 : template <class MSetter>
3253 : int ReadMArray(MSetter &setter, GByte *&pabyCur, GByte *pabyEnd,
3254 : GUInt32 nPoints, GIntBig &dm);
3255 :
3256 : OGRGeometry *CreateCurveGeometry(GUInt32 nBaseShapeType, GUInt32 nParts,
3257 : GUInt32 nPoints, GUInt32 nCurves,
3258 : bool bHasZ, bool bHasM, GByte *&pabyCur,
3259 : GByte *pabyEnd);
3260 :
3261 : FileGDBOGRGeometryConverterImpl(const FileGDBOGRGeometryConverterImpl &) =
3262 : delete;
3263 : FileGDBOGRGeometryConverterImpl &
3264 : operator=(const FileGDBOGRGeometryConverterImpl &) = delete;
3265 :
3266 : public:
3267 : explicit FileGDBOGRGeometryConverterImpl(
3268 : const FileGDBGeomField *poGeomField);
3269 : ~FileGDBOGRGeometryConverterImpl() override;
3270 :
3271 : OGRGeometry *GetAsGeometry(const OGRField *psField) override;
3272 : };
3273 :
3274 : /************************************************************************/
3275 : /* FileGDBOGRGeometryConverterImpl() */
3276 : /************************************************************************/
3277 :
3278 969 : FileGDBOGRGeometryConverterImpl::FileGDBOGRGeometryConverterImpl(
3279 969 : const FileGDBGeomField *poGeomFieldIn)
3280 969 : : poGeomField(poGeomFieldIn)
3281 : #ifdef ASSUME_INNER_RINGS_IMMEDIATELY_AFTER_OUTER_RING
3282 : ,
3283 : bUseOrganize(CPLGetConfigOption("OGR_ORGANIZE_POLYGONS", NULL) != NULL)
3284 : #endif
3285 : {
3286 969 : }
3287 :
3288 : /************************************************************************/
3289 : /* ~FileGDBOGRGeometryConverter() */
3290 : /************************************************************************/
3291 :
3292 1938 : FileGDBOGRGeometryConverterImpl::~FileGDBOGRGeometryConverterImpl()
3293 : {
3294 969 : CPLFree(panPointCount);
3295 1938 : }
3296 :
3297 : /************************************************************************/
3298 : /* ReadPartDefs() */
3299 : /************************************************************************/
3300 :
3301 5633 : bool FileGDBOGRGeometryConverterImpl::ReadPartDefs(
3302 : GByte *&pabyCur, GByte *pabyEnd, GUInt32 &nPoints, GUInt32 &nParts,
3303 : GUInt32 &nCurves, bool bHasCurveDesc, bool bIsMultiPatch)
3304 : {
3305 5633 : const bool errorRetValue = false;
3306 5633 : returnErrorIf(!ReadVarUInt32(pabyCur, pabyEnd, nPoints));
3307 5633 : if (nPoints == 0)
3308 : {
3309 10 : nParts = 0;
3310 10 : nCurves = 0;
3311 10 : return true;
3312 : }
3313 5623 : returnErrorIf(nPoints > static_cast<size_t>(pabyEnd - pabyCur));
3314 5623 : if (bIsMultiPatch)
3315 564 : returnErrorIf(!SkipVarUInt(pabyCur, pabyEnd));
3316 5623 : returnErrorIf(!ReadVarUInt32(pabyCur, pabyEnd, nParts));
3317 5623 : returnErrorIf(nParts > static_cast<size_t>(pabyEnd - pabyCur));
3318 5623 : returnErrorIf(nParts > static_cast<GUInt32>(INT_MAX) / sizeof(GUInt32) - 1);
3319 5623 : if (bHasCurveDesc)
3320 : {
3321 71 : returnErrorIf(!ReadVarUInt32(pabyCur, pabyEnd, nCurves));
3322 71 : returnErrorIf(nCurves > static_cast<size_t>(pabyEnd - pabyCur));
3323 : }
3324 : else
3325 5552 : nCurves = 0;
3326 5623 : if (nParts == 0)
3327 0 : return true;
3328 : GUInt32 i;
3329 5623 : returnErrorIf(!SkipVarUInt(pabyCur, pabyEnd, 4));
3330 5623 : if (nParts > nPointCountMax)
3331 : {
3332 : GUInt32 *panPointCountNew = static_cast<GUInt32 *>(
3333 315 : VSI_REALLOC_VERBOSE(panPointCount, nParts * sizeof(GUInt32)));
3334 315 : returnErrorIf(panPointCountNew == nullptr);
3335 315 : panPointCount = panPointCountNew;
3336 315 : nPointCountMax = nParts;
3337 : }
3338 5623 : GUIntBig nSumNPartsM1 = 0;
3339 9146 : for (i = 0; i < nParts - 1; i++)
3340 : {
3341 : GUInt32 nTmp;
3342 3523 : returnErrorIf(!ReadVarUInt32(pabyCur, pabyEnd, nTmp));
3343 3523 : returnErrorIf(nTmp > static_cast<size_t>(pabyEnd - pabyCur));
3344 3523 : panPointCount[i] = nTmp;
3345 3523 : nSumNPartsM1 += nTmp;
3346 : }
3347 5623 : returnErrorIf(nSumNPartsM1 > nPoints);
3348 5623 : panPointCount[nParts - 1] = static_cast<GUInt32>(nPoints - nSumNPartsM1);
3349 :
3350 5623 : return true;
3351 : }
3352 :
3353 : /************************************************************************/
3354 : /* XYLineStringSetter */
3355 : /************************************************************************/
3356 :
3357 4620 : class FileGDBOGRLineString final : public OGRLineString
3358 : {
3359 : public:
3360 2310 : FileGDBOGRLineString() = default;
3361 :
3362 : ~FileGDBOGRLineString() override;
3363 :
3364 2310 : OGRRawPoint *GetPoints() const
3365 : {
3366 2310 : return paoPoints;
3367 : }
3368 : };
3369 :
3370 : FileGDBOGRLineString::~FileGDBOGRLineString() = default;
3371 :
3372 7846 : class FileGDBOGRLinearRing final : public OGRLinearRing
3373 : {
3374 : public:
3375 3923 : FileGDBOGRLinearRing() = default;
3376 :
3377 : ~FileGDBOGRLinearRing() override;
3378 :
3379 3923 : OGRRawPoint *GetPoints() const
3380 : {
3381 3923 : return paoPoints;
3382 : }
3383 : };
3384 :
3385 : FileGDBOGRLinearRing::~FileGDBOGRLinearRing() = default;
3386 :
3387 : class XYLineStringSetter
3388 : {
3389 : OGRRawPoint *paoPoints;
3390 :
3391 : public:
3392 6233 : explicit XYLineStringSetter(OGRRawPoint *paoPointsIn)
3393 6233 : : paoPoints(paoPointsIn)
3394 : {
3395 6233 : }
3396 :
3397 33186 : void set(int i, double dfX, double dfY)
3398 : {
3399 33186 : paoPoints[i].x = dfX;
3400 33186 : paoPoints[i].y = dfY;
3401 33186 : }
3402 : };
3403 :
3404 : /************************************************************************/
3405 : /* XYMultiPointSetter */
3406 : /************************************************************************/
3407 :
3408 : class XYMultiPointSetter
3409 : {
3410 : OGRMultiPoint *poMPoint;
3411 :
3412 : public:
3413 894 : explicit XYMultiPointSetter(OGRMultiPoint *poMPointIn)
3414 894 : : poMPoint(poMPointIn)
3415 : {
3416 894 : }
3417 :
3418 1794 : void set(int i, double dfX, double dfY)
3419 : {
3420 : (void)i;
3421 1794 : poMPoint->addGeometryDirectly(new OGRPoint(dfX, dfY));
3422 1794 : }
3423 : };
3424 :
3425 : /************************************************************************/
3426 : /* XYArraySetter */
3427 : /************************************************************************/
3428 :
3429 : class XYArraySetter
3430 : {
3431 : double *padfX;
3432 : double *padfY;
3433 :
3434 : public:
3435 564 : XYArraySetter(double *padfXIn, double *padfYIn)
3436 564 : : padfX(padfXIn), padfY(padfYIn)
3437 : {
3438 564 : }
3439 :
3440 11816 : void set(int i, double dfX, double dfY)
3441 : {
3442 11816 : padfX[i] = dfX;
3443 11816 : padfY[i] = dfY;
3444 11816 : }
3445 : };
3446 :
3447 : /************************************************************************/
3448 : /* ReadXYArray() */
3449 : /************************************************************************/
3450 :
3451 : template <class XYSetter>
3452 7762 : int FileGDBOGRGeometryConverterImpl::ReadXYArray(XYSetter &setter,
3453 : GByte *&pabyCur,
3454 : GByte *pabyEnd,
3455 : GUInt32 nPoints, GIntBig &dx,
3456 : GIntBig &dy)
3457 : {
3458 7762 : const int errorRetValue = FALSE;
3459 7762 : GIntBig dxLocal = dx;
3460 7762 : GIntBig dyLocal = dy;
3461 :
3462 55081 : for (GUInt32 i = 0; i < nPoints; i++)
3463 : {
3464 47319 : returnErrorIf(pabyCur /*+ 1*/ >= pabyEnd);
3465 :
3466 47319 : ReadVarIntAndAddNoCheck(pabyCur, dxLocal);
3467 47319 : ReadVarIntAndAddNoCheck(pabyCur, dyLocal);
3468 :
3469 47319 : double dfX =
3470 47319 : dxLocal / poGeomField->GetXYScale() + poGeomField->GetXOrigin();
3471 47319 : double dfY =
3472 47319 : dyLocal / poGeomField->GetXYScale() + poGeomField->GetYOrigin();
3473 47319 : setter.set(i, dfX, dfY);
3474 : }
3475 :
3476 7762 : dx = dxLocal;
3477 7762 : dy = dyLocal;
3478 7762 : return TRUE;
3479 : }
3480 :
3481 : /************************************************************************/
3482 : /* ZLineStringSetter */
3483 : /************************************************************************/
3484 :
3485 : class ZLineStringSetter
3486 : {
3487 : OGRLineString *poLS;
3488 :
3489 : public:
3490 2127 : explicit ZLineStringSetter(OGRLineString *poLSIn) : poLS(poLSIn)
3491 : {
3492 2127 : }
3493 :
3494 7220 : void set(int i, double dfZ)
3495 : {
3496 7220 : poLS->setZ(i, dfZ);
3497 7220 : }
3498 : };
3499 :
3500 : /************************************************************************/
3501 : /* ZMultiPointSetter */
3502 : /************************************************************************/
3503 :
3504 : class ZMultiPointSetter
3505 : {
3506 : OGRMultiPoint *poMPoint;
3507 :
3508 : public:
3509 446 : explicit ZMultiPointSetter(OGRMultiPoint *poMPointIn) : poMPoint(poMPointIn)
3510 : {
3511 446 : }
3512 :
3513 896 : void set(int i, double dfZ)
3514 : {
3515 896 : poMPoint->getGeometryRef(i)->setZ(dfZ);
3516 896 : }
3517 : };
3518 :
3519 : /************************************************************************/
3520 : /* FileGDBArraySetter */
3521 : /************************************************************************/
3522 :
3523 : class FileGDBArraySetter
3524 : {
3525 : double *padfValues;
3526 :
3527 : public:
3528 564 : explicit FileGDBArraySetter(double *padfValuesIn) : padfValues(padfValuesIn)
3529 : {
3530 564 : }
3531 :
3532 11816 : void set(int i, double dfValue)
3533 : {
3534 11816 : padfValues[i] = dfValue;
3535 11816 : }
3536 : };
3537 :
3538 : /************************************************************************/
3539 : /* ReadZArray() */
3540 : /************************************************************************/
3541 :
3542 : template <class ZSetter>
3543 3153 : int FileGDBOGRGeometryConverterImpl::ReadZArray(ZSetter &setter,
3544 : GByte *&pabyCur, GByte *pabyEnd,
3545 : GUInt32 nPoints, GIntBig &dz)
3546 : {
3547 3153 : const int errorRetValue = FALSE;
3548 3153 : const double dfZScale = SanitizeScale(poGeomField->GetZScale());
3549 23159 : for (GUInt32 i = 0; i < nPoints; i++)
3550 : {
3551 20006 : returnErrorIf(pabyCur >= pabyEnd);
3552 20006 : ReadVarIntAndAddNoCheck(pabyCur, dz);
3553 :
3554 20006 : double dfZ = dz / dfZScale + poGeomField->GetZOrigin();
3555 20006 : setter.set(i, dfZ);
3556 : }
3557 3153 : return TRUE;
3558 : }
3559 :
3560 : /************************************************************************/
3561 : /* MLineStringSetter */
3562 : /************************************************************************/
3563 :
3564 : class MLineStringSetter
3565 : {
3566 : OGRLineString *poLS;
3567 :
3568 : public:
3569 244 : explicit MLineStringSetter(OGRLineString *poLSIn) : poLS(poLSIn)
3570 : {
3571 244 : }
3572 :
3573 868 : void set(int i, double dfM)
3574 : {
3575 868 : poLS->setM(i, dfM);
3576 868 : }
3577 : };
3578 :
3579 : /************************************************************************/
3580 : /* MMultiPointSetter */
3581 : /************************************************************************/
3582 :
3583 : class MMultiPointSetter
3584 : {
3585 : OGRMultiPoint *poMPoint;
3586 :
3587 : public:
3588 56 : explicit MMultiPointSetter(OGRMultiPoint *poMPointIn) : poMPoint(poMPointIn)
3589 : {
3590 56 : }
3591 :
3592 118 : void set(int i, double dfM)
3593 : {
3594 118 : poMPoint->getGeometryRef(i)->setM(dfM);
3595 118 : }
3596 : };
3597 :
3598 : /************************************************************************/
3599 : /* ReadMArray() */
3600 : /************************************************************************/
3601 :
3602 : template <class MSetter>
3603 314 : int FileGDBOGRGeometryConverterImpl::ReadMArray(MSetter &setter,
3604 : GByte *&pabyCur, GByte *pabyEnd,
3605 : GUInt32 nPoints, GIntBig &dm)
3606 : {
3607 314 : const int errorRetValue = FALSE;
3608 314 : const double dfMScale = SanitizeScale(poGeomField->GetMScale());
3609 1364 : for (GUInt32 i = 0; i < nPoints; i++)
3610 : {
3611 1050 : returnErrorIf(pabyCur >= pabyEnd);
3612 1050 : ReadVarIntAndAddNoCheck(pabyCur, dm);
3613 :
3614 1050 : double dfM = dm / dfMScale + poGeomField->GetMOrigin();
3615 1050 : setter.set(i, dfM);
3616 : }
3617 314 : return TRUE;
3618 : }
3619 :
3620 : /************************************************************************/
3621 : /* CreateCurveGeometry() */
3622 : /************************************************************************/
3623 :
3624 : class XYBufferSetter
3625 : {
3626 : GByte *pabyBuffer;
3627 :
3628 : public:
3629 71 : explicit XYBufferSetter(GByte *pabyBufferIn) : pabyBuffer(pabyBufferIn)
3630 : {
3631 71 : }
3632 :
3633 523 : void set(int i, double dfX, double dfY)
3634 : {
3635 523 : CPL_LSBPTR64(&dfX);
3636 523 : memcpy(pabyBuffer + 16 * i, &dfX, 8);
3637 523 : CPL_LSBPTR64(&dfY);
3638 523 : memcpy(pabyBuffer + 16 * i + 8, &dfY, 8);
3639 523 : }
3640 : };
3641 :
3642 : class ZOrMBufferSetter
3643 : {
3644 : GByte *pabyBuffer;
3645 :
3646 : public:
3647 30 : explicit ZOrMBufferSetter(GByte *pabyBufferIn) : pabyBuffer(pabyBufferIn)
3648 : {
3649 30 : }
3650 :
3651 138 : void set(int i, double dfValue)
3652 : {
3653 138 : CPL_LSBPTR64(&dfValue);
3654 138 : memcpy(pabyBuffer + 8 * i, &dfValue, 8);
3655 138 : }
3656 : };
3657 :
3658 : /* We first create an extended shape buffer from the compressed stream */
3659 : /* and finally use OGRCreateFromShapeBin() to make a geometry from it */
3660 :
3661 71 : OGRGeometry *FileGDBOGRGeometryConverterImpl::CreateCurveGeometry(
3662 : GUInt32 nBaseShapeType, GUInt32 nParts, GUInt32 nPoints, GUInt32 nCurves,
3663 : bool bHasZ, bool bHasM, GByte *&pabyCur, GByte *pabyEnd)
3664 : {
3665 71 : OGRGeometry *errorRetValue = nullptr;
3666 : GUInt32 i;
3667 71 : const int nDims = 2 + (bHasZ ? 1 : 0) + (bHasM ? 1 : 0);
3668 71 : GIntBig nMaxSize64 = 44 + 4 * static_cast<GUIntBig>(nParts) +
3669 71 : 8 * nDims * static_cast<GUIntBig>(nPoints);
3670 71 : nMaxSize64 += 4; // nCurves
3671 71 : nMaxSize64 +=
3672 71 : static_cast<GUIntBig>(nCurves) * (4 + /* start index */
3673 : 4 + /* curve type */
3674 : 44 /* size of ellipse struct */);
3675 71 : nMaxSize64 +=
3676 71 : ((bHasZ ? 1 : 0) + (bHasM ? 1 : 0)) * 16; // space for bounding boxes
3677 71 : if (nMaxSize64 >= INT_MAX)
3678 : {
3679 0 : returnError();
3680 : }
3681 71 : const int nMaxSize = static_cast<int>(nMaxSize64 & INT_MAX);
3682 : // coverity[overflow_sink]
3683 : GByte *pabyExtShapeBuffer =
3684 71 : static_cast<GByte *>(VSI_MALLOC_VERBOSE(nMaxSize));
3685 71 : if (pabyExtShapeBuffer == nullptr)
3686 : {
3687 0 : VSIFree(pabyExtShapeBuffer);
3688 0 : returnError();
3689 : }
3690 71 : GUInt32 nShapeType = nBaseShapeType | EXT_SHAPE_CURVE_FLAG;
3691 71 : if (bHasZ)
3692 16 : nShapeType |= EXT_SHAPE_Z_FLAG;
3693 71 : if (bHasM)
3694 16 : nShapeType |= EXT_SHAPE_M_FLAG;
3695 : GUInt32 nTmp;
3696 71 : nTmp = CPL_LSBWORD32(nShapeType);
3697 71 : GByte *pabyShapeTypePtr = pabyExtShapeBuffer;
3698 71 : memcpy(pabyExtShapeBuffer, &nTmp, 4);
3699 71 : memset(pabyExtShapeBuffer + 4, 0, 32); /* bbox: unused */
3700 71 : nTmp = CPL_LSBWORD32(nParts);
3701 71 : memcpy(pabyExtShapeBuffer + 36, &nTmp, 4);
3702 71 : nTmp = CPL_LSBWORD32(nPoints);
3703 71 : memcpy(pabyExtShapeBuffer + 40, &nTmp, 4);
3704 71 : GUInt32 nIdx = 0;
3705 170 : for (i = 0; i < nParts; i++)
3706 : {
3707 99 : nTmp = CPL_LSBWORD32(nIdx);
3708 99 : nIdx += panPointCount[i];
3709 99 : memcpy(pabyExtShapeBuffer + 44 + 4 * i, &nTmp, 4);
3710 : }
3711 71 : int nOffset = 44 + 4 * nParts;
3712 71 : GIntBig dx = 0;
3713 71 : GIntBig dy = 0;
3714 71 : XYBufferSetter arraySetter(pabyExtShapeBuffer + nOffset);
3715 71 : if (!ReadXYArray<XYBufferSetter>(arraySetter, pabyCur, pabyEnd, nPoints, dx,
3716 : dy))
3717 : {
3718 0 : VSIFree(pabyExtShapeBuffer);
3719 0 : returnError();
3720 : }
3721 71 : nOffset += 16 * nPoints;
3722 :
3723 71 : if (bHasZ)
3724 : {
3725 16 : memset(pabyExtShapeBuffer + nOffset, 0, 16); /* bbox: unused */
3726 16 : nOffset += 16;
3727 16 : GIntBig dz = 0;
3728 16 : ZOrMBufferSetter arrayzSetter(pabyExtShapeBuffer + nOffset);
3729 16 : if (!ReadZArray<ZOrMBufferSetter>(arrayzSetter, pabyCur, pabyEnd,
3730 : nPoints, dz))
3731 : {
3732 0 : VSIFree(pabyExtShapeBuffer);
3733 0 : returnError();
3734 : }
3735 16 : nOffset += 8 * nPoints;
3736 : }
3737 :
3738 71 : if (bHasM)
3739 : {
3740 : // It seems that absence of M is marked with a single byte
3741 : // with value 66.
3742 16 : if (*pabyCur == 66)
3743 : {
3744 2 : pabyCur++;
3745 : #if 1
3746 : // In other code paths of this file, we drop the M component when
3747 : // it is at null. The disabled code path would fill it with NaN
3748 : // instead.
3749 2 : nShapeType &= ~EXT_SHAPE_M_FLAG;
3750 2 : nTmp = CPL_LSBWORD32(nShapeType);
3751 2 : memcpy(pabyShapeTypePtr, &nTmp, 4);
3752 : #else
3753 : memset(pabyExtShapeBuffer + nOffset, 0, 16); /* bbox: unused */
3754 : nOffset += 16;
3755 : const double myNan = std::numeric_limits<double>::quiet_NaN();
3756 : for (i = 0; i < nPoints; i++)
3757 : {
3758 : memcpy(pabyExtShapeBuffer + nOffset + 8 * i, &myNan, 8);
3759 : CPL_LSBPTR64(pabyExtShapeBuffer + nOffset + 8 * i);
3760 : }
3761 : nOffset += 8 * nPoints;
3762 : #endif
3763 : }
3764 : else
3765 : {
3766 14 : memset(pabyExtShapeBuffer + nOffset, 0, 16); /* bbox: unused */
3767 14 : nOffset += 16;
3768 14 : ZOrMBufferSetter arraymSetter(pabyExtShapeBuffer + nOffset);
3769 14 : GIntBig dm = 0;
3770 14 : if (!ReadMArray<ZOrMBufferSetter>(arraymSetter, pabyCur, pabyEnd,
3771 : nPoints, dm))
3772 : {
3773 0 : VSIFree(pabyExtShapeBuffer);
3774 0 : returnError();
3775 : }
3776 14 : nOffset += 8 * nPoints;
3777 : }
3778 : }
3779 :
3780 71 : nTmp = CPL_LSBWORD32(nCurves);
3781 71 : memcpy(pabyExtShapeBuffer + nOffset, &nTmp, 4);
3782 71 : nOffset += 4;
3783 198 : for (i = 0; i < nCurves; i++)
3784 : {
3785 : // start index
3786 127 : returnErrorAndCleanupIf(!ReadVarUInt32(pabyCur, pabyEnd, nTmp),
3787 : VSIFree(pabyExtShapeBuffer));
3788 127 : CPL_LSBPTR32(&nTmp);
3789 127 : memcpy(pabyExtShapeBuffer + nOffset, &nTmp, 4);
3790 127 : nOffset += 4;
3791 :
3792 : GUInt32 nCurveType;
3793 127 : returnErrorAndCleanupIf(!ReadVarUInt32(pabyCur, pabyEnd, nCurveType),
3794 : VSIFree(pabyExtShapeBuffer));
3795 127 : nTmp = CPL_LSBWORD32(nCurveType);
3796 127 : memcpy(pabyExtShapeBuffer + nOffset, &nTmp, 4);
3797 127 : nOffset += 4;
3798 :
3799 127 : int nStructureSize = 0;
3800 127 : if (nCurveType == EXT_SHAPE_SEGMENT_ARC)
3801 101 : nStructureSize = 2 * 8 + 4;
3802 26 : else if (nCurveType == EXT_SHAPE_SEGMENT_BEZIER)
3803 19 : nStructureSize = 4 * 8;
3804 7 : else if (nCurveType == EXT_SHAPE_SEGMENT_ELLIPSE)
3805 7 : nStructureSize = 5 * 8 + 4;
3806 127 : if (nStructureSize == 0 || pabyCur + nStructureSize > pabyEnd)
3807 : {
3808 0 : VSIFree(pabyExtShapeBuffer);
3809 0 : returnError();
3810 : }
3811 127 : memcpy(pabyExtShapeBuffer + nOffset, pabyCur, nStructureSize);
3812 127 : pabyCur += nStructureSize;
3813 127 : nOffset += nStructureSize;
3814 : }
3815 71 : CPLAssert(nOffset <= nMaxSize);
3816 :
3817 71 : OGRGeometry *poRet = nullptr;
3818 71 : OGRCreateFromShapeBin(pabyExtShapeBuffer, &poRet, nOffset,
3819 : /* pszOrganizePolygonsMethod = */ "DEFAULT");
3820 71 : VSIFree(pabyExtShapeBuffer);
3821 71 : return poRet;
3822 : }
3823 :
3824 : /************************************************************************/
3825 : /* GetAsGeometry() */
3826 : /************************************************************************/
3827 :
3828 : OGRGeometry *
3829 15750 : FileGDBOGRGeometryConverterImpl::GetAsGeometry(const OGRField *psField)
3830 : {
3831 15750 : OGRGeometry *errorRetValue = nullptr;
3832 15750 : GByte *pabyCur = psField->Binary.paData;
3833 15750 : GByte *pabyEnd = pabyCur + psField->Binary.nCount;
3834 : GUInt32 nGeomType, i, nPoints, nParts, nCurves;
3835 : GUIntBig x, y, z;
3836 : GIntBig dx, dy, dz;
3837 :
3838 15750 : ReadVarUInt32NoCheck(pabyCur, nGeomType);
3839 :
3840 15750 : bool bHasZ = (nGeomType & EXT_SHAPE_Z_FLAG) != 0;
3841 15750 : bool bHasM = (nGeomType & EXT_SHAPE_M_FLAG) != 0;
3842 15750 : switch ((nGeomType & 0xff))
3843 : {
3844 0 : case SHPT_NULL:
3845 0 : return nullptr;
3846 :
3847 458 : case SHPT_POINTZ:
3848 : case SHPT_POINTZM:
3849 458 : bHasZ = true; /* go on */
3850 : [[fallthrough]];
3851 9223 : case SHPT_POINT:
3852 : case SHPT_POINTM:
3853 : case SHPT_GENERALPOINT:
3854 : {
3855 9223 : if (nGeomType == SHPT_POINTM || nGeomType == SHPT_POINTZM)
3856 58 : bHasM = true;
3857 :
3858 9223 : ReadVarUInt64NoCheck(pabyCur, x);
3859 9223 : ReadVarUInt64NoCheck(pabyCur, y);
3860 :
3861 18438 : const double dfX = x == 0 ? std::numeric_limits<double>::quiet_NaN()
3862 9215 : : (x - 1U) / poGeomField->GetXYScale() +
3863 9215 : poGeomField->GetXOrigin();
3864 18438 : const double dfY = y == 0 ? std::numeric_limits<double>::quiet_NaN()
3865 9215 : : (y - 1U) / poGeomField->GetXYScale() +
3866 9215 : poGeomField->GetYOrigin();
3867 9223 : if (bHasZ)
3868 : {
3869 458 : ReadVarUInt64NoCheck(pabyCur, z);
3870 458 : const double dfZScale = SanitizeScale(poGeomField->GetZScale());
3871 : const double dfZ =
3872 912 : z == 0 ? std::numeric_limits<double>::quiet_NaN()
3873 454 : : (z - 1U) / dfZScale + poGeomField->GetZOrigin();
3874 458 : if (bHasM)
3875 : {
3876 30 : GUIntBig m = 0;
3877 30 : ReadVarUInt64NoCheck(pabyCur, m);
3878 : const double dfMScale =
3879 30 : SanitizeScale(poGeomField->GetMScale());
3880 30 : if (m == 0)
3881 : {
3882 : return new OGRPoint(
3883 : dfX, dfY, dfZ,
3884 2 : std::numeric_limits<double>::quiet_NaN());
3885 : }
3886 : else
3887 : {
3888 28 : assert(m >= 1U);
3889 : const double dfM =
3890 28 : (m - 1U) / dfMScale + poGeomField->GetMOrigin();
3891 28 : return new OGRPoint(dfX, dfY, dfZ, dfM);
3892 : }
3893 : }
3894 428 : return new OGRPoint(dfX, dfY, dfZ);
3895 : }
3896 8765 : else if (bHasM)
3897 : {
3898 28 : OGRPoint *poPoint = new OGRPoint(dfX, dfY);
3899 28 : GUIntBig m = 0;
3900 28 : ReadVarUInt64NoCheck(pabyCur, m);
3901 28 : const double dfMScale = SanitizeScale(poGeomField->GetMScale());
3902 : const double dfM =
3903 54 : m == 0 ? std::numeric_limits<double>::quiet_NaN()
3904 26 : : (m - 1U) / dfMScale + poGeomField->GetMOrigin();
3905 28 : poPoint->setM(dfM);
3906 28 : return poPoint;
3907 : }
3908 8737 : return new OGRPoint(dfX, dfY);
3909 : }
3910 :
3911 446 : case SHPT_MULTIPOINTZM:
3912 : case SHPT_MULTIPOINTZ:
3913 446 : bHasZ = true; /* go on */
3914 : [[fallthrough]];
3915 894 : case SHPT_MULTIPOINT:
3916 : case SHPT_MULTIPOINTM:
3917 : {
3918 894 : if (nGeomType == SHPT_MULTIPOINTM || nGeomType == SHPT_MULTIPOINTZM)
3919 56 : bHasM = true;
3920 :
3921 894 : returnErrorIf(!ReadVarUInt32(pabyCur, pabyEnd, nPoints));
3922 894 : if (nPoints == 0)
3923 : {
3924 0 : OGRMultiPoint *poMP = new OGRMultiPoint();
3925 0 : if (bHasZ)
3926 0 : poMP->set3D(TRUE);
3927 0 : if (bHasM)
3928 0 : poMP->setMeasured(TRUE);
3929 0 : return poMP;
3930 : }
3931 :
3932 894 : returnErrorIf(!SkipVarUInt(pabyCur, pabyEnd, 4));
3933 :
3934 894 : dx = dy = dz = 0;
3935 :
3936 894 : OGRMultiPoint *poMP = new OGRMultiPoint();
3937 894 : XYMultiPointSetter mpSetter(poMP);
3938 894 : if (!ReadXYArray<XYMultiPointSetter>(mpSetter, pabyCur, pabyEnd,
3939 : nPoints, dx, dy))
3940 : {
3941 0 : delete poMP;
3942 0 : returnError();
3943 : }
3944 :
3945 894 : if (bHasZ)
3946 : {
3947 446 : poMP->setCoordinateDimension(3);
3948 446 : ZMultiPointSetter mpzSetter(poMP);
3949 446 : if (!ReadZArray<ZMultiPointSetter>(mpzSetter, pabyCur, pabyEnd,
3950 : nPoints, dz))
3951 : {
3952 0 : delete poMP;
3953 0 : returnError();
3954 : }
3955 : }
3956 :
3957 : // It seems that absence of M is marked with a single byte
3958 : // with value 66. Be more tolerant and only try to parse the M
3959 : // array is there are at least as many remaining bytes as
3960 : // expected points
3961 894 : if (bHasM && pabyCur + nPoints <= pabyEnd)
3962 : {
3963 56 : poMP->setMeasured(TRUE);
3964 56 : GIntBig dm = 0;
3965 56 : MMultiPointSetter mpmSetter(poMP);
3966 56 : if (!ReadMArray<MMultiPointSetter>(mpmSetter, pabyCur, pabyEnd,
3967 : nPoints, dm))
3968 : {
3969 0 : delete poMP;
3970 0 : returnError();
3971 : }
3972 : }
3973 :
3974 894 : return poMP;
3975 : }
3976 :
3977 1006 : case SHPT_ARCZ:
3978 : case SHPT_ARCZM:
3979 1006 : bHasZ = true; /* go on */
3980 : [[fallthrough]];
3981 2056 : case SHPT_ARC:
3982 : case SHPT_ARCM:
3983 : case SHPT_GENERALPOLYLINE:
3984 : {
3985 2056 : if (nGeomType == SHPT_ARCM || nGeomType == SHPT_ARCZM)
3986 115 : bHasM = true;
3987 :
3988 2056 : returnErrorIf(
3989 : !ReadPartDefs(pabyCur, pabyEnd, nPoints, nParts, nCurves,
3990 : (nGeomType & EXT_SHAPE_CURVE_FLAG) != 0, false));
3991 :
3992 2056 : if (nPoints == 0 || nParts == 0)
3993 : {
3994 4 : OGRLineString *poLS = new OGRLineString();
3995 4 : if (bHasZ)
3996 2 : poLS->set3D(TRUE);
3997 4 : if (bHasM)
3998 2 : poLS->setMeasured(TRUE);
3999 4 : return poLS;
4000 : }
4001 :
4002 2052 : if (nCurves)
4003 : {
4004 30 : GByte *pabyCurBackup = pabyCur;
4005 30 : OGRGeometry *poRet = CreateCurveGeometry(
4006 : SHPT_GENERALPOLYLINE, nParts, nPoints, nCurves, bHasZ,
4007 : bHasM, pabyCur, pabyEnd);
4008 30 : if (poRet)
4009 30 : return poRet;
4010 : // In case something went wrong, go on without curves
4011 0 : pabyCur = pabyCurBackup;
4012 : }
4013 :
4014 2022 : OGRMultiLineString *poMLS = nullptr;
4015 2022 : FileGDBOGRLineString *poLS = nullptr;
4016 2022 : if (nParts > 1)
4017 : {
4018 288 : poMLS = new OGRMultiLineString();
4019 288 : if (bHasZ)
4020 137 : poMLS->set3D(TRUE);
4021 288 : if (bHasM)
4022 6 : poMLS->setMeasured(TRUE);
4023 : }
4024 :
4025 2022 : dx = dy = dz = 0;
4026 4332 : for (i = 0; i < nParts; i++)
4027 : {
4028 2310 : poLS = new FileGDBOGRLineString();
4029 2310 : poLS->setNumPoints(panPointCount[i], FALSE);
4030 2310 : if (nParts > 1)
4031 576 : poMLS->addGeometryDirectly(poLS);
4032 :
4033 2310 : XYLineStringSetter lsSetter(poLS->GetPoints());
4034 2310 : if (!ReadXYArray<XYLineStringSetter>(lsSetter, pabyCur, pabyEnd,
4035 2310 : panPointCount[i], dx, dy))
4036 : {
4037 0 : if (nParts > 1)
4038 0 : delete poMLS;
4039 : else
4040 0 : delete poLS;
4041 0 : returnError();
4042 : }
4043 : }
4044 :
4045 2022 : if (bHasZ)
4046 : {
4047 2145 : for (i = 0; i < nParts; i++)
4048 : {
4049 1141 : if (nParts > 1)
4050 274 : poLS = cpl::down_cast<FileGDBOGRLineString *>(
4051 : poMLS->getGeometryRef(i));
4052 :
4053 1141 : ZLineStringSetter lszSetter(poLS);
4054 1141 : if (!ReadZArray<ZLineStringSetter>(
4055 1141 : lszSetter, pabyCur, pabyEnd, panPointCount[i], dz))
4056 : {
4057 0 : if (nParts > 1)
4058 0 : delete poMLS;
4059 : else
4060 0 : delete poLS;
4061 0 : returnError();
4062 : }
4063 : }
4064 : }
4065 :
4066 2022 : if (bHasM)
4067 : {
4068 113 : GIntBig dm = 0;
4069 231 : for (i = 0; i < nParts; i++)
4070 : {
4071 119 : if (nParts > 1)
4072 12 : poLS = cpl::down_cast<FileGDBOGRLineString *>(
4073 : poMLS->getGeometryRef(i));
4074 :
4075 : // It seems that absence of M is marked with a single byte
4076 : // with value 66. Be more tolerant and only try to parse the
4077 : // M array is there are at least as many remaining bytes as
4078 : // expected points
4079 119 : if (pabyCur + panPointCount[i] > pabyEnd)
4080 : {
4081 1 : if (nParts > 1)
4082 0 : poMLS->setMeasured(FALSE);
4083 1 : break;
4084 : }
4085 :
4086 118 : MLineStringSetter lsmSetter(poLS);
4087 118 : if (!ReadMArray<MLineStringSetter>(
4088 118 : lsmSetter, pabyCur, pabyEnd, panPointCount[i], dm))
4089 : {
4090 0 : if (nParts > 1)
4091 0 : delete poMLS;
4092 : else
4093 0 : delete poLS;
4094 0 : returnError();
4095 : }
4096 : }
4097 : }
4098 :
4099 2022 : return poMLS ? static_cast<OGRGeometry *>(poMLS)
4100 2022 : : static_cast<OGRGeometry *>(poLS);
4101 : }
4102 :
4103 982 : case SHPT_POLYGONZ:
4104 : case SHPT_POLYGONZM:
4105 982 : bHasZ = true; /* go on */
4106 : [[fallthrough]];
4107 3013 : case SHPT_POLYGON:
4108 : case SHPT_POLYGONM:
4109 : case SHPT_GENERALPOLYGON:
4110 : {
4111 3013 : if (nGeomType == SHPT_POLYGONM || nGeomType == SHPT_POLYGONZM)
4112 124 : bHasM = true;
4113 :
4114 3013 : returnErrorIf(
4115 : !ReadPartDefs(pabyCur, pabyEnd, nPoints, nParts, nCurves,
4116 : (nGeomType & EXT_SHAPE_CURVE_FLAG) != 0, false));
4117 :
4118 3013 : if (nPoints == 0 || nParts == 0)
4119 : {
4120 6 : OGRPolygon *poPoly = new OGRPolygon();
4121 6 : if (bHasZ)
4122 2 : poPoly->set3D(TRUE);
4123 6 : if (bHasM)
4124 2 : poPoly->setMeasured(TRUE);
4125 6 : return poPoly;
4126 : }
4127 :
4128 3007 : if (nCurves)
4129 : {
4130 41 : GByte *pabyCurBackup = pabyCur;
4131 41 : OGRGeometry *poRet = CreateCurveGeometry(
4132 : SHPT_GENERALPOLYGON, nParts, nPoints, nCurves, bHasZ, bHasM,
4133 : pabyCur, pabyEnd);
4134 41 : if (poRet)
4135 41 : return poRet;
4136 : // In case something went wrong, go on without curves
4137 0 : pabyCur = pabyCurBackup;
4138 : }
4139 :
4140 5932 : std::vector<std::unique_ptr<OGRLinearRing>> apoRings;
4141 2966 : apoRings.reserve(nParts);
4142 :
4143 2966 : dx = dy = dz = 0;
4144 6889 : for (i = 0; i < nParts; i++)
4145 : {
4146 3923 : auto poRing = std::make_unique<FileGDBOGRLinearRing>();
4147 3923 : poRing->setNumPoints(panPointCount[i], FALSE);
4148 :
4149 3923 : XYLineStringSetter lsSetter(poRing->GetPoints());
4150 3923 : if (!ReadXYArray<XYLineStringSetter>(lsSetter, pabyCur, pabyEnd,
4151 3923 : panPointCount[i], dx, dy))
4152 : {
4153 : // For some reason things that papoRings is leaking
4154 : // cppcheck-suppress memleak
4155 0 : returnError();
4156 : }
4157 3923 : apoRings.push_back(std::move(poRing));
4158 : }
4159 :
4160 2966 : if (bHasZ)
4161 : {
4162 1966 : for (i = 0; i < nParts; i++)
4163 : {
4164 986 : apoRings[i]->setCoordinateDimension(3);
4165 :
4166 986 : ZLineStringSetter lszSetter(apoRings[i].get());
4167 986 : if (!ReadZArray<ZLineStringSetter>(
4168 986 : lszSetter, pabyCur, pabyEnd, panPointCount[i], dz))
4169 : {
4170 0 : returnError();
4171 : }
4172 : }
4173 : }
4174 :
4175 2966 : if (bHasM)
4176 : {
4177 122 : GIntBig dm = 0;
4178 248 : for (i = 0; i < nParts; i++)
4179 : {
4180 : // It seems that absence of M is marked with a single byte
4181 : // with value 66. Be more tolerant and only try to parse the
4182 : // M array is there are at least as many remaining bytes as
4183 : // expected points
4184 128 : if (pabyCur + panPointCount[i] > pabyEnd)
4185 : {
4186 2 : while (i != 0)
4187 : {
4188 0 : --i;
4189 0 : apoRings[i]->setMeasured(FALSE);
4190 : }
4191 2 : break;
4192 : }
4193 :
4194 126 : apoRings[i]->setMeasured(TRUE);
4195 :
4196 126 : MLineStringSetter lsmSetter(apoRings[i].get());
4197 126 : if (!ReadMArray<MLineStringSetter>(
4198 126 : lsmSetter, pabyCur, pabyEnd, panPointCount[i], dm))
4199 : {
4200 0 : returnError();
4201 : }
4202 : }
4203 : }
4204 :
4205 2966 : std::unique_ptr<OGRGeometry> poRet;
4206 2966 : if (nParts == 1)
4207 : {
4208 4964 : auto poPoly = std::make_unique<OGRPolygon>();
4209 2482 : poPoly->addRing(std::move(apoRings[0]));
4210 2482 : poRet = std::move(poPoly);
4211 : }
4212 : else
4213 : // Note: ASSUME_INNER_RINGS_IMMEDIATELY_AFTER_OUTER_RING is *not* defined
4214 : #ifdef ASSUME_INNER_RINGS_IMMEDIATELY_AFTER_OUTER_RING
4215 : if (bUseOrganize || !(apoRings[0]->isClockwise()))
4216 : #endif
4217 : {
4218 : /* Slow method: we do a rather expensive topological analysis of
4219 : * the rings to figure out which ones are inner rings from outer
4220 : * rings, and to which outer ring an inner ring belongs too.
4221 : * In most cases, inner rings are CCW oriented and follow
4222 : * immediately the outer ring in which they are included,
4223 : * (that situation is the commented code in the below else
4224 : * branch).
4225 : * In nearly all cases, inner rings are CCW and outer rings
4226 : * are CW oriented, so we could call organizePolygons() with
4227 : * the relatively lightweight METHOD=ONLY_CCW strategy (which
4228 : * is what the shapefile drivers does at time of writing).
4229 : * Unfortunately in https://github.com/OSGeo/gdal/issues/1369,
4230 : * we found likely broken datasets where a polygon with inner
4231 : * rings has its exterior ring with wrong orientation, hence
4232 : * we use the slowest but bullet-proof method.
4233 : */
4234 484 : std::vector<std::unique_ptr<OGRGeometry>> apoPolys;
4235 484 : apoPolys.reserve(nParts);
4236 1925 : for (i = 0; i < nParts; i++)
4237 : {
4238 1441 : auto poPoly = std::make_unique<OGRPolygon>();
4239 1441 : poPoly->addRing(std::move(apoRings[i]));
4240 1441 : apoPolys.push_back(std::move(poPoly));
4241 : }
4242 484 : poRet = OGRGeometryFactory::organizePolygons(apoPolys);
4243 : }
4244 : #ifdef ASSUME_INNER_RINGS_IMMEDIATELY_AFTER_OUTER_RING
4245 : else
4246 : {
4247 : /* Inner rings are CCW oriented and follow immediately the outer
4248 : */
4249 : /* ring (that is CW oriented) in which they are included */
4250 : std::unique_ptr<OGRMultiPolygon> poMulti;
4251 : auto poCur = std::make_unique<OGRPolygon>();
4252 : /* We have already checked that the first ring is CW */
4253 : OGREnvelope sEnvelope;
4254 : apoRings[0]->getEnvelope(&sEnvelope);
4255 : poCur->addRing(std::move(apoRings[0]));
4256 : for (i = 1; i < nParts; i++)
4257 : {
4258 : const int bIsCW = apoRings[i]->isClockwise();
4259 : if (bIsCW)
4260 : {
4261 : if (poMulti == nullptr)
4262 : {
4263 : poMulti = std::make_unique<OGRMultiPolygon>();
4264 : poMulti->addGeometry(std::move(poCur));
4265 : }
4266 : poCur = std::make_unique<OGRPolygon>();
4267 : apoRings[i]->getEnvelope(&sEnvelope);
4268 : poCur->addRing(std::move(apoRings[i]));
4269 : poMulti->addGeometry(std::move(poCur));
4270 : }
4271 : else
4272 : {
4273 : OGRPoint oPoint;
4274 : apoRings[i]->getPoint(0, &oPoint);
4275 : if (poCur)
4276 : {
4277 : poCur->addRing(std::move(apoRings[i]));
4278 : }
4279 : else
4280 : {
4281 : poMulti
4282 : ->getGeometryRef(poMulti->getNumGeometries() -
4283 : 1)
4284 : ->addRing(std::move(apoRings[i]));
4285 : }
4286 : CPLAssert(oPoint.getX() >= sEnvelope.MinX &&
4287 : oPoint.getX() <= sEnvelope.MaxX &&
4288 : oPoint.getY() >= sEnvelope.MinY &&
4289 : oPoint.getY() <= sEnvelope.MaxY);
4290 : }
4291 : }
4292 : if (poCur)
4293 : poRet = std::move(poCur);
4294 : else
4295 : poRet = std::move(poMulti);
4296 : }
4297 : #endif
4298 :
4299 2966 : return poRet.release();
4300 : }
4301 :
4302 284 : case SHPT_MULTIPATCHM:
4303 : case SHPT_MULTIPATCH:
4304 284 : bHasZ = true; /* go on */
4305 : [[fallthrough]];
4306 564 : case SHPT_GENERALMULTIPATCH:
4307 : {
4308 564 : returnErrorIf(!ReadPartDefs(pabyCur, pabyEnd, nPoints, nParts,
4309 : nCurves, false, true));
4310 :
4311 564 : if (nPoints == 0 || nParts == 0)
4312 : {
4313 0 : OGRPolygon *poPoly = new OGRPolygon();
4314 0 : if (bHasZ)
4315 0 : poPoly->setCoordinateDimension(3);
4316 0 : return poPoly;
4317 : }
4318 : int *panPartType =
4319 564 : static_cast<int *>(VSI_MALLOC_VERBOSE(sizeof(int) * nParts));
4320 : // The + 1 is to add an extra element, not actually used, but
4321 : // to please Coverity Scan
4322 : int *panPartStart = static_cast<int *>(
4323 564 : VSI_MALLOC_VERBOSE(sizeof(int) * (nParts + 1)));
4324 : double *padfXYZ = static_cast<double *>(
4325 564 : VSI_MALLOC_VERBOSE(3 * sizeof(double) * nPoints));
4326 564 : double *padfX = padfXYZ;
4327 564 : double *padfY = padfXYZ ? padfXYZ + nPoints : nullptr;
4328 564 : double *padfZ = padfXYZ ? padfXYZ + 2 * nPoints : nullptr;
4329 564 : if (panPartType == nullptr || panPartStart == nullptr ||
4330 : padfXYZ == nullptr)
4331 : {
4332 0 : VSIFree(panPartType);
4333 0 : VSIFree(panPartStart);
4334 0 : VSIFree(padfXYZ);
4335 0 : returnError();
4336 : }
4337 3378 : for (i = 0; i < nParts; i++)
4338 : {
4339 : GUInt32 nPartType;
4340 2814 : if (!ReadVarUInt32(pabyCur, pabyEnd, nPartType))
4341 : {
4342 0 : VSIFree(panPartType);
4343 0 : VSIFree(panPartStart);
4344 0 : VSIFree(padfXYZ);
4345 0 : returnError();
4346 : }
4347 2814 : panPartType[i] = static_cast<int>(nPartType);
4348 : }
4349 564 : dx = dy = dz = 0;
4350 :
4351 564 : XYArraySetter arraySetter(padfX, padfY);
4352 564 : if (!ReadXYArray<XYArraySetter>(arraySetter, pabyCur, pabyEnd,
4353 : nPoints, dx, dy))
4354 : {
4355 0 : VSIFree(panPartType);
4356 0 : VSIFree(panPartStart);
4357 0 : VSIFree(padfXYZ);
4358 0 : returnError();
4359 : }
4360 :
4361 564 : if (bHasZ)
4362 : {
4363 564 : FileGDBArraySetter arrayzSetter(padfZ);
4364 564 : if (!ReadZArray<FileGDBArraySetter>(arrayzSetter, pabyCur,
4365 : pabyEnd, nPoints, dz))
4366 : {
4367 0 : VSIFree(panPartType);
4368 0 : VSIFree(panPartStart);
4369 0 : VSIFree(padfXYZ);
4370 0 : returnError();
4371 : }
4372 : }
4373 : else
4374 : {
4375 0 : memset(padfZ, 0, nPoints * sizeof(double));
4376 : }
4377 :
4378 564 : panPartStart[0] = 0;
4379 2814 : for (i = 1; i < nParts; ++i)
4380 2250 : panPartStart[i] = panPartStart[i - 1] + panPointCount[i - 1];
4381 : // Not used, but avoids a Coverity Scan warning
4382 564 : panPartStart[nParts] = nPoints;
4383 564 : OGRGeometry *poRet = OGRCreateFromMultiPatch(
4384 : static_cast<int>(nParts), panPartStart, panPartType,
4385 : static_cast<int>(nPoints), padfX, padfY, padfZ);
4386 :
4387 564 : VSIFree(panPartType);
4388 564 : VSIFree(panPartStart);
4389 564 : VSIFree(padfXYZ);
4390 :
4391 564 : return poRet;
4392 : }
4393 :
4394 0 : default:
4395 0 : CPLDebug("OpenFileGDB", "Unhandled geometry type = %d",
4396 : static_cast<int>(nGeomType));
4397 0 : break;
4398 : /*
4399 : #define SHPT_GENERALMULTIPOINT 53
4400 : */
4401 : }
4402 0 : return nullptr;
4403 : }
4404 :
4405 : /************************************************************************/
4406 : /* BuildConverter() */
4407 : /************************************************************************/
4408 :
4409 : FileGDBOGRGeometryConverter *
4410 969 : FileGDBOGRGeometryConverter::BuildConverter(const FileGDBGeomField *poGeomField)
4411 : {
4412 969 : return new FileGDBOGRGeometryConverterImpl(poGeomField);
4413 : }
4414 :
4415 : /************************************************************************/
4416 : /* GetGeometryTypeFromESRI() */
4417 : /************************************************************************/
4418 :
4419 : static const struct
4420 : {
4421 : const char *pszStr;
4422 : OGRwkbGeometryType eType;
4423 : } AssocESRIGeomTypeToOGRGeomType[] = {
4424 : {"esriGeometryPoint", wkbPoint},
4425 : {"esriGeometryMultipoint", wkbMultiPoint},
4426 : {"esriGeometryLine", wkbMultiLineString},
4427 : {"esriGeometryPolyline", wkbMultiLineString},
4428 : {"esriGeometryPolygon", wkbMultiPolygon},
4429 : {"esriGeometryMultiPatch", wkbUnknown}};
4430 :
4431 : OGRwkbGeometryType
4432 2799 : FileGDBOGRGeometryConverter::GetGeometryTypeFromESRI(const char *pszESRIType)
4433 : {
4434 9783 : for (size_t i = 0; i < sizeof(AssocESRIGeomTypeToOGRGeomType) /
4435 : sizeof(AssocESRIGeomTypeToOGRGeomType[0]);
4436 : i++)
4437 : {
4438 9783 : if (strcmp(pszESRIType, AssocESRIGeomTypeToOGRGeomType[i].pszStr) == 0)
4439 2799 : return AssocESRIGeomTypeToOGRGeomType[i].eType;
4440 : }
4441 0 : CPLDebug("OpenFileGDB", "Unhandled geometry type : %s", pszESRIType);
4442 0 : return wkbUnknown;
4443 : }
4444 :
4445 : #ifdef _MSC_VER
4446 : #pragma warning(pop)
4447 : #endif
4448 :
4449 : } /* namespace OpenFileGDB */
|