#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_globals.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "inifile.h"
char *inifile_version()
{
return "1.0, $Id$";
}
void inifile_key_free(key_type *key)
{
if (key->group) {
efree(key->group);
}
if (key->name) {
efree(key->name);
}
memset(key, 0, sizeof(key_type));
}
void inifile_val_free(val_type *val)
{
if (val->value) {
efree(val->value);
}
memset(val, 0, sizeof(val_type));
}
void inifile_line_free(line_type *ln)
{
inifile_key_free(&ln->key);
inifile_val_free(&ln->val);
ln->pos = 0;
}
inifile * inifile_alloc(php_stream *fp, int readonly, int persistent TSRMLS_DC)
{
inifile *dba;
if (!readonly) {
if (!php_stream_truncate_supported(fp)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't truncate this stream");
return NULL;
}
}
dba = pemalloc(sizeof(inifile), persistent);
memset(dba, 0, sizeof(inifile));
dba->fp = fp;
dba->readonly = readonly;
return dba;
}
void inifile_free(inifile *dba, int persistent)
{
if (dba) {
inifile_line_free(&dba->curr);
inifile_line_free(&dba->next);
pefree(dba, persistent);
}
}
key_type inifile_key_split(const char *group_name)
{
key_type key;
char *name;
if (group_name[0] == '[' && (name = strchr(group_name, ']')) != NULL) {
key.group = estrndup(group_name+1, name - (group_name + 1));
key.name = estrdup(name+1);
} else {
key.group = estrdup("");
key.name = estrdup(group_name);
}
return key;
}
char * inifile_key_string(const key_type *key)
{
if (key->group && *key->group) {
char *result;
spprintf(&result, 0, "[%s]%s", key->group, key->name ? key->name : "");
return result;
} else if (key->name) {
return estrdup(key->name);
} else {
return NULL;
}
}
static char *etrim(const char *str)
{
char *val;
size_t l;
if (!str) {
return NULL;
}
val = (char*)str;
while (*val && strchr(" \t\r\n", *val)) {
val++;
}
l = strlen(val);
while (l && (strchr(" \t\r\n", val[l-1]))) {
l--;
}
return estrndup(val, l);
}
static int inifile_read(inifile *dba, line_type *ln TSRMLS_DC) {
char *fline;
char *pos;
inifile_val_free(&ln->val);
while ((fline = php_stream_gets(dba->fp, NULL, 0)) != NULL) {
if (fline) {
if (fline[0] == '[') {
pos = strchr(fline+1, ']');
if (pos) {
*pos = '\0';
inifile_key_free(&ln->key);
ln->key.group = etrim(fline+1);
ln->key.name = estrdup("");
ln->pos = php_stream_tell(dba->fp);
efree(fline);
return 1;
} else {
efree(fline);
continue;
}
} else {
pos = strchr(fline, '=');
if (pos) {
*pos = '\0';
if (!ln->key.group) {
ln->key.group = estrdup("");
}
if (ln->key.name) {
efree(ln->key.name);
}
ln->key.name = etrim(fline);
ln->val.value = etrim(pos+1);
ln->pos = php_stream_tell(dba->fp);
efree(fline);
return 1;
} else {
efree(fline);
continue;
}
}
}
}
inifile_line_free(ln);
return 0;
}
static int inifile_key_cmp(const key_type *k1, const key_type *k2 TSRMLS_DC)
{
assert(k1->group && k1->name && k2->group && k2->name);
if (!strcasecmp(k1->group, k2->group)) {
if (!strcasecmp(k1->name, k2->name)) {
return 0;
} else {
return 1;
}
} else {
return 2;
}
}
val_type inifile_fetch(inifile *dba, const key_type *key, int skip TSRMLS_DC) {
line_type ln = {{NULL,NULL},{NULL}};
val_type val;
int res, grp_eq = 0;
if (skip == -1 && dba->next.key.group && dba->next.key.name && !inifile_key_cmp(&dba->next.key, key TSRMLS_CC)) {
php_stream_seek(dba->fp, dba->next.pos, SEEK_SET);
ln.key.group = estrdup(dba->next.key.group);
} else {
php_stream_rewind(dba->fp);
inifile_line_free(&dba->next);
}
if (skip == -1) {
skip = 0;
}
while(inifile_read(dba, &ln TSRMLS_CC)) {
if (!(res=inifile_key_cmp(&ln.key, key TSRMLS_CC))) {
if (!skip) {
val.value = estrdup(ln.val.value ? ln.val.value : "");
inifile_line_free(&dba->next);
dba->next = ln;
dba->next.pos = php_stream_tell(dba->fp);
return val;
}
skip--;
} else if (res == 1) {
grp_eq = 1;
} else if (grp_eq) {
break;
}
}
inifile_line_free(&ln);
dba->next.pos = php_stream_tell(dba->fp);
return ln.val;
}
int inifile_firstkey(inifile *dba TSRMLS_DC) {
inifile_line_free(&dba->curr);
dba->curr.pos = 0;
return inifile_nextkey(dba TSRMLS_CC);
}
int inifile_nextkey(inifile *dba TSRMLS_DC) {
line_type ln = {{NULL,NULL},{NULL}};
php_stream_seek(dba->fp, dba->curr.pos, SEEK_SET);
ln.key.group = estrdup(dba->curr.key.group ? dba->curr.key.group : "");
inifile_read(dba, &ln TSRMLS_CC);
inifile_line_free(&dba->curr);
dba->curr = ln;
return ln.key.group || ln.key.name;
}
static int inifile_truncate(inifile *dba, size_t size TSRMLS_DC)
{
int res;
if ((res=php_stream_truncate_set_size(dba->fp, size)) != 0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error in ftruncate: %d", res);
return FAILURE;
}
php_stream_seek(dba->fp, size, SEEK_SET);
return SUCCESS;
}
static int inifile_find_group(inifile *dba, const key_type *key, size_t *pos_grp_start TSRMLS_DC)
{
int ret = FAILURE;
php_stream_flush(dba->fp);
php_stream_seek(dba->fp, 0, SEEK_SET);
inifile_line_free(&dba->curr);
inifile_line_free(&dba->next);
if (key->group && strlen(key->group)) {
int res;
line_type ln = {{NULL,NULL},{NULL}};
res = 1;
while(inifile_read(dba, &ln TSRMLS_CC)) {
if ((res=inifile_key_cmp(&ln.key, key TSRMLS_CC)) < 2) {
ret = SUCCESS;
break;
}
*pos_grp_start = php_stream_tell(dba->fp);
}
inifile_line_free(&ln);
} else {
*pos_grp_start = 0;
ret = SUCCESS;
}
if (ret == FAILURE) {
*pos_grp_start = php_stream_tell(dba->fp);
}
return ret;
}
static int inifile_next_group(inifile *dba, const key_type *key, size_t *pos_grp_start TSRMLS_DC)
{
int ret = FAILURE;
line_type ln = {{NULL,NULL},{NULL}};
*pos_grp_start = php_stream_tell(dba->fp);
ln.key.group = estrdup(key->group);
while(inifile_read(dba, &ln TSRMLS_CC)) {
if (inifile_key_cmp(&ln.key, key TSRMLS_CC) == 2) {
ret = SUCCESS;
break;
}
*pos_grp_start = php_stream_tell(dba->fp);
}
inifile_line_free(&ln);
return ret;
}
static int inifile_copy_to(inifile *dba, size_t pos_start, size_t pos_end, inifile **ini_copy TSRMLS_DC)
{
php_stream *fp;
if (pos_start == pos_end) {
*ini_copy = NULL;
return SUCCESS;
}
if ((fp = php_stream_temp_create(0, 64 * 1024)) == NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not create temporary stream");
*ini_copy = NULL;
return FAILURE;
}
if ((*ini_copy = inifile_alloc(fp, 1, 0 TSRMLS_CC)) == NULL) {
return FAILURE;
}
php_stream_seek(dba->fp, pos_start, SEEK_SET);
if (SUCCESS != php_stream_copy_to_stream_ex(dba->fp, fp, pos_end - pos_start, NULL)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy group [%zu - %zu] to temporary stream", pos_start, pos_end);
return FAILURE;
}
return SUCCESS;
}
static int inifile_filter(inifile *dba, inifile *from, const key_type *key TSRMLS_DC)
{
size_t pos_start = 0, pos_next = 0, pos_curr;
int ret = SUCCESS;
line_type ln = {{NULL,NULL},{NULL}};
php_stream_seek(from->fp, 0, SEEK_SET);
php_stream_seek(dba->fp, 0, SEEK_END);
while(inifile_read(from, &ln TSRMLS_CC)) {
switch(inifile_key_cmp(&ln.key, key TSRMLS_CC)) {
case 0:
pos_curr = php_stream_tell(from->fp);
if (pos_start != pos_next) {
php_stream_seek(from->fp, pos_start, SEEK_SET);
if (SUCCESS != php_stream_copy_to_stream_ex(from->fp, dba->fp, pos_next - pos_start, NULL)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy [%zu - %zu] from temporary stream", pos_next, pos_start);
ret = FAILURE;
}
php_stream_seek(from->fp, pos_curr, SEEK_SET);
}
pos_next = pos_start = pos_curr;
break;
case 1:
pos_next = php_stream_tell(from->fp);
break;
case 2:
assert(0);
break;
}
}
if (pos_start != pos_next) {
php_stream_seek(from->fp, pos_start, SEEK_SET);
if (SUCCESS != php_stream_copy_to_stream_ex(from->fp, dba->fp, pos_next - pos_start, NULL)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy [%zu - %zu] from temporary stream", pos_next, pos_start);
ret = FAILURE;
}
}
inifile_line_free(&ln);
return ret;
}
static int inifile_delete_replace_append(inifile *dba, const key_type *key, const val_type *value, int append TSRMLS_DC)
{
size_t pos_grp_start=0, pos_grp_next;
inifile *ini_tmp = NULL;
php_stream *fp_tmp = NULL;
int ret;
assert(!append || (key->name && value));
inifile_find_group(dba, key, &pos_grp_start TSRMLS_CC);
inifile_next_group(dba, key, &pos_grp_next TSRMLS_CC);
if (append) {
ret = SUCCESS;
} else {
ret = inifile_copy_to(dba, pos_grp_start, pos_grp_next, &ini_tmp TSRMLS_CC);
}
if (ret == SUCCESS) {
fp_tmp = php_stream_temp_create(0, 64 * 1024);
if (!fp_tmp) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not create temporary stream");
ret = FAILURE;
} else {
php_stream_seek(dba->fp, 0, SEEK_END);
if (pos_grp_next != (size_t)php_stream_tell(dba->fp)) {
php_stream_seek(dba->fp, pos_grp_next, SEEK_SET);
if (SUCCESS != php_stream_copy_to_stream_ex(dba->fp, fp_tmp, PHP_STREAM_COPY_ALL, NULL)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy remainder to temporary stream");
ret = FAILURE;
}
}
}
}
if (ret == SUCCESS) {
if (!value || (key->name && strlen(key->name))) {
ret = inifile_truncate(dba, append ? pos_grp_next : pos_grp_start TSRMLS_CC);
}
}
if (ret == SUCCESS) {
if (key->name && strlen(key->name)) {
if (!append && ini_tmp) {
ret = inifile_filter(dba, ini_tmp, key TSRMLS_CC);
}
if (value) {
if (pos_grp_start == pos_grp_next && key->group && strlen(key->group)) {
php_stream_printf(dba->fp TSRMLS_CC, "[%s]\n", key->group);
}
php_stream_printf(dba->fp TSRMLS_CC, "%s=%s\n", key->name, value->value ? value->value : "");
}
}
if (fp_tmp && php_stream_tell(fp_tmp)) {
php_stream_seek(fp_tmp, 0, SEEK_SET);
php_stream_seek(dba->fp, 0, SEEK_END);
if (SUCCESS != php_stream_copy_to_stream_ex(fp_tmp, dba->fp, PHP_STREAM_COPY_ALL, NULL)) {
php_error_docref(NULL TSRMLS_CC, E_RECOVERABLE_ERROR, "Could not copy from temporary stream - ini file truncated");
ret = FAILURE;
}
}
}
if (ini_tmp) {
php_stream_close(ini_tmp->fp);
inifile_free(ini_tmp, 0);
}
if (fp_tmp) {
php_stream_close(fp_tmp);
}
php_stream_flush(dba->fp);
php_stream_seek(dba->fp, 0, SEEK_SET);
return ret;
}
int inifile_delete(inifile *dba, const key_type *key TSRMLS_DC)
{
return inifile_delete_replace_append(dba, key, NULL, 0 TSRMLS_CC);
}
int inifile_replace(inifile *dba, const key_type *key, const val_type *value TSRMLS_DC)
{
return inifile_delete_replace_append(dba, key, value, 0 TSRMLS_CC);
}
int inifile_append(inifile *dba, const key_type *key, const val_type *value TSRMLS_DC)
{
return inifile_delete_replace_append(dba, key, value, 1 TSRMLS_CC);
}