xref: /php-src/ext/dba/libinifile/inifile.c (revision 84a0da15)
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