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