1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 5 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2016 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: 95ac9a8ea61cf97a1e4d5acd2efe1dd6092e9a39 $ */
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 */
inifile_version()43 char *inifile_version()
44 {
45 return "1.0, $Id: 95ac9a8ea61cf97a1e4d5acd2efe1dd6092e9a39 $";
46 }
47 /* }}} */
48
49 /* {{{ inifile_free_key */
inifile_key_free(key_type * key)50 void inifile_key_free(key_type *key)
51 {
52 if (key->group) {
53 efree(key->group);
54 }
55 if (key->name) {
56 efree(key->name);
57 }
58 memset(key, 0, sizeof(key_type));
59 }
60 /* }}} */
61
62 /* {{{ inifile_free_val */
inifile_val_free(val_type * val)63 void inifile_val_free(val_type *val)
64 {
65 if (val->value) {
66 efree(val->value);
67 }
68 memset(val, 0, sizeof(val_type));
69 }
70 /* }}} */
71
72 /* {{{ inifile_free_val */
inifile_line_free(line_type * ln)73 void inifile_line_free(line_type *ln)
74 {
75 inifile_key_free(&ln->key);
76 inifile_val_free(&ln->val);
77 ln->pos = 0;
78 }
79 /* }}} */
80
81 /* {{{ inifile_alloc */
inifile_alloc(php_stream * fp,int readonly,int persistent TSRMLS_DC)82 inifile * inifile_alloc(php_stream *fp, int readonly, int persistent TSRMLS_DC)
83 {
84 inifile *dba;
85
86 if (!readonly) {
87 if (!php_stream_truncate_supported(fp)) {
88 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't truncate this stream");
89 return NULL;
90 }
91 }
92
93 dba = pemalloc(sizeof(inifile), persistent);
94 memset(dba, 0, sizeof(inifile));
95 dba->fp = fp;
96 dba->readonly = readonly;
97 return dba;
98 }
99 /* }}} */
100
101 /* {{{ inifile_free */
inifile_free(inifile * dba,int persistent)102 void inifile_free(inifile *dba, int persistent)
103 {
104 if (dba) {
105 inifile_line_free(&dba->curr);
106 inifile_line_free(&dba->next);
107 pefree(dba, persistent);
108 }
109 }
110 /* }}} */
111
112 /* {{{ inifile_key_split */
inifile_key_split(const char * group_name)113 key_type inifile_key_split(const char *group_name)
114 {
115 key_type key;
116 char *name;
117
118 if (group_name[0] == '[' && (name = strchr(group_name, ']')) != NULL) {
119 key.group = estrndup(group_name+1, name - (group_name + 1));
120 key.name = estrdup(name+1);
121 } else {
122 key.group = estrdup("");
123 key.name = estrdup(group_name);
124 }
125 return key;
126 }
127 /* }}} */
128
129 /* {{{ inifile_key_string */
inifile_key_string(const key_type * key)130 char * inifile_key_string(const key_type *key)
131 {
132 if (key->group && *key->group) {
133 char *result;
134 spprintf(&result, 0, "[%s]%s", key->group, key->name ? key->name : "");
135 return result;
136 } else if (key->name) {
137 return estrdup(key->name);
138 } else {
139 return NULL;
140 }
141 }
142 /* }}} */
143
144 /* {{{ etrim */
etrim(const char * str)145 static char *etrim(const char *str)
146 {
147 char *val;
148 size_t l;
149
150 if (!str) {
151 return NULL;
152 }
153 val = (char*)str;
154 while (*val && strchr(" \t\r\n", *val)) {
155 val++;
156 }
157 l = strlen(val);
158 while (l && (strchr(" \t\r\n", val[l-1]))) {
159 l--;
160 }
161 return estrndup(val, l);
162 }
163 /* }}} */
164
165 /* {{{ inifile_findkey
166 */
inifile_read(inifile * dba,line_type * ln TSRMLS_DC)167 static int inifile_read(inifile *dba, line_type *ln TSRMLS_DC) {
168 char *fline;
169 char *pos;
170
171 inifile_val_free(&ln->val);
172 while ((fline = php_stream_gets(dba->fp, NULL, 0)) != NULL) {
173 if (fline) {
174 if (fline[0] == '[') {
175 /* A value name cannot start with '['
176 * So either we find a ']' or we found an error
177 */
178 pos = strchr(fline+1, ']');
179 if (pos) {
180 *pos = '\0';
181 inifile_key_free(&ln->key);
182 ln->key.group = etrim(fline+1);
183 ln->key.name = estrdup("");
184 ln->pos = php_stream_tell(dba->fp);
185 efree(fline);
186 return 1;
187 } else {
188 efree(fline);
189 continue;
190 }
191 } else {
192 pos = strchr(fline, '=');
193 if (pos) {
194 *pos = '\0';
195 /* keep group or make empty if not existent */
196 if (!ln->key.group) {
197 ln->key.group = estrdup("");
198 }
199 if (ln->key.name) {
200 efree(ln->key.name);
201 }
202 ln->key.name = etrim(fline);
203 ln->val.value = etrim(pos+1);
204 ln->pos = php_stream_tell(dba->fp);
205 efree(fline);
206 return 1;
207 } else {
208 /* simply ignore lines without '='
209 * those should be comments
210 */
211 efree(fline);
212 continue;
213 }
214 }
215 }
216 }
217 inifile_line_free(ln);
218 return 0;
219 }
220 /* }}} */
221
222 /* {{{ inifile_key_cmp */
223 /* 0 = EQUAL
224 * 1 = GROUP-EQUAL,NAME-DIFFERENT
225 * 2 = DIFFERENT
226 */
inifile_key_cmp(const key_type * k1,const key_type * k2 TSRMLS_DC)227 static int inifile_key_cmp(const key_type *k1, const key_type *k2 TSRMLS_DC)
228 {
229 assert(k1->group && k1->name && k2->group && k2->name);
230
231 if (!strcasecmp(k1->group, k2->group)) {
232 if (!strcasecmp(k1->name, k2->name)) {
233 return 0;
234 } else {
235 return 1;
236 }
237 } else {
238 return 2;
239 }
240 }
241 /* }}} */
242
243 /* {{{ inifile_fetch
244 */
inifile_fetch(inifile * dba,const key_type * key,int skip TSRMLS_DC)245 val_type inifile_fetch(inifile *dba, const key_type *key, int skip TSRMLS_DC) {
246 line_type ln = {{NULL,NULL},{NULL}};
247 val_type val;
248 int res, grp_eq = 0;
249
250 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 php_stream_seek(dba->fp, dba->next.pos, SEEK_SET);
253 ln.key.group = estrdup(dba->next.key.group);
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 */
inifile_firstkey(inifile * dba TSRMLS_DC)289 int 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 */
inifile_nextkey(inifile * dba TSRMLS_DC)298 int 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 */
inifile_truncate(inifile * dba,size_t size TSRMLS_DC)313 static 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 */
inifile_find_group(inifile * dba,const key_type * key,size_t * pos_grp_start TSRMLS_DC)329 static 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 */
inifile_next_group(inifile * dba,const key_type * key,size_t * pos_grp_start TSRMLS_DC)366 static 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 */
inifile_copy_to(inifile * dba,size_t pos_start,size_t pos_end,inifile ** ini_copy TSRMLS_DC)387 static 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 (SUCCESS != php_stream_copy_to_stream_ex(dba->fp, fp, pos_end - pos_start, NULL)) {
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 */
inifile_filter(inifile * dba,inifile * from,const key_type * key TSRMLS_DC)417 static 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 (SUCCESS != php_stream_copy_to_stream_ex(from->fp, dba->fp, pos_next - pos_start, NULL)) {
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 (SUCCESS != php_stream_copy_to_stream_ex(from->fp, dba->fp, pos_next - pos_start, NULL)) {
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 ret;
457 }
458 /* }}} */
459
460 /* {{{ inifile_delete_replace_append
461 */
inifile_delete_replace_append(inifile * dba,const key_type * key,const val_type * value,int append TSRMLS_DC)462 static 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=0, 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 (SUCCESS != php_stream_copy_to_stream_ex(dba->fp, fp_tmp, PHP_STREAM_COPY_ALL, NULL)) {
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 (SUCCESS != php_stream_copy_to_stream_ex(fp_tmp, dba->fp, PHP_STREAM_COPY_ALL, NULL)) {
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 */
inifile_delete(inifile * dba,const key_type * key TSRMLS_DC)565 int 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 */
inifile_replace(inifile * dba,const key_type * key,const val_type * value TSRMLS_DC)573 int 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 */
inifile_append(inifile * dba,const key_type * key,const val_type * value TSRMLS_DC)581 int 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