/*
* Copyright (c) 2003-2016 Hypertriton, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Simple file-based table implementation.
*/
#define _PERCGI_NO_FASTCGI
#include "cgi.h"
#include
#include
#include
#include
#include
#include
#include
/* Allocate and initialize a new table. */
TBL *
TBL_New(Uint nBuckets, Uint nFields)
{
TBL *t;
if ((t = TryMalloc(sizeof(TBL))) == NULL) {
return (NULL);
}
TBL_Init(t, nBuckets, nFields);
return (t);
}
/* Initialize a table. */
void
TBL_Init(TBL *t, Uint nBuckets, Uint nFields)
{
Uint i;
t->data = NULL;
t->dataSize = 0;
t->nBuckets = nBuckets;
t->buckets = Malloc(nBuckets*sizeof(TBL_Bucket));
t->nFields = nFields;
t->sep = ":";
for (i = 0; i < nBuckets; i++) {
SLIST_INIT(&t->buckets[i].ents);
}
SLIST_INIT(&t->ents);
}
/* Release the resources allocated by a table. */
void
TBL_Destroy(TBL *t)
{
TBL_Entry *ent, *nent;
Uint i, j;
for (i = 0; i < t->nBuckets; i++) {
for (ent = SLIST_FIRST(&t->buckets[i].ents);
ent != SLIST_END(&t->buckets[i].ents);
ent = nent) {
nent = SLIST_NEXT(ent, bents);
if (t->data == NULL) {
free(ent->key);
for (j = 0; j < t->nFields; j++)
free(ent->fields[j]);
}
free(ent);
}
}
if (t->data != NULL)
free(t->data);
}
void
TBL_Free(TBL *t)
{
TBL_Destroy(t);
free(t);
}
/* Look up a named table entry. */
TBL_Entry *
TBL_Lookup(TBL *ta, const char *key, Uint h)
{
TBL_Entry *ent;
SLIST_FOREACH(ent, &ta->buckets[h].ents, bents) {
if (strcmp(key, ent->key) == 0)
break;
}
if (ent == NULL) {
CGI_SetError(_("Element not found in table: %s"), key);
}
return (ent);
}
/* Return a pointer to the given table entry field. */
char *
TBL_LookupField(TBL *ta, const char *key, Uint field)
{
TBL_Entry *ent;
if ((ent = TBL_Lookup(ta, key, TBL_Hash(ta,key))) != NULL) {
if (field >= ta->nFields) {
CGI_SetError(_("Element (%s) has no #%d field"), key,
field);
return (NULL);
}
return (ent->fields[field]);
}
CGI_SetError(_("Element not found in table: %s"), key);
return (NULL);
}
/* Copy a named table entry field to a fixed-size buffer. */
int
TBL_CopyField(TBL *ta, const char *key, Uint field, char *buf, size_t buf_len)
{
TBL_Entry *ent;
if ((ent = TBL_Lookup(ta, key, TBL_Hash(ta,key))) != NULL) {
if (field >= ta->nFields) {
CGI_SetError(_("Element (%s) has no #%d field"), key,
field);
return (-1);
}
if (Strlcpy(buf, ent->fields[field], buf_len) >= buf_len) {
CGI_SetError(_("Table string too large for buffer"));
return (-1);
}
return (0);
}
return (-1);
}
/* Insert a new table entry (fail on collision) */
int
TBL_InsertUnique(TBL *ta, TBL_Entry *ent)
{
Uint h = TBL_Hash(ta,ent->key);
if (TBL_Lookup(ta, ent->key, h) != NULL) {
CGI_SetError(_("Existing element in table: %s"), ent->key);
return (-1);
}
TBL_Insert(ta, ent, h);
return (0);
}
/* Remove a named table entry. */
int
TBL_Delete(TBL *ta, const char *key)
{
Uint h = TBL_Hash(ta,key), i;
TBL_Entry *ent;
if ((ent = TBL_Lookup(ta, key, h)) == NULL) {
CGI_SetError(_("Element not found in table: %s"), key);
return (-1);
}
SLIST_REMOVE(&ta->buckets[h].ents, ent, tbl_entry, bents);
SLIST_REMOVE(&ta->ents, ent, tbl_entry, ents);
if (ta->data == NULL) {
for (i = 0; i < ta->nFields; i++) {
if (ent->fields[i] != NULL)
free(ent->fields[i]);
}
free(ent->key);
}
free(ent);
return (0);
}
static int
TBL_LoadLine(TBL *ta, char *s, int flags)
{
TBL_Entry *ent;
char *key, **pField;
Uint i, h;
if ((key = Strsep(&s, ta->sep)) == NULL) {
return (0); /* Empty line, ignore */
}
h = TBL_Hash(ta, key);
if ((flags & TBL_LOAD_NODUPS) &&
TBL_Lookup(ta, key, h) != NULL) {
CGI_SetError("Duplicate key: %s", key);
return (-1);
}
if ((ent = TryMalloc(sizeof(TBL_Entry))) == NULL) {
return (-1);
}
ent->key = key;
for (i = 0; i < ta->nFields; i++) {
ent->fields[i] = NULL;
}
for (pField = &ent->fields[0];
pField < &ent->fields[ta->nFields] &&
(*pField = Strsep(&s, ta->sep)) != NULL; ) {
if (**pField != '\0')
pField++;
}
#if 0
for (i = 0; i < ta->nFields; i++) {
if (ent->fields[i] == NULL) {
break;
}
CGI_Log(CGI_LOG_DEBUG, "Table: \"%s\"[%d] => %s",
key, i, ent->fields[i]);
}
#endif
SLIST_INSERT_HEAD(&ta->buckets[h].ents, ent, bents);
SLIST_INSERT_HEAD(&ta->ents, ent, ents);
ta->buckets[h].nEnts++;
return (0);
fail:
free(ent);
return (-1);
}
/*
* Load a table from the given file. Leading spacing characters and
* characters between '#' and end of line are ignored.
*/
TBL *
TBL_Load(const char *path, Uint nFields, const char *sep, int flags)
{
TBL *ta;
char *data, *p, *eol;
size_t len, nRead;
ssize_t rv;
Uint nLines;
int fd;
if (nFields > TABLE_MAX_FIELDS) {
CGI_SetError("Too many fields");
return (NULL);
}
if ((ta = TryMalloc(sizeof(TBL))) == NULL) {
return (NULL);
}
if ((fd = open(path, O_RDONLY)) == -1) {
CGI_SetError("%s: %s", path, strerror(errno));
free(ta);
return (NULL);
}
len = (size_t)lseek(fd, 0, SEEK_END);
(void)lseek(fd, 0, SEEK_SET);
if ((data = TryMalloc(len+1)) == NULL) {
goto fail;
}
nRead = 0;
while (nRead < len) {
rv = read(fd, &data[nRead], len-nRead);
if (rv == -1) {
if (errno == EINTR) {
continue;
}
CGI_SetError("%s: %s", path, strerror(errno));
free(data);
goto fail;
}
nRead += rv;
}
data[len] = '\0';
close(fd);
/* Allocate the hash table. */
nLines = 0;
for (p = &data[0]; *p != '\0'; p++) {
if (*p == '\n')
nLines++;
}
TBL_Init(ta, nLines, nFields);
ta->data = data;
ta->dataSize = len;
ta->nFields = nFields;
ta->sep = sep;
if (data != NULL) {
nLines = 0;
for (p = &data[0]; *p != '\0'; p++) {
while (isspace(*p)) {
p++;
}
if (*p == '#') {
while (*(++p) != '\n')
;;
}
for (eol = p;
*eol != '\n' && *eol != '\0';
eol++) {
if (*eol == '#') {
for (eol--; isspace(*eol); eol--)
;;
eol++;
break;
}
}
*eol = '\0';
if (p < eol) {
char *sp;
for (sp = &eol[-1]; isspace(*sp); sp--)
;;
sp[1] = '\0';
if (TBL_LoadLine(ta, p, flags) == -1) {
if (flags & TBL_LOAD_NOERRORS) {
goto fail;
}
CGI_Log(CGI_LOG_WARNING,
"%s:%u: %s", path, nLines,
CGI_GetError());
}
}
p = eol;
nLines++;
}
}
return (ta);
fail:
close(fd);
free(ta);
return (NULL);
}
/* Save table contents to named file. */
int
TBL_Save(TBL *ta, const char *path)
{
TBL_Entry *ent;
FILE *f;
int i;
if ((f = fopen(path, "w")) == NULL) {
CGI_SetError("%s: %s", path, strerror(errno));
return (-1);
}
TBL_FOREACH(ent, ta) {
if (ent->key != NULL) {
fputs(ent->key, f);
}
for (i = 0; i < ta->nFields; i++) {
if (ent->fields[i] == NULL) {
break;
}
fputc(':', f);
fputs(ent->fields[i], f);
}
fputc('\n', f);
}
fclose(f);
return (0);
}
/*
* Export table contents to a JavaScript array or object.
*
* (0 fields) -> JS Array
* (1 field) -> JS Object strings
* (>1 fields) -> JS Object arrays of strings
*/
void
TBL_SaveJS(TBL *ta, VAR *v)
{
TBL_Entry *ent;
Uint i;
TBL_FOREACH(ent, ta) {
if (ent->key == NULL) {
continue;
}
if (ta->nFields == 0) {
CatJS(v, ent->key);
} else if (ta->nFields == 1) {
CatC(v, '"');
CatS(v, ent->key);
CatS(v, "\": \"");
CatS(v, ent->fields[0]);
CatC(v, '"');
} else if (ta->nFields > 1) {
CatC(v, '"');
CatS(v, ent->key);
CatS(v, "\": [");
for (i = 0; i < ta->nFields; i++) {
if (ent->fields[i] == NULL) {
break;
}
CatJS(v, ent->fields[i]);
}
CatC(v, ']');
}
}
}