Coverage Report

Created: 2025-06-22 06:59

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