1 : /*
2 : +----------------------------------------------------------------------+
3 : | PHP Version 5 |
4 : +----------------------------------------------------------------------+
5 : | Copyright (c) 1997-2009 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: inifile.c 272370 2008-12-31 11:15:49Z sebastian $ */
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 */
44 : char *inifile_version()
45 0 : {
46 0 : return "1.0, $Revision: 272370 $";
47 : }
48 : /* }}} */
49 :
50 : /* {{{ inifile_free_key */
51 : void inifile_key_free(key_type *key)
52 284 : {
53 284 : if (key->group) {
54 128 : efree(key->group);
55 : }
56 284 : if (key->name) {
57 122 : efree(key->name);
58 : }
59 284 : memset(key, 0, sizeof(key_type));
60 284 : }
61 : /* }}} */
62 :
63 : /* {{{ inifile_free_val */
64 : void inifile_val_free(val_type *val)
65 480 : {
66 480 : if (val->value) {
67 204 : efree(val->value);
68 : }
69 480 : memset(val, 0, sizeof(val_type));
70 480 : }
71 : /* }}} */
72 :
73 : /* {{{ inifile_free_val */
74 : void inifile_line_free(line_type *ln)
75 234 : {
76 234 : inifile_key_free(&ln->key);
77 234 : inifile_val_free(&ln->val);
78 234 : ln->pos = 0;
79 234 : }
80 : /* }}} */
81 :
82 : /* {{{ inifile_alloc */
83 : inifile * inifile_alloc(php_stream *fp, int readonly, int persistent TSRMLS_DC)
84 18 : {
85 : inifile *dba;
86 :
87 18 : if (!readonly) {
88 4 : if (!php_stream_truncate_supported(fp)) {
89 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't truncate this stream");
90 0 : return NULL;
91 : }
92 : }
93 :
94 18 : dba = pemalloc(sizeof(inifile), persistent);
95 18 : memset(dba, 0, sizeof(inifile));
96 18 : dba->fp = fp;
97 18 : dba->readonly = readonly;
98 18 : return dba;
99 : }
100 : /* }}} */
101 :
102 : /* {{{ inifile_free */
103 : void inifile_free(inifile *dba, int persistent)
104 18 : {
105 18 : if (dba) {
106 18 : inifile_line_free(&dba->curr);
107 18 : inifile_line_free(&dba->next);
108 18 : pefree(dba, persistent);
109 : }
110 18 : }
111 : /* }}} */
112 :
113 : /* {{{ inifile_key_split */
114 : key_type inifile_key_split(const char *group_name)
115 50 : {
116 : key_type key;
117 : char *name;
118 :
119 50 : if (group_name[0] == '[' && (name = strchr(group_name, ']')) != NULL) {
120 0 : key.group = estrndup(group_name+1, name - (group_name + 1));
121 0 : key.name = estrdup(name+1);
122 : } else {
123 50 : key.group = estrdup("");
124 50 : key.name = estrdup(group_name);
125 : }
126 50 : return key;
127 : }
128 : /* }}} */
129 :
130 : /* {{{ inifile_key_string */
131 : char * inifile_key_string(const key_type *key)
132 14 : {
133 14 : if (key->group && *key->group) {
134 : char *result;
135 0 : spprintf(&result, 0, "[%s]%s", key->group, key->name ? key->name : "");
136 0 : return result;
137 14 : } else if (key->name) {
138 14 : return estrdup(key->name);
139 : } else {
140 0 : return NULL;
141 : }
142 : }
143 : /* }}} */
144 :
145 : /* {{{ etrim */
146 : static char *etrim(const char *str)
147 396 : {
148 : char *val;
149 : size_t l;
150 :
151 396 : if (!str) {
152 0 : return NULL;
153 : }
154 396 : val = (char*)str;
155 792 : while (*val && strchr(" \t\r\n", *val)) {
156 0 : val++;
157 : }
158 396 : l = strlen(val);
159 990 : while (l && (strchr(" \t\r\n", val[l-1]))) {
160 198 : l--;
161 : }
162 396 : return estrndup(val, l);
163 : }
164 : /* }}} */
165 :
166 : /* {{{ inifile_findkey
167 : */
168 240 : static int inifile_read(inifile *dba, line_type *ln TSRMLS_DC) {
169 : char *fline;
170 : char *pos;
171 :
172 240 : inifile_val_free(&ln->val);
173 480 : while ((fline = php_stream_gets(dba->fp, NULL, 0)) != NULL) {
174 198 : if (fline) {
175 198 : if (fline[0] == '[') {
176 : /* A value name cannot start with '['
177 : * So either we find a ']' or we found an error
178 : */
179 0 : pos = strchr(fline+1, ']');
180 0 : if (pos) {
181 0 : *pos = '\0';
182 0 : inifile_key_free(&ln->key);
183 0 : ln->key.group = etrim(fline+1);
184 0 : ln->key.name = estrdup("");
185 0 : ln->pos = php_stream_tell(dba->fp);
186 0 : efree(fline);
187 0 : return 1;
188 : } else {
189 0 : efree(fline);
190 0 : continue;
191 : }
192 : } else {
193 198 : pos = strchr(fline, '=');
194 198 : if (pos) {
195 198 : *pos = '\0';
196 : /* keep group or make empty if not existent */
197 198 : if (!ln->key.group) {
198 36 : ln->key.group = estrdup("");
199 : }
200 198 : if (ln->key.name) {
201 126 : efree(ln->key.name);
202 : }
203 198 : ln->key.name = etrim(fline);
204 198 : ln->val.value = etrim(pos+1);
205 198 : ln->pos = php_stream_tell(dba->fp);
206 198 : efree(fline);
207 198 : return 1;
208 : } else {
209 : /* simply ignore lines without '='
210 : * those should be comments
211 : */
212 0 : efree(fline);
213 0 : continue;
214 : }
215 : }
216 : }
217 : }
218 42 : inifile_line_free(ln);
219 42 : return 0;
220 : }
221 : /* }}} */
222 :
223 : /* {{{ inifile_key_cmp */
224 : /* 0 = EQUAL
225 : * 1 = GROUP-EQUAL,NAME-DIFFERENT
226 : * 2 = DIFFERENT
227 : */
228 : static int inifile_key_cmp(const key_type *k1, const key_type *k2 TSRMLS_DC)
229 184 : {
230 : assert(k1->group && k1->name && k2->group && k2->name);
231 :
232 184 : if (!strcasecmp(k1->group, k2->group)) {
233 184 : if (!strcasecmp(k1->name, k2->name)) {
234 44 : return 0;
235 : } else {
236 140 : return 1;
237 : }
238 : } else {
239 0 : return 2;
240 : }
241 : }
242 : /* }}} */
243 :
244 : /* {{{ inifile_fetch
245 : */
246 26 : val_type inifile_fetch(inifile *dba, const key_type *key, int skip TSRMLS_DC) {
247 26 : line_type ln = {{NULL,NULL},{NULL}};
248 : val_type val;
249 26 : int res, grp_eq = 0;
250 :
251 26 : 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 0 : 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 26 : php_stream_rewind(dba->fp);
258 26 : inifile_line_free(&dba->next);
259 : }
260 26 : if (skip == -1) {
261 0 : skip = 0;
262 : }
263 92 : while(inifile_read(dba, &ln TSRMLS_CC)) {
264 62 : if (!(res=inifile_key_cmp(&ln.key, key TSRMLS_CC))) {
265 22 : if (!skip) {
266 22 : val.value = estrdup(ln.val.value ? ln.val.value : "");
267 : /* allow faster access by updating key read into next */
268 22 : inifile_line_free(&dba->next);
269 22 : dba->next = ln;
270 22 : dba->next.pos = php_stream_tell(dba->fp);
271 22 : return val;
272 : }
273 0 : skip--;
274 40 : } else if (res == 1) {
275 40 : grp_eq = 1;
276 0 : } else if (grp_eq) {
277 : /* we are leaving group now: that means we cannot find the key */
278 0 : break;
279 : }
280 : }
281 4 : inifile_line_free(&ln);
282 4 : dba->next.pos = php_stream_tell(dba->fp);
283 4 : return ln.val;
284 : }
285 : /* }}} */
286 :
287 : /* {{{ inifile_firstkey
288 : */
289 4 : int inifile_firstkey(inifile *dba TSRMLS_DC) {
290 4 : inifile_line_free(&dba->curr);
291 4 : dba->curr.pos = 0;
292 4 : return inifile_nextkey(dba TSRMLS_CC);
293 : }
294 : /* }}} */
295 :
296 : /* {{{ inifile_nextkey
297 : */
298 18 : int inifile_nextkey(inifile *dba TSRMLS_DC) {
299 18 : line_type ln = {{NULL,NULL},{NULL}};
300 :
301 : /*inifile_line_free(&dba->next); ??? */
302 18 : php_stream_seek(dba->fp, dba->curr.pos, SEEK_SET);
303 18 : ln.key.group = estrdup(dba->curr.key.group ? dba->curr.key.group : "");
304 18 : inifile_read(dba, &ln TSRMLS_CC);
305 18 : inifile_line_free(&dba->curr);
306 18 : dba->curr = ln;
307 18 : return ln.key.group || ln.key.name;
308 : }
309 : /* }}} */
310 :
311 : /* {{{ inifile_truncate
312 : */
313 : static int inifile_truncate(inifile *dba, size_t size TSRMLS_DC)
314 24 : {
315 : int res;
316 :
317 24 : if ((res=php_stream_truncate_set_size(dba->fp, size)) != 0) {
318 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error in ftruncate: %d", res);
319 0 : return FAILURE;
320 : }
321 24 : php_stream_seek(dba->fp, size, SEEK_SET);
322 24 : return SUCCESS;
323 : }
324 : /* }}} */
325 :
326 : /* {{{ inifile_find_group
327 : * if found pos_grp_start points to "[group_name]"
328 : */
329 : static int inifile_find_group(inifile *dba, const key_type *key, size_t *pos_grp_start TSRMLS_DC)
330 24 : {
331 24 : int ret = FAILURE;
332 :
333 24 : php_stream_flush(dba->fp);
334 24 : php_stream_seek(dba->fp, 0, SEEK_SET);
335 24 : inifile_line_free(&dba->curr);
336 24 : inifile_line_free(&dba->next);
337 :
338 24 : if (key->group && strlen(key->group)) {
339 : int res;
340 0 : line_type ln = {{NULL,NULL},{NULL}};
341 :
342 0 : res = 1;
343 0 : while(inifile_read(dba, &ln TSRMLS_CC)) {
344 0 : if ((res=inifile_key_cmp(&ln.key, key TSRMLS_CC)) < 2) {
345 0 : ret = SUCCESS;
346 0 : break;
347 : }
348 0 : *pos_grp_start = php_stream_tell(dba->fp);
349 : }
350 0 : inifile_line_free(&ln);
351 : } else {
352 24 : *pos_grp_start = 0;
353 24 : ret = SUCCESS;
354 : }
355 24 : if (ret == FAILURE) {
356 0 : *pos_grp_start = php_stream_tell(dba->fp);
357 : }
358 24 : 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 : */
366 : static int inifile_next_group(inifile *dba, const key_type *key, size_t *pos_grp_start TSRMLS_DC)
367 24 : {
368 24 : int ret = FAILURE;
369 24 : line_type ln = {{NULL,NULL},{NULL}};
370 :
371 24 : *pos_grp_start = php_stream_tell(dba->fp);
372 24 : ln.key.group = estrdup(key->group);
373 126 : while(inifile_read(dba, &ln TSRMLS_CC)) {
374 78 : if (inifile_key_cmp(&ln.key, key TSRMLS_CC) == 2) {
375 0 : ret = SUCCESS;
376 0 : break;
377 : }
378 78 : *pos_grp_start = php_stream_tell(dba->fp);
379 : }
380 24 : inifile_line_free(&ln);
381 24 : return ret;
382 : }
383 : /* }}} */
384 :
385 : /* {{{ inifile_copy_to
386 : */
387 : static int inifile_copy_to(inifile *dba, size_t pos_start, size_t pos_end, inifile **ini_copy TSRMLS_DC)
388 10 : {
389 : php_stream *fp;
390 :
391 10 : if (pos_start == pos_end) {
392 0 : *ini_copy = NULL;
393 0 : return SUCCESS;
394 : }
395 10 : if ((fp = php_stream_temp_create(0, 64 * 1024)) == NULL) {
396 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not create temporary stream");
397 0 : *ini_copy = NULL;
398 0 : return FAILURE;
399 : }
400 :
401 10 : if ((*ini_copy = inifile_alloc(fp, 1, 0 TSRMLS_CC)) == NULL) {
402 : /* writes error */
403 0 : return FAILURE;
404 : }
405 10 : php_stream_seek(dba->fp, pos_start, SEEK_SET);
406 10 : if (!php_stream_copy_to_stream(dba->fp, fp, pos_end - pos_start)) {
407 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy group [%zu - %zu] to temporary stream", pos_start, pos_end);
408 0 : return FAILURE;
409 : }
410 10 : return SUCCESS;
411 : }
412 : /* }}} */
413 :
414 : /* {{{ inifile_filter
415 : * copy from to dba while ignoring key name (group must equal)
416 : */
417 : static int inifile_filter(inifile *dba, inifile *from, const key_type *key TSRMLS_DC)
418 10 : {
419 10 : size_t pos_start = 0, pos_next = 0, pos_curr;
420 10 : int ret = SUCCESS;
421 10 : line_type ln = {{NULL,NULL},{NULL}};
422 :
423 10 : php_stream_seek(from->fp, 0, SEEK_SET);
424 10 : php_stream_seek(dba->fp, 0, SEEK_END);
425 64 : while(inifile_read(from, &ln TSRMLS_CC)) {
426 44 : switch(inifile_key_cmp(&ln.key, key TSRMLS_CC)) {
427 : case 0:
428 10 : pos_curr = php_stream_tell(from->fp);
429 10 : if (pos_start != pos_next) {
430 4 : php_stream_seek(from->fp, pos_start, SEEK_SET);
431 4 : if (!php_stream_copy_to_stream(from->fp, dba->fp, pos_next - pos_start)) {
432 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy [%zu - %zu] from temporary stream", pos_next, pos_start);
433 0 : ret = FAILURE;
434 : }
435 4 : php_stream_seek(from->fp, pos_curr, SEEK_SET);
436 : }
437 10 : pos_next = pos_start = pos_curr;
438 10 : break;
439 : case 1:
440 34 : 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 10 : if (pos_start != pos_next) {
449 10 : php_stream_seek(from->fp, pos_start, SEEK_SET);
450 10 : if (!php_stream_copy_to_stream(from->fp, dba->fp, pos_next - pos_start)) {
451 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy [%zu - %zu] from temporary stream", pos_next, pos_start);
452 0 : ret = FAILURE;
453 : }
454 : }
455 10 : inifile_line_free(&ln);
456 10 : return SUCCESS;
457 : }
458 : /* }}} */
459 :
460 : /* {{{ inifile_delete_replace_append
461 : */
462 : static int inifile_delete_replace_append(inifile *dba, const key_type *key, const val_type *value, int append TSRMLS_DC)
463 24 : {
464 : size_t pos_grp_start, pos_grp_next;
465 24 : inifile *ini_tmp = NULL;
466 24 : 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 24 : inifile_find_group(dba, key, &pos_grp_start TSRMLS_CC);
484 24 : inifile_next_group(dba, key, &pos_grp_next TSRMLS_CC);
485 24 : if (append) {
486 14 : ret = SUCCESS;
487 : } else {
488 10 : ret = inifile_copy_to(dba, pos_grp_start, pos_grp_next, &ini_tmp TSRMLS_CC);
489 : }
490 :
491 : /* 4 */
492 24 : if (ret == SUCCESS) {
493 24 : fp_tmp = php_stream_temp_create(0, 64 * 1024);
494 24 : if (!fp_tmp) {
495 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not create temporary stream");
496 0 : ret = FAILURE;
497 : } else {
498 24 : php_stream_seek(dba->fp, 0, SEEK_END);
499 24 : if (pos_grp_next != (size_t)php_stream_tell(dba->fp)) {
500 0 : php_stream_seek(dba->fp, pos_grp_next, SEEK_SET);
501 0 : if (!php_stream_copy_to_stream(dba->fp, fp_tmp, PHP_STREAM_COPY_ALL)) {
502 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy remainder to temporary stream");
503 0 : ret = FAILURE;
504 : }
505 : }
506 : }
507 : }
508 :
509 : /* 5 */
510 24 : if (ret == SUCCESS) {
511 24 : if (!value || (key->name && strlen(key->name))) {
512 24 : ret = inifile_truncate(dba, append ? pos_grp_next : pos_grp_start TSRMLS_CC); /* writes error on fail */
513 : }
514 : }
515 :
516 24 : if (ret == SUCCESS) {
517 24 : if (key->name && strlen(key->name)) {
518 : /* 6 */
519 24 : if (!append && ini_tmp) {
520 10 : 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 24 : if (value) {
528 18 : if (pos_grp_start == pos_grp_next && key->group && strlen(key->group)) {
529 0 : php_stream_printf(dba->fp TSRMLS_CC, "[%s]\n", key->group);
530 : }
531 18 : 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 24 : if (fp_tmp && php_stream_tell(fp_tmp)) {
540 0 : php_stream_seek(fp_tmp, 0, SEEK_SET);
541 0 : php_stream_seek(dba->fp, 0, SEEK_END);
542 0 : if (!php_stream_copy_to_stream(fp_tmp, dba->fp, PHP_STREAM_COPY_ALL)) {
543 0 : php_error_docref(NULL TSRMLS_CC, E_RECOVERABLE_ERROR, "Could not copy from temporary stream - ini file truncated");
544 0 : ret = FAILURE;
545 : }
546 : }
547 : }
548 :
549 24 : if (ini_tmp) {
550 10 : php_stream_close(ini_tmp->fp);
551 10 : inifile_free(ini_tmp, 0);
552 : }
553 24 : if (fp_tmp) {
554 24 : php_stream_close(fp_tmp);
555 : }
556 24 : php_stream_flush(dba->fp);
557 24 : php_stream_seek(dba->fp, 0, SEEK_SET);
558 :
559 24 : return ret;
560 : }
561 : /* }}} */
562 :
563 : /* {{{ inifile_delete
564 : */
565 : int inifile_delete(inifile *dba, const key_type *key TSRMLS_DC)
566 6 : {
567 6 : return inifile_delete_replace_append(dba, key, NULL, 0 TSRMLS_CC);
568 : }
569 : /* }}} */
570 :
571 : /* {{{ inifile_relace
572 : */
573 : int inifile_replace(inifile *dba, const key_type *key, const val_type *value TSRMLS_DC)
574 4 : {
575 4 : return inifile_delete_replace_append(dba, key, value, 0 TSRMLS_CC);
576 : }
577 : /* }}} */
578 :
579 : /* {{{ inifile_append
580 : */
581 : int inifile_append(inifile *dba, const key_type *key, const val_type *value TSRMLS_DC)
582 14 : {
583 14 : 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 : */
|