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 */ 44char *inifile_version() 45{ 46 return "1.0, $Id: 82b364a6bddf54572494867fc63eeee2f715a792 $"; 47} 48/* }}} */ 49 50/* {{{ inifile_free_key */ 51void 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 */ 64void 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 */ 74void 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 */ 83inifile * 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 */ 103void 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 */ 114key_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 */ 131char * 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 */ 146static 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 */ 168static 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 */ 228static 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 */ 246val_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 */ 289int 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 */ 298int 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 */ 313static 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 */ 329static 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 */ 366static 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 */ 387static 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 */ 417static 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 */ 462static 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 */ 565int 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 */ 573int 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 */ 581int 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