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