1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2013 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Author: Marcus Boerger <helly@php.net>                               |
16   +----------------------------------------------------------------------+
17 */
18
19/* $Id: 82b364a6bddf54572494867fc63eeee2f715a792 $ */
20
21#ifdef HAVE_CONFIG_H
22#include "config.h"
23#endif
24
25#include "php.h"
26#include "php_globals.h"
27#include "safe_mode.h"
28
29#include <stdlib.h>
30#include <string.h>
31#include <errno.h>
32#if HAVE_UNISTD_H
33#include <unistd.h>
34#endif
35
36#include "inifile.h"
37
38/* ret = -1 means that database was opened for read-only
39 * ret = 0  success
40 * ret = 1  key already exists - nothing done
41 */
42
43/* {{{ inifile_version */
44char *inifile_version()
45{
46    return "1.0, $Id: 82b364a6bddf54572494867fc63eeee2f715a792 $";
47}
48/* }}} */
49
50/* {{{ inifile_free_key */
51void inifile_key_free(key_type *key)
52{
53    if (key->group) {
54        efree(key->group);
55    }
56    if (key->name) {
57        efree(key->name);
58    }
59    memset(key, 0, sizeof(key_type));
60}
61/* }}} */
62
63/* {{{ inifile_free_val */
64void inifile_val_free(val_type *val)
65{
66    if (val->value) {
67        efree(val->value);
68    }
69    memset(val, 0, sizeof(val_type));
70}
71/* }}} */
72
73/* {{{ inifile_free_val */
74void inifile_line_free(line_type *ln)
75{
76    inifile_key_free(&ln->key);
77    inifile_val_free(&ln->val);
78    ln->pos = 0;
79}
80/* }}} */
81
82/* {{{ inifile_alloc */
83inifile * inifile_alloc(php_stream *fp, int readonly, int persistent TSRMLS_DC)
84{
85    inifile *dba;
86
87    if (!readonly) {
88        if (!php_stream_truncate_supported(fp)) {
89            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't truncate this stream");
90            return NULL;
91        }
92    }
93
94    dba = pemalloc(sizeof(inifile), persistent);
95    memset(dba, 0, sizeof(inifile));
96    dba->fp = fp;
97    dba->readonly = readonly;
98    return dba;
99}
100/* }}} */
101
102/* {{{ inifile_free */
103void inifile_free(inifile *dba, int persistent)
104{
105    if (dba) {
106        inifile_line_free(&dba->curr);
107        inifile_line_free(&dba->next);
108        pefree(dba, persistent);
109    }
110}
111/* }}} */
112
113/* {{{ inifile_key_split */
114key_type inifile_key_split(const char *group_name)
115{
116    key_type key;
117    char *name;
118
119    if (group_name[0] == '[' && (name = strchr(group_name, ']')) != NULL) {
120        key.group = estrndup(group_name+1, name - (group_name + 1));
121        key.name = estrdup(name+1);
122    } else {
123        key.group = estrdup("");
124        key.name = estrdup(group_name);
125    }
126    return key;
127}
128/* }}} */
129
130/* {{{ inifile_key_string */
131char * inifile_key_string(const key_type *key)
132{
133    if (key->group && *key->group) {
134        char *result;
135        spprintf(&result, 0, "[%s]%s", key->group, key->name ? key->name : "");
136        return result;
137    } else if (key->name) {
138        return estrdup(key->name);
139    } else {
140        return NULL;
141    }
142}
143/* }}} */
144
145/* {{{ etrim */
146static char *etrim(const char *str)
147{
148    char *val;
149    size_t l;
150
151    if (!str) {
152        return NULL;
153    }
154    val = (char*)str;
155    while (*val && strchr(" \t\r\n", *val)) {
156        val++;
157    }
158    l = strlen(val);
159    while (l && (strchr(" \t\r\n", val[l-1]))) {
160        l--;
161    }
162    return estrndup(val, l);
163}
164/* }}} */
165
166/* {{{ inifile_findkey
167 */
168static int inifile_read(inifile *dba, line_type *ln TSRMLS_DC) {
169    char *fline;
170    char *pos;
171
172    inifile_val_free(&ln->val);
173    while ((fline = php_stream_gets(dba->fp, NULL, 0)) != NULL) {
174        if (fline) {
175            if (fline[0] == '[') {
176                /* A value name cannot start with '['
177                 * So either we find a ']' or we found an error
178                 */
179                pos = strchr(fline+1, ']');
180                if (pos) {
181                    *pos = '\0';
182                    inifile_key_free(&ln->key);
183                    ln->key.group = etrim(fline+1);
184                    ln->key.name = estrdup("");
185                    ln->pos = php_stream_tell(dba->fp);
186                    efree(fline);
187                    return 1;
188                } else {
189                    efree(fline);
190                    continue;
191                }
192            } else {
193                pos = strchr(fline, '=');
194                if (pos) {
195                    *pos = '\0';
196                    /* keep group or make empty if not existent */
197                    if (!ln->key.group) {
198                        ln->key.group = estrdup("");
199                    }
200                    if (ln->key.name) {
201                        efree(ln->key.name);
202                    }
203                    ln->key.name = etrim(fline);
204                    ln->val.value = etrim(pos+1);
205                    ln->pos = php_stream_tell(dba->fp);
206                    efree(fline);
207                    return 1;
208                } else {
209                    /* simply ignore lines without '='
210                     * those should be comments
211                     */
212                     efree(fline);
213                     continue;
214                }
215            }
216        }
217    }
218    inifile_line_free(ln);
219    return 0;
220}
221/* }}} */
222
223/* {{{ inifile_key_cmp */
224/* 0 = EQUAL
225 * 1 = GROUP-EQUAL,NAME-DIFFERENT
226 * 2 = DIFFERENT
227 */
228static int inifile_key_cmp(const key_type *k1, const key_type *k2 TSRMLS_DC)
229{
230    assert(k1->group && k1->name && k2->group && k2->name);
231
232    if (!strcasecmp(k1->group, k2->group)) {
233        if (!strcasecmp(k1->name, k2->name)) {
234            return 0;
235        } else {
236            return 1;
237        }
238    } else {
239        return 2;
240    }
241}
242/* }}} */
243
244/* {{{ inifile_fetch
245 */
246val_type inifile_fetch(inifile *dba, const key_type *key, int skip TSRMLS_DC) {
247    line_type ln = {{NULL,NULL},{NULL}};
248    val_type val;
249    int res, grp_eq = 0;
250
251    if (skip == -1 && dba->next.key.group && dba->next.key.name && !inifile_key_cmp(&dba->next.key, key TSRMLS_CC)) {
252        /* we got position already from last fetch */
253        php_stream_seek(dba->fp, dba->next.pos, SEEK_SET);
254    } else {
255        /* specific instance or not same key -> restart search */
256        /* the slow way: restart and seacrch */
257        php_stream_rewind(dba->fp);
258        inifile_line_free(&dba->next);
259    }
260    if (skip == -1) {
261        skip = 0;
262    }
263    while(inifile_read(dba, &ln TSRMLS_CC)) {
264        if (!(res=inifile_key_cmp(&ln.key, key TSRMLS_CC))) {
265            if (!skip) {
266                val.value = estrdup(ln.val.value ? ln.val.value : "");
267                /* allow faster access by updating key read into next */
268                inifile_line_free(&dba->next);
269                dba->next = ln;
270                dba->next.pos = php_stream_tell(dba->fp);
271                return val;
272            }
273            skip--;
274        } else if (res == 1) {
275            grp_eq = 1;
276        } else if (grp_eq) {
277            /* we are leaving group now: that means we cannot find the key */
278            break;
279        }
280    }
281    inifile_line_free(&ln);
282    dba->next.pos = php_stream_tell(dba->fp);
283    return ln.val;
284}
285/* }}} */
286
287/* {{{ inifile_firstkey
288 */
289int inifile_firstkey(inifile *dba TSRMLS_DC) {
290    inifile_line_free(&dba->curr);
291    dba->curr.pos = 0;
292    return inifile_nextkey(dba TSRMLS_CC);
293}
294/* }}} */
295
296/* {{{ inifile_nextkey
297 */
298int inifile_nextkey(inifile *dba TSRMLS_DC) {
299    line_type ln = {{NULL,NULL},{NULL}};
300
301    /*inifile_line_free(&dba->next); ??? */
302    php_stream_seek(dba->fp, dba->curr.pos, SEEK_SET);
303    ln.key.group = estrdup(dba->curr.key.group ? dba->curr.key.group : "");
304    inifile_read(dba, &ln TSRMLS_CC);
305    inifile_line_free(&dba->curr);
306    dba->curr = ln;
307    return ln.key.group || ln.key.name;
308}
309/* }}} */
310
311/* {{{ inifile_truncate
312 */
313static int inifile_truncate(inifile *dba, size_t size TSRMLS_DC)
314{
315    int res;
316
317    if ((res=php_stream_truncate_set_size(dba->fp, size)) != 0) {
318        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error in ftruncate: %d", res);
319        return FAILURE;
320    }
321    php_stream_seek(dba->fp, size, SEEK_SET);
322    return SUCCESS;
323}
324/* }}} */
325
326/* {{{ inifile_find_group
327 * if found pos_grp_start points to "[group_name]"
328 */
329static int inifile_find_group(inifile *dba, const key_type *key, size_t *pos_grp_start TSRMLS_DC)
330{
331    int ret = FAILURE;
332
333    php_stream_flush(dba->fp);
334    php_stream_seek(dba->fp, 0, SEEK_SET);
335    inifile_line_free(&dba->curr);
336    inifile_line_free(&dba->next);
337
338    if (key->group && strlen(key->group)) {
339        int res;
340        line_type ln = {{NULL,NULL},{NULL}};
341
342        res = 1;
343        while(inifile_read(dba, &ln TSRMLS_CC)) {
344            if ((res=inifile_key_cmp(&ln.key, key TSRMLS_CC)) < 2) {
345                ret = SUCCESS;
346                break;
347            }
348            *pos_grp_start = php_stream_tell(dba->fp);
349        }
350        inifile_line_free(&ln);
351    } else {
352        *pos_grp_start = 0;
353        ret = SUCCESS;
354    }
355    if (ret == FAILURE) {
356        *pos_grp_start = php_stream_tell(dba->fp);
357    }
358    return ret;
359}
360/* }}} */
361
362/* {{{ inifile_next_group
363 * only valid after a call to inifile_find_group
364 * if any next group is found pos_grp_start points to "[group_name]" or whitespace before that
365 */
366static int inifile_next_group(inifile *dba, const key_type *key, size_t *pos_grp_start TSRMLS_DC)
367{
368    int ret = FAILURE;
369    line_type ln = {{NULL,NULL},{NULL}};
370
371    *pos_grp_start = php_stream_tell(dba->fp);
372    ln.key.group = estrdup(key->group);
373    while(inifile_read(dba, &ln TSRMLS_CC)) {
374        if (inifile_key_cmp(&ln.key, key TSRMLS_CC) == 2) {
375            ret = SUCCESS;
376            break;
377        }
378        *pos_grp_start = php_stream_tell(dba->fp);
379    }
380    inifile_line_free(&ln);
381    return ret;
382}
383/* }}} */
384
385/* {{{ inifile_copy_to
386 */
387static int inifile_copy_to(inifile *dba, size_t pos_start, size_t pos_end, inifile **ini_copy TSRMLS_DC)
388{
389    php_stream *fp;
390
391    if (pos_start == pos_end) {
392        *ini_copy = NULL;
393        return SUCCESS;
394    }
395    if ((fp = php_stream_temp_create(0, 64 * 1024)) == NULL) {
396        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not create temporary stream");
397        *ini_copy = NULL;
398        return FAILURE;
399    }
400
401    if ((*ini_copy = inifile_alloc(fp, 1, 0 TSRMLS_CC)) == NULL) {
402        /* writes error */
403        return FAILURE;
404    }
405    php_stream_seek(dba->fp, pos_start, SEEK_SET);
406    if (!php_stream_copy_to_stream(dba->fp, fp, pos_end - pos_start)) {
407        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy group [%zu - %zu] to temporary stream", pos_start, pos_end);
408        return FAILURE;
409    }
410    return SUCCESS;
411}
412/* }}} */
413
414/* {{{ inifile_filter
415 * copy from to dba while ignoring key name (group must equal)
416 */
417static int inifile_filter(inifile *dba, inifile *from, const key_type *key TSRMLS_DC)
418{
419    size_t pos_start = 0, pos_next = 0, pos_curr;
420    int ret = SUCCESS;
421    line_type ln = {{NULL,NULL},{NULL}};
422
423    php_stream_seek(from->fp, 0, SEEK_SET);
424    php_stream_seek(dba->fp, 0, SEEK_END);
425    while(inifile_read(from, &ln TSRMLS_CC)) {
426        switch(inifile_key_cmp(&ln.key, key TSRMLS_CC)) {
427        case 0:
428            pos_curr = php_stream_tell(from->fp);
429            if (pos_start != pos_next) {
430                php_stream_seek(from->fp, pos_start, SEEK_SET);
431                if (!php_stream_copy_to_stream(from->fp, dba->fp, pos_next - pos_start)) {
432                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy [%zu - %zu] from temporary stream", pos_next, pos_start);
433                    ret = FAILURE;
434                }
435                php_stream_seek(from->fp, pos_curr, SEEK_SET);
436            }
437            pos_next = pos_start = pos_curr;
438            break;
439        case 1:
440            pos_next = php_stream_tell(from->fp);
441            break;
442        case 2:
443            /* the function is meant to process only entries from same group */
444            assert(0);
445            break;
446        }
447    }
448    if (pos_start != pos_next) {
449        php_stream_seek(from->fp, pos_start, SEEK_SET);
450        if (!php_stream_copy_to_stream(from->fp, dba->fp, pos_next - pos_start)) {
451            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy [%zu - %zu] from temporary stream", pos_next, pos_start);
452            ret = FAILURE;
453        }
454    }
455    inifile_line_free(&ln);
456    return SUCCESS;
457}
458/* }}} */
459
460/* {{{ inifile_delete_replace_append
461 */
462static int inifile_delete_replace_append(inifile *dba, const key_type *key, const val_type *value, int append TSRMLS_DC)
463{
464    size_t pos_grp_start, pos_grp_next;
465    inifile *ini_tmp = NULL;
466    php_stream *fp_tmp = NULL;
467    int ret;
468
469    /* 1) Search group start
470     * 2) Search next group
471     * 3) If not append: Copy group to ini_tmp
472     * 4) Open temp_stream and copy remainder
473     * 5) Truncate stream
474     * 6) If not append AND key.name given: Filtered copy back from ini_tmp
475     *    to stream. Otherwise the user wanted to delete the group.
476     * 7) Append value if given
477     * 8) Append temporary stream
478     */
479
480    assert(!append || (key->name && value)); /* missuse */
481
482    /* 1 - 3 */
483    inifile_find_group(dba, key, &pos_grp_start TSRMLS_CC);
484    inifile_next_group(dba, key, &pos_grp_next TSRMLS_CC);
485    if (append) {
486        ret = SUCCESS;
487    } else {
488        ret = inifile_copy_to(dba, pos_grp_start, pos_grp_next, &ini_tmp TSRMLS_CC);
489    }
490
491    /* 4 */
492    if (ret == SUCCESS) {
493        fp_tmp = php_stream_temp_create(0, 64 * 1024);
494        if (!fp_tmp) {
495            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not create temporary stream");
496            ret = FAILURE;
497        } else {
498            php_stream_seek(dba->fp, 0, SEEK_END);
499            if (pos_grp_next != (size_t)php_stream_tell(dba->fp)) {
500                php_stream_seek(dba->fp, pos_grp_next, SEEK_SET);
501                if (!php_stream_copy_to_stream(dba->fp, fp_tmp, PHP_STREAM_COPY_ALL)) {
502                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy remainder to temporary stream");
503                    ret = FAILURE;
504                }
505            }
506        }
507    }
508
509    /* 5 */
510    if (ret == SUCCESS) {
511        if (!value || (key->name && strlen(key->name))) {
512            ret = inifile_truncate(dba, append ? pos_grp_next : pos_grp_start TSRMLS_CC); /* writes error on fail */
513        }
514    }
515
516    if (ret == SUCCESS) {
517        if (key->name && strlen(key->name)) {
518            /* 6 */
519            if (!append && ini_tmp) {
520                ret = inifile_filter(dba, ini_tmp, key TSRMLS_CC);
521            }
522
523            /* 7 */
524            /* important: do not query ret==SUCCESS again: inifile_filter might fail but
525             * however next operation must be done.
526             */
527            if (value) {
528                if (pos_grp_start == pos_grp_next && key->group && strlen(key->group)) {
529                    php_stream_printf(dba->fp TSRMLS_CC, "[%s]\n", key->group);
530                }
531                php_stream_printf(dba->fp TSRMLS_CC, "%s=%s\n", key->name, value->value ? value->value : "");
532            }
533        }
534
535        /* 8 */
536        /* important: do not query ret==SUCCESS again: inifile_filter might fail but
537         * however next operation must be done.
538         */
539        if (fp_tmp && php_stream_tell(fp_tmp)) {
540            php_stream_seek(fp_tmp, 0, SEEK_SET);
541            php_stream_seek(dba->fp, 0, SEEK_END);
542            if (!php_stream_copy_to_stream(fp_tmp, dba->fp, PHP_STREAM_COPY_ALL)) {
543                php_error_docref(NULL TSRMLS_CC, E_RECOVERABLE_ERROR, "Could not copy from temporary stream - ini file truncated");
544                ret = FAILURE;
545            }
546        }
547    }
548
549    if (ini_tmp) {
550        php_stream_close(ini_tmp->fp);
551        inifile_free(ini_tmp, 0);
552    }
553    if (fp_tmp) {
554        php_stream_close(fp_tmp);
555    }
556    php_stream_flush(dba->fp);
557    php_stream_seek(dba->fp, 0, SEEK_SET);
558
559    return ret;
560}
561/* }}} */
562
563/* {{{ inifile_delete
564 */
565int inifile_delete(inifile *dba, const key_type *key TSRMLS_DC)
566{
567    return inifile_delete_replace_append(dba, key, NULL, 0 TSRMLS_CC);
568}
569/* }}} */
570
571/* {{{ inifile_relace
572 */
573int inifile_replace(inifile *dba, const key_type *key, const val_type *value TSRMLS_DC)
574{
575    return inifile_delete_replace_append(dba, key, value, 0 TSRMLS_CC);
576}
577/* }}} */
578
579/* {{{ inifile_append
580 */
581int inifile_append(inifile *dba, const key_type *key, const val_type *value TSRMLS_DC)
582{
583    return inifile_delete_replace_append(dba, key, value, 1 TSRMLS_CC);
584}
585/* }}} */
586
587/*
588 * Local variables:
589 * tab-width: 4
590 * c-basic-offset: 4
591 * End:
592 * vim600: sw=4 ts=4 fdm=marker
593 * vim<600: sw=4 ts=4
594 */
595