Coverage Report

Created: 2025-06-13 06:18

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