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