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