Coverage Report

Created: 2026-11-12 08:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/shape/shapelib/dbfopen.c
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  Shapelib
4
 * Purpose:  Implementation of .dbf access API documented in dbf_api.html.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 1999, Frank Warmerdam
9
 * Copyright (c) 2012-2024, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
12
 ******************************************************************************/
13
14
#include "shapefil_private.h"
15
16
#include <assert.h>
17
#include <errno.h>
18
#include <math.h>
19
#include <stdbool.h>
20
#include <stdio.h>
21
#include <stdlib.h>
22
#include <ctype.h>
23
#include <string.h>
24
25
#ifdef USE_CPL
26
#include "cpl_string.h"
27
#else
28
29
#ifndef STRCASECMP
30
#if defined(_MSC_VER)
31
#define STRCASECMP(a, b) (_stricmp(a, b))
32
#elif defined(_WIN32)
33
#define STRCASECMP(a, b) (stricmp(a, b))
34
#else
35
#include <strings.h>
36
#define STRCASECMP(a, b) (strcasecmp(a, b))
37
#endif
38
#endif
39
40
#if defined(_MSC_VER)
41
#if _MSC_VER < 1900
42
#define snprintf _snprintf
43
#endif
44
#elif defined(_WIN32)
45
#ifndef snprintf
46
#define snprintf _snprintf
47
#endif
48
#endif
49
50
#define CPLsprintf sprintf
51
#define CPLsnprintf snprintf
52
#endif
53
54
#ifndef FALSE
55
#define FALSE 0
56
#define TRUE 1
57
#endif
58
59
/* File header size */
60
80.5k
#define XBASE_FILEHDR_SZ 32
61
62
525k
#define HEADER_RECORD_TERMINATOR 0x0D
63
64
/* See http://www.manmrk.net/tutorials/database/xbase/dbf.html */
65
445k
#define END_OF_FILE_CHARACTER 0x1A
66
67
#ifdef USE_CPL
68
CPL_INLINE static void CPL_IGNORE_RET_VAL_INT(CPL_UNUSED int unused)
69
13.5k
{
70
13.5k
}
71
#else
72
#define CPL_IGNORE_RET_VAL_INT(x) x
73
#endif
74
75
/************************************************************************/
76
/*                           DBFWriteHeader()                           */
77
/*                                                                      */
78
/*      This is called to write out the file header, and field          */
79
/*      descriptions before writing any actual data records.  This      */
80
/*      also computes all the DBFDataSet field offset/size/decimals     */
81
/*      and so forth values.                                            */
82
/************************************************************************/
83
84
static void DBFWriteHeader(DBFHandle psDBF)
85
9.12k
{
86
9.12k
    unsigned char abyHeader[XBASE_FILEHDR_SZ] = {0};
87
88
9.12k
    if (!psDBF->bNoHeader)
89
0
        return;
90
91
9.12k
    psDBF->bNoHeader = FALSE;
92
93
    /* -------------------------------------------------------------------- */
94
    /*      Initialize the file header information.                         */
95
    /* -------------------------------------------------------------------- */
96
9.12k
    abyHeader[0] = 0x03; /* memo field? - just copying */
97
98
    /* write out update date */
99
9.12k
    abyHeader[1] = STATIC_CAST(unsigned char, psDBF->nUpdateYearSince1900);
100
9.12k
    abyHeader[2] = STATIC_CAST(unsigned char, psDBF->nUpdateMonth);
101
9.12k
    abyHeader[3] = STATIC_CAST(unsigned char, psDBF->nUpdateDay);
102
103
    /* record count preset at zero */
104
105
9.12k
    abyHeader[8] = STATIC_CAST(unsigned char, psDBF->nHeaderLength % 256);
106
9.12k
    abyHeader[9] = STATIC_CAST(unsigned char, psDBF->nHeaderLength / 256);
107
108
9.12k
    abyHeader[10] = STATIC_CAST(unsigned char, psDBF->nRecordLength % 256);
109
9.12k
    abyHeader[11] = STATIC_CAST(unsigned char, psDBF->nRecordLength / 256);
110
111
9.12k
    abyHeader[29] = STATIC_CAST(unsigned char, psDBF->iLanguageDriver);
112
113
    /* -------------------------------------------------------------------- */
114
    /*      Write the initial 32 byte file header, and all the field        */
115
    /*      descriptions.                                                   */
116
    /* -------------------------------------------------------------------- */
117
9.12k
    psDBF->sHooks.FSeek(psDBF->fp, 0, 0);
118
9.12k
    psDBF->sHooks.FWrite(abyHeader, XBASE_FILEHDR_SZ, 1, psDBF->fp);
119
9.12k
    psDBF->sHooks.FWrite(psDBF->pszHeader, XBASE_FLDHDR_SZ, psDBF->nFields,
120
9.12k
                         psDBF->fp);
121
122
    /* -------------------------------------------------------------------- */
123
    /*      Write out the newline character if there is room for it.        */
124
    /* -------------------------------------------------------------------- */
125
9.12k
    if (psDBF->nHeaderLength >
126
9.12k
        XBASE_FLDHDR_SZ * psDBF->nFields + XBASE_FLDHDR_SZ)
127
9.12k
    {
128
9.12k
        char cNewline = HEADER_RECORD_TERMINATOR;
129
9.12k
        psDBF->sHooks.FWrite(&cNewline, 1, 1, psDBF->fp);
130
9.12k
    }
131
132
    /* -------------------------------------------------------------------- */
133
    /*      If the file is new, add a EOF character.                        */
134
    /* -------------------------------------------------------------------- */
135
9.12k
    if (psDBF->nRecords == 0 && psDBF->bWriteEndOfFileChar)
136
3.97k
    {
137
3.97k
        char ch = END_OF_FILE_CHARACTER;
138
139
3.97k
        psDBF->sHooks.FWrite(&ch, 1, 1, psDBF->fp);
140
3.97k
    }
141
9.12k
}
142
143
/************************************************************************/
144
/*                           DBFFlushRecord()                           */
145
/*                                                                      */
146
/*      Write out the current record if there is one.                   */
147
/************************************************************************/
148
149
static bool DBFFlushRecord(DBFHandle psDBF)
150
680k
{
151
680k
    if (psDBF->bCurrentRecordModified && psDBF->nCurrentRecord > -1)
152
469k
    {
153
469k
        psDBF->bCurrentRecordModified = FALSE;
154
155
469k
        const SAOffset nRecordOffset =
156
469k
            psDBF->nRecordLength *
157
469k
                STATIC_CAST(SAOffset, psDBF->nCurrentRecord) +
158
469k
            psDBF->nHeaderLength;
159
160
        /* -------------------------------------------------------------------- */
161
        /*      Guard FSeek with check for whether we're already at position;   */
162
        /*      no-op FSeeks defeat network filesystems' write buffering.       */
163
        /* -------------------------------------------------------------------- */
164
469k
        if (psDBF->bRequireNextWriteSeek ||
165
432k
            psDBF->sHooks.FTell(psDBF->fp) != nRecordOffset)
166
469k
        {
167
469k
            if (psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0) != 0)
168
0
            {
169
0
                char szMessage[128];
170
0
                snprintf(
171
0
                    szMessage, sizeof(szMessage),
172
0
                    "Failure seeking to position before writing DBF record %d.",
173
0
                    psDBF->nCurrentRecord);
174
0
                psDBF->sHooks.Error(szMessage);
175
0
                return false;
176
0
            }
177
469k
        }
178
179
469k
        if (psDBF->sHooks.FWrite(psDBF->pszCurrentRecord, psDBF->nRecordLength,
180
469k
                                 1, psDBF->fp) != 1)
181
32.9k
        {
182
32.9k
            char szMessage[128];
183
32.9k
            snprintf(szMessage, sizeof(szMessage),
184
32.9k
                     "Failure writing DBF record %d.", psDBF->nCurrentRecord);
185
32.9k
            psDBF->sHooks.Error(szMessage);
186
32.9k
            return false;
187
32.9k
        }
188
189
        /* -------------------------------------------------------------------- */
190
        /*      If next op is also a write, allow possible skipping of FSeek.   */
191
        /* -------------------------------------------------------------------- */
192
436k
        psDBF->bRequireNextWriteSeek = FALSE;
193
194
436k
        if (psDBF->nCurrentRecord == psDBF->nRecords - 1)
195
436k
        {
196
436k
            if (psDBF->bWriteEndOfFileChar)
197
436k
            {
198
436k
                char ch = END_OF_FILE_CHARACTER;
199
436k
                psDBF->sHooks.FWrite(&ch, 1, 1, psDBF->fp);
200
436k
            }
201
436k
        }
202
436k
    }
203
204
647k
    return true;
205
680k
}
206
207
/************************************************************************/
208
/*                           DBFLoadRecord()                            */
209
/************************************************************************/
210
211
static bool DBFLoadRecord(DBFHandle psDBF, int iRecord)
212
3.48M
{
213
3.48M
    if (psDBF->nCurrentRecord != iRecord)
214
116k
    {
215
116k
        if (!DBFFlushRecord(psDBF))
216
0
            return false;
217
218
116k
        const SAOffset nRecordOffset =
219
116k
            psDBF->nRecordLength * STATIC_CAST(SAOffset, iRecord) +
220
116k
            psDBF->nHeaderLength;
221
222
116k
        if (psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, SEEK_SET) != 0)
223
0
        {
224
0
            char szMessage[128];
225
0
            snprintf(szMessage, sizeof(szMessage),
226
0
                     "fseek(%ld) failed on DBF file.",
227
0
                     STATIC_CAST(long, nRecordOffset));
228
0
            psDBF->sHooks.Error(szMessage);
229
0
            return false;
230
0
        }
231
232
116k
        if (psDBF->sHooks.FRead(psDBF->pszCurrentRecord, psDBF->nRecordLength,
233
116k
                                1, psDBF->fp) != 1)
234
3.47k
        {
235
3.47k
            char szMessage[128];
236
3.47k
            snprintf(szMessage, sizeof(szMessage),
237
3.47k
                     "fread(%d) failed on DBF file.", psDBF->nRecordLength);
238
3.47k
            psDBF->sHooks.Error(szMessage);
239
3.47k
            return false;
240
3.47k
        }
241
242
113k
        psDBF->nCurrentRecord = iRecord;
243
        /* -------------------------------------------------------------------- */
244
        /*      Require a seek for next write in case of mixed R/W operations.  */
245
        /* -------------------------------------------------------------------- */
246
113k
        psDBF->bRequireNextWriteSeek = TRUE;
247
113k
    }
248
249
3.47M
    return true;
250
3.48M
}
251
252
/************************************************************************/
253
/*                          DBFUpdateHeader()                           */
254
/************************************************************************/
255
256
void SHPAPI_CALL DBFUpdateHeader(DBFHandle psDBF)
257
11.0k
{
258
11.0k
    if (psDBF->bNoHeader)
259
5.15k
        DBFWriteHeader(psDBF);
260
261
11.0k
    if (!DBFFlushRecord(psDBF))
262
53
        return;
263
264
10.9k
    psDBF->sHooks.FSeek(psDBF->fp, 0, 0);
265
266
10.9k
    unsigned char abyFileHeader[XBASE_FILEHDR_SZ] = {0};
267
10.9k
    psDBF->sHooks.FRead(abyFileHeader, 1, sizeof(abyFileHeader), psDBF->fp);
268
269
10.9k
    abyFileHeader[1] = STATIC_CAST(unsigned char, psDBF->nUpdateYearSince1900);
270
10.9k
    abyFileHeader[2] = STATIC_CAST(unsigned char, psDBF->nUpdateMonth);
271
10.9k
    abyFileHeader[3] = STATIC_CAST(unsigned char, psDBF->nUpdateDay);
272
10.9k
    abyFileHeader[4] = STATIC_CAST(unsigned char, psDBF->nRecords & 0xFF);
273
10.9k
    abyFileHeader[5] =
274
10.9k
        STATIC_CAST(unsigned char, (psDBF->nRecords >> 8) & 0xFF);
275
10.9k
    abyFileHeader[6] =
276
10.9k
        STATIC_CAST(unsigned char, (psDBF->nRecords >> 16) & 0xFF);
277
10.9k
    abyFileHeader[7] =
278
10.9k
        STATIC_CAST(unsigned char, (psDBF->nRecords >> 24) & 0xFF);
279
280
10.9k
    psDBF->sHooks.FSeek(psDBF->fp, 0, 0);
281
10.9k
    psDBF->sHooks.FWrite(abyFileHeader, sizeof(abyFileHeader), 1, psDBF->fp);
282
283
10.9k
    psDBF->sHooks.FFlush(psDBF->fp);
284
10.9k
}
285
286
/************************************************************************/
287
/*                       DBFSetLastModifiedDate()                       */
288
/************************************************************************/
289
290
void SHPAPI_CALL DBFSetLastModifiedDate(DBFHandle psDBF, int nYYSince1900,
291
                                        int nMM, int nDD)
292
29.7k
{
293
29.7k
    psDBF->nUpdateYearSince1900 = nYYSince1900;
294
29.7k
    psDBF->nUpdateMonth = nMM;
295
29.7k
    psDBF->nUpdateDay = nDD;
296
29.7k
}
297
298
/************************************************************************/
299
/*                              DBFOpen()                               */
300
/*                                                                      */
301
/*      Open a .dbf file.                                               */
302
/************************************************************************/
303
304
DBFHandle SHPAPI_CALL DBFOpen(const char *pszFilename, const char *pszAccess)
305
342
{
306
342
    SAHooks sHooks;
307
308
342
    SASetupDefaultHooks(&sHooks);
309
310
342
    return DBFOpenLL(pszFilename, pszAccess, &sHooks);
311
342
}
312
313
/************************************************************************/
314
/*                     DBFGetLenWithoutExtension()                      */
315
/************************************************************************/
316
317
static int DBFGetLenWithoutExtension(const char *pszBasename)
318
18.7k
{
319
18.7k
    const int nLen = STATIC_CAST(int, strlen(pszBasename));
320
18.7k
    for (int i = nLen - 1;
321
73.9k
         i > 0 && pszBasename[i] != '/' && pszBasename[i] != '\\'; i--)
322
73.6k
    {
323
73.6k
        if (pszBasename[i] == '.')
324
18.4k
        {
325
18.4k
            return i;
326
18.4k
        }
327
73.6k
    }
328
342
    return nLen;
329
18.7k
}
330
331
/************************************************************************/
332
/*                              DBFOpen()                               */
333
/*                                                                      */
334
/*      Open a .dbf file.                                               */
335
/************************************************************************/
336
337
DBFHandle SHPAPI_CALL DBFOpenLL(const char *pszFilename, const char *pszAccess,
338
                                const SAHooks *psHooks)
339
14.7k
{
340
    /* -------------------------------------------------------------------- */
341
    /*      We only allow the access strings "rb" and "r+".                  */
342
    /* -------------------------------------------------------------------- */
343
14.7k
    if (strcmp(pszAccess, "r") != 0 && strcmp(pszAccess, "r+") != 0 &&
344
342
        strcmp(pszAccess, "rb") != 0 && strcmp(pszAccess, "rb+") != 0 &&
345
0
        strcmp(pszAccess, "r+b") != 0)
346
0
        return SHPLIB_NULLPTR;
347
348
14.7k
    if (strcmp(pszAccess, "r") == 0)
349
14.4k
        pszAccess = "rb";
350
351
14.7k
    if (strcmp(pszAccess, "r+") == 0)
352
0
        pszAccess = "rb+";
353
354
    /* -------------------------------------------------------------------- */
355
    /*      Compute the base (layer) name.  If there is any extension       */
356
    /*      on the passed in filename we will strip it off.                 */
357
    /* -------------------------------------------------------------------- */
358
14.7k
    const int nLenWithoutExtension = DBFGetLenWithoutExtension(pszFilename);
359
14.7k
    char *pszFullname = STATIC_CAST(char *, malloc(nLenWithoutExtension + 5));
360
14.7k
    if (!pszFullname)
361
0
    {
362
0
        return SHPLIB_NULLPTR;
363
0
    }
364
14.7k
    memcpy(pszFullname, pszFilename, nLenWithoutExtension);
365
14.7k
    memcpy(pszFullname + nLenWithoutExtension, ".dbf", 5);
366
367
14.7k
    DBFHandle psDBF = STATIC_CAST(DBFHandle, calloc(1, sizeof(DBFInfo)));
368
14.7k
    if (!psDBF)
369
0
    {
370
0
        free(pszFullname);
371
0
        return SHPLIB_NULLPTR;
372
0
    }
373
14.7k
    psDBF->fp = psHooks->FOpen(pszFullname, pszAccess, psHooks->pvUserData);
374
14.7k
    memcpy(&(psDBF->sHooks), psHooks, sizeof(SAHooks));
375
376
14.7k
    if (psDBF->fp == SHPLIB_NULLPTR)
377
1.16k
    {
378
1.16k
        memcpy(pszFullname + nLenWithoutExtension, ".DBF", 5);
379
1.16k
        psDBF->fp =
380
1.16k
            psDBF->sHooks.FOpen(pszFullname, pszAccess, psHooks->pvUserData);
381
1.16k
    }
382
383
14.7k
    memcpy(pszFullname + nLenWithoutExtension, ".cpg", 5);
384
14.7k
    SAFile pfCPG = psHooks->FOpen(pszFullname, "r", psHooks->pvUserData);
385
14.7k
    if (pfCPG == SHPLIB_NULLPTR)
386
14.7k
    {
387
14.7k
        memcpy(pszFullname + nLenWithoutExtension, ".CPG", 5);
388
14.7k
        pfCPG = psHooks->FOpen(pszFullname, "r", psHooks->pvUserData);
389
14.7k
    }
390
391
14.7k
    free(pszFullname);
392
393
14.7k
    if (psDBF->fp == SHPLIB_NULLPTR)
394
897
    {
395
897
        free(psDBF);
396
897
        if (pfCPG)
397
7
            psHooks->FClose(pfCPG);
398
897
        return SHPLIB_NULLPTR;
399
897
    }
400
401
13.8k
    psDBF->bNoHeader = FALSE;
402
13.8k
    psDBF->nCurrentRecord = -1;
403
13.8k
    psDBF->bCurrentRecordModified = FALSE;
404
405
    /* -------------------------------------------------------------------- */
406
    /*  Read Table Header info                                              */
407
    /* -------------------------------------------------------------------- */
408
13.8k
    const int nBufSize = 500;
409
13.8k
    unsigned char *pabyBuf = STATIC_CAST(unsigned char *, malloc(nBufSize));
410
13.8k
    if (!pabyBuf)
411
0
    {
412
0
        psDBF->sHooks.FClose(psDBF->fp);
413
0
        if (pfCPG)
414
0
            psHooks->FClose(pfCPG);
415
0
        free(psDBF);
416
0
        return SHPLIB_NULLPTR;
417
0
    }
418
13.8k
    if (psDBF->sHooks.FRead(pabyBuf, XBASE_FILEHDR_SZ, 1, psDBF->fp) != 1)
419
304
    {
420
304
        psDBF->sHooks.FClose(psDBF->fp);
421
304
        if (pfCPG)
422
6
            psDBF->sHooks.FClose(pfCPG);
423
304
        free(pabyBuf);
424
304
        free(psDBF);
425
304
        return SHPLIB_NULLPTR;
426
304
    }
427
428
13.5k
    DBFSetLastModifiedDate(psDBF, pabyBuf[1], pabyBuf[2], pabyBuf[3]);
429
430
13.5k
    psDBF->nRecords = pabyBuf[4] | (pabyBuf[5] << 8) | (pabyBuf[6] << 16) |
431
13.5k
                      ((pabyBuf[7] & 0x7f) << 24);
432
433
13.5k
    const int nHeadLen = pabyBuf[8] | (pabyBuf[9] << 8);
434
13.5k
    psDBF->nHeaderLength = nHeadLen;
435
13.5k
    psDBF->nRecordLength = pabyBuf[10] | (pabyBuf[11] << 8);
436
13.5k
    psDBF->iLanguageDriver = pabyBuf[29];
437
438
13.5k
    if (psDBF->nRecordLength == 0 || nHeadLen < XBASE_FILEHDR_SZ)
439
201
    {
440
201
        psDBF->sHooks.FClose(psDBF->fp);
441
201
        if (pfCPG)
442
7
            psDBF->sHooks.FClose(pfCPG);
443
201
        free(pabyBuf);
444
201
        free(psDBF);
445
201
        return SHPLIB_NULLPTR;
446
201
    }
447
448
13.3k
    const int nFields = (nHeadLen - XBASE_FILEHDR_SZ) / XBASE_FLDHDR_SZ;
449
13.3k
    psDBF->nFields = nFields;
450
451
13.3k
    assert(psDBF->nRecordLength < 65536);
452
13.3k
    psDBF->pszCurrentRecord = STATIC_CAST(char *, malloc(psDBF->nRecordLength));
453
13.3k
    if (!psDBF->pszCurrentRecord)
454
0
    {
455
0
        psDBF->sHooks.FClose(psDBF->fp);
456
0
        if (pfCPG)
457
0
            psDBF->sHooks.FClose(pfCPG);
458
0
        free(pabyBuf);
459
0
        free(psDBF);
460
0
        return SHPLIB_NULLPTR;
461
0
    }
462
463
    /* -------------------------------------------------------------------- */
464
    /*  Figure out the code page from the LDID and CPG                      */
465
    /* -------------------------------------------------------------------- */
466
13.3k
    psDBF->pszCodePage = SHPLIB_NULLPTR;
467
13.3k
    if (pfCPG)
468
40
    {
469
40
        memset(pabyBuf, 0, nBufSize);
470
40
        psDBF->sHooks.FRead(pabyBuf, 1, nBufSize - 1, pfCPG);
471
40
        const size_t n = strcspn(REINTERPRET_CAST(char *, pabyBuf), "\n\r");
472
40
        if (n > 0)
473
29
        {
474
29
            pabyBuf[n] = '\0';
475
29
            psDBF->pszCodePage = STATIC_CAST(char *, malloc(n + 1));
476
29
            if (psDBF->pszCodePage)
477
29
                memcpy(psDBF->pszCodePage, pabyBuf, n + 1);
478
29
        }
479
40
        psDBF->sHooks.FClose(pfCPG);
480
40
    }
481
13.3k
    if (psDBF->pszCodePage == SHPLIB_NULLPTR && pabyBuf[29] != 0)
482
12.1k
    {
483
12.1k
        snprintf(REINTERPRET_CAST(char *, pabyBuf), nBufSize, "LDID/%d",
484
12.1k
                 psDBF->iLanguageDriver);
485
12.1k
        psDBF->pszCodePage = STATIC_CAST(
486
12.1k
            char *, malloc(strlen(REINTERPRET_CAST(char *, pabyBuf)) + 1));
487
12.1k
        if (psDBF->pszCodePage)
488
12.1k
            strcpy(psDBF->pszCodePage, REINTERPRET_CAST(char *, pabyBuf));
489
12.1k
    }
490
491
    /* -------------------------------------------------------------------- */
492
    /*  Read in Field Definitions                                           */
493
    /* -------------------------------------------------------------------- */
494
495
    // To please Coverity Scan
496
13.3k
    assert(nHeadLen < 65536);
497
13.3k
    unsigned char *pabyBufNew =
498
13.3k
        STATIC_CAST(unsigned char *, realloc(pabyBuf, nHeadLen));
499
13.3k
    if (!pabyBufNew)
500
0
    {
501
0
        psDBF->sHooks.FClose(psDBF->fp);
502
0
        free(pabyBuf);
503
0
        free(psDBF->pszCurrentRecord);
504
0
        free(psDBF->pszCodePage);
505
0
        free(psDBF);
506
0
        return SHPLIB_NULLPTR;
507
0
    }
508
13.3k
    pabyBuf = pabyBufNew;
509
13.3k
    psDBF->pszHeader = REINTERPRET_CAST(char *, pabyBuf);
510
511
13.3k
    psDBF->sHooks.FSeek(psDBF->fp, XBASE_FILEHDR_SZ, 0);
512
13.3k
    if (psDBF->sHooks.FRead(pabyBuf, nHeadLen - XBASE_FILEHDR_SZ, 1,
513
13.3k
                            psDBF->fp) != 1)
514
3.81k
    {
515
3.81k
        psDBF->sHooks.FClose(psDBF->fp);
516
3.81k
        free(pabyBuf);
517
3.81k
        free(psDBF->pszCurrentRecord);
518
3.81k
        free(psDBF->pszCodePage);
519
3.81k
        free(psDBF);
520
3.81k
        return SHPLIB_NULLPTR;
521
3.81k
    }
522
523
9.55k
    psDBF->panFieldOffset = STATIC_CAST(int *, malloc(sizeof(int) * nFields));
524
9.55k
    psDBF->panFieldSize = STATIC_CAST(int *, malloc(sizeof(int) * nFields));
525
9.55k
    psDBF->panFieldDecimals = STATIC_CAST(int *, malloc(sizeof(int) * nFields));
526
9.55k
    psDBF->pachFieldType = STATIC_CAST(char *, malloc(sizeof(char) * nFields));
527
9.55k
    if (!psDBF->panFieldOffset || !psDBF->panFieldSize ||
528
9.55k
        !psDBF->panFieldDecimals || !psDBF->pachFieldType)
529
0
    {
530
0
        DBFClose(psDBF);
531
0
        return SHPLIB_NULLPTR;
532
0
    }
533
534
521k
    for (int iField = 0; iField < nFields; iField++)
535
516k
    {
536
516k
        const unsigned char *pabyFInfo = pabyBuf + iField * XBASE_FLDHDR_SZ;
537
516k
        if (pabyFInfo[0] == HEADER_RECORD_TERMINATOR)
538
4.04k
        {
539
4.04k
            psDBF->nFields = iField;
540
4.04k
            break;
541
4.04k
        }
542
543
511k
        if (pabyFInfo[11] == 'N' || pabyFInfo[11] == 'F')
544
31.8k
        {
545
31.8k
            psDBF->panFieldSize[iField] = pabyFInfo[16];
546
31.8k
            psDBF->panFieldDecimals[iField] = pabyFInfo[17];
547
31.8k
        }
548
480k
        else
549
480k
        {
550
480k
            psDBF->panFieldSize[iField] = pabyFInfo[16];
551
480k
            psDBF->panFieldDecimals[iField] = 0;
552
553
            /*
554
            ** The following seemed to be used sometimes to handle files with
555
            long
556
            ** string fields, but in other cases (such as bug 1202) the decimals
557
            field
558
            ** just seems to indicate some sort of preferred formatting, not
559
            very
560
            ** wide fields.  So I have disabled this code.  FrankW.
561
                    psDBF->panFieldSize[iField] = pabyFInfo[16] +
562
            pabyFInfo[17]*256; psDBF->panFieldDecimals[iField] = 0;
563
            */
564
480k
        }
565
566
511k
        psDBF->pachFieldType[iField] = STATIC_CAST(char, pabyFInfo[11]);
567
511k
        if (iField == 0)
568
8.37k
            psDBF->panFieldOffset[iField] = 1;
569
503k
        else
570
503k
            psDBF->panFieldOffset[iField] = psDBF->panFieldOffset[iField - 1] +
571
503k
                                            psDBF->panFieldSize[iField - 1];
572
511k
    }
573
574
    /* Check that the total width of fields does not exceed the record width */
575
9.55k
    if (psDBF->nFields > 0 && psDBF->panFieldOffset[psDBF->nFields - 1] +
576
8.37k
                                      psDBF->panFieldSize[psDBF->nFields - 1] >
577
8.37k
                                  psDBF->nRecordLength)
578
1.05k
    {
579
1.05k
        DBFClose(psDBF);
580
1.05k
        return SHPLIB_NULLPTR;
581
1.05k
    }
582
583
8.49k
    DBFSetWriteEndOfFileChar(psDBF, TRUE);
584
585
8.49k
    psDBF->bRequireNextWriteSeek = TRUE;
586
587
8.49k
    return (psDBF);
588
9.55k
}
589
590
/************************************************************************/
591
/*                              DBFClose()                              */
592
/************************************************************************/
593
594
void SHPAPI_CALL DBFClose(DBFHandle psDBF)
595
13.5k
{
596
13.5k
    if (psDBF == SHPLIB_NULLPTR)
597
0
        return;
598
599
    /* -------------------------------------------------------------------- */
600
    /*      Write out header if not already written.                        */
601
    /* -------------------------------------------------------------------- */
602
13.5k
    if (psDBF->bNoHeader)
603
1.00k
        DBFWriteHeader(psDBF);
604
605
13.5k
    CPL_IGNORE_RET_VAL_INT(DBFFlushRecord(psDBF));
606
607
    /* -------------------------------------------------------------------- */
608
    /*      Update last access date, and number of records if we have       */
609
    /*      write access.                                                   */
610
    /* -------------------------------------------------------------------- */
611
13.5k
    if (psDBF->bUpdated)
612
2.91k
        DBFUpdateHeader(psDBF);
613
614
    /* -------------------------------------------------------------------- */
615
    /*      Close, and free resources.                                      */
616
    /* -------------------------------------------------------------------- */
617
13.5k
    psDBF->sHooks.FClose(psDBF->fp);
618
619
13.5k
    if (psDBF->panFieldOffset != SHPLIB_NULLPTR)
620
13.4k
    {
621
13.4k
        free(psDBF->panFieldOffset);
622
13.4k
        free(psDBF->panFieldSize);
623
13.4k
        free(psDBF->panFieldDecimals);
624
13.4k
        free(psDBF->pachFieldType);
625
13.4k
    }
626
627
13.5k
    if (psDBF->pszWorkField != SHPLIB_NULLPTR)
628
1.91k
        free(psDBF->pszWorkField);
629
630
13.5k
    free(psDBF->pszHeader);
631
13.5k
    free(psDBF->pszCurrentRecord);
632
13.5k
    free(psDBF->pszCodePage);
633
634
13.5k
    free(psDBF);
635
13.5k
}
636
637
/************************************************************************/
638
/*                             DBFCreate()                              */
639
/*                                                                      */
640
/* Create a new .dbf file with default code page LDID/87 (0x57)         */
641
/************************************************************************/
642
643
DBFHandle SHPAPI_CALL DBFCreate(const char *pszFilename)
644
0
{
645
0
    return DBFCreateEx(pszFilename, "LDID/87");  // 0x57
646
0
}
647
648
/************************************************************************/
649
/*                            DBFCreateEx()                             */
650
/*                                                                      */
651
/*      Create a new .dbf file.                                         */
652
/************************************************************************/
653
654
DBFHandle SHPAPI_CALL DBFCreateEx(const char *pszFilename,
655
                                  const char *pszCodePage)
656
0
{
657
0
    SAHooks sHooks;
658
659
0
    SASetupDefaultHooks(&sHooks);
660
661
0
    return DBFCreateLL(pszFilename, pszCodePage, &sHooks);
662
0
}
663
664
/************************************************************************/
665
/*                             DBFCreate()                              */
666
/*                                                                      */
667
/*      Create a new .dbf file.                                         */
668
/************************************************************************/
669
670
DBFHandle SHPAPI_CALL DBFCreateLL(const char *pszFilename,
671
                                  const char *pszCodePage,
672
                                  const SAHooks *psHooks)
673
3.97k
{
674
    /* -------------------------------------------------------------------- */
675
    /*      Compute the base (layer) name.  If there is any extension       */
676
    /*      on the passed in filename we will strip it off.                 */
677
    /* -------------------------------------------------------------------- */
678
3.97k
    const int nLenWithoutExtension = DBFGetLenWithoutExtension(pszFilename);
679
3.97k
    char *pszFullname = STATIC_CAST(char *, malloc(nLenWithoutExtension + 5));
680
3.97k
    if (!pszFullname)
681
0
        return SHPLIB_NULLPTR;
682
3.97k
    memcpy(pszFullname, pszFilename, nLenWithoutExtension);
683
3.97k
    memcpy(pszFullname + nLenWithoutExtension, ".dbf", 5);
684
685
    /* -------------------------------------------------------------------- */
686
    /*      Create the file.                                                */
687
    /* -------------------------------------------------------------------- */
688
3.97k
    SAFile fp = psHooks->FOpen(pszFullname, "wb+", psHooks->pvUserData);
689
3.97k
    if (fp == SHPLIB_NULLPTR)
690
4
    {
691
4
        const size_t nMessageLen = strlen(pszFullname) + 256;
692
4
        char *pszMessage = STATIC_CAST(char *, malloc(nMessageLen));
693
4
        if (pszMessage)
694
4
        {
695
4
            snprintf(pszMessage, nMessageLen, "Failed to create file %s: %s",
696
4
                     pszFullname, strerror(errno));
697
4
            psHooks->Error(pszMessage);
698
4
            free(pszMessage);
699
4
        }
700
701
4
        free(pszFullname);
702
4
        return SHPLIB_NULLPTR;
703
4
    }
704
705
3.97k
    memcpy(pszFullname + nLenWithoutExtension, ".cpg", 5);
706
3.97k
    int ldid = -1;
707
3.97k
    if (pszCodePage != SHPLIB_NULLPTR)
708
3.97k
    {
709
3.97k
        if (strncmp(pszCodePage, "LDID/", 5) == 0)
710
3.97k
        {
711
3.97k
            ldid = atoi(pszCodePage + 5);
712
3.97k
            if (ldid > 255)
713
0
                ldid = -1;  // don't use 0 to indicate out of range as LDID/0 is
714
                            // a valid one
715
3.97k
        }
716
3.97k
        if (ldid < 0)
717
0
        {
718
0
            SAFile fpCPG =
719
0
                psHooks->FOpen(pszFullname, "w", psHooks->pvUserData);
720
0
            psHooks->FWrite(
721
0
                CONST_CAST(void *, STATIC_CAST(const void *, pszCodePage)),
722
0
                strlen(pszCodePage), 1, fpCPG);
723
0
            psHooks->FClose(fpCPG);
724
0
        }
725
3.97k
    }
726
3.97k
    if (pszCodePage == SHPLIB_NULLPTR || ldid >= 0)
727
3.97k
    {
728
3.97k
        psHooks->Remove(pszFullname, psHooks->pvUserData);
729
3.97k
    }
730
731
3.97k
    free(pszFullname);
732
733
    /* -------------------------------------------------------------------- */
734
    /*      Create the info structure.                                      */
735
    /* -------------------------------------------------------------------- */
736
3.97k
    DBFHandle psDBF = STATIC_CAST(DBFHandle, calloc(1, sizeof(DBFInfo)));
737
3.97k
    if (!psDBF)
738
0
    {
739
0
        return SHPLIB_NULLPTR;
740
0
    }
741
742
3.97k
    memcpy(&(psDBF->sHooks), psHooks, sizeof(SAHooks));
743
3.97k
    psDBF->fp = fp;
744
3.97k
    psDBF->nRecords = 0;
745
3.97k
    psDBF->nFields = 0;
746
3.97k
    psDBF->nRecordLength = 1;
747
3.97k
    psDBF->nHeaderLength =
748
3.97k
        XBASE_FILEHDR_SZ + 1; /* + 1 for HEADER_RECORD_TERMINATOR */
749
750
3.97k
    psDBF->panFieldOffset = SHPLIB_NULLPTR;
751
3.97k
    psDBF->panFieldSize = SHPLIB_NULLPTR;
752
3.97k
    psDBF->panFieldDecimals = SHPLIB_NULLPTR;
753
3.97k
    psDBF->pachFieldType = SHPLIB_NULLPTR;
754
3.97k
    psDBF->pszHeader = SHPLIB_NULLPTR;
755
756
3.97k
    psDBF->nCurrentRecord = -1;
757
3.97k
    psDBF->bCurrentRecordModified = FALSE;
758
3.97k
    psDBF->pszCurrentRecord = SHPLIB_NULLPTR;
759
760
3.97k
    psDBF->bNoHeader = TRUE;
761
762
3.97k
    psDBF->iLanguageDriver = ldid > 0 ? ldid : 0;
763
3.97k
    psDBF->pszCodePage = SHPLIB_NULLPTR;
764
3.97k
    if (pszCodePage)
765
3.97k
    {
766
3.97k
        psDBF->pszCodePage =
767
3.97k
            STATIC_CAST(char *, malloc(strlen(pszCodePage) + 1));
768
3.97k
        if (psDBF->pszCodePage)
769
3.97k
            strcpy(psDBF->pszCodePage, pszCodePage);
770
3.97k
    }
771
3.97k
    DBFSetLastModifiedDate(psDBF, 95, 7, 26); /* dummy date */
772
773
3.97k
    DBFSetWriteEndOfFileChar(psDBF, TRUE);
774
775
3.97k
    psDBF->bRequireNextWriteSeek = TRUE;
776
777
3.97k
    return (psDBF);
778
3.97k
}
779
780
/************************************************************************/
781
/*                            DBFAddField()                             */
782
/*                                                                      */
783
/*      Add a field to a newly created .dbf or to an existing one       */
784
/************************************************************************/
785
786
int SHPAPI_CALL DBFAddField(DBFHandle psDBF, const char *pszFieldName,
787
                            DBFFieldType eType, int nWidth, int nDecimals)
788
85
{
789
85
    char chNativeType;
790
791
85
    if (eType == FTLogical)
792
0
        chNativeType = 'L';
793
85
    else if (eType == FTDate)
794
0
        chNativeType = 'D';
795
85
    else if (eType == FTString)
796
0
        chNativeType = 'C';
797
85
    else
798
85
        chNativeType = 'N';
799
800
85
    return DBFAddNativeFieldType(psDBF, pszFieldName, chNativeType, nWidth,
801
85
                                 nDecimals);
802
85
}
803
804
/************************************************************************/
805
/*                        DBFGetNullCharacter()                         */
806
/************************************************************************/
807
808
static char DBFGetNullCharacter(char chType)
809
1.93M
{
810
1.93M
    switch (chType)
811
1.93M
    {
812
115k
        case 'N':
813
115k
        case 'F':
814
115k
            return '*';
815
0
        case 'D':
816
0
            return '0';
817
0
        case 'L':
818
0
            return '?';
819
1.81M
        default:
820
1.81M
            return ' ';
821
1.93M
    }
822
1.93M
}
823
824
/************************************************************************/
825
/*                            DBFAddField()                             */
826
/*                                                                      */
827
/*      Add a field to a newly created .dbf file before any records     */
828
/*      are written.                                                    */
829
/************************************************************************/
830
831
int SHPAPI_CALL DBFAddNativeFieldType(DBFHandle psDBF, const char *pszFieldName,
832
                                      char chType, int nWidth, int nDecimals)
833
34.6k
{
834
    /* make sure that everything is written in .dbf */
835
34.6k
    if (!DBFFlushRecord(psDBF))
836
8
        return -1;
837
838
34.5k
    if (psDBF->nHeaderLength + XBASE_FLDHDR_SZ > 65535)
839
0
    {
840
0
        char szMessage[128];
841
0
        snprintf(szMessage, sizeof(szMessage),
842
0
                 "Cannot add field %s. Header length limit reached "
843
0
                 "(max 65535 bytes, 2046 fields).",
844
0
                 pszFieldName);
845
0
        psDBF->sHooks.Error(szMessage);
846
0
        return -1;
847
0
    }
848
849
    /* -------------------------------------------------------------------- */
850
    /*      Do some checking to ensure we can add records to this file.     */
851
    /* -------------------------------------------------------------------- */
852
34.5k
    if (nWidth < 1)
853
0
        return -1;
854
855
34.5k
    if (nWidth > XBASE_FLD_MAX_WIDTH)
856
2
        nWidth = XBASE_FLD_MAX_WIDTH;
857
858
34.5k
    if (psDBF->nRecordLength + nWidth > 65535)
859
1.32k
    {
860
1.32k
        char szMessage[128];
861
1.32k
        snprintf(szMessage, sizeof(szMessage),
862
1.32k
                 "Cannot add field %s. Record length limit reached "
863
1.32k
                 "(max 65535 bytes).",
864
1.32k
                 pszFieldName);
865
1.32k
        psDBF->sHooks.Error(szMessage);
866
1.32k
        return -1;
867
1.32k
    }
868
869
33.2k
    const int nOldRecordLength = psDBF->nRecordLength;
870
33.2k
    const int nOldHeaderLength = psDBF->nHeaderLength;
871
872
    /* -------------------------------------------------------------------- */
873
    /*      realloc all the arrays larger to hold the additional field      */
874
    /*      information.                                                    */
875
    /* -------------------------------------------------------------------- */
876
877
33.2k
    int *panFieldOffsetNew =
878
33.2k
        STATIC_CAST(int *, realloc(psDBF->panFieldOffset,
879
33.2k
                                   sizeof(int) * (psDBF->nFields + 1)));
880
881
33.2k
    int *panFieldSizeNew =
882
33.2k
        STATIC_CAST(int *, realloc(psDBF->panFieldSize,
883
33.2k
                                   sizeof(int) * (psDBF->nFields + 1)));
884
885
33.2k
    int *panFieldDecimalsNew =
886
33.2k
        STATIC_CAST(int *, realloc(psDBF->panFieldDecimals,
887
33.2k
                                   sizeof(int) * (psDBF->nFields + 1)));
888
889
33.2k
    char *pachFieldTypeNew =
890
33.2k
        STATIC_CAST(char *, realloc(psDBF->pachFieldType,
891
33.2k
                                    sizeof(char) * (psDBF->nFields + 1)));
892
893
33.2k
    char *pszHeaderNew =
894
33.2k
        STATIC_CAST(char *, realloc(psDBF->pszHeader,
895
33.2k
                                    (psDBF->nFields + 1) * XBASE_FLDHDR_SZ));
896
897
    /* -------------------------------------------------------------------- */
898
    /*      Make the current record buffer appropriately larger.            */
899
    /* -------------------------------------------------------------------- */
900
33.2k
    char *pszCurrentRecordNew =
901
33.2k
        STATIC_CAST(char *, realloc(psDBF->pszCurrentRecord,
902
33.2k
                                    psDBF->nRecordLength + nWidth));
903
904
33.2k
    if (panFieldOffsetNew)
905
33.2k
        psDBF->panFieldOffset = panFieldOffsetNew;
906
33.2k
    if (panFieldSizeNew)
907
33.2k
        psDBF->panFieldSize = panFieldSizeNew;
908
33.2k
    if (panFieldDecimalsNew)
909
33.2k
        psDBF->panFieldDecimals = panFieldDecimalsNew;
910
33.2k
    if (pachFieldTypeNew)
911
33.2k
        psDBF->pachFieldType = pachFieldTypeNew;
912
33.2k
    if (pszHeaderNew)
913
33.2k
        psDBF->pszHeader = pszHeaderNew;
914
33.2k
    if (pszCurrentRecordNew)
915
33.2k
        psDBF->pszCurrentRecord = pszCurrentRecordNew;
916
917
33.2k
    if (!panFieldOffsetNew || !panFieldSizeNew || !panFieldDecimalsNew ||
918
33.2k
        !pachFieldTypeNew || !pszHeaderNew || !pszCurrentRecordNew)
919
0
    {
920
0
        psDBF->sHooks.Error("Out of memory");
921
0
        return -1;
922
0
    }
923
924
    /* alloc record */
925
33.2k
    char *pszRecord = SHPLIB_NULLPTR;
926
33.2k
    if (!psDBF->bNoHeader)
927
1.75k
    {
928
1.75k
        pszRecord = STATIC_CAST(char *, malloc(psDBF->nRecordLength + nWidth));
929
1.75k
        if (!pszRecord)
930
0
        {
931
0
            psDBF->sHooks.Error("Out of memory");
932
0
            return -1;
933
0
        }
934
1.75k
    }
935
936
33.2k
    psDBF->nFields++;
937
938
    /* -------------------------------------------------------------------- */
939
    /*      Assign the new field information fields.                        */
940
    /* -------------------------------------------------------------------- */
941
33.2k
    psDBF->panFieldOffset[psDBF->nFields - 1] = psDBF->nRecordLength;
942
33.2k
    psDBF->nRecordLength += nWidth;
943
33.2k
    psDBF->panFieldSize[psDBF->nFields - 1] = nWidth;
944
33.2k
    psDBF->panFieldDecimals[psDBF->nFields - 1] = nDecimals;
945
33.2k
    psDBF->pachFieldType[psDBF->nFields - 1] = chType;
946
947
    /* -------------------------------------------------------------------- */
948
    /*      Extend the required header information.                         */
949
    /* -------------------------------------------------------------------- */
950
33.2k
    psDBF->nHeaderLength += XBASE_FLDHDR_SZ;
951
33.2k
    psDBF->bUpdated = FALSE;
952
953
33.2k
    char *pszFInfo = psDBF->pszHeader + XBASE_FLDHDR_SZ * (psDBF->nFields - 1);
954
955
1.09M
    for (int i = 0; i < XBASE_FLDHDR_SZ; i++)
956
1.06M
        pszFInfo[i] = '\0';
957
958
33.2k
    strncpy(pszFInfo, pszFieldName, XBASE_FLDNAME_LEN_WRITE);
959
960
33.2k
    pszFInfo[11] = psDBF->pachFieldType[psDBF->nFields - 1];
961
962
33.2k
    if (chType == 'C')
963
32.8k
    {
964
32.8k
        pszFInfo[16] = STATIC_CAST(unsigned char, nWidth % 256);
965
32.8k
        pszFInfo[17] = STATIC_CAST(unsigned char, nWidth / 256);
966
32.8k
    }
967
458
    else
968
458
    {
969
458
        pszFInfo[16] = STATIC_CAST(unsigned char, nWidth);
970
458
        pszFInfo[17] = STATIC_CAST(unsigned char, nDecimals);
971
458
    }
972
973
    /* we're done if dealing with new .dbf */
974
33.2k
    if (psDBF->bNoHeader)
975
31.5k
        return (psDBF->nFields - 1);
976
977
    /* -------------------------------------------------------------------- */
978
    /*      For existing .dbf file, shift records                           */
979
    /* -------------------------------------------------------------------- */
980
981
1.75k
    const char chFieldFill = DBFGetNullCharacter(chType);
982
983
1.75k
    SAOffset nRecordOffset;
984
319k
    for (int i = psDBF->nRecords - 1; i >= 0; --i)
985
318k
    {
986
318k
        nRecordOffset =
987
318k
            nOldRecordLength * STATIC_CAST(SAOffset, i) + nOldHeaderLength;
988
989
        /* load record */
990
318k
        psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
991
318k
        if (psDBF->sHooks.FRead(pszRecord, nOldRecordLength, 1, psDBF->fp) != 1)
992
117
        {
993
117
            free(pszRecord);
994
117
            return -1;
995
117
        }
996
997
        /* set new field's value to NULL */
998
317k
        memset(pszRecord + nOldRecordLength, chFieldFill, nWidth);
999
1000
317k
        nRecordOffset = psDBF->nRecordLength * STATIC_CAST(SAOffset, i) +
1001
317k
                        psDBF->nHeaderLength;
1002
1003
        /* move record to the new place*/
1004
317k
        psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
1005
317k
        psDBF->sHooks.FWrite(pszRecord, psDBF->nRecordLength, 1, psDBF->fp);
1006
317k
    }
1007
1008
1.63k
    if (psDBF->bWriteEndOfFileChar)
1009
1.63k
    {
1010
1.63k
        char ch = END_OF_FILE_CHARACTER;
1011
1012
1.63k
        nRecordOffset =
1013
1.63k
            psDBF->nRecordLength * STATIC_CAST(SAOffset, psDBF->nRecords) +
1014
1.63k
            psDBF->nHeaderLength;
1015
1016
1.63k
        psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
1017
1.63k
        psDBF->sHooks.FWrite(&ch, 1, 1, psDBF->fp);
1018
1.63k
    }
1019
1020
    /* free record */
1021
1.63k
    free(pszRecord);
1022
1023
    /* force update of header with new header, record length and new field */
1024
1.63k
    psDBF->bNoHeader = TRUE;
1025
1.63k
    DBFUpdateHeader(psDBF);
1026
1027
1.63k
    psDBF->nCurrentRecord = -1;
1028
1.63k
    psDBF->bCurrentRecordModified = FALSE;
1029
1.63k
    psDBF->bUpdated = TRUE;
1030
1031
1.63k
    return (psDBF->nFields - 1);
1032
1.75k
}
1033
1034
/************************************************************************/
1035
/*                          DBFReadAttribute()                          */
1036
/*                                                                      */
1037
/*      Read one of the attribute fields of a record.                   */
1038
/************************************************************************/
1039
1040
static void *DBFReadAttribute(DBFHandle psDBF, int hEntity, int iField,
1041
                              char chReqType)
1042
873k
{
1043
    /* -------------------------------------------------------------------- */
1044
    /*      Verify selection.                                               */
1045
    /* -------------------------------------------------------------------- */
1046
873k
    if (hEntity < 0 || hEntity >= psDBF->nRecords)
1047
0
        return SHPLIB_NULLPTR;
1048
1049
873k
    if (iField < 0 || iField >= psDBF->nFields)
1050
0
        return SHPLIB_NULLPTR;
1051
1052
    /* -------------------------------------------------------------------- */
1053
    /*     Have we read the record?                                         */
1054
    /* -------------------------------------------------------------------- */
1055
873k
    if (!DBFLoadRecord(psDBF, hEntity))
1056
0
        return SHPLIB_NULLPTR;
1057
1058
873k
    const unsigned char *pabyRec =
1059
873k
        REINTERPRET_CAST(const unsigned char *, psDBF->pszCurrentRecord);
1060
1061
    /* -------------------------------------------------------------------- */
1062
    /*      Ensure we have room to extract the target field.                */
1063
    /* -------------------------------------------------------------------- */
1064
873k
    if (psDBF->panFieldSize[iField] >= psDBF->nWorkFieldLength)
1065
3.01k
    {
1066
3.01k
        psDBF->nWorkFieldLength = psDBF->panFieldSize[iField] + 100;
1067
3.01k
        if (psDBF->pszWorkField == SHPLIB_NULLPTR)
1068
1.91k
            psDBF->pszWorkField =
1069
1.91k
                STATIC_CAST(char *, malloc(psDBF->nWorkFieldLength));
1070
1.10k
        else
1071
1.10k
            psDBF->pszWorkField = STATIC_CAST(
1072
3.01k
                char *, realloc(psDBF->pszWorkField, psDBF->nWorkFieldLength));
1073
3.01k
    }
1074
1075
    /* -------------------------------------------------------------------- */
1076
    /*      Extract the requested field.                                    */
1077
    /* -------------------------------------------------------------------- */
1078
873k
    memcpy(psDBF->pszWorkField,
1079
873k
           REINTERPRET_CAST(const char *, pabyRec) +
1080
873k
               psDBF->panFieldOffset[iField],
1081
873k
           psDBF->panFieldSize[iField]);
1082
873k
    psDBF->pszWorkField[psDBF->panFieldSize[iField]] = '\0';
1083
1084
873k
    void *pReturnField = psDBF->pszWorkField;
1085
1086
    /* -------------------------------------------------------------------- */
1087
    /*      Decode the field.                                               */
1088
    /* -------------------------------------------------------------------- */
1089
873k
    if (chReqType == 'I')
1090
0
    {
1091
0
        psDBF->fieldValue.nIntField = atoi(psDBF->pszWorkField);
1092
1093
0
        pReturnField = &(psDBF->fieldValue.nIntField);
1094
0
    }
1095
873k
    else if (chReqType == 'N')
1096
0
    {
1097
0
        psDBF->fieldValue.dfDoubleField =
1098
0
            psDBF->sHooks.Atof(psDBF->pszWorkField);
1099
1100
0
        pReturnField = &(psDBF->fieldValue.dfDoubleField);
1101
0
    }
1102
1103
/* -------------------------------------------------------------------- */
1104
/*      Should we trim white space off the string attribute value?      */
1105
/* -------------------------------------------------------------------- */
1106
873k
#ifdef TRIM_DBF_WHITESPACE
1107
873k
    else
1108
873k
    {
1109
873k
        char *pchSrc = psDBF->pszWorkField;
1110
873k
        char *pchDst = pchSrc;
1111
1112
1.16M
        while (*pchSrc == ' ')
1113
291k
            pchSrc++;
1114
1115
40.6M
        while (*pchSrc != '\0')
1116
39.8M
            *(pchDst++) = *(pchSrc++);
1117
873k
        *pchDst = '\0';
1118
1119
979k
        while (pchDst != psDBF->pszWorkField && *(--pchDst) == ' ')
1120
106k
            *pchDst = '\0';
1121
873k
    }
1122
873k
#endif
1123
1124
873k
    return pReturnField;
1125
873k
}
1126
1127
/************************************************************************/
1128
/*                        DBFReadIntAttribute()                         */
1129
/*                                                                      */
1130
/*      Read an integer attribute.                                      */
1131
/************************************************************************/
1132
1133
int SHPAPI_CALL DBFReadIntegerAttribute(DBFHandle psDBF, int iRecord,
1134
                                        int iField)
1135
0
{
1136
0
    int *pnValue =
1137
0
        STATIC_CAST(int *, DBFReadAttribute(psDBF, iRecord, iField, 'I'));
1138
1139
0
    if (pnValue == SHPLIB_NULLPTR)
1140
0
        return 0;
1141
0
    else
1142
0
        return *pnValue;
1143
0
}
1144
1145
/************************************************************************/
1146
/*                        DBFReadDoubleAttribute()                      */
1147
/*                                                                      */
1148
/*      Read a double attribute.                                        */
1149
/************************************************************************/
1150
1151
double SHPAPI_CALL DBFReadDoubleAttribute(DBFHandle psDBF, int iRecord,
1152
                                          int iField)
1153
0
{
1154
0
    double *pdValue =
1155
0
        STATIC_CAST(double *, DBFReadAttribute(psDBF, iRecord, iField, 'N'));
1156
1157
0
    if (pdValue == SHPLIB_NULLPTR)
1158
0
        return 0.0;
1159
0
    else
1160
0
        return *pdValue;
1161
0
}
1162
1163
/************************************************************************/
1164
/*                        DBFReadStringAttribute()                      */
1165
/*                                                                      */
1166
/*      Read a string attribute.                                        */
1167
/************************************************************************/
1168
1169
const char SHPAPI_CALL1(*)
1170
    DBFReadStringAttribute(DBFHandle psDBF, int iRecord, int iField)
1171
829k
{
1172
829k
    return STATIC_CAST(const char *,
1173
829k
                       DBFReadAttribute(psDBF, iRecord, iField, 'C'));
1174
829k
}
1175
1176
/************************************************************************/
1177
/*                        DBFReadLogicalAttribute()                     */
1178
/*                                                                      */
1179
/*      Read a logical attribute.                                       */
1180
/************************************************************************/
1181
1182
const char SHPAPI_CALL1(*)
1183
    DBFReadLogicalAttribute(DBFHandle psDBF, int iRecord, int iField)
1184
44.4k
{
1185
44.4k
    return STATIC_CAST(const char *,
1186
44.4k
                       DBFReadAttribute(psDBF, iRecord, iField, 'L'));
1187
44.4k
}
1188
1189
/************************************************************************/
1190
/*                        DBFReadDateAttribute()                        */
1191
/*                                                                      */
1192
/*      Read a date attribute.                                          */
1193
/************************************************************************/
1194
1195
SHPDate SHPAPI_CALL DBFReadDateAttribute(DBFHandle psDBF, int iRecord,
1196
                                         int iField)
1197
0
{
1198
0
    const char *pdateValue = STATIC_CAST(
1199
0
        const char *, DBFReadAttribute(psDBF, iRecord, iField, 'D'));
1200
1201
0
    SHPDate date;
1202
1203
0
    if (pdateValue == SHPLIB_NULLPTR)
1204
0
    {
1205
0
        date.year = 0;
1206
0
        date.month = 0;
1207
0
        date.day = 0;
1208
0
    }
1209
0
    else if (3 != sscanf(pdateValue, "%4d%2d%2d", &date.year, &date.month,
1210
0
                         &date.day))
1211
0
    {
1212
0
        date.year = 0;
1213
0
        date.month = 0;
1214
0
        date.day = 0;
1215
0
    }
1216
1217
0
    return date;
1218
0
}
1219
1220
/************************************************************************/
1221
/*                         DBFIsValueNULL()                             */
1222
/*                                                                      */
1223
/*      Return TRUE if the passed string is NULL.                       */
1224
/************************************************************************/
1225
1226
static bool DBFIsValueNULL(char chType, const char *pszValue, int size)
1227
854k
{
1228
854k
    if (pszValue == SHPLIB_NULLPTR)
1229
0
        return true;
1230
1231
854k
    switch (chType)
1232
854k
    {
1233
91.4k
        case 'N':
1234
104k
        case 'F':
1235
            /*
1236
            ** We accept all asterisks or all blanks as NULL
1237
            ** though according to the spec I think it should be all
1238
            ** asterisks.
1239
            */
1240
104k
            if (pszValue[0] == '*')
1241
24.6k
                return true;
1242
1243
83.7k
            for (int i = 0; pszValue[i] != '\0'; i++)
1244
75.5k
            {
1245
75.5k
                if (pszValue[i] != ' ')
1246
71.5k
                    return false;
1247
75.5k
            }
1248
8.23k
            return true;
1249
1250
47.1k
        case 'D':
1251
47.1k
        {
1252
47.1k
            const char DIGIT_ZERO = '0';
1253
            /* NULL date fields have value "00000000" or "0"*size */
1254
            /* Some DBF files have fields filled with spaces */
1255
            /* (trimmed by DBFReadStringAttribute) to indicate null */
1256
            /* values for dates (#4265). */
1257
            /* And others have '       0': https://lists.osgeo.org/pipermail/gdal-dev/2023-November/058010.html */
1258
            /* And others just empty string: https://github.com/OSGeo/gdal/issues/10405 */
1259
47.1k
            if (pszValue[0] == 0 || strncmp(pszValue, "00000000", 8) == 0 ||
1260
39.6k
                strcmp(pszValue, " ") == 0 || strcmp(pszValue, "0") == 0)
1261
7.72k
                return true;
1262
53.9k
            for (int i = 0; i < size; i++)
1263
53.0k
                if (pszValue[i] != DIGIT_ZERO)
1264
38.5k
                    return false;
1265
853
            return true;
1266
39.4k
        }
1267
1268
44.4k
        case 'L':
1269
            /* NULL boolean fields have value "?" */
1270
44.4k
            return pszValue[0] == '?';
1271
1272
658k
        default:
1273
            /* empty string fields are considered NULL */
1274
658k
            return strlen(pszValue) == 0;
1275
854k
    }
1276
854k
}
1277
1278
/************************************************************************/
1279
/*                         DBFIsAttributeNULL()                         */
1280
/*                                                                      */
1281
/*      Return TRUE if value for field is NULL.                         */
1282
/*                                                                      */
1283
/*      Contributed by Jim Matthews.                                    */
1284
/************************************************************************/
1285
1286
int SHPAPI_CALL DBFIsAttributeNULL(const DBFHandle psDBF, int iRecord,
1287
                                   int iField)
1288
172k
{
1289
172k
    const char *pszValue = DBFReadStringAttribute(psDBF, iRecord, iField);
1290
1291
172k
    if (pszValue == SHPLIB_NULLPTR)
1292
0
        return TRUE;
1293
1294
172k
    return DBFIsValueNULL(psDBF->pachFieldType[iField], pszValue,
1295
172k
                          psDBF->panFieldSize[iField]);
1296
172k
}
1297
1298
/************************************************************************/
1299
/*                          DBFGetFieldCount()                          */
1300
/*                                                                      */
1301
/*      Return the number of fields in this table.                      */
1302
/************************************************************************/
1303
1304
int SHPAPI_CALL DBFGetFieldCount(const DBFHandle psDBF)
1305
74.8k
{
1306
74.8k
    return (psDBF->nFields);
1307
74.8k
}
1308
1309
/************************************************************************/
1310
/*                         DBFGetRecordCount()                          */
1311
/*                                                                      */
1312
/*      Return the number of records in this table.                     */
1313
/************************************************************************/
1314
1315
int SHPAPI_CALL DBFGetRecordCount(const DBFHandle psDBF)
1316
1.00M
{
1317
1.00M
    return (psDBF->nRecords);
1318
1.00M
}
1319
1320
/************************************************************************/
1321
/*                          DBFGetFieldInfo()                           */
1322
/*                                                                      */
1323
/*      Return any requested information about the field.               */
1324
/*      pszFieldName must be at least XBASE_FLDNAME_LEN_READ+1 (=12)    */
1325
/*      bytes long.                                                     */
1326
/************************************************************************/
1327
1328
DBFFieldType SHPAPI_CALL DBFGetFieldInfo(const DBFHandle psDBF, int iField,
1329
                                         char *pszFieldName, int *pnWidth,
1330
                                         int *pnDecimals)
1331
362k
{
1332
362k
    if (iField < 0 || iField >= psDBF->nFields)
1333
0
        return (FTInvalid);
1334
1335
362k
    if (pnWidth != SHPLIB_NULLPTR)
1336
362k
        *pnWidth = psDBF->panFieldSize[iField];
1337
1338
362k
    if (pnDecimals != SHPLIB_NULLPTR)
1339
362k
        *pnDecimals = psDBF->panFieldDecimals[iField];
1340
1341
362k
    if (pszFieldName != SHPLIB_NULLPTR)
1342
362k
    {
1343
362k
        strncpy(pszFieldName,
1344
362k
                STATIC_CAST(char *, psDBF->pszHeader) +
1345
362k
                    iField * XBASE_FLDHDR_SZ,
1346
362k
                XBASE_FLDNAME_LEN_READ);
1347
362k
        pszFieldName[XBASE_FLDNAME_LEN_READ] = '\0';
1348
362k
        for (int i = XBASE_FLDNAME_LEN_READ - 1;
1349
430k
             i > 0 && pszFieldName[i] == ' '; i--)
1350
67.8k
            pszFieldName[i] = '\0';
1351
362k
    }
1352
1353
362k
    if (psDBF->pachFieldType[iField] == 'L')
1354
6.01k
        return (FTLogical);
1355
1356
356k
    else if (psDBF->pachFieldType[iField] == 'D')
1357
4.13k
        return (FTDate);
1358
1359
352k
    else if (psDBF->pachFieldType[iField] == 'N' ||
1360
333k
             psDBF->pachFieldType[iField] == 'F')
1361
22.8k
    {
1362
22.8k
        if (psDBF->panFieldDecimals[iField] > 0 ||
1363
2.84k
            psDBF->panFieldSize[iField] >= 10)
1364
20.9k
            return (FTDouble);
1365
1.92k
        else
1366
1.92k
            return (FTInteger);
1367
22.8k
    }
1368
329k
    else
1369
329k
    {
1370
329k
        return (FTString);
1371
329k
    }
1372
362k
}
1373
1374
/************************************************************************/
1375
/*                         DBFWriteAttribute()                          */
1376
/*                                                                      */
1377
/*      Write an attribute record to the file.                          */
1378
/************************************************************************/
1379
1380
static bool DBFWriteAttribute(DBFHandle psDBF, int hEntity, int iField,
1381
                              void *pValue)
1382
2.41M
{
1383
    /* -------------------------------------------------------------------- */
1384
    /*      Is this a valid record?                                         */
1385
    /* -------------------------------------------------------------------- */
1386
2.41M
    if (hEntity < 0 || hEntity > psDBF->nRecords)
1387
0
        return false;
1388
1389
2.41M
    if (psDBF->bNoHeader)
1390
2.87k
        DBFWriteHeader(psDBF);
1391
1392
    /* -------------------------------------------------------------------- */
1393
    /*      Is this a brand new record?                                     */
1394
    /* -------------------------------------------------------------------- */
1395
2.41M
    if (hEntity == psDBF->nRecords)
1396
499k
    {
1397
499k
        if (!DBFFlushRecord(psDBF))
1398
32.3k
            return false;
1399
1400
467k
        psDBF->nRecords++;
1401
301M
        for (int i = 0; i < psDBF->nRecordLength; i++)
1402
301M
            psDBF->pszCurrentRecord[i] = ' ';
1403
1404
467k
        psDBF->nCurrentRecord = hEntity;
1405
467k
    }
1406
1407
    /* -------------------------------------------------------------------- */
1408
    /*      Is this an existing record, but different than the last one     */
1409
    /*      we accessed?                                                    */
1410
    /* -------------------------------------------------------------------- */
1411
2.38M
    if (!DBFLoadRecord(psDBF, hEntity))
1412
0
        return false;
1413
1414
2.38M
    unsigned char *pabyRec =
1415
2.38M
        REINTERPRET_CAST(unsigned char *, psDBF->pszCurrentRecord);
1416
1417
2.38M
    psDBF->bCurrentRecordModified = TRUE;
1418
2.38M
    psDBF->bUpdated = TRUE;
1419
1420
    /* -------------------------------------------------------------------- */
1421
    /*      Translate NULL value to valid DBF file representation.          */
1422
    /*                                                                      */
1423
    /*      Contributed by Jim Matthews.                                    */
1424
    /* -------------------------------------------------------------------- */
1425
2.38M
    if (pValue == SHPLIB_NULLPTR)
1426
1.92M
    {
1427
1.92M
        memset(pabyRec + psDBF->panFieldOffset[iField],
1428
1.92M
               DBFGetNullCharacter(psDBF->pachFieldType[iField]),
1429
1.92M
               psDBF->panFieldSize[iField]);
1430
1.92M
        return true;
1431
1.92M
    }
1432
1433
    /* -------------------------------------------------------------------- */
1434
    /*      Assign all the record fields.                                   */
1435
    /* -------------------------------------------------------------------- */
1436
453k
    bool nRetResult = true;
1437
1438
453k
    switch (psDBF->pachFieldType[iField])
1439
453k
    {
1440
0
        case 'D':
1441
2.04k
        case 'N':
1442
2.04k
        case 'F':
1443
2.04k
        {
1444
2.04k
            int nWidth = psDBF->panFieldSize[iField];
1445
1446
2.04k
            char szSField[XBASE_FLD_MAX_WIDTH + 1];
1447
2.04k
            if (STATIC_CAST(int, sizeof(szSField)) - 2 < nWidth)
1448
1
                nWidth = sizeof(szSField) - 2;
1449
1450
2.04k
            char szFormat[20];
1451
2.04k
            snprintf(szFormat, sizeof(szFormat), "%%%d.%df", nWidth,
1452
2.04k
                     psDBF->panFieldDecimals[iField]);
1453
2.04k
            const double value = *STATIC_CAST(double *, pValue);
1454
2.04k
            CPLsnprintf(szSField, sizeof(szSField), szFormat, value);
1455
2.04k
            szSField[sizeof(szSField) - 1] = '\0';
1456
2.04k
            if (STATIC_CAST(int, strlen(szSField)) >
1457
2.04k
                psDBF->panFieldSize[iField])
1458
39
            {
1459
39
                szSField[psDBF->panFieldSize[iField]] = '\0';
1460
39
                nRetResult = psDBF->sHooks.Atof(szSField) == value;
1461
39
            }
1462
2.04k
            memcpy(REINTERPRET_CAST(char *,
1463
2.04k
                                    pabyRec + psDBF->panFieldOffset[iField]),
1464
2.04k
                   szSField, strlen(szSField));
1465
2.04k
            break;
1466
2.04k
        }
1467
1468
0
        case 'L':
1469
0
            if (psDBF->panFieldSize[iField] >= 1 &&
1470
0
                (*STATIC_CAST(char *, pValue) == 'F' ||
1471
0
                 *STATIC_CAST(char *, pValue) == 'T'))
1472
0
            {
1473
0
                *(pabyRec + psDBF->panFieldOffset[iField]) =
1474
0
                    *STATIC_CAST(char *, pValue);
1475
0
            }
1476
0
            else
1477
0
            {
1478
0
                nRetResult = false;
1479
0
            }
1480
0
            break;
1481
1482
451k
        default:
1483
451k
        {
1484
451k
            int j;
1485
451k
            if (STATIC_CAST(int, strlen(STATIC_CAST(char *, pValue))) >
1486
451k
                psDBF->panFieldSize[iField])
1487
6.89k
            {
1488
6.89k
                j = psDBF->panFieldSize[iField];
1489
6.89k
                nRetResult = false;
1490
6.89k
            }
1491
444k
            else
1492
444k
            {
1493
444k
                memset(pabyRec + psDBF->panFieldOffset[iField], ' ',
1494
444k
                       psDBF->panFieldSize[iField]);
1495
444k
                j = STATIC_CAST(int, strlen(STATIC_CAST(char *, pValue)));
1496
444k
            }
1497
1498
451k
            strncpy(REINTERPRET_CAST(char *,
1499
451k
                                     pabyRec + psDBF->panFieldOffset[iField]),
1500
451k
                    STATIC_CAST(const char *, pValue), j);
1501
451k
            break;
1502
2.04k
        }
1503
453k
    }
1504
1505
453k
    return nRetResult;
1506
453k
}
1507
1508
/************************************************************************/
1509
/*                     DBFWriteAttributeDirectly()                      */
1510
/*                                                                      */
1511
/*      Write an attribute record to the file, but without any          */
1512
/*      reformatting based on type.  The provided buffer is written     */
1513
/*      as is to the field position in the record.                      */
1514
/************************************************************************/
1515
1516
int SHPAPI_CALL DBFWriteAttributeDirectly(DBFHandle psDBF, int hEntity,
1517
                                          int iField, const void *pValue)
1518
3.24k
{
1519
    /* -------------------------------------------------------------------- */
1520
    /*      Is this a valid record?                                         */
1521
    /* -------------------------------------------------------------------- */
1522
3.24k
    if (hEntity < 0 || hEntity > psDBF->nRecords)
1523
0
        return (FALSE);
1524
1525
3.24k
    if (psDBF->bNoHeader)
1526
84
        DBFWriteHeader(psDBF);
1527
1528
    /* -------------------------------------------------------------------- */
1529
    /*      Is this a brand new record?                                     */
1530
    /* -------------------------------------------------------------------- */
1531
3.24k
    if (hEntity == psDBF->nRecords)
1532
1.31k
    {
1533
1.31k
        if (!DBFFlushRecord(psDBF))
1534
0
            return FALSE;
1535
1536
1.31k
        psDBF->nRecords++;
1537
311k
        for (int i = 0; i < psDBF->nRecordLength; i++)
1538
310k
            psDBF->pszCurrentRecord[i] = ' ';
1539
1540
1.31k
        psDBF->nCurrentRecord = hEntity;
1541
1.31k
    }
1542
1543
    /* -------------------------------------------------------------------- */
1544
    /*      Is this an existing record, but different than the last one     */
1545
    /*      we accessed?                                                    */
1546
    /* -------------------------------------------------------------------- */
1547
3.24k
    if (!DBFLoadRecord(psDBF, hEntity))
1548
0
        return FALSE;
1549
1550
3.24k
    if (iField >= 0)
1551
3.24k
    {
1552
3.24k
        unsigned char *pabyRec =
1553
3.24k
            REINTERPRET_CAST(unsigned char *, psDBF->pszCurrentRecord);
1554
1555
        /* -------------------------------------------------------------------- */
1556
        /*      Assign all the record fields.                                   */
1557
        /* -------------------------------------------------------------------- */
1558
3.24k
        int j;
1559
3.24k
        if (STATIC_CAST(int, strlen(STATIC_CAST(const char *, pValue))) >
1560
3.24k
            psDBF->panFieldSize[iField])
1561
0
            j = psDBF->panFieldSize[iField];
1562
3.24k
        else
1563
3.24k
        {
1564
3.24k
            memset(pabyRec + psDBF->panFieldOffset[iField], ' ',
1565
3.24k
                   psDBF->panFieldSize[iField]);
1566
3.24k
            j = STATIC_CAST(int, strlen(STATIC_CAST(const char *, pValue)));
1567
3.24k
        }
1568
1569
3.24k
        memcpy(
1570
3.24k
            REINTERPRET_CAST(char *, pabyRec + psDBF->panFieldOffset[iField]),
1571
3.24k
            STATIC_CAST(const char *, pValue), j);
1572
3.24k
    }
1573
1574
3.24k
    psDBF->bCurrentRecordModified = TRUE;
1575
3.24k
    psDBF->bUpdated = TRUE;
1576
1577
3.24k
    return (TRUE);
1578
3.24k
}
1579
1580
/************************************************************************/
1581
/*                      DBFWriteDoubleAttribute()                       */
1582
/*                                                                      */
1583
/*      Write a double attribute.                                       */
1584
/************************************************************************/
1585
1586
int SHPAPI_CALL DBFWriteDoubleAttribute(DBFHandle psDBF, int iRecord,
1587
                                        int iField, double dValue)
1588
503
{
1589
503
    return (DBFWriteAttribute(psDBF, iRecord, iField,
1590
503
                              STATIC_CAST(void *, &dValue)));
1591
503
}
1592
1593
/************************************************************************/
1594
/*                      DBFWriteIntegerAttribute()                      */
1595
/*                                                                      */
1596
/*      Write an integer attribute.                                     */
1597
/************************************************************************/
1598
1599
int SHPAPI_CALL DBFWriteIntegerAttribute(DBFHandle psDBF, int iRecord,
1600
                                         int iField, int nValue)
1601
11.3k
{
1602
11.3k
    double dValue = nValue;
1603
1604
11.3k
    return (DBFWriteAttribute(psDBF, iRecord, iField,
1605
11.3k
                              STATIC_CAST(void *, &dValue)));
1606
11.3k
}
1607
1608
/************************************************************************/
1609
/*                      DBFWriteStringAttribute()                       */
1610
/*                                                                      */
1611
/*      Write a string attribute.                                       */
1612
/************************************************************************/
1613
1614
int SHPAPI_CALL DBFWriteStringAttribute(DBFHandle psDBF, int iRecord,
1615
                                        int iField, const char *pszValue)
1616
474k
{
1617
474k
    return (
1618
474k
        DBFWriteAttribute(psDBF, iRecord, iField,
1619
474k
                          STATIC_CAST(void *, CONST_CAST(char *, pszValue))));
1620
474k
}
1621
1622
/************************************************************************/
1623
/*                      DBFWriteNULLAttribute()                         */
1624
/*                                                                      */
1625
/*      Write a NULL attribute.                                         */
1626
/************************************************************************/
1627
1628
int SHPAPI_CALL DBFWriteNULLAttribute(DBFHandle psDBF, int iRecord, int iField)
1629
1.92M
{
1630
1.92M
    return (DBFWriteAttribute(psDBF, iRecord, iField, SHPLIB_NULLPTR));
1631
1.92M
}
1632
1633
/************************************************************************/
1634
/*                      DBFWriteLogicalAttribute()                      */
1635
/*                                                                      */
1636
/*      Write a logical attribute.                                      */
1637
/************************************************************************/
1638
1639
int SHPAPI_CALL DBFWriteLogicalAttribute(DBFHandle psDBF, int iRecord,
1640
                                         int iField, const char lValue)
1641
0
{
1642
0
    return (
1643
0
        DBFWriteAttribute(psDBF, iRecord, iField,
1644
0
                          STATIC_CAST(void *, CONST_CAST(char *, &lValue))));
1645
0
}
1646
1647
/************************************************************************/
1648
/*                      DBFWriteDateAttribute()                         */
1649
/*                                                                      */
1650
/*      Write a date attribute.                                         */
1651
/************************************************************************/
1652
1653
int SHPAPI_CALL DBFWriteDateAttribute(DBFHandle psDBF, int iRecord, int iField,
1654
                                      const SHPDate *lValue)
1655
0
{
1656
0
    if (SHPLIB_NULLPTR == lValue)
1657
0
        return false;
1658
    /* check for supported digit range, but do not check for valid date */
1659
0
    if (lValue->year < 0 || lValue->year > 9999)
1660
0
        return false;
1661
0
    if (lValue->month < 0 || lValue->month > 99)
1662
0
        return false;
1663
0
    if (lValue->day < 0 || lValue->day > 99)
1664
0
        return false;
1665
0
    char dateValue[9]; /* "yyyyMMdd\0" */
1666
0
    snprintf(dateValue, sizeof(dateValue), "%04d%02d%02d", lValue->year,
1667
0
             lValue->month, lValue->day);
1668
0
    return (DBFWriteAttributeDirectly(psDBF, iRecord, iField, dateValue));
1669
0
}
1670
1671
/************************************************************************/
1672
/*                         DBFWriteTuple()                              */
1673
/*                                                                      */
1674
/*      Write an attribute record to the file.                          */
1675
/************************************************************************/
1676
1677
int SHPAPI_CALL DBFWriteTuple(DBFHandle psDBF, int hEntity,
1678
                              const void *pRawTuple)
1679
0
{
1680
    /* -------------------------------------------------------------------- */
1681
    /*      Is this a valid record?                                         */
1682
    /* -------------------------------------------------------------------- */
1683
0
    if (hEntity < 0 || hEntity > psDBF->nRecords)
1684
0
        return (FALSE);
1685
1686
0
    if (psDBF->bNoHeader)
1687
0
        DBFWriteHeader(psDBF);
1688
1689
    /* -------------------------------------------------------------------- */
1690
    /*      Is this a brand new record?                                     */
1691
    /* -------------------------------------------------------------------- */
1692
0
    if (hEntity == psDBF->nRecords)
1693
0
    {
1694
0
        if (!DBFFlushRecord(psDBF))
1695
0
            return FALSE;
1696
1697
0
        psDBF->nRecords++;
1698
0
        for (int i = 0; i < psDBF->nRecordLength; i++)
1699
0
            psDBF->pszCurrentRecord[i] = ' ';
1700
1701
0
        psDBF->nCurrentRecord = hEntity;
1702
0
    }
1703
1704
    /* -------------------------------------------------------------------- */
1705
    /*      Is this an existing record, but different than the last one     */
1706
    /*      we accessed?                                                    */
1707
    /* -------------------------------------------------------------------- */
1708
0
    if (!DBFLoadRecord(psDBF, hEntity))
1709
0
        return FALSE;
1710
1711
0
    unsigned char *pabyRec =
1712
0
        REINTERPRET_CAST(unsigned char *, psDBF->pszCurrentRecord);
1713
1714
0
    memcpy(pabyRec, pRawTuple, psDBF->nRecordLength);
1715
1716
0
    psDBF->bCurrentRecordModified = TRUE;
1717
0
    psDBF->bUpdated = TRUE;
1718
1719
0
    return (TRUE);
1720
0
}
1721
1722
/************************************************************************/
1723
/*                            DBFReadTuple()                            */
1724
/*                                                                      */
1725
/*      Read a complete record.  Note that the result is only valid     */
1726
/*      till the next record read for any reason.                       */
1727
/************************************************************************/
1728
1729
const char SHPAPI_CALL1(*) DBFReadTuple(DBFHandle psDBF, int hEntity)
1730
0
{
1731
0
    if (hEntity < 0 || hEntity >= psDBF->nRecords)
1732
0
        return SHPLIB_NULLPTR;
1733
1734
0
    if (!DBFLoadRecord(psDBF, hEntity))
1735
0
        return SHPLIB_NULLPTR;
1736
1737
0
    return STATIC_CAST(const char *, psDBF->pszCurrentRecord);
1738
0
}
1739
1740
/************************************************************************/
1741
/*                          DBFCloneEmpty()                             */
1742
/*                                                                      */
1743
/*      Create a new .dbf file with same code page and field            */
1744
/*      definitions as the given handle.                                */
1745
/************************************************************************/
1746
1747
DBFHandle SHPAPI_CALL DBFCloneEmpty(const DBFHandle psDBF,
1748
                                    const char *pszFilename)
1749
0
{
1750
0
    DBFHandle newDBF =
1751
0
        DBFCreateLL(pszFilename, psDBF->pszCodePage, &psDBF->sHooks);
1752
0
    if (newDBF == SHPLIB_NULLPTR)
1753
0
        return SHPLIB_NULLPTR;
1754
1755
0
    newDBF->nFields = psDBF->nFields;
1756
0
    newDBF->nRecordLength = psDBF->nRecordLength;
1757
0
    newDBF->nHeaderLength = psDBF->nHeaderLength;
1758
1759
0
    if (psDBF->pszHeader)
1760
0
    {
1761
0
        newDBF->pszHeader =
1762
0
            STATIC_CAST(char *, malloc(XBASE_FLDHDR_SZ * psDBF->nFields));
1763
0
        memcpy(newDBF->pszHeader, psDBF->pszHeader,
1764
0
               XBASE_FLDHDR_SZ * psDBF->nFields);
1765
0
    }
1766
1767
0
    newDBF->panFieldOffset =
1768
0
        STATIC_CAST(int *, malloc(sizeof(int) * psDBF->nFields));
1769
0
    memcpy(newDBF->panFieldOffset, psDBF->panFieldOffset,
1770
0
           sizeof(int) * psDBF->nFields);
1771
0
    newDBF->panFieldSize =
1772
0
        STATIC_CAST(int *, malloc(sizeof(int) * psDBF->nFields));
1773
0
    memcpy(newDBF->panFieldSize, psDBF->panFieldSize,
1774
0
           sizeof(int) * psDBF->nFields);
1775
0
    newDBF->panFieldDecimals =
1776
0
        STATIC_CAST(int *, malloc(sizeof(int) * psDBF->nFields));
1777
0
    memcpy(newDBF->panFieldDecimals, psDBF->panFieldDecimals,
1778
0
           sizeof(int) * psDBF->nFields);
1779
0
    newDBF->pachFieldType =
1780
0
        STATIC_CAST(char *, malloc(sizeof(char) * psDBF->nFields));
1781
0
    memcpy(newDBF->pachFieldType, psDBF->pachFieldType,
1782
0
           sizeof(char) * psDBF->nFields);
1783
1784
0
    newDBF->bNoHeader = TRUE;
1785
0
    newDBF->bUpdated = TRUE;
1786
0
    newDBF->bWriteEndOfFileChar = psDBF->bWriteEndOfFileChar;
1787
1788
0
    DBFWriteHeader(newDBF);
1789
0
    DBFClose(newDBF);
1790
1791
0
    newDBF = DBFOpen(pszFilename, "rb+");
1792
0
    newDBF->bWriteEndOfFileChar = psDBF->bWriteEndOfFileChar;
1793
1794
0
    return (newDBF);
1795
0
}
1796
1797
/************************************************************************/
1798
/*                       DBFGetNativeFieldType()                        */
1799
/*                                                                      */
1800
/*      Return the DBase field type for the specified field.            */
1801
/*                                                                      */
1802
/*      Value can be one of: 'C' (String), 'D' (Date), 'F' (Float),     */
1803
/*                           'N' (Numeric, with or without decimal),    */
1804
/*                           'L' (Logical),                             */
1805
/*                           'M' (Memo: 10 digits .DBT block ptr)       */
1806
/************************************************************************/
1807
1808
char SHPAPI_CALL DBFGetNativeFieldType(const DBFHandle psDBF, int iField)
1809
35.0k
{
1810
35.0k
    if (iField >= 0 && iField < psDBF->nFields)
1811
35.0k
        return psDBF->pachFieldType[iField];
1812
1813
0
    return ' ';
1814
35.0k
}
1815
1816
/************************************************************************/
1817
/*                          DBFGetFieldIndex()                          */
1818
/*                                                                      */
1819
/*      Get the index number for a field in a .dbf file.                */
1820
/*                                                                      */
1821
/*      Contributed by Jim Matthews.                                    */
1822
/************************************************************************/
1823
1824
int SHPAPI_CALL DBFGetFieldIndex(const DBFHandle psDBF,
1825
                                 const char *pszFieldName)
1826
0
{
1827
0
    char name[XBASE_FLDNAME_LEN_READ + 1];
1828
1829
0
    for (int i = 0; i < DBFGetFieldCount(psDBF); i++)
1830
0
    {
1831
0
        DBFGetFieldInfo(psDBF, i, name, SHPLIB_NULLPTR, SHPLIB_NULLPTR);
1832
0
        if (!STRCASECMP(pszFieldName, name))
1833
0
            return (i);
1834
0
    }
1835
0
    return (-1);
1836
0
}
1837
1838
/************************************************************************/
1839
/*                         DBFIsRecordDeleted()                         */
1840
/*                                                                      */
1841
/*      Returns TRUE if the indicated record is deleted, otherwise      */
1842
/*      it returns FALSE.                                               */
1843
/************************************************************************/
1844
1845
int SHPAPI_CALL DBFIsRecordDeleted(const DBFHandle psDBF, int iShape)
1846
224k
{
1847
    /* -------------------------------------------------------------------- */
1848
    /*      Verify selection.                                               */
1849
    /* -------------------------------------------------------------------- */
1850
224k
    if (iShape < 0 || iShape >= psDBF->nRecords)
1851
21
        return TRUE;
1852
1853
    /* -------------------------------------------------------------------- */
1854
    /*      Have we read the record?                                        */
1855
    /* -------------------------------------------------------------------- */
1856
224k
    if (!DBFLoadRecord(psDBF, iShape))
1857
3.47k
        return FALSE;
1858
1859
    /* -------------------------------------------------------------------- */
1860
    /*      '*' means deleted.                                              */
1861
    /* -------------------------------------------------------------------- */
1862
220k
    return psDBF->pszCurrentRecord[0] == '*';
1863
224k
}
1864
1865
/************************************************************************/
1866
/*                        DBFMarkRecordDeleted()                        */
1867
/************************************************************************/
1868
1869
int SHPAPI_CALL DBFMarkRecordDeleted(DBFHandle psDBF, int iShape,
1870
                                     int bIsDeleted)
1871
0
{
1872
    /* -------------------------------------------------------------------- */
1873
    /*      Verify selection.                                               */
1874
    /* -------------------------------------------------------------------- */
1875
0
    if (iShape < 0 || iShape >= psDBF->nRecords)
1876
0
        return FALSE;
1877
1878
    /* -------------------------------------------------------------------- */
1879
    /*      Is this an existing record, but different than the last one     */
1880
    /*      we accessed?                                                    */
1881
    /* -------------------------------------------------------------------- */
1882
0
    if (!DBFLoadRecord(psDBF, iShape))
1883
0
        return FALSE;
1884
1885
    /* -------------------------------------------------------------------- */
1886
    /*      Assign value, marking record as dirty if it changes.            */
1887
    /* -------------------------------------------------------------------- */
1888
0
    const char chNewFlag = bIsDeleted ? '*' : ' ';
1889
1890
0
    if (psDBF->pszCurrentRecord[0] != chNewFlag)
1891
0
    {
1892
0
        psDBF->bCurrentRecordModified = TRUE;
1893
0
        psDBF->bUpdated = TRUE;
1894
0
        psDBF->pszCurrentRecord[0] = chNewFlag;
1895
0
    }
1896
1897
0
    return TRUE;
1898
0
}
1899
1900
/************************************************************************/
1901
/*                            DBFGetCodePage                            */
1902
/************************************************************************/
1903
1904
const char SHPAPI_CALL1(*) DBFGetCodePage(const DBFHandle psDBF)
1905
0
{
1906
0
    if (psDBF == SHPLIB_NULLPTR)
1907
0
        return SHPLIB_NULLPTR;
1908
0
    return psDBF->pszCodePage;
1909
0
}
1910
1911
/************************************************************************/
1912
/*                          DBFDeleteField()                            */
1913
/*                                                                      */
1914
/*      Remove a field from a .dbf file                                 */
1915
/************************************************************************/
1916
1917
int SHPAPI_CALL DBFDeleteField(DBFHandle psDBF, int iField)
1918
25
{
1919
25
    if (iField < 0 || iField >= psDBF->nFields)
1920
0
        return FALSE;
1921
1922
    /* make sure that everything is written in .dbf */
1923
25
    if (!DBFFlushRecord(psDBF))
1924
3
        return FALSE;
1925
1926
    /* get information about field to be deleted */
1927
22
    int nOldRecordLength = psDBF->nRecordLength;
1928
22
    int nOldHeaderLength = psDBF->nHeaderLength;
1929
22
    int nDeletedFieldOffset = psDBF->panFieldOffset[iField];
1930
22
    int nDeletedFieldSize = psDBF->panFieldSize[iField];
1931
1932
    /* update fields info */
1933
22
    for (int i = iField + 1; i < psDBF->nFields; i++)
1934
0
    {
1935
0
        psDBF->panFieldOffset[i - 1] =
1936
0
            psDBF->panFieldOffset[i] - nDeletedFieldSize;
1937
0
        psDBF->panFieldSize[i - 1] = psDBF->panFieldSize[i];
1938
0
        psDBF->panFieldDecimals[i - 1] = psDBF->panFieldDecimals[i];
1939
0
        psDBF->pachFieldType[i - 1] = psDBF->pachFieldType[i];
1940
0
    }
1941
1942
    /* resize fields arrays */
1943
22
    psDBF->nFields--;
1944
1945
22
    psDBF->panFieldOffset = STATIC_CAST(
1946
22
        int *, realloc(psDBF->panFieldOffset, sizeof(int) * psDBF->nFields));
1947
1948
22
    psDBF->panFieldSize = STATIC_CAST(
1949
22
        int *, realloc(psDBF->panFieldSize, sizeof(int) * psDBF->nFields));
1950
1951
22
    psDBF->panFieldDecimals = STATIC_CAST(
1952
22
        int *, realloc(psDBF->panFieldDecimals, sizeof(int) * psDBF->nFields));
1953
1954
22
    psDBF->pachFieldType = STATIC_CAST(
1955
22
        char *, realloc(psDBF->pachFieldType, sizeof(char) * psDBF->nFields));
1956
1957
    /* update header information */
1958
22
    psDBF->nHeaderLength -= XBASE_FLDHDR_SZ;
1959
22
    psDBF->nRecordLength -= nDeletedFieldSize;
1960
1961
    /* overwrite field information in header */
1962
22
    memmove(psDBF->pszHeader + iField * XBASE_FLDHDR_SZ,
1963
22
            psDBF->pszHeader + (iField + 1) * XBASE_FLDHDR_SZ,
1964
22
            sizeof(char) * (psDBF->nFields - iField) * XBASE_FLDHDR_SZ);
1965
1966
22
    psDBF->pszHeader = STATIC_CAST(
1967
22
        char *, realloc(psDBF->pszHeader, psDBF->nFields * XBASE_FLDHDR_SZ));
1968
1969
    /* update size of current record appropriately */
1970
22
    psDBF->pszCurrentRecord = STATIC_CAST(
1971
22
        char *, realloc(psDBF->pszCurrentRecord, psDBF->nRecordLength));
1972
1973
    /* we're done if we're dealing with not yet created .dbf */
1974
22
    if (psDBF->bNoHeader && psDBF->nRecords == 0)
1975
0
        return TRUE;
1976
1977
    /* force update of header with new header and record length */
1978
22
    psDBF->bNoHeader = TRUE;
1979
22
    DBFUpdateHeader(psDBF);
1980
1981
    /* alloc record */
1982
22
    char *pszRecord =
1983
22
        STATIC_CAST(char *, malloc(sizeof(char) * nOldRecordLength));
1984
1985
    /* shift records to their new positions */
1986
55
    for (int iRecord = 0; iRecord < psDBF->nRecords; iRecord++)
1987
48
    {
1988
48
        SAOffset nRecordOffset =
1989
48
            nOldRecordLength * STATIC_CAST(SAOffset, iRecord) +
1990
48
            nOldHeaderLength;
1991
1992
        /* load record */
1993
48
        psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
1994
48
        if (psDBF->sHooks.FRead(pszRecord, nOldRecordLength, 1, psDBF->fp) != 1)
1995
15
        {
1996
15
            free(pszRecord);
1997
15
            return FALSE;
1998
15
        }
1999
2000
33
        nRecordOffset = psDBF->nRecordLength * STATIC_CAST(SAOffset, iRecord) +
2001
33
                        psDBF->nHeaderLength;
2002
2003
        /* move record in two steps */
2004
33
        psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2005
33
        psDBF->sHooks.FWrite(pszRecord, nDeletedFieldOffset, 1, psDBF->fp);
2006
33
        psDBF->sHooks.FWrite(
2007
33
            pszRecord + nDeletedFieldOffset + nDeletedFieldSize,
2008
33
            nOldRecordLength - nDeletedFieldOffset - nDeletedFieldSize, 1,
2009
33
            psDBF->fp);
2010
33
    }
2011
2012
7
    if (psDBF->bWriteEndOfFileChar)
2013
7
    {
2014
7
        char ch = END_OF_FILE_CHARACTER;
2015
7
        SAOffset nEOFOffset =
2016
7
            psDBF->nRecordLength * STATIC_CAST(SAOffset, psDBF->nRecords) +
2017
7
            psDBF->nHeaderLength;
2018
2019
7
        psDBF->sHooks.FSeek(psDBF->fp, nEOFOffset, 0);
2020
7
        psDBF->sHooks.FWrite(&ch, 1, 1, psDBF->fp);
2021
7
    }
2022
2023
    /* TODO: truncate file */
2024
2025
    /* free record */
2026
7
    free(pszRecord);
2027
2028
7
    psDBF->nCurrentRecord = -1;
2029
7
    psDBF->bCurrentRecordModified = FALSE;
2030
7
    psDBF->bUpdated = TRUE;
2031
2032
7
    return TRUE;
2033
22
}
2034
2035
/************************************************************************/
2036
/*                          DBFReorderFields()                          */
2037
/*                                                                      */
2038
/*      Reorder the fields of a .dbf file                               */
2039
/*                                                                      */
2040
/* panMap must be exactly psDBF->nFields long and be a permutation      */
2041
/* of [0, psDBF->nFields-1]. This assumption will not be asserted in the*/
2042
/* code of DBFReorderFields.                                            */
2043
/************************************************************************/
2044
2045
int SHPAPI_CALL DBFReorderFields(DBFHandle psDBF, const int *panMap)
2046
0
{
2047
0
    if (psDBF->nFields == 0)
2048
0
        return TRUE;
2049
2050
    /* make sure that everything is written in .dbf */
2051
0
    if (!DBFFlushRecord(psDBF))
2052
0
        return FALSE;
2053
2054
    /* a simple malloc() would be enough, but calloc() helps clang static
2055
     * analyzer */
2056
0
    int *panFieldOffsetNew =
2057
0
        STATIC_CAST(int *, calloc(psDBF->nFields, sizeof(int)));
2058
0
    int *panFieldSizeNew =
2059
0
        STATIC_CAST(int *, calloc(psDBF->nFields, sizeof(int)));
2060
0
    int *panFieldDecimalsNew =
2061
0
        STATIC_CAST(int *, calloc(psDBF->nFields, sizeof(int)));
2062
0
    char *pachFieldTypeNew =
2063
0
        STATIC_CAST(char *, calloc(psDBF->nFields, sizeof(char)));
2064
0
    char *pszHeaderNew = STATIC_CAST(
2065
0
        char *, malloc(sizeof(char) * XBASE_FLDHDR_SZ * psDBF->nFields));
2066
0
    char *pszRecord = SHPLIB_NULLPTR;
2067
0
    char *pszRecordNew = SHPLIB_NULLPTR;
2068
0
    if (!(psDBF->bNoHeader && psDBF->nRecords == 0))
2069
0
    {
2070
        /* alloc record */
2071
0
        pszRecord =
2072
0
            STATIC_CAST(char *, malloc(sizeof(char) * psDBF->nRecordLength));
2073
0
        pszRecordNew =
2074
0
            STATIC_CAST(char *, malloc(sizeof(char) * psDBF->nRecordLength));
2075
0
    }
2076
0
    if (!panFieldOffsetNew || !panFieldSizeNew || !panFieldDecimalsNew ||
2077
0
        !pachFieldTypeNew || !pszHeaderNew ||
2078
0
        (!(psDBF->bNoHeader && psDBF->nRecords == 0) &&
2079
0
         (!pszRecord || !pszRecordNew)))
2080
0
    {
2081
0
        free(panFieldOffsetNew);
2082
0
        free(panFieldSizeNew);
2083
0
        free(panFieldDecimalsNew);
2084
0
        free(pachFieldTypeNew);
2085
0
        free(pszHeaderNew);
2086
0
        free(pszRecord);
2087
0
        free(pszRecordNew);
2088
0
        psDBF->sHooks.Error("Out of memory");
2089
0
        return FALSE;
2090
0
    }
2091
2092
    /* shuffle fields definitions */
2093
0
    for (int i = 0; i < psDBF->nFields; i++)
2094
0
    {
2095
0
        panFieldSizeNew[i] = psDBF->panFieldSize[panMap[i]];
2096
0
        panFieldDecimalsNew[i] = psDBF->panFieldDecimals[panMap[i]];
2097
0
        pachFieldTypeNew[i] = psDBF->pachFieldType[panMap[i]];
2098
0
        memcpy(pszHeaderNew + i * XBASE_FLDHDR_SZ,
2099
0
               psDBF->pszHeader + panMap[i] * XBASE_FLDHDR_SZ, XBASE_FLDHDR_SZ);
2100
0
    }
2101
0
    panFieldOffsetNew[0] = 1;
2102
0
    for (int i = 1; i < psDBF->nFields; i++)
2103
0
    {
2104
0
        panFieldOffsetNew[i] =
2105
0
            panFieldOffsetNew[i - 1] + panFieldSizeNew[i - 1];
2106
0
    }
2107
2108
0
    free(psDBF->pszHeader);
2109
0
    psDBF->pszHeader = pszHeaderNew;
2110
2111
0
    bool errorAbort = false;
2112
2113
    /* we're done if we're dealing with not yet created .dbf */
2114
0
    if (!(psDBF->bNoHeader && psDBF->nRecords == 0))
2115
0
    {
2116
        /* force update of header with new header and record length */
2117
0
        psDBF->bNoHeader = TRUE;
2118
0
        DBFUpdateHeader(psDBF);
2119
2120
        /* shuffle fields in records */
2121
0
        for (int iRecord = 0; iRecord < psDBF->nRecords; iRecord++)
2122
0
        {
2123
0
            const SAOffset nRecordOffset =
2124
0
                psDBF->nRecordLength * STATIC_CAST(SAOffset, iRecord) +
2125
0
                psDBF->nHeaderLength;
2126
2127
            /* load record */
2128
0
            psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2129
0
            if (psDBF->sHooks.FRead(pszRecord, psDBF->nRecordLength, 1,
2130
0
                                    psDBF->fp) != 1)
2131
0
            {
2132
0
                errorAbort = true;
2133
0
                break;
2134
0
            }
2135
2136
0
            pszRecordNew[0] = pszRecord[0];
2137
2138
0
            for (int i = 0; i < psDBF->nFields; i++)
2139
0
            {
2140
0
                memcpy(pszRecordNew + panFieldOffsetNew[i],
2141
0
                       pszRecord + psDBF->panFieldOffset[panMap[i]],
2142
0
                       psDBF->panFieldSize[panMap[i]]);
2143
0
            }
2144
2145
            /* write record */
2146
0
            psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2147
0
            psDBF->sHooks.FWrite(pszRecordNew, psDBF->nRecordLength, 1,
2148
0
                                 psDBF->fp);
2149
0
        }
2150
0
    }
2151
2152
    /* free record */
2153
0
    free(pszRecord);
2154
0
    free(pszRecordNew);
2155
2156
0
    if (errorAbort)
2157
0
    {
2158
0
        free(panFieldOffsetNew);
2159
0
        free(panFieldSizeNew);
2160
0
        free(panFieldDecimalsNew);
2161
0
        free(pachFieldTypeNew);
2162
0
        psDBF->nCurrentRecord = -1;
2163
0
        psDBF->bCurrentRecordModified = FALSE;
2164
0
        psDBF->bUpdated = FALSE;
2165
0
        return FALSE;
2166
0
    }
2167
2168
0
    free(psDBF->panFieldOffset);
2169
0
    free(psDBF->panFieldSize);
2170
0
    free(psDBF->panFieldDecimals);
2171
0
    free(psDBF->pachFieldType);
2172
2173
0
    psDBF->panFieldOffset = panFieldOffsetNew;
2174
0
    psDBF->panFieldSize = panFieldSizeNew;
2175
0
    psDBF->panFieldDecimals = panFieldDecimalsNew;
2176
0
    psDBF->pachFieldType = pachFieldTypeNew;
2177
2178
0
    psDBF->nCurrentRecord = -1;
2179
0
    psDBF->bCurrentRecordModified = FALSE;
2180
0
    psDBF->bUpdated = TRUE;
2181
2182
0
    return TRUE;
2183
0
}
2184
2185
/************************************************************************/
2186
/*                          DBFAlterFieldDefn()                         */
2187
/*                                                                      */
2188
/*      Alter a field definition in a .dbf file                         */
2189
/************************************************************************/
2190
2191
int SHPAPI_CALL DBFAlterFieldDefn(DBFHandle psDBF, int iField,
2192
                                  const char *pszFieldName, char chType,
2193
                                  int nWidth, int nDecimals)
2194
4.11k
{
2195
4.11k
    if (iField < 0 || iField >= psDBF->nFields)
2196
0
        return FALSE;
2197
2198
    /* make sure that everything is written in .dbf */
2199
4.11k
    if (!DBFFlushRecord(psDBF))
2200
514
        return FALSE;
2201
2202
3.60k
    const char chFieldFill = DBFGetNullCharacter(chType);
2203
2204
3.60k
    const char chOldType = psDBF->pachFieldType[iField];
2205
3.60k
    const int nOffset = psDBF->panFieldOffset[iField];
2206
3.60k
    const int nOldWidth = psDBF->panFieldSize[iField];
2207
3.60k
    const int nOldRecordLength = psDBF->nRecordLength;
2208
2209
    /* -------------------------------------------------------------------- */
2210
    /*      Do some checking to ensure we can add records to this file.     */
2211
    /* -------------------------------------------------------------------- */
2212
3.60k
    if (nWidth < 1)
2213
0
        return -1;
2214
2215
3.60k
    if (nWidth > XBASE_FLD_MAX_WIDTH)
2216
0
        nWidth = XBASE_FLD_MAX_WIDTH;
2217
2218
3.60k
    char *pszRecord = STATIC_CAST(
2219
3.60k
        char *, malloc(nOldRecordLength +
2220
3.60k
                       ((nWidth > nOldWidth) ? nWidth - nOldWidth : 0)));
2221
3.60k
    char *pszOldField = STATIC_CAST(char *, malloc(nOldWidth + 1));
2222
3.60k
    if (!pszRecord || !pszOldField)
2223
0
    {
2224
0
        free(pszRecord);
2225
0
        free(pszOldField);
2226
0
        return FALSE;
2227
0
    }
2228
2229
3.60k
    if (nWidth != nOldWidth)
2230
3.57k
    {
2231
3.57k
        char *pszCurrentRecordNew = STATIC_CAST(
2232
3.57k
            char *, realloc(psDBF->pszCurrentRecord,
2233
3.57k
                            psDBF->nRecordLength + nWidth - nOldWidth));
2234
3.57k
        if (!pszCurrentRecordNew)
2235
0
        {
2236
0
            free(pszRecord);
2237
0
            free(pszOldField);
2238
0
            return FALSE;
2239
0
        }
2240
3.57k
        psDBF->pszCurrentRecord = pszCurrentRecordNew;
2241
3.57k
    }
2242
2243
    /* -------------------------------------------------------------------- */
2244
    /*      Assign the new field information fields.                        */
2245
    /* -------------------------------------------------------------------- */
2246
3.60k
    psDBF->panFieldSize[iField] = nWidth;
2247
3.60k
    psDBF->panFieldDecimals[iField] = nDecimals;
2248
3.60k
    psDBF->pachFieldType[iField] = chType;
2249
2250
    /* -------------------------------------------------------------------- */
2251
    /*      Update the header information.                                  */
2252
    /* -------------------------------------------------------------------- */
2253
3.60k
    char *pszFInfo = psDBF->pszHeader + XBASE_FLDHDR_SZ * iField;
2254
2255
118k
    for (int i = 0; i < XBASE_FLDHDR_SZ; i++)
2256
115k
        pszFInfo[i] = '\0';
2257
2258
3.60k
    strncpy(pszFInfo, pszFieldName, XBASE_FLDNAME_LEN_WRITE);
2259
2260
3.60k
    pszFInfo[11] = psDBF->pachFieldType[iField];
2261
2262
3.60k
    if (chType == 'C')
2263
3.55k
    {
2264
3.55k
        pszFInfo[16] = STATIC_CAST(unsigned char, nWidth % 256);
2265
3.55k
        pszFInfo[17] = STATIC_CAST(unsigned char, nWidth / 256);
2266
3.55k
    }
2267
47
    else
2268
47
    {
2269
47
        pszFInfo[16] = STATIC_CAST(unsigned char, nWidth);
2270
47
        pszFInfo[17] = STATIC_CAST(unsigned char, nDecimals);
2271
47
    }
2272
2273
    /* -------------------------------------------------------------------- */
2274
    /*      Update offsets                                                  */
2275
    /* -------------------------------------------------------------------- */
2276
3.60k
    if (nWidth != nOldWidth)
2277
3.57k
    {
2278
12.7k
        for (int i = iField + 1; i < psDBF->nFields; i++)
2279
9.19k
            psDBF->panFieldOffset[i] += nWidth - nOldWidth;
2280
3.57k
        psDBF->nRecordLength += nWidth - nOldWidth;
2281
3.57k
    }
2282
2283
    /* we're done if we're dealing with not yet created .dbf */
2284
3.60k
    if (psDBF->bNoHeader && psDBF->nRecords == 0)
2285
113
    {
2286
113
        free(pszRecord);
2287
113
        free(pszOldField);
2288
113
        return TRUE;
2289
113
    }
2290
2291
    /* force update of header with new header and record length */
2292
3.49k
    psDBF->bNoHeader = TRUE;
2293
3.49k
    DBFUpdateHeader(psDBF);
2294
2295
3.49k
    bool errorAbort = false;
2296
2297
3.49k
    if (nWidth < nOldWidth || (nWidth == nOldWidth && chType != chOldType))
2298
224
    {
2299
224
        pszOldField[nOldWidth] = 0;
2300
2301
        /* move records to their new positions */
2302
224
        for (int iRecord = 0; iRecord < psDBF->nRecords; iRecord++)
2303
224
        {
2304
224
            SAOffset nRecordOffset =
2305
224
                nOldRecordLength * STATIC_CAST(SAOffset, iRecord) +
2306
224
                psDBF->nHeaderLength;
2307
2308
            /* load record */
2309
224
            psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2310
224
            if (psDBF->sHooks.FRead(pszRecord, nOldRecordLength, 1,
2311
224
                                    psDBF->fp) != 1)
2312
224
            {
2313
224
                errorAbort = true;
2314
224
                break;
2315
224
            }
2316
2317
0
            memcpy(pszOldField, pszRecord + nOffset, nOldWidth);
2318
0
            const bool bIsNULL =
2319
0
                DBFIsValueNULL(chOldType, pszOldField, nOldWidth);
2320
2321
0
            if (nWidth != nOldWidth)
2322
0
            {
2323
0
                if ((chOldType == 'N' || chOldType == 'F' ||
2324
0
                     chOldType == 'D') &&
2325
0
                    pszOldField[0] == ' ')
2326
0
                {
2327
                    /* Strip leading spaces when truncating a numeric field */
2328
0
                    memmove(pszRecord + nOffset,
2329
0
                            pszRecord + nOffset + nOldWidth - nWidth, nWidth);
2330
0
                }
2331
0
                if (nOffset + nOldWidth < nOldRecordLength)
2332
0
                {
2333
0
                    memmove(pszRecord + nOffset + nWidth,
2334
0
                            pszRecord + nOffset + nOldWidth,
2335
0
                            nOldRecordLength - (nOffset + nOldWidth));
2336
0
                }
2337
0
            }
2338
2339
            /* Convert null value to the appropriate value of the new type */
2340
0
            if (bIsNULL)
2341
0
            {
2342
0
                memset(pszRecord + nOffset, chFieldFill, nWidth);
2343
0
            }
2344
2345
0
            nRecordOffset =
2346
0
                psDBF->nRecordLength * STATIC_CAST(SAOffset, iRecord) +
2347
0
                psDBF->nHeaderLength;
2348
2349
            /* write record */
2350
0
            psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2351
0
            psDBF->sHooks.FWrite(pszRecord, psDBF->nRecordLength, 1, psDBF->fp);
2352
0
        }
2353
2354
224
        if (!errorAbort && psDBF->bWriteEndOfFileChar)
2355
0
        {
2356
0
            char ch = END_OF_FILE_CHARACTER;
2357
2358
0
            SAOffset nRecordOffset =
2359
0
                psDBF->nRecordLength * STATIC_CAST(SAOffset, psDBF->nRecords) +
2360
0
                psDBF->nHeaderLength;
2361
2362
0
            psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2363
0
            psDBF->sHooks.FWrite(&ch, 1, 1, psDBF->fp);
2364
0
        }
2365
        /* TODO: truncate file */
2366
224
    }
2367
3.26k
    else if (nWidth > nOldWidth)
2368
3.23k
    {
2369
3.23k
        pszOldField[nOldWidth] = 0;
2370
2371
        /* move records to their new positions */
2372
685k
        for (int iRecord = psDBF->nRecords - 1; iRecord >= 0; iRecord--)
2373
682k
        {
2374
682k
            SAOffset nRecordOffset =
2375
682k
                nOldRecordLength * STATIC_CAST(SAOffset, iRecord) +
2376
682k
                psDBF->nHeaderLength;
2377
2378
            /* load record */
2379
682k
            psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2380
682k
            if (psDBF->sHooks.FRead(pszRecord, nOldRecordLength, 1,
2381
682k
                                    psDBF->fp) != 1)
2382
427
            {
2383
427
                errorAbort = true;
2384
427
                break;
2385
427
            }
2386
2387
682k
            memcpy(pszOldField, pszRecord + nOffset, nOldWidth);
2388
682k
            const bool bIsNULL =
2389
682k
                DBFIsValueNULL(chOldType, pszOldField, nOldWidth);
2390
2391
682k
            if (nOffset + nOldWidth < nOldRecordLength)
2392
185k
            {
2393
185k
                memmove(pszRecord + nOffset + nWidth,
2394
185k
                        pszRecord + nOffset + nOldWidth,
2395
185k
                        nOldRecordLength - (nOffset + nOldWidth));
2396
185k
            }
2397
2398
            /* Convert null value to the appropriate value of the new type */
2399
682k
            if (bIsNULL)
2400
23.1k
            {
2401
23.1k
                memset(pszRecord + nOffset, chFieldFill, nWidth);
2402
23.1k
            }
2403
659k
            else
2404
659k
            {
2405
659k
                if ((chOldType == 'N' || chOldType == 'F'))
2406
441
                {
2407
                    /* Add leading spaces when expanding a numeric field */
2408
441
                    memmove(pszRecord + nOffset + nWidth - nOldWidth,
2409
441
                            pszRecord + nOffset, nOldWidth);
2410
441
                    memset(pszRecord + nOffset, ' ', nWidth - nOldWidth);
2411
441
                }
2412
658k
                else
2413
658k
                {
2414
                    /* Add trailing spaces */
2415
658k
                    memset(pszRecord + nOffset + nOldWidth, ' ',
2416
658k
                           nWidth - nOldWidth);
2417
658k
                }
2418
659k
            }
2419
2420
682k
            nRecordOffset =
2421
682k
                psDBF->nRecordLength * STATIC_CAST(SAOffset, iRecord) +
2422
682k
                psDBF->nHeaderLength;
2423
2424
            /* write record */
2425
682k
            psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2426
682k
            psDBF->sHooks.FWrite(pszRecord, psDBF->nRecordLength, 1, psDBF->fp);
2427
682k
        }
2428
2429
3.23k
        if (!errorAbort && psDBF->bWriteEndOfFileChar)
2430
2.80k
        {
2431
2.80k
            const char ch = END_OF_FILE_CHARACTER;
2432
2433
2.80k
            SAOffset nRecordOffset =
2434
2.80k
                psDBF->nRecordLength * STATIC_CAST(SAOffset, psDBF->nRecords) +
2435
2.80k
                psDBF->nHeaderLength;
2436
2437
2.80k
            psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2438
2.80k
            psDBF->sHooks.FWrite(&ch, 1, 1, psDBF->fp);
2439
2.80k
        }
2440
3.23k
    }
2441
2442
3.49k
    free(pszRecord);
2443
3.49k
    free(pszOldField);
2444
2445
3.49k
    if (errorAbort)
2446
651
    {
2447
651
        psDBF->nCurrentRecord = -1;
2448
651
        psDBF->bCurrentRecordModified = TRUE;
2449
651
        psDBF->bUpdated = FALSE;
2450
2451
651
        return FALSE;
2452
651
    }
2453
2.83k
    psDBF->nCurrentRecord = -1;
2454
2.83k
    psDBF->bCurrentRecordModified = FALSE;
2455
2.83k
    psDBF->bUpdated = TRUE;
2456
2457
2.83k
    return TRUE;
2458
3.49k
}
2459
2460
/************************************************************************/
2461
/*                      DBFSetWriteEndOfFileChar()                      */
2462
/************************************************************************/
2463
2464
void SHPAPI_CALL DBFSetWriteEndOfFileChar(DBFHandle psDBF, int bWriteFlag)
2465
24.6k
{
2466
24.6k
    psDBF->bWriteEndOfFileChar = bWriteFlag;
2467
24.6k
}