Coverage Report

Created: 2026-05-16 08:20

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
59.3k
#define XBASE_FILEHDR_SZ 32
61
62
465k
#define HEADER_RECORD_TERMINATOR 0x0D
63
64
/* See http://www.manmrk.net/tutorials/database/xbase/dbf.html */
65
515k
#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
11.3k
{
70
11.3k
}
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
8.94k
{
86
8.94k
    unsigned char abyHeader[XBASE_FILEHDR_SZ] = {0};
87
88
8.94k
    if (!psDBF->bNoHeader)
89
0
        return;
90
91
8.94k
    psDBF->bNoHeader = FALSE;
92
93
    /* -------------------------------------------------------------------- */
94
    /*      Initialize the file header information.                         */
95
    /* -------------------------------------------------------------------- */
96
8.94k
    abyHeader[0] = 0x03; /* memo field? - just copying */
97
98
    /* write out update date */
99
8.94k
    abyHeader[1] = STATIC_CAST(unsigned char, psDBF->nUpdateYearSince1900);
100
8.94k
    abyHeader[2] = STATIC_CAST(unsigned char, psDBF->nUpdateMonth);
101
8.94k
    abyHeader[3] = STATIC_CAST(unsigned char, psDBF->nUpdateDay);
102
103
    /* record count preset at zero */
104
105
8.94k
    abyHeader[8] = STATIC_CAST(unsigned char, psDBF->nHeaderLength % 256);
106
8.94k
    abyHeader[9] = STATIC_CAST(unsigned char, psDBF->nHeaderLength / 256);
107
108
8.94k
    abyHeader[10] = STATIC_CAST(unsigned char, psDBF->nRecordLength % 256);
109
8.94k
    abyHeader[11] = STATIC_CAST(unsigned char, psDBF->nRecordLength / 256);
110
111
8.94k
    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
8.94k
    psDBF->sHooks.FSeek(psDBF->fp, 0, 0);
118
8.94k
    psDBF->sHooks.FWrite(abyHeader, XBASE_FILEHDR_SZ, 1, psDBF->fp);
119
8.94k
    psDBF->sHooks.FWrite(psDBF->pszHeader, XBASE_FLDHDR_SZ, psDBF->nFields,
120
8.94k
                         psDBF->fp);
121
122
    /* -------------------------------------------------------------------- */
123
    /*      Write out the newline character if there is room for it.        */
124
    /* -------------------------------------------------------------------- */
125
8.94k
    if (psDBF->nHeaderLength >
126
8.94k
        XBASE_FLDHDR_SZ * psDBF->nFields + XBASE_FLDHDR_SZ)
127
8.94k
    {
128
8.94k
        char cNewline = HEADER_RECORD_TERMINATOR;
129
8.94k
        psDBF->sHooks.FWrite(&cNewline, 1, 1, psDBF->fp);
130
8.94k
    }
131
132
    /* -------------------------------------------------------------------- */
133
    /*      If the file is new, add a EOF character.                        */
134
    /* -------------------------------------------------------------------- */
135
8.94k
    if (psDBF->nRecords == 0 && psDBF->bWriteEndOfFileChar)
136
3.94k
    {
137
3.94k
        char ch = END_OF_FILE_CHARACTER;
138
139
3.94k
        psDBF->sHooks.FWrite(&ch, 1, 1, psDBF->fp);
140
3.94k
    }
141
8.94k
}
142
143
/************************************************************************/
144
/*                           DBFFlushRecord()                           */
145
/*                                                                      */
146
/*      Write out the current record if there is one.                   */
147
/************************************************************************/
148
149
static bool DBFFlushRecord(DBFHandle psDBF)
150
724k
{
151
724k
    if (psDBF->bCurrentRecordModified && psDBF->nCurrentRecord > -1)
152
546k
    {
153
546k
        psDBF->bCurrentRecordModified = FALSE;
154
155
546k
        const SAOffset nRecordOffset =
156
546k
            psDBF->nRecordLength *
157
546k
                STATIC_CAST(SAOffset, psDBF->nCurrentRecord) +
158
546k
            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
546k
        if (psDBF->bRequireNextWriteSeek ||
165
503k
            psDBF->sHooks.FTell(psDBF->fp) != nRecordOffset)
166
546k
        {
167
546k
            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
546k
        }
178
179
546k
        if (psDBF->sHooks.FWrite(psDBF->pszCurrentRecord, psDBF->nRecordLength,
180
546k
                                 1, psDBF->fp) != 1)
181
38.6k
        {
182
38.6k
            char szMessage[128];
183
38.6k
            snprintf(szMessage, sizeof(szMessage),
184
38.6k
                     "Failure writing DBF record %d.", psDBF->nCurrentRecord);
185
38.6k
            psDBF->sHooks.Error(szMessage);
186
38.6k
            return false;
187
38.6k
        }
188
189
        /* -------------------------------------------------------------------- */
190
        /*      If next op is also a write, allow possible skipping of FSeek.   */
191
        /* -------------------------------------------------------------------- */
192
507k
        psDBF->bRequireNextWriteSeek = FALSE;
193
194
507k
        if (psDBF->nCurrentRecord == psDBF->nRecords - 1)
195
507k
        {
196
507k
            if (psDBF->bWriteEndOfFileChar)
197
507k
            {
198
507k
                char ch = END_OF_FILE_CHARACTER;
199
507k
                psDBF->sHooks.FWrite(&ch, 1, 1, psDBF->fp);
200
507k
            }
201
507k
        }
202
507k
    }
203
204
686k
    return true;
205
724k
}
206
207
/************************************************************************/
208
/*                           DBFLoadRecord()                            */
209
/************************************************************************/
210
211
static bool DBFLoadRecord(DBFHandle psDBF, int iRecord)
212
4.51M
{
213
4.51M
    if (psDBF->nCurrentRecord != iRecord)
214
84.7k
    {
215
84.7k
        if (!DBFFlushRecord(psDBF))
216
0
            return false;
217
218
84.7k
        const SAOffset nRecordOffset =
219
84.7k
            psDBF->nRecordLength * STATIC_CAST(SAOffset, iRecord) +
220
84.7k
            psDBF->nHeaderLength;
221
222
84.7k
        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
84.7k
        if (psDBF->sHooks.FRead(psDBF->pszCurrentRecord, psDBF->nRecordLength,
233
84.7k
                                1, psDBF->fp) != 1)
234
2.50k
        {
235
2.50k
            char szMessage[128];
236
2.50k
            snprintf(szMessage, sizeof(szMessage),
237
2.50k
                     "fread(%d) failed on DBF file.", psDBF->nRecordLength);
238
2.50k
            psDBF->sHooks.Error(szMessage);
239
2.50k
            return false;
240
2.50k
        }
241
242
82.2k
        psDBF->nCurrentRecord = iRecord;
243
        /* -------------------------------------------------------------------- */
244
        /*      Require a seek for next write in case of mixed R/W operations.  */
245
        /* -------------------------------------------------------------------- */
246
82.2k
        psDBF->bRequireNextWriteSeek = TRUE;
247
82.2k
    }
248
249
4.51M
    return true;
250
4.51M
}
251
252
/************************************************************************/
253
/*                          DBFUpdateHeader()                           */
254
/************************************************************************/
255
256
void SHPAPI_CALL DBFUpdateHeader(DBFHandle psDBF)
257
10.7k
{
258
10.7k
    if (psDBF->bNoHeader)
259
4.99k
        DBFWriteHeader(psDBF);
260
261
10.7k
    if (!DBFFlushRecord(psDBF))
262
69
        return;
263
264
10.6k
    psDBF->sHooks.FSeek(psDBF->fp, 0, 0);
265
266
10.6k
    unsigned char abyFileHeader[XBASE_FILEHDR_SZ] = {0};
267
10.6k
    psDBF->sHooks.FRead(abyFileHeader, 1, sizeof(abyFileHeader), psDBF->fp);
268
269
10.6k
    abyFileHeader[1] = STATIC_CAST(unsigned char, psDBF->nUpdateYearSince1900);
270
10.6k
    abyFileHeader[2] = STATIC_CAST(unsigned char, psDBF->nUpdateMonth);
271
10.6k
    abyFileHeader[3] = STATIC_CAST(unsigned char, psDBF->nUpdateDay);
272
10.6k
    abyFileHeader[4] = STATIC_CAST(unsigned char, psDBF->nRecords & 0xFF);
273
10.6k
    abyFileHeader[5] =
274
10.6k
        STATIC_CAST(unsigned char, (psDBF->nRecords >> 8) & 0xFF);
275
10.6k
    abyFileHeader[6] =
276
10.6k
        STATIC_CAST(unsigned char, (psDBF->nRecords >> 16) & 0xFF);
277
10.6k
    abyFileHeader[7] =
278
10.6k
        STATIC_CAST(unsigned char, (psDBF->nRecords >> 24) & 0xFF);
279
280
10.6k
    psDBF->sHooks.FSeek(psDBF->fp, 0, 0);
281
10.6k
    psDBF->sHooks.FWrite(abyFileHeader, sizeof(abyFileHeader), 1, psDBF->fp);
282
283
10.6k
    psDBF->sHooks.FFlush(psDBF->fp);
284
10.6k
}
285
286
/************************************************************************/
287
/*                       DBFSetLastModifiedDate()                       */
288
/************************************************************************/
289
290
void SHPAPI_CALL DBFSetLastModifiedDate(DBFHandle psDBF, int nYYSince1900,
291
                                        int nMM, int nDD)
292
23.3k
{
293
23.3k
    psDBF->nUpdateYearSince1900 = nYYSince1900;
294
23.3k
    psDBF->nUpdateMonth = nMM;
295
23.3k
    psDBF->nUpdateDay = nDD;
296
23.3k
}
297
298
/************************************************************************/
299
/*                              DBFOpen()                               */
300
/*                                                                      */
301
/*      Open a .dbf file.                                               */
302
/************************************************************************/
303
304
DBFHandle SHPAPI_CALL DBFOpen(const char *pszFilename, const char *pszAccess)
305
325
{
306
325
    SAHooks sHooks;
307
308
325
    SASetupDefaultHooks(&sHooks);
309
310
325
    return DBFOpenLL(pszFilename, pszAccess, &sHooks);
311
325
}
312
313
/************************************************************************/
314
/*                     DBFGetLenWithoutExtension()                      */
315
/************************************************************************/
316
317
static int DBFGetLenWithoutExtension(const char *pszBasename)
318
14.3k
{
319
14.3k
    const int nLen = STATIC_CAST(int, strlen(pszBasename));
320
14.3k
    for (int i = nLen - 1;
321
56.5k
         i > 0 && pszBasename[i] != '/' && pszBasename[i] != '\\'; i--)
322
56.1k
    {
323
56.1k
        if (pszBasename[i] == '.')
324
14.0k
        {
325
14.0k
            return i;
326
14.0k
        }
327
56.1k
    }
328
325
    return nLen;
329
14.3k
}
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
10.4k
{
340
    /* -------------------------------------------------------------------- */
341
    /*      We only allow the access strings "rb" and "r+".                  */
342
    /* -------------------------------------------------------------------- */
343
10.4k
    if (strcmp(pszAccess, "r") != 0 && strcmp(pszAccess, "r+") != 0 &&
344
325
        strcmp(pszAccess, "rb") != 0 && strcmp(pszAccess, "rb+") != 0 &&
345
0
        strcmp(pszAccess, "r+b") != 0)
346
0
        return SHPLIB_NULLPTR;
347
348
10.4k
    if (strcmp(pszAccess, "r") == 0)
349
10.0k
        pszAccess = "rb";
350
351
10.4k
    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
10.4k
    const int nLenWithoutExtension = DBFGetLenWithoutExtension(pszFilename);
359
10.4k
    char *pszFullname = STATIC_CAST(char *, malloc(nLenWithoutExtension + 5));
360
10.4k
    if (!pszFullname)
361
0
    {
362
0
        return SHPLIB_NULLPTR;
363
0
    }
364
10.4k
    memcpy(pszFullname, pszFilename, nLenWithoutExtension);
365
10.4k
    memcpy(pszFullname + nLenWithoutExtension, ".dbf", 5);
366
367
10.4k
    DBFHandle psDBF = STATIC_CAST(DBFHandle, calloc(1, sizeof(DBFInfo)));
368
10.4k
    if (!psDBF)
369
0
    {
370
0
        free(pszFullname);
371
0
        return SHPLIB_NULLPTR;
372
0
    }
373
10.4k
    psDBF->fp = psHooks->FOpen(pszFullname, pszAccess, psHooks->pvUserData);
374
10.4k
    memcpy(&(psDBF->sHooks), psHooks, sizeof(SAHooks));
375
376
10.4k
    if (psDBF->fp == SHPLIB_NULLPTR)
377
1.07k
    {
378
1.07k
        memcpy(pszFullname + nLenWithoutExtension, ".DBF", 5);
379
1.07k
        psDBF->fp =
380
1.07k
            psDBF->sHooks.FOpen(pszFullname, pszAccess, psHooks->pvUserData);
381
1.07k
    }
382
383
10.4k
    memcpy(pszFullname + nLenWithoutExtension, ".cpg", 5);
384
10.4k
    SAFile pfCPG = psHooks->FOpen(pszFullname, "r", psHooks->pvUserData);
385
10.4k
    if (pfCPG == SHPLIB_NULLPTR)
386
10.3k
    {
387
10.3k
        memcpy(pszFullname + nLenWithoutExtension, ".CPG", 5);
388
10.3k
        pfCPG = psHooks->FOpen(pszFullname, "r", psHooks->pvUserData);
389
10.3k
    }
390
391
10.4k
    free(pszFullname);
392
393
10.4k
    if (psDBF->fp == SHPLIB_NULLPTR)
394
797
    {
395
797
        free(psDBF);
396
797
        if (pfCPG)
397
9
            psHooks->FClose(pfCPG);
398
797
        return SHPLIB_NULLPTR;
399
797
    }
400
401
9.62k
    psDBF->bNoHeader = FALSE;
402
9.62k
    psDBF->nCurrentRecord = -1;
403
9.62k
    psDBF->bCurrentRecordModified = FALSE;
404
405
    /* -------------------------------------------------------------------- */
406
    /*  Read Table Header info                                              */
407
    /* -------------------------------------------------------------------- */
408
9.62k
    const int nBufSize = 500;
409
9.62k
    unsigned char *pabyBuf = STATIC_CAST(unsigned char *, malloc(nBufSize));
410
9.62k
    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
9.62k
    if (psDBF->sHooks.FRead(pabyBuf, XBASE_FILEHDR_SZ, 1, psDBF->fp) != 1)
419
335
    {
420
335
        psDBF->sHooks.FClose(psDBF->fp);
421
335
        if (pfCPG)
422
5
            psDBF->sHooks.FClose(pfCPG);
423
335
        free(pabyBuf);
424
335
        free(psDBF);
425
335
        return SHPLIB_NULLPTR;
426
335
    }
427
428
9.29k
    DBFSetLastModifiedDate(psDBF, pabyBuf[1], pabyBuf[2], pabyBuf[3]);
429
430
9.29k
    psDBF->nRecords = pabyBuf[4] | (pabyBuf[5] << 8) | (pabyBuf[6] << 16) |
431
9.29k
                      ((pabyBuf[7] & 0x7f) << 24);
432
433
9.29k
    const int nHeadLen = pabyBuf[8] | (pabyBuf[9] << 8);
434
9.29k
    psDBF->nHeaderLength = nHeadLen;
435
9.29k
    psDBF->nRecordLength = pabyBuf[10] | (pabyBuf[11] << 8);
436
9.29k
    psDBF->iLanguageDriver = pabyBuf[29];
437
438
9.29k
    if (psDBF->nRecordLength == 0 || nHeadLen < XBASE_FILEHDR_SZ)
439
100
    {
440
100
        psDBF->sHooks.FClose(psDBF->fp);
441
100
        if (pfCPG)
442
8
            psDBF->sHooks.FClose(pfCPG);
443
100
        free(pabyBuf);
444
100
        free(psDBF);
445
100
        return SHPLIB_NULLPTR;
446
100
    }
447
448
9.19k
    const int nFields = (nHeadLen - XBASE_FILEHDR_SZ) / XBASE_FLDHDR_SZ;
449
9.19k
    psDBF->nFields = nFields;
450
451
9.19k
    assert(psDBF->nRecordLength < 65536);
452
9.19k
    psDBF->pszCurrentRecord = STATIC_CAST(char *, malloc(psDBF->nRecordLength));
453
9.19k
    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
9.19k
    psDBF->pszCodePage = SHPLIB_NULLPTR;
467
9.19k
    if (pfCPG)
468
48
    {
469
48
        memset(pabyBuf, 0, nBufSize);
470
48
        psDBF->sHooks.FRead(pabyBuf, 1, nBufSize - 1, pfCPG);
471
48
        const size_t n = strcspn(REINTERPRET_CAST(char *, pabyBuf), "\n\r");
472
48
        if (n > 0)
473
24
        {
474
24
            pabyBuf[n] = '\0';
475
24
            psDBF->pszCodePage = STATIC_CAST(char *, malloc(n + 1));
476
24
            if (psDBF->pszCodePage)
477
24
                memcpy(psDBF->pszCodePage, pabyBuf, n + 1);
478
24
        }
479
48
        psDBF->sHooks.FClose(pfCPG);
480
48
    }
481
9.19k
    if (psDBF->pszCodePage == SHPLIB_NULLPTR && pabyBuf[29] != 0)
482
8.40k
    {
483
8.40k
        snprintf(REINTERPRET_CAST(char *, pabyBuf), nBufSize, "LDID/%d",
484
8.40k
                 psDBF->iLanguageDriver);
485
8.40k
        psDBF->pszCodePage = STATIC_CAST(
486
8.40k
            char *, malloc(strlen(REINTERPRET_CAST(char *, pabyBuf)) + 1));
487
8.40k
        if (psDBF->pszCodePage)
488
8.40k
            strcpy(psDBF->pszCodePage, REINTERPRET_CAST(char *, pabyBuf));
489
8.40k
    }
490
491
    /* -------------------------------------------------------------------- */
492
    /*  Read in Field Definitions                                           */
493
    /* -------------------------------------------------------------------- */
494
495
    // To please Coverity Scan
496
9.19k
    assert(nHeadLen < 65536);
497
9.19k
    unsigned char *pabyBufNew =
498
9.19k
        STATIC_CAST(unsigned char *, realloc(pabyBuf, nHeadLen));
499
9.19k
    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
9.19k
    pabyBuf = pabyBufNew;
509
9.19k
    psDBF->pszHeader = REINTERPRET_CAST(char *, pabyBuf);
510
511
9.19k
    psDBF->sHooks.FSeek(psDBF->fp, XBASE_FILEHDR_SZ, 0);
512
9.19k
    if (psDBF->sHooks.FRead(pabyBuf, nHeadLen - XBASE_FILEHDR_SZ, 1,
513
9.19k
                            psDBF->fp) != 1)
514
1.77k
    {
515
1.77k
        psDBF->sHooks.FClose(psDBF->fp);
516
1.77k
        free(pabyBuf);
517
1.77k
        free(psDBF->pszCurrentRecord);
518
1.77k
        free(psDBF->pszCodePage);
519
1.77k
        free(psDBF);
520
1.77k
        return SHPLIB_NULLPTR;
521
1.77k
    }
522
523
7.41k
    psDBF->panFieldOffset = STATIC_CAST(int *, malloc(sizeof(int) * nFields));
524
7.41k
    psDBF->panFieldSize = STATIC_CAST(int *, malloc(sizeof(int) * nFields));
525
7.41k
    psDBF->panFieldDecimals = STATIC_CAST(int *, malloc(sizeof(int) * nFields));
526
7.41k
    psDBF->pachFieldType = STATIC_CAST(char *, malloc(sizeof(char) * nFields));
527
7.41k
    if (!psDBF->panFieldOffset || !psDBF->panFieldSize ||
528
7.41k
        !psDBF->panFieldDecimals || !psDBF->pachFieldType)
529
0
    {
530
0
        DBFClose(psDBF);
531
0
        return SHPLIB_NULLPTR;
532
0
    }
533
534
461k
    for (int iField = 0; iField < nFields; iField++)
535
456k
    {
536
456k
        const unsigned char *pabyFInfo = pabyBuf + iField * XBASE_FLDHDR_SZ;
537
456k
        if (pabyFInfo[0] == HEADER_RECORD_TERMINATOR)
538
3.20k
        {
539
3.20k
            psDBF->nFields = iField;
540
3.20k
            break;
541
3.20k
        }
542
543
453k
        if (pabyFInfo[11] == 'N' || pabyFInfo[11] == 'F')
544
29.6k
        {
545
29.6k
            psDBF->panFieldSize[iField] = pabyFInfo[16];
546
29.6k
            psDBF->panFieldDecimals[iField] = pabyFInfo[17];
547
29.6k
        }
548
424k
        else
549
424k
        {
550
424k
            psDBF->panFieldSize[iField] = pabyFInfo[16];
551
424k
            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
424k
        }
565
566
453k
        psDBF->pachFieldType[iField] = STATIC_CAST(char, pabyFInfo[11]);
567
453k
        if (iField == 0)
568
7.16k
            psDBF->panFieldOffset[iField] = 1;
569
446k
        else
570
446k
            psDBF->panFieldOffset[iField] = psDBF->panFieldOffset[iField - 1] +
571
446k
                                            psDBF->panFieldSize[iField - 1];
572
453k
    }
573
574
    /* Check that the total width of fields does not exceed the record width */
575
7.41k
    if (psDBF->nFields > 0 && psDBF->panFieldOffset[psDBF->nFields - 1] +
576
7.16k
                                      psDBF->panFieldSize[psDBF->nFields - 1] >
577
7.16k
                                  psDBF->nRecordLength)
578
1.03k
    {
579
1.03k
        DBFClose(psDBF);
580
1.03k
        return SHPLIB_NULLPTR;
581
1.03k
    }
582
583
6.38k
    DBFSetWriteEndOfFileChar(psDBF, TRUE);
584
585
6.38k
    psDBF->bRequireNextWriteSeek = TRUE;
586
587
6.38k
    return (psDBF);
588
7.41k
}
589
590
/************************************************************************/
591
/*                              DBFClose()                              */
592
/************************************************************************/
593
594
void SHPAPI_CALL DBFClose(DBFHandle psDBF)
595
11.3k
{
596
11.3k
    if (psDBF == SHPLIB_NULLPTR)
597
0
        return;
598
599
    /* -------------------------------------------------------------------- */
600
    /*      Write out header if not already written.                        */
601
    /* -------------------------------------------------------------------- */
602
11.3k
    if (psDBF->bNoHeader)
603
1.03k
        DBFWriteHeader(psDBF);
604
605
11.3k
    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
11.3k
    if (psDBF->bUpdated)
612
2.84k
        DBFUpdateHeader(psDBF);
613
614
    /* -------------------------------------------------------------------- */
615
    /*      Close, and free resources.                                      */
616
    /* -------------------------------------------------------------------- */
617
11.3k
    psDBF->sHooks.FClose(psDBF->fp);
618
619
11.3k
    if (psDBF->panFieldOffset != SHPLIB_NULLPTR)
620
11.2k
    {
621
11.2k
        free(psDBF->panFieldOffset);
622
11.2k
        free(psDBF->panFieldSize);
623
11.2k
        free(psDBF->panFieldDecimals);
624
11.2k
        free(psDBF->pachFieldType);
625
11.2k
    }
626
627
11.3k
    if (psDBF->pszWorkField != SHPLIB_NULLPTR)
628
1.62k
        free(psDBF->pszWorkField);
629
630
11.3k
    free(psDBF->pszHeader);
631
11.3k
    free(psDBF->pszCurrentRecord);
632
11.3k
    free(psDBF->pszCodePage);
633
634
11.3k
    free(psDBF);
635
11.3k
}
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.95k
{
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.95k
    const int nLenWithoutExtension = DBFGetLenWithoutExtension(pszFilename);
679
3.95k
    char *pszFullname = STATIC_CAST(char *, malloc(nLenWithoutExtension + 5));
680
3.95k
    if (!pszFullname)
681
0
        return SHPLIB_NULLPTR;
682
3.95k
    memcpy(pszFullname, pszFilename, nLenWithoutExtension);
683
3.95k
    memcpy(pszFullname + nLenWithoutExtension, ".dbf", 5);
684
685
    /* -------------------------------------------------------------------- */
686
    /*      Create the file.                                                */
687
    /* -------------------------------------------------------------------- */
688
3.95k
    SAFile fp = psHooks->FOpen(pszFullname, "wb+", psHooks->pvUserData);
689
3.95k
    if (fp == SHPLIB_NULLPTR)
690
3
    {
691
3
        const size_t nMessageLen = strlen(pszFullname) + 256;
692
3
        char *pszMessage = STATIC_CAST(char *, malloc(nMessageLen));
693
3
        if (pszMessage)
694
3
        {
695
3
            snprintf(pszMessage, nMessageLen, "Failed to create file %s: %s",
696
3
                     pszFullname, strerror(errno));
697
3
            psHooks->Error(pszMessage);
698
3
            free(pszMessage);
699
3
        }
700
701
3
        free(pszFullname);
702
3
        return SHPLIB_NULLPTR;
703
3
    }
704
705
3.94k
    memcpy(pszFullname + nLenWithoutExtension, ".cpg", 5);
706
3.94k
    int ldid = -1;
707
3.94k
    if (pszCodePage != SHPLIB_NULLPTR)
708
3.94k
    {
709
3.94k
        if (strncmp(pszCodePage, "LDID/", 5) == 0)
710
3.94k
        {
711
3.94k
            ldid = atoi(pszCodePage + 5);
712
3.94k
            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.94k
        }
716
3.94k
        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.94k
    }
726
3.94k
    if (pszCodePage == SHPLIB_NULLPTR || ldid >= 0)
727
3.94k
    {
728
3.94k
        psHooks->Remove(pszFullname, psHooks->pvUserData);
729
3.94k
    }
730
731
3.94k
    free(pszFullname);
732
733
    /* -------------------------------------------------------------------- */
734
    /*      Create the info structure.                                      */
735
    /* -------------------------------------------------------------------- */
736
3.94k
    DBFHandle psDBF = STATIC_CAST(DBFHandle, calloc(1, sizeof(DBFInfo)));
737
3.94k
    if (!psDBF)
738
0
    {
739
0
        return SHPLIB_NULLPTR;
740
0
    }
741
742
3.94k
    memcpy(&(psDBF->sHooks), psHooks, sizeof(SAHooks));
743
3.94k
    psDBF->fp = fp;
744
3.94k
    psDBF->nRecords = 0;
745
3.94k
    psDBF->nFields = 0;
746
3.94k
    psDBF->nRecordLength = 1;
747
3.94k
    psDBF->nHeaderLength =
748
3.94k
        XBASE_FILEHDR_SZ + 1; /* + 1 for HEADER_RECORD_TERMINATOR */
749
750
3.94k
    psDBF->panFieldOffset = SHPLIB_NULLPTR;
751
3.94k
    psDBF->panFieldSize = SHPLIB_NULLPTR;
752
3.94k
    psDBF->panFieldDecimals = SHPLIB_NULLPTR;
753
3.94k
    psDBF->pachFieldType = SHPLIB_NULLPTR;
754
3.94k
    psDBF->pszHeader = SHPLIB_NULLPTR;
755
756
3.94k
    psDBF->nCurrentRecord = -1;
757
3.94k
    psDBF->bCurrentRecordModified = FALSE;
758
3.94k
    psDBF->pszCurrentRecord = SHPLIB_NULLPTR;
759
760
3.94k
    psDBF->bNoHeader = TRUE;
761
762
3.94k
    psDBF->iLanguageDriver = ldid > 0 ? ldid : 0;
763
3.94k
    psDBF->pszCodePage = SHPLIB_NULLPTR;
764
3.94k
    if (pszCodePage)
765
3.94k
    {
766
3.94k
        psDBF->pszCodePage =
767
3.94k
            STATIC_CAST(char *, malloc(strlen(pszCodePage) + 1));
768
3.94k
        if (psDBF->pszCodePage)
769
3.94k
            strcpy(psDBF->pszCodePage, pszCodePage);
770
3.94k
    }
771
3.94k
    DBFSetLastModifiedDate(psDBF, 95, 7, 26); /* dummy date */
772
773
3.94k
    DBFSetWriteEndOfFileChar(psDBF, TRUE);
774
775
3.94k
    psDBF->bRequireNextWriteSeek = TRUE;
776
777
3.94k
    return (psDBF);
778
3.94k
}
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
83
{
789
83
    char chNativeType;
790
791
83
    if (eType == FTLogical)
792
0
        chNativeType = 'L';
793
83
    else if (eType == FTDate)
794
0
        chNativeType = 'D';
795
83
    else if (eType == FTString)
796
0
        chNativeType = 'C';
797
83
    else
798
83
        chNativeType = 'N';
799
800
83
    return DBFAddNativeFieldType(psDBF, pszFieldName, chNativeType, nWidth,
801
83
                                 nDecimals);
802
83
}
803
804
/************************************************************************/
805
/*                        DBFGetNullCharacter()                         */
806
/************************************************************************/
807
808
static char DBFGetNullCharacter(char chType)
809
3.05M
{
810
3.05M
    switch (chType)
811
3.05M
    {
812
109k
        case 'N':
813
109k
        case 'F':
814
109k
            return '*';
815
0
        case 'D':
816
0
            return '0';
817
0
        case 'L':
818
0
            return '?';
819
2.94M
        default:
820
2.94M
            return ' ';
821
3.05M
    }
822
3.05M
}
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
30.6k
{
834
    /* make sure that everything is written in .dbf */
835
30.6k
    if (!DBFFlushRecord(psDBF))
836
9
        return -1;
837
838
30.6k
    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
30.6k
    if (nWidth < 1)
853
0
        return -1;
854
855
30.6k
    if (nWidth > XBASE_FLD_MAX_WIDTH)
856
0
        nWidth = XBASE_FLD_MAX_WIDTH;
857
858
30.6k
    if (psDBF->nRecordLength + nWidth > 65535)
859
142
    {
860
142
        char szMessage[128];
861
142
        snprintf(szMessage, sizeof(szMessage),
862
142
                 "Cannot add field %s. Record length limit reached "
863
142
                 "(max 65535 bytes).",
864
142
                 pszFieldName);
865
142
        psDBF->sHooks.Error(szMessage);
866
142
        return -1;
867
142
    }
868
869
30.5k
    const int nOldRecordLength = psDBF->nRecordLength;
870
30.5k
    const int nOldHeaderLength = psDBF->nHeaderLength;
871
872
    /* -------------------------------------------------------------------- */
873
    /*      realloc all the arrays larger to hold the additional field      */
874
    /*      information.                                                    */
875
    /* -------------------------------------------------------------------- */
876
877
30.5k
    int *panFieldOffsetNew =
878
30.5k
        STATIC_CAST(int *, realloc(psDBF->panFieldOffset,
879
30.5k
                                   sizeof(int) * (psDBF->nFields + 1)));
880
881
30.5k
    int *panFieldSizeNew =
882
30.5k
        STATIC_CAST(int *, realloc(psDBF->panFieldSize,
883
30.5k
                                   sizeof(int) * (psDBF->nFields + 1)));
884
885
30.5k
    int *panFieldDecimalsNew =
886
30.5k
        STATIC_CAST(int *, realloc(psDBF->panFieldDecimals,
887
30.5k
                                   sizeof(int) * (psDBF->nFields + 1)));
888
889
30.5k
    char *pachFieldTypeNew =
890
30.5k
        STATIC_CAST(char *, realloc(psDBF->pachFieldType,
891
30.5k
                                    sizeof(char) * (psDBF->nFields + 1)));
892
893
30.5k
    char *pszHeaderNew =
894
30.5k
        STATIC_CAST(char *, realloc(psDBF->pszHeader,
895
30.5k
                                    (psDBF->nFields + 1) * XBASE_FLDHDR_SZ));
896
897
    /* -------------------------------------------------------------------- */
898
    /*      Make the current record buffer appropriately larger.            */
899
    /* -------------------------------------------------------------------- */
900
30.5k
    char *pszCurrentRecordNew =
901
30.5k
        STATIC_CAST(char *, realloc(psDBF->pszCurrentRecord,
902
30.5k
                                    psDBF->nRecordLength + nWidth));
903
904
30.5k
    if (panFieldOffsetNew)
905
30.5k
        psDBF->panFieldOffset = panFieldOffsetNew;
906
30.5k
    if (panFieldSizeNew)
907
30.5k
        psDBF->panFieldSize = panFieldSizeNew;
908
30.5k
    if (panFieldDecimalsNew)
909
30.5k
        psDBF->panFieldDecimals = panFieldDecimalsNew;
910
30.5k
    if (pachFieldTypeNew)
911
30.5k
        psDBF->pachFieldType = pachFieldTypeNew;
912
30.5k
    if (pszHeaderNew)
913
30.5k
        psDBF->pszHeader = pszHeaderNew;
914
30.5k
    if (pszCurrentRecordNew)
915
30.5k
        psDBF->pszCurrentRecord = pszCurrentRecordNew;
916
917
30.5k
    if (!panFieldOffsetNew || !panFieldSizeNew || !panFieldDecimalsNew ||
918
30.5k
        !pachFieldTypeNew || !pszHeaderNew || !pszCurrentRecordNew)
919
0
    {
920
0
        psDBF->sHooks.Error("Out of memory");
921
0
        return -1;
922
0
    }
923
924
    /* alloc record */
925
30.5k
    char *pszRecord = SHPLIB_NULLPTR;
926
30.5k
    if (!psDBF->bNoHeader)
927
1.30k
    {
928
1.30k
        pszRecord = STATIC_CAST(char *, malloc(psDBF->nRecordLength + nWidth));
929
1.30k
        if (!pszRecord)
930
0
        {
931
0
            psDBF->sHooks.Error("Out of memory");
932
0
            return -1;
933
0
        }
934
1.30k
    }
935
936
30.5k
    psDBF->nFields++;
937
938
    /* -------------------------------------------------------------------- */
939
    /*      Assign the new field information fields.                        */
940
    /* -------------------------------------------------------------------- */
941
30.5k
    psDBF->panFieldOffset[psDBF->nFields - 1] = psDBF->nRecordLength;
942
30.5k
    psDBF->nRecordLength += nWidth;
943
30.5k
    psDBF->panFieldSize[psDBF->nFields - 1] = nWidth;
944
30.5k
    psDBF->panFieldDecimals[psDBF->nFields - 1] = nDecimals;
945
30.5k
    psDBF->pachFieldType[psDBF->nFields - 1] = chType;
946
947
    /* -------------------------------------------------------------------- */
948
    /*      Extend the required header information.                         */
949
    /* -------------------------------------------------------------------- */
950
30.5k
    psDBF->nHeaderLength += XBASE_FLDHDR_SZ;
951
30.5k
    psDBF->bUpdated = FALSE;
952
953
30.5k
    char *pszFInfo = psDBF->pszHeader + XBASE_FLDHDR_SZ * (psDBF->nFields - 1);
954
955
1.00M
    for (int i = 0; i < XBASE_FLDHDR_SZ; i++)
956
976k
        pszFInfo[i] = '\0';
957
958
30.5k
    strncpy(pszFInfo, pszFieldName, XBASE_FLDNAME_LEN_WRITE);
959
960
30.5k
    pszFInfo[11] = psDBF->pachFieldType[psDBF->nFields - 1];
961
962
30.5k
    if (chType == 'C')
963
30.0k
    {
964
30.0k
        pszFInfo[16] = STATIC_CAST(unsigned char, nWidth % 256);
965
30.0k
        pszFInfo[17] = STATIC_CAST(unsigned char, nWidth / 256);
966
30.0k
    }
967
462
    else
968
462
    {
969
462
        pszFInfo[16] = STATIC_CAST(unsigned char, nWidth);
970
462
        pszFInfo[17] = STATIC_CAST(unsigned char, nDecimals);
971
462
    }
972
973
    /* we're done if dealing with new .dbf */
974
30.5k
    if (psDBF->bNoHeader)
975
29.2k
        return (psDBF->nFields - 1);
976
977
    /* -------------------------------------------------------------------- */
978
    /*      For existing .dbf file, shift records                           */
979
    /* -------------------------------------------------------------------- */
980
981
1.30k
    const char chFieldFill = DBFGetNullCharacter(chType);
982
983
1.30k
    SAOffset nRecordOffset;
984
692k
    for (int i = psDBF->nRecords - 1; i >= 0; --i)
985
691k
    {
986
691k
        nRecordOffset =
987
691k
            nOldRecordLength * STATIC_CAST(SAOffset, i) + nOldHeaderLength;
988
989
        /* load record */
990
691k
        psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
991
691k
        if (psDBF->sHooks.FRead(pszRecord, nOldRecordLength, 1, psDBF->fp) != 1)
992
119
        {
993
119
            free(pszRecord);
994
119
            return -1;
995
119
        }
996
997
        /* set new field's value to NULL */
998
691k
        memset(pszRecord + nOldRecordLength, chFieldFill, nWidth);
999
1000
691k
        nRecordOffset = psDBF->nRecordLength * STATIC_CAST(SAOffset, i) +
1001
691k
                        psDBF->nHeaderLength;
1002
1003
        /* move record to the new place*/
1004
691k
        psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
1005
691k
        psDBF->sHooks.FWrite(pszRecord, psDBF->nRecordLength, 1, psDBF->fp);
1006
691k
    }
1007
1008
1.18k
    if (psDBF->bWriteEndOfFileChar)
1009
1.18k
    {
1010
1.18k
        char ch = END_OF_FILE_CHARACTER;
1011
1012
1.18k
        nRecordOffset =
1013
1.18k
            psDBF->nRecordLength * STATIC_CAST(SAOffset, psDBF->nRecords) +
1014
1.18k
            psDBF->nHeaderLength;
1015
1016
1.18k
        psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
1017
1.18k
        psDBF->sHooks.FWrite(&ch, 1, 1, psDBF->fp);
1018
1.18k
    }
1019
1020
    /* free record */
1021
1.18k
    free(pszRecord);
1022
1023
    /* force update of header with new header, record length and new field */
1024
1.18k
    psDBF->bNoHeader = TRUE;
1025
1.18k
    DBFUpdateHeader(psDBF);
1026
1027
1.18k
    psDBF->nCurrentRecord = -1;
1028
1.18k
    psDBF->bCurrentRecordModified = FALSE;
1029
1.18k
    psDBF->bUpdated = TRUE;
1030
1031
1.18k
    return (psDBF->nFields - 1);
1032
1.30k
}
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
734k
{
1043
    /* -------------------------------------------------------------------- */
1044
    /*      Verify selection.                                               */
1045
    /* -------------------------------------------------------------------- */
1046
734k
    if (hEntity < 0 || hEntity >= psDBF->nRecords)
1047
0
        return SHPLIB_NULLPTR;
1048
1049
734k
    if (iField < 0 || iField >= psDBF->nFields)
1050
0
        return SHPLIB_NULLPTR;
1051
1052
    /* -------------------------------------------------------------------- */
1053
    /*     Have we read the record?                                         */
1054
    /* -------------------------------------------------------------------- */
1055
734k
    if (!DBFLoadRecord(psDBF, hEntity))
1056
0
        return SHPLIB_NULLPTR;
1057
1058
734k
    const unsigned char *pabyRec =
1059
734k
        REINTERPRET_CAST(const unsigned char *, psDBF->pszCurrentRecord);
1060
1061
    /* -------------------------------------------------------------------- */
1062
    /*      Ensure we have room to extract the target field.                */
1063
    /* -------------------------------------------------------------------- */
1064
734k
    if (psDBF->panFieldSize[iField] >= psDBF->nWorkFieldLength)
1065
2.58k
    {
1066
2.58k
        psDBF->nWorkFieldLength = psDBF->panFieldSize[iField] + 100;
1067
2.58k
        if (psDBF->pszWorkField == SHPLIB_NULLPTR)
1068
1.62k
            psDBF->pszWorkField =
1069
1.62k
                STATIC_CAST(char *, malloc(psDBF->nWorkFieldLength));
1070
958
        else
1071
958
            psDBF->pszWorkField = STATIC_CAST(
1072
2.58k
                char *, realloc(psDBF->pszWorkField, psDBF->nWorkFieldLength));
1073
2.58k
    }
1074
1075
    /* -------------------------------------------------------------------- */
1076
    /*      Extract the requested field.                                    */
1077
    /* -------------------------------------------------------------------- */
1078
734k
    memcpy(psDBF->pszWorkField,
1079
734k
           REINTERPRET_CAST(const char *, pabyRec) +
1080
734k
               psDBF->panFieldOffset[iField],
1081
734k
           psDBF->panFieldSize[iField]);
1082
734k
    psDBF->pszWorkField[psDBF->panFieldSize[iField]] = '\0';
1083
1084
734k
    void *pReturnField = psDBF->pszWorkField;
1085
1086
    /* -------------------------------------------------------------------- */
1087
    /*      Decode the field.                                               */
1088
    /* -------------------------------------------------------------------- */
1089
734k
    if (chReqType == 'I')
1090
0
    {
1091
0
        psDBF->fieldValue.nIntField = atoi(psDBF->pszWorkField);
1092
1093
0
        pReturnField = &(psDBF->fieldValue.nIntField);
1094
0
    }
1095
734k
    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
734k
#ifdef TRIM_DBF_WHITESPACE
1107
734k
    else
1108
734k
    {
1109
734k
        char *pchSrc = psDBF->pszWorkField;
1110
734k
        char *pchDst = pchSrc;
1111
1112
1.05M
        while (*pchSrc == ' ')
1113
319k
            pchSrc++;
1114
1115
32.6M
        while (*pchSrc != '\0')
1116
31.9M
            *(pchDst++) = *(pchSrc++);
1117
734k
        *pchDst = '\0';
1118
1119
837k
        while (pchDst != psDBF->pszWorkField && *(--pchDst) == ' ')
1120
103k
            *pchDst = '\0';
1121
734k
    }
1122
734k
#endif
1123
1124
734k
    return pReturnField;
1125
734k
}
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
701k
{
1172
701k
    return STATIC_CAST(const char *,
1173
701k
                       DBFReadAttribute(psDBF, iRecord, iField, 'C'));
1174
701k
}
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
33.0k
{
1185
33.0k
    return STATIC_CAST(const char *,
1186
33.0k
                       DBFReadAttribute(psDBF, iRecord, iField, 'L'));
1187
33.0k
}
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
895k
{
1228
895k
    if (pszValue == SHPLIB_NULLPTR)
1229
0
        return true;
1230
1231
895k
    switch (chType)
1232
895k
    {
1233
86.3k
        case 'N':
1234
99.2k
        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
99.2k
            if (pszValue[0] == '*')
1241
22.1k
                return true;
1242
1243
80.5k
            for (int i = 0; pszValue[i] != '\0'; i++)
1244
72.4k
            {
1245
72.4k
                if (pszValue[i] != ' ')
1246
69.0k
                    return false;
1247
72.4k
            }
1248
8.08k
            return true;
1249
1250
41.2k
        case 'D':
1251
41.2k
        {
1252
41.2k
            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
41.2k
            if (pszValue[0] == 0 || strncmp(pszValue, "00000000", 8) == 0 ||
1260
34.8k
                strcmp(pszValue, " ") == 0 || strcmp(pszValue, "0") == 0)
1261
6.55k
                return true;
1262
42.8k
            for (int i = 0; i < size; i++)
1263
42.4k
                if (pszValue[i] != DIGIT_ZERO)
1264
34.2k
                    return false;
1265
353
            return true;
1266
34.6k
        }
1267
1268
33.1k
        case 'L':
1269
            /* NULL boolean fields have value "?" */
1270
33.1k
            return pszValue[0] == '?';
1271
1272
722k
        default:
1273
            /* empty string fields are considered NULL */
1274
722k
            return strlen(pszValue) == 0;
1275
895k
    }
1276
895k
}
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
152k
{
1289
152k
    const char *pszValue = DBFReadStringAttribute(psDBF, iRecord, iField);
1290
1291
152k
    if (pszValue == SHPLIB_NULLPTR)
1292
0
        return TRUE;
1293
1294
152k
    return DBFIsValueNULL(psDBF->pachFieldType[iField], pszValue,
1295
152k
                          psDBF->panFieldSize[iField]);
1296
152k
}
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
70.4k
{
1306
70.4k
    return (psDBF->nFields);
1307
70.4k
}
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.15M
{
1317
1.15M
    return (psDBF->nRecords);
1318
1.15M
}
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
275k
{
1332
275k
    if (iField < 0 || iField >= psDBF->nFields)
1333
0
        return (FTInvalid);
1334
1335
275k
    if (pnWidth != SHPLIB_NULLPTR)
1336
275k
        *pnWidth = psDBF->panFieldSize[iField];
1337
1338
275k
    if (pnDecimals != SHPLIB_NULLPTR)
1339
275k
        *pnDecimals = psDBF->panFieldDecimals[iField];
1340
1341
275k
    if (pszFieldName != SHPLIB_NULLPTR)
1342
275k
    {
1343
275k
        strncpy(pszFieldName,
1344
275k
                STATIC_CAST(char *, psDBF->pszHeader) +
1345
275k
                    iField * XBASE_FLDHDR_SZ,
1346
275k
                XBASE_FLDNAME_LEN_READ);
1347
275k
        pszFieldName[XBASE_FLDNAME_LEN_READ] = '\0';
1348
275k
        for (int i = XBASE_FLDNAME_LEN_READ - 1;
1349
321k
             i > 0 && pszFieldName[i] == ' '; i--)
1350
46.0k
            pszFieldName[i] = '\0';
1351
275k
    }
1352
1353
275k
    if (psDBF->pachFieldType[iField] == 'L')
1354
4.96k
        return (FTLogical);
1355
1356
270k
    else if (psDBF->pachFieldType[iField] == 'D')
1357
2.55k
        return (FTDate);
1358
1359
267k
    else if (psDBF->pachFieldType[iField] == 'N' ||
1360
251k
             psDBF->pachFieldType[iField] == 'F')
1361
19.5k
    {
1362
19.5k
        if (psDBF->panFieldDecimals[iField] > 0 ||
1363
2.21k
            psDBF->panFieldSize[iField] >= 10)
1364
18.0k
            return (FTDouble);
1365
1.45k
        else
1366
1.45k
            return (FTInteger);
1367
19.5k
    }
1368
248k
    else
1369
248k
    {
1370
248k
        return (FTString);
1371
248k
    }
1372
275k
}
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
3.65M
{
1383
    /* -------------------------------------------------------------------- */
1384
    /*      Is this a valid record?                                         */
1385
    /* -------------------------------------------------------------------- */
1386
3.65M
    if (hEntity < 0 || hEntity > psDBF->nRecords)
1387
0
        return false;
1388
1389
3.65M
    if (psDBF->bNoHeader)
1390
2.82k
        DBFWriteHeader(psDBF);
1391
1392
    /* -------------------------------------------------------------------- */
1393
    /*      Is this a brand new record?                                     */
1394
    /* -------------------------------------------------------------------- */
1395
3.65M
    if (hEntity == psDBF->nRecords)
1396
581k
    {
1397
581k
        if (!DBFFlushRecord(psDBF))
1398
37.9k
            return false;
1399
1400
543k
        psDBF->nRecords++;
1401
430M
        for (int i = 0; i < psDBF->nRecordLength; i++)
1402
429M
            psDBF->pszCurrentRecord[i] = ' ';
1403
1404
543k
        psDBF->nCurrentRecord = hEntity;
1405
543k
    }
1406
1407
    /* -------------------------------------------------------------------- */
1408
    /*      Is this an existing record, but different than the last one     */
1409
    /*      we accessed?                                                    */
1410
    /* -------------------------------------------------------------------- */
1411
3.61M
    if (!DBFLoadRecord(psDBF, hEntity))
1412
0
        return false;
1413
1414
3.61M
    unsigned char *pabyRec =
1415
3.61M
        REINTERPRET_CAST(unsigned char *, psDBF->pszCurrentRecord);
1416
1417
3.61M
    psDBF->bCurrentRecordModified = TRUE;
1418
3.61M
    psDBF->bUpdated = TRUE;
1419
1420
    /* -------------------------------------------------------------------- */
1421
    /*      Translate NULL value to valid DBF file representation.          */
1422
    /*                                                                      */
1423
    /*      Contributed by Jim Matthews.                                    */
1424
    /* -------------------------------------------------------------------- */
1425
3.61M
    if (pValue == SHPLIB_NULLPTR)
1426
3.04M
    {
1427
3.04M
        memset(pabyRec + psDBF->panFieldOffset[iField],
1428
3.04M
               DBFGetNullCharacter(psDBF->pachFieldType[iField]),
1429
3.04M
               psDBF->panFieldSize[iField]);
1430
3.04M
        return true;
1431
3.04M
    }
1432
1433
    /* -------------------------------------------------------------------- */
1434
    /*      Assign all the record fields.                                   */
1435
    /* -------------------------------------------------------------------- */
1436
564k
    bool nRetResult = true;
1437
1438
564k
    switch (psDBF->pachFieldType[iField])
1439
564k
    {
1440
0
        case 'D':
1441
5.85k
        case 'N':
1442
5.85k
        case 'F':
1443
5.85k
        {
1444
5.85k
            int nWidth = psDBF->panFieldSize[iField];
1445
1446
5.85k
            char szSField[XBASE_FLD_MAX_WIDTH + 1];
1447
5.85k
            if (STATIC_CAST(int, sizeof(szSField)) - 2 < nWidth)
1448
0
                nWidth = sizeof(szSField) - 2;
1449
1450
5.85k
            char szFormat[20];
1451
5.85k
            snprintf(szFormat, sizeof(szFormat), "%%%d.%df", nWidth,
1452
5.85k
                     psDBF->panFieldDecimals[iField]);
1453
5.85k
            const double value = *STATIC_CAST(double *, pValue);
1454
5.85k
            CPLsnprintf(szSField, sizeof(szSField), szFormat, value);
1455
5.85k
            szSField[sizeof(szSField) - 1] = '\0';
1456
5.85k
            if (STATIC_CAST(int, strlen(szSField)) >
1457
5.85k
                psDBF->panFieldSize[iField])
1458
39
            {
1459
39
                szSField[psDBF->panFieldSize[iField]] = '\0';
1460
39
                nRetResult = psDBF->sHooks.Atof(szSField) == value;
1461
39
            }
1462
5.85k
            memcpy(REINTERPRET_CAST(char *,
1463
5.85k
                                    pabyRec + psDBF->panFieldOffset[iField]),
1464
5.85k
                   szSField, strlen(szSField));
1465
5.85k
            break;
1466
5.85k
        }
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
559k
        default:
1483
559k
        {
1484
559k
            int j;
1485
559k
            if (STATIC_CAST(int, strlen(STATIC_CAST(char *, pValue))) >
1486
559k
                psDBF->panFieldSize[iField])
1487
10.2k
            {
1488
10.2k
                j = psDBF->panFieldSize[iField];
1489
10.2k
                nRetResult = false;
1490
10.2k
            }
1491
548k
            else
1492
548k
            {
1493
548k
                memset(pabyRec + psDBF->panFieldOffset[iField], ' ',
1494
548k
                       psDBF->panFieldSize[iField]);
1495
548k
                j = STATIC_CAST(int, strlen(STATIC_CAST(char *, pValue)));
1496
548k
            }
1497
1498
559k
            strncpy(REINTERPRET_CAST(char *,
1499
559k
                                     pabyRec + psDBF->panFieldOffset[iField]),
1500
559k
                    STATIC_CAST(const char *, pValue), j);
1501
559k
            break;
1502
5.85k
        }
1503
564k
    }
1504
1505
564k
    return nRetResult;
1506
564k
}
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
2.98k
{
1519
    /* -------------------------------------------------------------------- */
1520
    /*      Is this a valid record?                                         */
1521
    /* -------------------------------------------------------------------- */
1522
2.98k
    if (hEntity < 0 || hEntity > psDBF->nRecords)
1523
0
        return (FALSE);
1524
1525
2.98k
    if (psDBF->bNoHeader)
1526
79
        DBFWriteHeader(psDBF);
1527
1528
    /* -------------------------------------------------------------------- */
1529
    /*      Is this a brand new record?                                     */
1530
    /* -------------------------------------------------------------------- */
1531
2.98k
    if (hEntity == psDBF->nRecords)
1532
1.29k
    {
1533
1.29k
        if (!DBFFlushRecord(psDBF))
1534
0
            return FALSE;
1535
1536
1.29k
        psDBF->nRecords++;
1537
309k
        for (int i = 0; i < psDBF->nRecordLength; i++)
1538
308k
            psDBF->pszCurrentRecord[i] = ' ';
1539
1540
1.29k
        psDBF->nCurrentRecord = hEntity;
1541
1.29k
    }
1542
1543
    /* -------------------------------------------------------------------- */
1544
    /*      Is this an existing record, but different than the last one     */
1545
    /*      we accessed?                                                    */
1546
    /* -------------------------------------------------------------------- */
1547
2.98k
    if (!DBFLoadRecord(psDBF, hEntity))
1548
0
        return FALSE;
1549
1550
2.98k
    if (iField >= 0)
1551
2.98k
    {
1552
2.98k
        unsigned char *pabyRec =
1553
2.98k
            REINTERPRET_CAST(unsigned char *, psDBF->pszCurrentRecord);
1554
1555
        /* -------------------------------------------------------------------- */
1556
        /*      Assign all the record fields.                                   */
1557
        /* -------------------------------------------------------------------- */
1558
2.98k
        int j;
1559
2.98k
        if (STATIC_CAST(int, strlen(STATIC_CAST(const char *, pValue))) >
1560
2.98k
            psDBF->panFieldSize[iField])
1561
0
            j = psDBF->panFieldSize[iField];
1562
2.98k
        else
1563
2.98k
        {
1564
2.98k
            memset(pabyRec + psDBF->panFieldOffset[iField], ' ',
1565
2.98k
                   psDBF->panFieldSize[iField]);
1566
2.98k
            j = STATIC_CAST(int, strlen(STATIC_CAST(const char *, pValue)));
1567
2.98k
        }
1568
1569
2.98k
        memcpy(
1570
2.98k
            REINTERPRET_CAST(char *, pabyRec + psDBF->panFieldOffset[iField]),
1571
2.98k
            STATIC_CAST(const char *, pValue), j);
1572
2.98k
    }
1573
1574
2.98k
    psDBF->bCurrentRecordModified = TRUE;
1575
2.98k
    psDBF->bUpdated = TRUE;
1576
1577
2.98k
    return (TRUE);
1578
2.98k
}
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
513
{
1589
513
    return (DBFWriteAttribute(psDBF, iRecord, iField,
1590
513
                              STATIC_CAST(void *, &dValue)));
1591
513
}
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
15.1k
{
1602
15.1k
    double dValue = nValue;
1603
1604
15.1k
    return (DBFWriteAttribute(psDBF, iRecord, iField,
1605
15.1k
                              STATIC_CAST(void *, &dValue)));
1606
15.1k
}
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
584k
{
1617
584k
    return (
1618
584k
        DBFWriteAttribute(psDBF, iRecord, iField,
1619
584k
                          STATIC_CAST(void *, CONST_CAST(char *, pszValue))));
1620
584k
}
1621
1622
/************************************************************************/
1623
/*                      DBFWriteNULLAttribute()                         */
1624
/*                                                                      */
1625
/*      Write a NULL attribute.                                         */
1626
/************************************************************************/
1627
1628
int SHPAPI_CALL DBFWriteNULLAttribute(DBFHandle psDBF, int iRecord, int iField)
1629
3.05M
{
1630
3.05M
    return (DBFWriteAttribute(psDBF, iRecord, iField, SHPLIB_NULLPTR));
1631
3.05M
}
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
31.6k
{
1810
31.6k
    if (iField >= 0 && iField < psDBF->nFields)
1811
31.6k
        return psDBF->pachFieldType[iField];
1812
1813
0
    return ' ';
1814
31.6k
}
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
162k
{
1847
    /* -------------------------------------------------------------------- */
1848
    /*      Verify selection.                                               */
1849
    /* -------------------------------------------------------------------- */
1850
162k
    if (iShape < 0 || iShape >= psDBF->nRecords)
1851
14
        return TRUE;
1852
1853
    /* -------------------------------------------------------------------- */
1854
    /*      Have we read the record?                                        */
1855
    /* -------------------------------------------------------------------- */
1856
162k
    if (!DBFLoadRecord(psDBF, iShape))
1857
2.50k
        return FALSE;
1858
1859
    /* -------------------------------------------------------------------- */
1860
    /*      '*' means deleted.                                              */
1861
    /* -------------------------------------------------------------------- */
1862
159k
    return psDBF->pszCurrentRecord[0] == '*';
1863
162k
}
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
21
{
1919
21
    if (iField < 0 || iField >= psDBF->nFields)
1920
0
        return FALSE;
1921
1922
    /* make sure that everything is written in .dbf */
1923
21
    if (!DBFFlushRecord(psDBF))
1924
2
        return FALSE;
1925
1926
    /* get information about field to be deleted */
1927
19
    int nOldRecordLength = psDBF->nRecordLength;
1928
19
    int nOldHeaderLength = psDBF->nHeaderLength;
1929
19
    int nDeletedFieldOffset = psDBF->panFieldOffset[iField];
1930
19
    int nDeletedFieldSize = psDBF->panFieldSize[iField];
1931
1932
    /* update fields info */
1933
19
    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
19
    psDBF->nFields--;
1944
1945
19
    psDBF->panFieldOffset = STATIC_CAST(
1946
19
        int *, realloc(psDBF->panFieldOffset, sizeof(int) * psDBF->nFields));
1947
1948
19
    psDBF->panFieldSize = STATIC_CAST(
1949
19
        int *, realloc(psDBF->panFieldSize, sizeof(int) * psDBF->nFields));
1950
1951
19
    psDBF->panFieldDecimals = STATIC_CAST(
1952
19
        int *, realloc(psDBF->panFieldDecimals, sizeof(int) * psDBF->nFields));
1953
1954
19
    psDBF->pachFieldType = STATIC_CAST(
1955
19
        char *, realloc(psDBF->pachFieldType, sizeof(char) * psDBF->nFields));
1956
1957
    /* update header information */
1958
19
    psDBF->nHeaderLength -= XBASE_FLDHDR_SZ;
1959
19
    psDBF->nRecordLength -= nDeletedFieldSize;
1960
1961
    /* overwrite field information in header */
1962
19
    memmove(psDBF->pszHeader + iField * XBASE_FLDHDR_SZ,
1963
19
            psDBF->pszHeader + (iField + 1) * XBASE_FLDHDR_SZ,
1964
19
            sizeof(char) * (psDBF->nFields - iField) * XBASE_FLDHDR_SZ);
1965
1966
19
    psDBF->pszHeader = STATIC_CAST(
1967
19
        char *, realloc(psDBF->pszHeader, psDBF->nFields * XBASE_FLDHDR_SZ));
1968
1969
    /* update size of current record appropriately */
1970
19
    psDBF->pszCurrentRecord = STATIC_CAST(
1971
19
        char *, realloc(psDBF->pszCurrentRecord, psDBF->nRecordLength));
1972
1973
    /* we're done if we're dealing with not yet created .dbf */
1974
19
    if (psDBF->bNoHeader && psDBF->nRecords == 0)
1975
0
        return TRUE;
1976
1977
    /* force update of header with new header and record length */
1978
19
    psDBF->bNoHeader = TRUE;
1979
19
    DBFUpdateHeader(psDBF);
1980
1981
    /* alloc record */
1982
19
    char *pszRecord =
1983
19
        STATIC_CAST(char *, malloc(sizeof(char) * nOldRecordLength));
1984
1985
    /* shift records to their new positions */
1986
40
    for (int iRecord = 0; iRecord < psDBF->nRecords; iRecord++)
1987
36
    {
1988
36
        SAOffset nRecordOffset =
1989
36
            nOldRecordLength * STATIC_CAST(SAOffset, iRecord) +
1990
36
            nOldHeaderLength;
1991
1992
        /* load record */
1993
36
        psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
1994
36
        if (psDBF->sHooks.FRead(pszRecord, nOldRecordLength, 1, psDBF->fp) != 1)
1995
15
        {
1996
15
            free(pszRecord);
1997
15
            return FALSE;
1998
15
        }
1999
2000
21
        nRecordOffset = psDBF->nRecordLength * STATIC_CAST(SAOffset, iRecord) +
2001
21
                        psDBF->nHeaderLength;
2002
2003
        /* move record in two steps */
2004
21
        psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2005
21
        psDBF->sHooks.FWrite(pszRecord, nDeletedFieldOffset, 1, psDBF->fp);
2006
21
        psDBF->sHooks.FWrite(
2007
21
            pszRecord + nDeletedFieldOffset + nDeletedFieldSize,
2008
21
            nOldRecordLength - nDeletedFieldOffset - nDeletedFieldSize, 1,
2009
21
            psDBF->fp);
2010
21
    }
2011
2012
4
    if (psDBF->bWriteEndOfFileChar)
2013
4
    {
2014
4
        char ch = END_OF_FILE_CHARACTER;
2015
4
        SAOffset nEOFOffset =
2016
4
            psDBF->nRecordLength * STATIC_CAST(SAOffset, psDBF->nRecords) +
2017
4
            psDBF->nHeaderLength;
2018
2019
4
        psDBF->sHooks.FSeek(psDBF->fp, nEOFOffset, 0);
2020
4
        psDBF->sHooks.FWrite(&ch, 1, 1, psDBF->fp);
2021
4
    }
2022
2023
    /* TODO: truncate file */
2024
2025
    /* free record */
2026
4
    free(pszRecord);
2027
2028
4
    psDBF->nCurrentRecord = -1;
2029
4
    psDBF->bCurrentRecordModified = FALSE;
2030
4
    psDBF->bUpdated = TRUE;
2031
2032
4
    return TRUE;
2033
19
}
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.47k
{
2195
4.47k
    if (iField < 0 || iField >= psDBF->nFields)
2196
0
        return FALSE;
2197
2198
    /* make sure that everything is written in .dbf */
2199
4.47k
    if (!DBFFlushRecord(psDBF))
2200
572
        return FALSE;
2201
2202
3.90k
    const char chFieldFill = DBFGetNullCharacter(chType);
2203
2204
3.90k
    const char chOldType = psDBF->pachFieldType[iField];
2205
3.90k
    const int nOffset = psDBF->panFieldOffset[iField];
2206
3.90k
    const int nOldWidth = psDBF->panFieldSize[iField];
2207
3.90k
    const int nOldRecordLength = psDBF->nRecordLength;
2208
2209
    /* -------------------------------------------------------------------- */
2210
    /*      Do some checking to ensure we can add records to this file.     */
2211
    /* -------------------------------------------------------------------- */
2212
3.90k
    if (nWidth < 1)
2213
0
        return -1;
2214
2215
3.90k
    if (nWidth > XBASE_FLD_MAX_WIDTH)
2216
0
        nWidth = XBASE_FLD_MAX_WIDTH;
2217
2218
3.90k
    char *pszRecord = STATIC_CAST(
2219
3.90k
        char *, malloc(nOldRecordLength +
2220
3.90k
                       ((nWidth > nOldWidth) ? nWidth - nOldWidth : 0)));
2221
3.90k
    char *pszOldField = STATIC_CAST(char *, malloc(nOldWidth + 1));
2222
3.90k
    if (!pszRecord || !pszOldField)
2223
0
    {
2224
0
        free(pszRecord);
2225
0
        free(pszOldField);
2226
0
        return FALSE;
2227
0
    }
2228
2229
3.90k
    if (nWidth != nOldWidth)
2230
3.87k
    {
2231
3.87k
        char *pszCurrentRecordNew = STATIC_CAST(
2232
3.87k
            char *, realloc(psDBF->pszCurrentRecord,
2233
3.87k
                            psDBF->nRecordLength + nWidth - nOldWidth));
2234
3.87k
        if (!pszCurrentRecordNew)
2235
0
        {
2236
0
            free(pszRecord);
2237
0
            free(pszOldField);
2238
0
            return FALSE;
2239
0
        }
2240
3.87k
        psDBF->pszCurrentRecord = pszCurrentRecordNew;
2241
3.87k
    }
2242
2243
    /* -------------------------------------------------------------------- */
2244
    /*      Assign the new field information fields.                        */
2245
    /* -------------------------------------------------------------------- */
2246
3.90k
    psDBF->panFieldSize[iField] = nWidth;
2247
3.90k
    psDBF->panFieldDecimals[iField] = nDecimals;
2248
3.90k
    psDBF->pachFieldType[iField] = chType;
2249
2250
    /* -------------------------------------------------------------------- */
2251
    /*      Update the header information.                                  */
2252
    /* -------------------------------------------------------------------- */
2253
3.90k
    char *pszFInfo = psDBF->pszHeader + XBASE_FLDHDR_SZ * iField;
2254
2255
128k
    for (int i = 0; i < XBASE_FLDHDR_SZ; i++)
2256
124k
        pszFInfo[i] = '\0';
2257
2258
3.90k
    strncpy(pszFInfo, pszFieldName, XBASE_FLDNAME_LEN_WRITE);
2259
2260
3.90k
    pszFInfo[11] = psDBF->pachFieldType[iField];
2261
2262
3.90k
    if (chType == 'C')
2263
3.86k
    {
2264
3.86k
        pszFInfo[16] = STATIC_CAST(unsigned char, nWidth % 256);
2265
3.86k
        pszFInfo[17] = STATIC_CAST(unsigned char, nWidth / 256);
2266
3.86k
    }
2267
38
    else
2268
38
    {
2269
38
        pszFInfo[16] = STATIC_CAST(unsigned char, nWidth);
2270
38
        pszFInfo[17] = STATIC_CAST(unsigned char, nDecimals);
2271
38
    }
2272
2273
    /* -------------------------------------------------------------------- */
2274
    /*      Update offsets                                                  */
2275
    /* -------------------------------------------------------------------- */
2276
3.90k
    if (nWidth != nOldWidth)
2277
3.87k
    {
2278
14.0k
        for (int i = iField + 1; i < psDBF->nFields; i++)
2279
10.1k
            psDBF->panFieldOffset[i] += nWidth - nOldWidth;
2280
3.87k
        psDBF->nRecordLength += nWidth - nOldWidth;
2281
3.87k
    }
2282
2283
    /* we're done if we're dealing with not yet created .dbf */
2284
3.90k
    if (psDBF->bNoHeader && psDBF->nRecords == 0)
2285
112
    {
2286
112
        free(pszRecord);
2287
112
        free(pszOldField);
2288
112
        return TRUE;
2289
112
    }
2290
2291
    /* force update of header with new header and record length */
2292
3.79k
    psDBF->bNoHeader = TRUE;
2293
3.79k
    DBFUpdateHeader(psDBF);
2294
2295
3.79k
    bool errorAbort = false;
2296
2297
3.79k
    if (nWidth < nOldWidth || (nWidth == nOldWidth && chType != chOldType))
2298
234
    {
2299
234
        pszOldField[nOldWidth] = 0;
2300
2301
        /* move records to their new positions */
2302
234
        for (int iRecord = 0; iRecord < psDBF->nRecords; iRecord++)
2303
234
        {
2304
234
            SAOffset nRecordOffset =
2305
234
                nOldRecordLength * STATIC_CAST(SAOffset, iRecord) +
2306
234
                psDBF->nHeaderLength;
2307
2308
            /* load record */
2309
234
            psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2310
234
            if (psDBF->sHooks.FRead(pszRecord, nOldRecordLength, 1,
2311
234
                                    psDBF->fp) != 1)
2312
234
            {
2313
234
                errorAbort = true;
2314
234
                break;
2315
234
            }
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
234
        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
234
    }
2367
3.55k
    else if (nWidth > nOldWidth)
2368
3.52k
    {
2369
3.52k
        pszOldField[nOldWidth] = 0;
2370
2371
        /* move records to their new positions */
2372
746k
        for (int iRecord = psDBF->nRecords - 1; iRecord >= 0; iRecord--)
2373
743k
        {
2374
743k
            SAOffset nRecordOffset =
2375
743k
                nOldRecordLength * STATIC_CAST(SAOffset, iRecord) +
2376
743k
                psDBF->nHeaderLength;
2377
2378
            /* load record */
2379
743k
            psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2380
743k
            if (psDBF->sHooks.FRead(pszRecord, nOldRecordLength, 1,
2381
743k
                                    psDBF->fp) != 1)
2382
491
            {
2383
491
                errorAbort = true;
2384
491
                break;
2385
491
            }
2386
2387
743k
            memcpy(pszOldField, pszRecord + nOffset, nOldWidth);
2388
743k
            const bool bIsNULL =
2389
743k
                DBFIsValueNULL(chOldType, pszOldField, nOldWidth);
2390
2391
743k
            if (nOffset + nOldWidth < nOldRecordLength)
2392
229k
            {
2393
229k
                memmove(pszRecord + nOffset + nWidth,
2394
229k
                        pszRecord + nOffset + nOldWidth,
2395
229k
                        nOldRecordLength - (nOffset + nOldWidth));
2396
229k
            }
2397
2398
            /* Convert null value to the appropriate value of the new type */
2399
743k
            if (bIsNULL)
2400
20.4k
            {
2401
20.4k
                memset(pszRecord + nOffset, chFieldFill, nWidth);
2402
20.4k
            }
2403
722k
            else
2404
722k
            {
2405
722k
                if ((chOldType == 'N' || chOldType == 'F'))
2406
382
                {
2407
                    /* Add leading spaces when expanding a numeric field */
2408
382
                    memmove(pszRecord + nOffset + nWidth - nOldWidth,
2409
382
                            pszRecord + nOffset, nOldWidth);
2410
382
                    memset(pszRecord + nOffset, ' ', nWidth - nOldWidth);
2411
382
                }
2412
722k
                else
2413
722k
                {
2414
                    /* Add trailing spaces */
2415
722k
                    memset(pszRecord + nOffset + nOldWidth, ' ',
2416
722k
                           nWidth - nOldWidth);
2417
722k
                }
2418
722k
            }
2419
2420
743k
            nRecordOffset =
2421
743k
                psDBF->nRecordLength * STATIC_CAST(SAOffset, iRecord) +
2422
743k
                psDBF->nHeaderLength;
2423
2424
            /* write record */
2425
743k
            psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2426
743k
            psDBF->sHooks.FWrite(pszRecord, psDBF->nRecordLength, 1, psDBF->fp);
2427
743k
        }
2428
2429
3.52k
        if (!errorAbort && psDBF->bWriteEndOfFileChar)
2430
3.03k
        {
2431
3.03k
            const char ch = END_OF_FILE_CHARACTER;
2432
2433
3.03k
            SAOffset nRecordOffset =
2434
3.03k
                psDBF->nRecordLength * STATIC_CAST(SAOffset, psDBF->nRecords) +
2435
3.03k
                psDBF->nHeaderLength;
2436
2437
3.03k
            psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2438
3.03k
            psDBF->sHooks.FWrite(&ch, 1, 1, psDBF->fp);
2439
3.03k
        }
2440
3.52k
    }
2441
2442
3.79k
    free(pszRecord);
2443
3.79k
    free(pszOldField);
2444
2445
3.79k
    if (errorAbort)
2446
725
    {
2447
725
        psDBF->nCurrentRecord = -1;
2448
725
        psDBF->bCurrentRecordModified = TRUE;
2449
725
        psDBF->bUpdated = FALSE;
2450
2451
725
        return FALSE;
2452
725
    }
2453
3.06k
    psDBF->nCurrentRecord = -1;
2454
3.06k
    psDBF->bCurrentRecordModified = FALSE;
2455
3.06k
    psDBF->bUpdated = TRUE;
2456
2457
3.06k
    return TRUE;
2458
3.79k
}
2459
2460
/************************************************************************/
2461
/*                      DBFSetWriteEndOfFileChar()                      */
2462
/************************************************************************/
2463
2464
void SHPAPI_CALL DBFSetWriteEndOfFileChar(DBFHandle psDBF, int bWriteFlag)
2465
20.4k
{
2466
20.4k
    psDBF->bWriteEndOfFileChar = bWriteFlag;
2467
20.4k
}