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: 89373b1e33a6204bf8d50f955dc09d37ecf29ea2 $ */ 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 */ 43char *inifile_version() 44{ 45 return "1.0, $Id: 89373b1e33a6204bf8d50f955dc09d37ecf29ea2 $"; 46} 47/* }}} */ 48 49/* {{{ inifile_free_key */ 50void 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 */ 63void 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 */ 73void 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 */ 82inifile * 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 */ 102void 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 */ 113key_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 */ 130char * 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 */ 145static 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 */ 167static 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 */ 227static 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 */ 245val_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 */ 288int 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 */ 297int 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 */ 312static 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 */ 328static 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 */ 365static 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 */ 386static 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(dba->fp, fp, pos_end - pos_start)) { 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 */ 416static 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(from->fp, dba->fp, pos_next - pos_start)) { 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(from->fp, dba->fp, pos_next - pos_start)) { 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 SUCCESS; 456} 457/* }}} */ 458 459/* {{{ inifile_delete_replace_append 460 */ 461static 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, 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(dba->fp, fp_tmp, PHP_STREAM_COPY_ALL)) { 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(fp_tmp, dba->fp, PHP_STREAM_COPY_ALL)) { 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 */ 564int 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 */ 572int 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 */ 580int 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