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