/* dbf.c */
/* Copyright (c) 2007-2012 by Troels K. All rights reserved. */
/* License: wxWindows Library Licence, Version 3.1 - see LICENSE.txt */
/* Partially based on MFC source code by www.pablosoftwaresolutions.com 2002 */
/* Partially based on Turbo C source code by Mark Sadler. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <locale.h>
#include <ctype.h>
#include <limits.h>
#include <math.h>
#include "bool.h"
#include "dbf.h"
#include "ioapi/zlib.h"
#include "ioapi/ioapi.h"
#include "minmax.h"
#include "stdint-ms.h"
#pragma warning(disable:4267)
#ifdef _MSC_VER
#define strcasecmp _stricmp
#define snprintf _snprintf
#endif
#ifndef PATH_MAX
#define PATH_MAX _MAX_PATH
#endif
#ifndef _countof
#define _countof(array) (sizeof(array)/sizeof((array)[0]))
#endif
#define C_ASSERT_(n,e) typedef char __C_ASSERT__##n[(e)?1:-1]
#ifndef offsetof
#define offsetof(s,m) (size_t)&(((s *)0)->m)
#endif
#define CPM_TEXT_TERMINATOR 0x1A
/* M */
#define MAGIC_DBASE3 0x03 /* 00000011 */
#define MAGIC_DBASE3_MEMO 0x83 /* 10000011 */
#define MAGIC_DBASE3_MEMO_2 0x8B /* 10001011 */
#define MAGIC_DBASE4 0x04 /* 00000100 */
#define MAGIC_DBASE4_MEMO 0x84 /* 10000100 */
#define MAGIC_DBASE7 0x0C /* 00001100 */
#define MAGIC_DBASE7_MEMO 0x8C /* 10001100 */
#define MAGIC_FOXPRO 0x30 /* 00110000 */
#define MAGIC_DBASE_DEFAULT MAGIC_DBASE3
#define MAGIC_DBASE_DEFAULT_MEMO MAGIC_DBASE3_MEMO
#define MEMO_BLOCK_SIZE 512 /* memo block size (dBase III) */
#define MAGIC_MEMO_BLOCK 0x0008FFFF
#define FIELDTERMINATOR '\r'
#define FIELDTERMINATOR_LEN 1
#define RECORD_POS_DELETED 0
#define RECORD_POS_DATA 1
#define RECORD_DELETED_MARKER '*'
#define RECORD_INCREMENT_BIT 0x80
#define FIELD_FILL_CHAR ' '
static void strcpy_dos2host(char* buf, const char* src, size_t buf_len, enum dbf_charconv);
static void strcpy_host2dos(char* buf, const char* src, size_t buf_len, enum dbf_charconv);
static char* strdup_host2dos(const char* src, size_t len, enum dbf_charconv, char* dup);
#pragma pack(1)
typedef struct _DBF_FILEHEADER_TIME
{
uint8_t yy;
uint8_t mm;
uint8_t dd;
} DBF_FILEHEADER_TIME;
typedef struct _DBF_FILEHEADER_3
{
uint8_t flags;
DBF_FILEHEADER_TIME lastupdate;
uint32_t recordcount;
uint16_t headerlength;
uint16_t recordlength;
uint16_t unused0;
uint8_t incomplete;
uint8_t crypt;
uint8_t unused1[16];
} DBF_FILEHEADER_3;
typedef struct _DBF_FILEHEADER_4
{
uint8_t flags;
DBF_FILEHEADER_TIME lastupdate;
uint32_t recordcount;
uint16_t headerlength;
uint16_t recordlength;
uint16_t unused0;
uint8_t incomplete;
uint8_t crypt;
uint8_t unused1[16];
uint8_t unused2[36];
} DBF_FILEHEADER_4;
typedef union _DBF_FILEHEADER
{
uint8_t flags;
DBF_FILEHEADER_3 v3;
DBF_FILEHEADER_4 v4;
} DBF_FILEHEADER;
typedef struct _DBT_FILEHEADER
{
uint32_t next;
uint8_t unused0[4];
char title[8];
uint8_t flag;
uint8_t unused1[3];
uint16_t blocksize;
uint8_t unused2[MEMO_BLOCK_SIZE - 22];
} DBT_FILEHEADER;
typedef struct _DBF_FILEFIELD_3
{
char name[DBF_DBASE3_FIELDNAMELENGTH]; // field name in ASCII zero-filled
char type; // field type in ASCII
uint8_t unused_0[4];
uint8_t length; // field length in binary
uint8_t deccount; // field decimal count in binary
uint8_t unused_1[14];
} DBF_FILEFIELD_3;
typedef struct _DBF_FILEFIELD_4
{
char name[DBF_DBASE4_FIELDNAMELENGTH]; // field name in ASCII zero-filled
char type; // field type in ASCII
uint8_t length; // field length in binary
uint8_t deccount; // field decimal count in binary
uint8_t unused_0[2];
uint8_t mdx;
uint8_t unused_1[2];
uint32_t autoincrement;
uint8_t unused_2[4];
} DBF_FILEFIELD_4;
typedef union _DBF_FILEFIELD
{
char name[1];
DBF_FILEFIELD_3 v3;
DBF_FILEFIELD_4 v4;
} DBF_FILEFIELD;
typedef union _DBF_MEMO_BLOCK
{
struct _NORMAL
{
uint32_t reserved; /* MAGIC_MEMO_BLOCK */
uint32_t len;
char text[MEMO_BLOCK_SIZE - 8];
} normal;
struct _HEADERLESS
{
char text[MEMO_BLOCK_SIZE];
} headerless;
} DBF_MEMO_BLOCK;
#pragma pack()
C_ASSERT_(1, sizeof(DBF_MEMO_BLOCK) == MEMO_BLOCK_SIZE);
C_ASSERT_(2, sizeof(DBF_FILEHEADER_3) == 32);
C_ASSERT_(3, sizeof(DBF_FILEHEADER_4) == 68);
C_ASSERT_(4, sizeof(DBT_FILEHEADER) == MEMO_BLOCK_SIZE);
C_ASSERT_(5, sizeof(DBF_FILEFIELD_3) == 32);
C_ASSERT_(6, sizeof(DBF_FILEFIELD_4) == 48);
typedef struct _DBF_MEMO_DATA
{
void* stream;
dbf_uint nextfreeblock;
DBF_MEMO_BLOCK block;
DBT_FILEHEADER header;
} DBF_MEMO_DATA;
typedef struct _DBF_DATA
{
char tablename[40];
dbf_uint currentrecord; // current record in memory
enum dbf_charconv charconv;
BOOL editable;
void* stream;
zlib_filefunc_def api;
uint8_t diskversion;
dbf_uint recordcount;
size_t recordlength;
size_t headerlength;
time_t lastupdate;
BOOL modified; // has database contents changed?
struct _DBF_FIELD_DATA* fieldarray; // linked list with field structure
dbf_uint fieldcount; // number of fields
char* recorddataptr; // pointer to current record struct
DBF_MEMO_DATA memo;
int lasterror;
char lasterrormsg[NAME_MAX];
char* dup;
} DBF_DATA;
typedef uint32_t hash_t;
typedef struct _DBF_FIELD_DATA
{
char m_Name[DBF_DBASE4_FIELDNAMELENGTH + 1];
enum dbf_data_type type;
size_t m_Length;
dbf_uint m_DecCount;
char* ptr;
hash_t namehash;
} DBF_FIELD_DATA;
static uint32_t strhash(const char* str, BOOL case_sensitive)
{
hash_t hash = 5381;
for (; *str; str++)
{
int c = case_sensitive ? *str : toupper(*str);
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
}
return hash;
}
int dbf_getlasterror(DBF_HANDLE handle)
{
return handle->lasterror;
}
const char* dbf_getlasterror_str(DBF_HANDLE handle)
{
return handle->lasterrormsg;
}
static BOOL dbf_memo_attach(DBF_HANDLE handle, void* stream)
{
BOOL ok = (stream != NULL);
if (ok)
{
handle->memo.stream = stream;
ZSEEK(handle->api, stream, 0, ZLIB_FILEFUNC_SEEK_SET);
ZREAD(handle->api, stream, &handle->memo.header, sizeof(handle->memo.header));
if (0 == handle->memo.header.blocksize) handle->memo.header.blocksize = MEMO_BLOCK_SIZE; /* OpenOffice2 */
}
return ok;
}
EXTERN_C DBF_HANDLE dbf_alloc(void)
{
DBF_DATA* handle = (DBF_DATA*)malloc(sizeof(DBF_DATA));
handle->stream = NULL;
handle->fieldcount = 0;
handle->memo.nextfreeblock = 1;
handle->memo.stream = NULL;
memset(&handle->memo.header, 0, sizeof(handle->memo.header));
handle->memo.header.blocksize = MEMO_BLOCK_SIZE;
handle->memo.header.flag = MAGIC_DBASE3;
handle->memo.header.next = 1;
handle->fieldarray = NULL;
handle->modified = FALSE;
handle->recorddataptr = NULL;
handle->editable = FALSE;
handle->currentrecord = (dbf_uint)-1;
handle->lastupdate = time(NULL);
handle->recordcount = 0;
handle->recordlength = 0;
handle->headerlength = 0;
handle->lasterror = DBASE_SUCCESS;
handle->diskversion = MAGIC_DBASE_DEFAULT;
handle->dup = NULL;
*handle->lasterrormsg = 0;
*handle->tablename = 0;
fill_fopen_filefunc(&handle->api);
handle->charconv = ENUM_dbf_charconv_compatible;
return handle;
}
static BOOL sanity_check_3(const DBF_FILEHEADER_3* header)
{
BOOL ok = ((header->lastupdate.mm >= 1) && (header->lastupdate.mm <= 12))
&& ((header->lastupdate.dd >= 1) && (header->lastupdate.dd <= 31))
;
return ok;
}
static BOOL sanity_check_4(const DBF_FILEHEADER_4* header)
{
BOOL ok = ((header->lastupdate.mm >= 1) && (header->lastupdate.mm <= 12))
&& ((header->lastupdate.dd >= 1) && (header->lastupdate.dd <= 31))
;
return ok;
}
DBF_HANDLE dbf_attach(void* stream, zlib_filefunc_def* api, BOOL editable, enum dbf_charconv charconv, void* memo, const char* tablename)
{
DBF_HANDLE handle = NULL;
size_t len = 0;
DBF_FILEHEADER header;
BOOL ok;
ZSEEK(*api, stream, 0, ZLIB_FILEFUNC_SEEK_SET);
len += ZREAD(*api, stream, &header.flags, sizeof(header.flags)