xref: /PHP-5.5/ext/dba/libinifile/inifile.c (revision 73c1be26)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 5                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2015 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: 1b985fa0072f7886f787255ae90c7e50d76f9539 $ */
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: 1b985fa0072f7886f787255ae90c7e50d76f9539 $";
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 	} else {
254 		/* specific instance or not same key -> restart search */
255 		/* the slow way: restart and seacrch */
256 		php_stream_rewind(dba->fp);
257 		inifile_line_free(&dba->next);
258 	}
259 	if (skip == -1) {
260 		skip = 0;
261 	}
262 	while(inifile_read(dba, &ln TSRMLS_CC)) {
263 		if (!(res=inifile_key_cmp(&ln.key, key TSRMLS_CC))) {
264 			if (!skip) {
265 				val.value = estrdup(ln.val.value ? ln.val.value : "");
266 				/* allow faster access by updating key read into next */
267 				inifile_line_free(&dba->next);
268 				dba->next = ln;
269 				dba->next.pos = php_stream_tell(dba->fp);
270 				return val;
271 			}
272 			skip--;
273 		} else if (res == 1) {
274 			grp_eq = 1;
275 		} else if (grp_eq) {
276 			/* we are leaving group now: that means we cannot find the key */
277 			break;
278 		}
279 	}
280 	inifile_line_free(&ln);
281 	dba->next.pos = php_stream_tell(dba->fp);
282 	return ln.val;
283 }
284 /* }}} */
285 
286 /* {{{ inifile_firstkey
287  */
inifile_firstkey(inifile * dba TSRMLS_DC)288 int inifile_firstkey(inifile *dba TSRMLS_DC) {
289 	inifile_line_free(&dba->curr);
290 	dba->curr.pos = 0;
291 	return inifile_nextkey(dba TSRMLS_CC);
292 }
293 /* }}} */
294 
295 /* {{{ inifile_nextkey
296  */
inifile_nextkey(inifile * dba TSRMLS_DC)297 int inifile_nextkey(inifile *dba TSRMLS_DC) {
298 	line_type ln = {{NULL,NULL},{NULL}};
299 
300 	/*inifile_line_free(&dba->next); ??? */
301 	php_stream_seek(dba->fp, dba->curr.pos, SEEK_SET);
302 	ln.key.group = estrdup(dba->curr.key.group ? dba->curr.key.group : "");
303 	inifile_read(dba, &ln TSRMLS_CC);
304 	inifile_line_free(&dba->curr);
305 	dba->curr = ln;
306 	return ln.key.group || ln.key.name;
307 }
308 /* }}} */
309 
310 /* {{{ inifile_truncate
311  */
inifile_truncate(inifile * dba,size_t size TSRMLS_DC)312 static int inifile_truncate(inifile *dba, size_t size TSRMLS_DC)
313 {
314 	int res;
315 
316 	if ((res=php_stream_truncate_set_size(dba->fp, size)) != 0) {
317 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error in ftruncate: %d", res);
318 		return FAILURE;
319 	}
320 	php_stream_seek(dba->fp, size, SEEK_SET);
321 	return SUCCESS;
322 }
323 /* }}} */
324 
325 /* {{{ inifile_find_group
326  * if found pos_grp_start points to "[group_name]"
327  */
inifile_find_group(inifile * dba,const key_type * key,size_t * pos_grp_start TSRMLS_DC)328 static int inifile_find_group(inifile *dba, const key_type *key, size_t *pos_grp_start TSRMLS_DC)
329 {
330 	int ret = FAILURE;
331 
332 	php_stream_flush(dba->fp);
333 	php_stream_seek(dba->fp, 0, SEEK_SET);
334 	inifile_line_free(&dba->curr);
335 	inifile_line_free(&dba->next);
336 
337 	if (key->group && strlen(key->group)) {
338 		int res;
339 		line_type ln = {{NULL,NULL},{NULL}};
340 
341 		res = 1;
342 		while(inifile_read(dba, &ln TSRMLS_CC)) {
343 			if ((res=inifile_key_cmp(&ln.key, key TSRMLS_CC)) < 2) {
344 				ret = SUCCESS;
345 				break;
346 			}
347 			*pos_grp_start = php_stream_tell(dba->fp);
348 		}
349 		inifile_line_free(&ln);
350 	} else {
351 		*pos_grp_start = 0;
352 		ret = SUCCESS;
353 	}
354 	if (ret == FAILURE) {
355 		*pos_grp_start = php_stream_tell(dba->fp);
356 	}
357 	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  */
inifile_next_group(inifile * dba,const key_type * key,size_t * pos_grp_start TSRMLS_DC)365 static int inifile_next_group(inifile *dba, const key_type *key, size_t *pos_grp_start TSRMLS_DC)
366 {
367 	int ret = FAILURE;
368 	line_type ln = {{NULL,NULL},{NULL}};
369 
370 	*pos_grp_start = php_stream_tell(dba->fp);
371 	ln.key.group = estrdup(key->group);
372 	while(inifile_read(dba, &ln TSRMLS_CC)) {
373 		if (inifile_key_cmp(&ln.key, key TSRMLS_CC) == 2) {
374 			ret = SUCCESS;
375 			break;
376 		}
377 		*pos_grp_start = php_stream_tell(dba->fp);
378 	}
379 	inifile_line_free(&ln);
380 	return ret;
381 }
382 /* }}} */
383 
384 /* {{{ inifile_copy_to
385  */
inifile_copy_to(inifile * dba,size_t pos_start,size_t pos_end,inifile ** ini_copy TSRMLS_DC)386 static int inifile_copy_to(inifile *dba, size_t pos_start, size_t pos_end, inifile **ini_copy TSRMLS_DC)
387 {
388 	php_stream *fp;
389 
390 	if (pos_start == pos_end) {
391 		*ini_copy = NULL;
392 		return SUCCESS;
393 	}
394 	if ((fp = php_stream_temp_create(0, 64 * 1024)) == NULL) {
395 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not create temporary stream");
396 		*ini_copy = NULL;
397 		return FAILURE;
398 	}
399 
400 	if ((*ini_copy = inifile_alloc(fp, 1, 0 TSRMLS_CC)) == NULL) {
401 		/* writes error */
402 		return FAILURE;
403 	}
404 	php_stream_seek(dba->fp, pos_start, SEEK_SET);
405 	if (!php_stream_copy_to_stream_ex(dba->fp, fp, pos_end - pos_start, NULL)) {
406 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy group [%zu - %zu] to temporary stream", pos_start, pos_end);
407 		return FAILURE;
408 	}
409 	return SUCCESS;
410 }
411 /* }}} */
412 
413 /* {{{ inifile_filter
414  * copy from to dba while ignoring key name (group must equal)
415  */
inifile_filter(inifile * dba,inifile * from,const key_type * key TSRMLS_DC)416 static int inifile_filter(inifile *dba, inifile *from, const key_type *key TSRMLS_DC)
417 {
418 	size_t pos_start = 0, pos_next = 0, pos_curr;
419 	int ret = SUCCESS;
420 	line_type ln = {{NULL,NULL},{NULL}};
421 
422 	php_stream_seek(from->fp, 0, SEEK_SET);
423 	php_stream_seek(dba->fp, 0, SEEK_END);
424 	while(inifile_read(from, &ln TSRMLS_CC)) {
425 		switch(inifile_key_cmp(&ln.key, key TSRMLS_CC)) {
426 		case 0:
427 			pos_curr = php_stream_tell(from->fp);
428 			if (pos_start != pos_next) {
429 				php_stream_seek(from->fp, pos_start, SEEK_SET);
430 				if (!php_stream_copy_to_stream_ex(from->fp, dba->fp, pos_next - pos_start, NULL)) {
431 					php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy [%zu - %zu] from temporary stream", pos_next, pos_start);
432 					ret = FAILURE;
433 				}
434 				php_stream_seek(from->fp, pos_curr, SEEK_SET);
435 			}
436 			pos_next = pos_start = pos_curr;
437 			break;
438 		case 1:
439 			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 	if (pos_start != pos_next) {
448 		php_stream_seek(from->fp, pos_start, SEEK_SET);
449 		if (!php_stream_copy_to_stream_ex(from->fp, dba->fp, pos_next - pos_start, NULL)) {
450 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy [%zu - %zu] from temporary stream", pos_next, pos_start);
451 			ret = FAILURE;
452 		}
453 	}
454 	inifile_line_free(&ln);
455 	return ret;
456 }
457 /* }}} */
458 
459 /* {{{ inifile_delete_replace_append
460  */
inifile_delete_replace_append(inifile * dba,const key_type * key,const val_type * value,int append TSRMLS_DC)461 static int inifile_delete_replace_append(inifile *dba, const key_type *key, const val_type *value, int append TSRMLS_DC)
462 {
463 	size_t pos_grp_start=0, pos_grp_next;
464 	inifile *ini_tmp = NULL;
465 	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 	inifile_find_group(dba, key, &pos_grp_start TSRMLS_CC);
483 	inifile_next_group(dba, key, &pos_grp_next TSRMLS_CC);
484 	if (append) {
485 		ret = SUCCESS;
486 	} else {
487 		ret = inifile_copy_to(dba, pos_grp_start, pos_grp_next, &ini_tmp TSRMLS_CC);
488 	}
489 
490 	/* 4 */
491 	if (ret == SUCCESS) {
492 		fp_tmp = php_stream_temp_create(0, 64 * 1024);
493 		if (!fp_tmp) {
494 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not create temporary stream");
495 			ret = FAILURE;
496 		} else {
497 			php_stream_seek(dba->fp, 0, SEEK_END);
498 			if (pos_grp_next != (size_t)php_stream_tell(dba->fp)) {
499 				php_stream_seek(dba->fp, pos_grp_next, SEEK_SET);
500 				if (!php_stream_copy_to_stream_ex(dba->fp, fp_tmp, PHP_STREAM_COPY_ALL, NULL)) {
501 					php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy remainder to temporary stream");
502 					ret = FAILURE;
503 				}
504 			}
505 		}
506 	}
507 
508 	/* 5 */
509 	if (ret == SUCCESS) {
510 		if (!value || (key->name && strlen(key->name))) {
511 			ret = inifile_truncate(dba, append ? pos_grp_next : pos_grp_start TSRMLS_CC); /* writes error on fail */
512 		}
513 	}
514 
515 	if (ret == SUCCESS) {
516 		if (key->name && strlen(key->name)) {
517 			/* 6 */
518 			if (!append && ini_tmp) {
519 				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 			if (value) {
527 				if (pos_grp_start == pos_grp_next && key->group && strlen(key->group)) {
528 					php_stream_printf(dba->fp TSRMLS_CC, "[%s]\n", key->group);
529 				}
530 				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 		if (fp_tmp && php_stream_tell(fp_tmp)) {
539 			php_stream_seek(fp_tmp, 0, SEEK_SET);
540 			php_stream_seek(dba->fp, 0, SEEK_END);
541 			if (!php_stream_copy_to_stream_ex(fp_tmp, dba->fp, PHP_STREAM_COPY_ALL, NULL)) {
542 				php_error_docref(NULL TSRMLS_CC, E_RECOVERABLE_ERROR, "Could not copy from temporary stream - ini file truncated");
543 				ret = FAILURE;
544 			}
545 		}
546 	}
547 
548 	if (ini_tmp) {
549 		php_stream_close(ini_tmp->fp);
550 		inifile_free(ini_tmp, 0);
551 	}
552 	if (fp_tmp) {
553 		php_stream_close(fp_tmp);
554 	}
555 	php_stream_flush(dba->fp);
556 	php_stream_seek(dba->fp, 0, SEEK_SET);
557 
558 	return ret;
559 }
560 /* }}} */
561 
562 /* {{{ inifile_delete
563  */
inifile_delete(inifile * dba,const key_type * key TSRMLS_DC)564 int inifile_delete(inifile *dba, const key_type *key TSRMLS_DC)
565 {
566 	return inifile_delete_replace_append(dba, key, NULL, 0 TSRMLS_CC);
567 }
568 /* }}} */
569 
570 /* {{{ inifile_relace
571  */
inifile_replace(inifile * dba,const key_type * key,const val_type * value TSRMLS_DC)572 int inifile_replace(inifile *dba, const key_type *key, const val_type *value TSRMLS_DC)
573 {
574 	return inifile_delete_replace_append(dba, key, value, 0 TSRMLS_CC);
575 }
576 /* }}} */
577 
578 /* {{{ inifile_append
579  */
inifile_append(inifile * dba,const key_type * key,const val_type * value TSRMLS_DC)580 int inifile_append(inifile *dba, const key_type *key, const val_type *value TSRMLS_DC)
581 {
582 	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  */
594