inifile.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. /*
  2. +----------------------------------------------------------------------+
  3. | PHP Version 5 |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 1997-2016 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. /* $Id$ */
  19. #ifdef HAVE_CONFIG_H
  20. #include "config.h"
  21. #endif
  22. #include "php.h"
  23. #include "php_globals.h"
  24. #include <stdlib.h>
  25. #include <string.h>
  26. #include <errno.h>
  27. #if HAVE_UNISTD_H
  28. #include <unistd.h>
  29. #endif
  30. #include "inifile.h"
  31. /* ret = -1 means that database was opened for read-only
  32. * ret = 0 success
  33. * ret = 1 key already exists - nothing done
  34. */
  35. /* {{{ inifile_version */
  36. char *inifile_version()
  37. {
  38. return "1.0, $Id$";
  39. }
  40. /* }}} */
  41. /* {{{ inifile_free_key */
  42. void inifile_key_free(key_type *key)
  43. {
  44. if (key->group) {
  45. efree(key->group);
  46. }
  47. if (key->name) {
  48. efree(key->name);
  49. }
  50. memset(key, 0, sizeof(key_type));
  51. }
  52. /* }}} */
  53. /* {{{ inifile_free_val */
  54. void inifile_val_free(val_type *val)
  55. {
  56. if (val->value) {
  57. efree(val->value);
  58. }
  59. memset(val, 0, sizeof(val_type));
  60. }
  61. /* }}} */
  62. /* {{{ inifile_free_val */
  63. void inifile_line_free(line_type *ln)
  64. {
  65. inifile_key_free(&ln->key);
  66. inifile_val_free(&ln->val);
  67. ln->pos = 0;
  68. }
  69. /* }}} */
  70. /* {{{ inifile_alloc */
  71. inifile * inifile_alloc(php_stream *fp, int readonly, int persistent TSRMLS_DC)
  72. {
  73. inifile *dba;
  74. if (!readonly) {
  75. if (!php_stream_truncate_supported(fp)) {
  76. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't truncate this stream");
  77. return NULL;
  78. }
  79. }
  80. dba = pemalloc(sizeof(inifile), persistent);
  81. memset(dba, 0, sizeof(inifile));
  82. dba->fp = fp;
  83. dba->readonly = readonly;
  84. return dba;
  85. }
  86. /* }}} */
  87. /* {{{ inifile_free */
  88. void inifile_free(inifile *dba, int persistent)
  89. {
  90. if (dba) {
  91. inifile_line_free(&dba->curr);
  92. inifile_line_free(&dba->next);
  93. pefree(dba, persistent);
  94. }
  95. }
  96. /* }}} */
  97. /* {{{ inifile_key_split */
  98. key_type inifile_key_split(const char *group_name)
  99. {
  100. key_type key;
  101. char *name;
  102. if (group_name[0] == '[' && (name = strchr(group_name, ']')) != NULL) {
  103. key.group = estrndup(group_name+1, name - (group_name + 1));
  104. key.name = estrdup(name+1);
  105. } else {
  106. key.group = estrdup("");
  107. key.name = estrdup(group_name);
  108. }
  109. return key;
  110. }
  111. /* }}} */
  112. /* {{{ inifile_key_string */
  113. char * inifile_key_string(const key_type *key)
  114. {
  115. if (key->group && *key->group) {
  116. char *result;
  117. spprintf(&result, 0, "[%s]%s", key->group, key->name ? key->name : "");
  118. return result;
  119. } else if (key->name) {
  120. return estrdup(key->name);
  121. } else {
  122. return NULL;
  123. }
  124. }
  125. /* }}} */
  126. /* {{{ etrim */
  127. static char *etrim(const char *str)
  128. {
  129. char *val;
  130. size_t l;
  131. if (!str) {
  132. return NULL;
  133. }
  134. val = (char*)str;
  135. while (*val && strchr(" \t\r\n", *val)) {
  136. val++;
  137. }
  138. l = strlen(val);
  139. while (l && (strchr(" \t\r\n", val[l-1]))) {
  140. l--;
  141. }
  142. return estrndup(val, l);
  143. }
  144. /* }}} */
  145. /* {{{ inifile_findkey
  146. */
  147. static int inifile_read(inifile *dba, line_type *ln TSRMLS_DC) {
  148. char *fline;
  149. char *pos;
  150. inifile_val_free(&ln->val);
  151. while ((fline = php_stream_gets(dba->fp, NULL, 0)) != NULL) {
  152. if (fline) {
  153. if (fline[0] == '[') {
  154. /* A value name cannot start with '['
  155. * So either we find a ']' or we found an error
  156. */
  157. pos = strchr(fline+1, ']');
  158. if (pos) {
  159. *pos = '\0';
  160. inifile_key_free(&ln->key);
  161. ln->key.group = etrim(fline+1);
  162. ln->key.name = estrdup("");
  163. ln->pos = php_stream_tell(dba->fp);
  164. efree(fline);
  165. return 1;
  166. } else {
  167. efree(fline);
  168. continue;
  169. }
  170. } else {
  171. pos = strchr(fline, '=');
  172. if (pos) {
  173. *pos = '\0';
  174. /* keep group or make empty if not existent */
  175. if (!ln->key.group) {
  176. ln->key.group = estrdup("");
  177. }
  178. if (ln->key.name) {
  179. efree(ln->key.name);
  180. }
  181. ln->key.name = etrim(fline);
  182. ln->val.value = etrim(pos+1);
  183. ln->pos = php_stream_tell(dba->fp);
  184. efree(fline);
  185. return 1;
  186. } else {
  187. /* simply ignore lines without '='
  188. * those should be comments
  189. */
  190. efree(fline);
  191. continue;
  192. }
  193. }
  194. }
  195. }
  196. inifile_line_free(ln);
  197. return 0;
  198. }
  199. /* }}} */
  200. /* {{{ inifile_key_cmp */
  201. /* 0 = EQUAL
  202. * 1 = GROUP-EQUAL,NAME-DIFFERENT
  203. * 2 = DIFFERENT
  204. */
  205. static int inifile_key_cmp(const key_type *k1, const key_type *k2 TSRMLS_DC)
  206. {
  207. assert(k1->group && k1->name && k2->group && k2->name);
  208. if (!strcasecmp(k1->group, k2->group)) {
  209. if (!strcasecmp(k1->name, k2->name)) {
  210. return 0;
  211. } else {
  212. return 1;
  213. }
  214. } else {
  215. return 2;
  216. }
  217. }
  218. /* }}} */
  219. /* {{{ inifile_fetch
  220. */
  221. val_type inifile_fetch(inifile *dba, const key_type *key, int skip TSRMLS_DC) {
  222. line_type ln = {{NULL,NULL},{NULL}};
  223. val_type val;
  224. int res, grp_eq = 0;
  225. if (skip == -1 && dba->next.key.group && dba->next.key.name && !inifile_key_cmp(&dba->next.key, key TSRMLS_CC)) {
  226. /* we got position already from last fetch */
  227. php_stream_seek(dba->fp, dba->next.pos, SEEK_SET);
  228. ln.key.group = estrdup(dba->next.key.group);
  229. } else {
  230. /* specific instance or not same key -> restart search */
  231. /* the slow way: restart and seacrch */
  232. php_stream_rewind(dba->fp);
  233. inifile_line_free(&dba->next);
  234. }
  235. if (skip == -1) {
  236. skip = 0;
  237. }
  238. while(inifile_read(dba, &ln TSRMLS_CC)) {
  239. if (!(res=inifile_key_cmp(&ln.key, key TSRMLS_CC))) {
  240. if (!skip) {
  241. val.value = estrdup(ln.val.value ? ln.val.value : "");
  242. /* allow faster access by updating key read into next */
  243. inifile_line_free(&dba->next);
  244. dba->next = ln;
  245. dba->next.pos = php_stream_tell(dba->fp);
  246. return val;
  247. }
  248. skip--;
  249. } else if (res == 1) {
  250. grp_eq = 1;
  251. } else if (grp_eq) {
  252. /* we are leaving group now: that means we cannot find the key */
  253. break;
  254. }
  255. }
  256. inifile_line_free(&ln);
  257. dba->next.pos = php_stream_tell(dba->fp);
  258. return ln.val;
  259. }
  260. /* }}} */
  261. /* {{{ inifile_firstkey
  262. */
  263. int inifile_firstkey(inifile *dba TSRMLS_DC) {
  264. inifile_line_free(&dba->curr);
  265. dba->curr.pos = 0;
  266. return inifile_nextkey(dba TSRMLS_CC);
  267. }
  268. /* }}} */
  269. /* {{{ inifile_nextkey
  270. */
  271. int inifile_nextkey(inifile *dba TSRMLS_DC) {
  272. line_type ln = {{NULL,NULL},{NULL}};
  273. /*inifile_line_free(&dba->next); ??? */
  274. php_stream_seek(dba->fp, dba->curr.pos, SEEK_SET);
  275. ln.key.group = estrdup(dba->curr.key.group ? dba->curr.key.group : "");
  276. inifile_read(dba, &ln TSRMLS_CC);
  277. inifile_line_free(&dba->curr);
  278. dba->curr = ln;
  279. return ln.key.group || ln.key.name;
  280. }
  281. /* }}} */
  282. /* {{{ inifile_truncate
  283. */
  284. static int inifile_truncate(inifile *dba, size_t size TSRMLS_DC)
  285. {
  286. int res;
  287. if ((res=php_stream_truncate_set_size(dba->fp, size)) != 0) {
  288. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error in ftruncate: %d", res);
  289. return FAILURE;
  290. }
  291. php_stream_seek(dba->fp, size, SEEK_SET);
  292. return SUCCESS;
  293. }
  294. /* }}} */
  295. /* {{{ inifile_find_group
  296. * if found pos_grp_start points to "[group_name]"
  297. */
  298. static int inifile_find_group(inifile *dba, const key_type *key, size_t *pos_grp_start TSRMLS_DC)
  299. {
  300. int ret = FAILURE;
  301. php_stream_flush(dba->fp);
  302. php_stream_seek(dba->fp, 0, SEEK_SET);
  303. inifile_line_free(&dba->curr);
  304. inifile_line_free(&dba->next);
  305. if (key->group && strlen(key->group)) {
  306. int res;
  307. line_type ln = {{NULL,NULL},{NULL}};
  308. res = 1;
  309. while(inifile_read(dba, &ln TSRMLS_CC)) {
  310. if ((res=inifile_key_cmp(&ln.key, key TSRMLS_CC)) < 2) {
  311. ret = SUCCESS;
  312. break;
  313. }
  314. *pos_grp_start = php_stream_tell(dba->fp);
  315. }
  316. inifile_line_free(&ln);
  317. } else {
  318. *pos_grp_start = 0;
  319. ret = SUCCESS;
  320. }
  321. if (ret == FAILURE) {
  322. *pos_grp_start = php_stream_tell(dba->fp);
  323. }
  324. return ret;
  325. }
  326. /* }}} */
  327. /* {{{ inifile_next_group
  328. * only valid after a call to inifile_find_group
  329. * if any next group is found pos_grp_start points to "[group_name]" or whitespace before that
  330. */
  331. static int inifile_next_group(inifile *dba, const key_type *key, size_t *pos_grp_start TSRMLS_DC)
  332. {
  333. int ret = FAILURE;
  334. line_type ln = {{NULL,NULL},{NULL}};
  335. *pos_grp_start = php_stream_tell(dba->fp);
  336. ln.key.group = estrdup(key->group);
  337. while(inifile_read(dba, &ln TSRMLS_CC)) {
  338. if (inifile_key_cmp(&ln.key, key TSRMLS_CC) == 2) {
  339. ret = SUCCESS;
  340. break;
  341. }
  342. *pos_grp_start = php_stream_tell(dba->fp);
  343. }
  344. inifile_line_free(&ln);
  345. return ret;
  346. }
  347. /* }}} */
  348. /* {{{ inifile_copy_to
  349. */
  350. static int inifile_copy_to(inifile *dba, size_t pos_start, size_t pos_end, inifile **ini_copy TSRMLS_DC)
  351. {
  352. php_stream *fp;
  353. if (pos_start == pos_end) {
  354. *ini_copy = NULL;
  355. return SUCCESS;
  356. }
  357. if ((fp = php_stream_temp_create(0, 64 * 1024)) == NULL) {
  358. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not create temporary stream");
  359. *ini_copy = NULL;
  360. return FAILURE;
  361. }
  362. if ((*ini_copy = inifile_alloc(fp, 1, 0 TSRMLS_CC)) == NULL) {
  363. /* writes error */
  364. return FAILURE;
  365. }
  366. php_stream_seek(dba->fp, pos_start, SEEK_SET);
  367. if (SUCCESS != php_stream_copy_to_stream_ex(dba->fp, fp, pos_end - pos_start, NULL)) {
  368. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy group [%zu - %zu] to temporary stream", pos_start, pos_end);
  369. return FAILURE;
  370. }
  371. return SUCCESS;
  372. }
  373. /* }}} */
  374. /* {{{ inifile_filter
  375. * copy from to dba while ignoring key name (group must equal)
  376. */
  377. static int inifile_filter(inifile *dba, inifile *from, const key_type *key TSRMLS_DC)
  378. {
  379. size_t pos_start = 0, pos_next = 0, pos_curr;
  380. int ret = SUCCESS;
  381. line_type ln = {{NULL,NULL},{NULL}};
  382. php_stream_seek(from->fp, 0, SEEK_SET);
  383. php_stream_seek(dba->fp, 0, SEEK_END);
  384. while(inifile_read(from, &ln TSRMLS_CC)) {
  385. switch(inifile_key_cmp(&ln.key, key TSRMLS_CC)) {
  386. case 0:
  387. pos_curr = php_stream_tell(from->fp);
  388. if (pos_start != pos_next) {
  389. php_stream_seek(from->fp, pos_start, SEEK_SET);
  390. if (SUCCESS != php_stream_copy_to_stream_ex(from->fp, dba->fp, pos_next - pos_start, NULL)) {
  391. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy [%zu - %zu] from temporary stream", pos_next, pos_start);
  392. ret = FAILURE;
  393. }
  394. php_stream_seek(from->fp, pos_curr, SEEK_SET);
  395. }
  396. pos_next = pos_start = pos_curr;
  397. break;
  398. case 1:
  399. pos_next = php_stream_tell(from->fp);
  400. break;
  401. case 2:
  402. /* the function is meant to process only entries from same group */
  403. assert(0);
  404. break;
  405. }
  406. }
  407. if (pos_start != pos_next) {
  408. php_stream_seek(from->fp, pos_start, SEEK_SET);
  409. if (SUCCESS != php_stream_copy_to_stream_ex(from->fp, dba->fp, pos_next - pos_start, NULL)) {
  410. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy [%zu - %zu] from temporary stream", pos_next, pos_start);
  411. ret = FAILURE;
  412. }
  413. }
  414. inifile_line_free(&ln);
  415. return ret;
  416. }
  417. /* }}} */
  418. /* {{{ inifile_delete_replace_append
  419. */
  420. static int inifile_delete_replace_append(inifile *dba, const key_type *key, const val_type *value, int append TSRMLS_DC)
  421. {
  422. size_t pos_grp_start=0, pos_grp_next;
  423. inifile *ini_tmp = NULL;
  424. php_stream *fp_tmp = NULL;
  425. int ret;
  426. /* 1) Search group start
  427. * 2) Search next group
  428. * 3) If not append: Copy group to ini_tmp
  429. * 4) Open temp_stream and copy remainder
  430. * 5) Truncate stream
  431. * 6) If not append AND key.name given: Filtered copy back from ini_tmp
  432. * to stream. Otherwise the user wanted to delete the group.
  433. * 7) Append value if given
  434. * 8) Append temporary stream
  435. */
  436. assert(!append || (key->name && value)); /* missuse */
  437. /* 1 - 3 */
  438. inifile_find_group(dba, key, &pos_grp_start TSRMLS_CC);
  439. inifile_next_group(dba, key, &pos_grp_next TSRMLS_CC);
  440. if (append) {
  441. ret = SUCCESS;
  442. } else {
  443. ret = inifile_copy_to(dba, pos_grp_start, pos_grp_next, &ini_tmp TSRMLS_CC);
  444. }
  445. /* 4 */
  446. if (ret == SUCCESS) {
  447. fp_tmp = php_stream_temp_create(0, 64 * 1024);
  448. if (!fp_tmp) {
  449. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not create temporary stream");
  450. ret = FAILURE;
  451. } else {
  452. php_stream_seek(dba->fp, 0, SEEK_END);
  453. if (pos_grp_next != (size_t)php_stream_tell(dba->fp)) {
  454. php_stream_seek(dba->fp, pos_grp_next, SEEK_SET);
  455. if (SUCCESS != php_stream_copy_to_stream_ex(dba->fp, fp_tmp, PHP_STREAM_COPY_ALL, NULL)) {
  456. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not copy remainder to temporary stream");
  457. ret = FAILURE;
  458. }
  459. }
  460. }
  461. }
  462. /* 5 */
  463. if (ret == SUCCESS) {
  464. if (!value || (key->name && strlen(key->name))) {
  465. ret = inifile_truncate(dba, append ? pos_grp_next : pos_grp_start TSRMLS_CC); /* writes error on fail */
  466. }
  467. }
  468. if (ret == SUCCESS) {
  469. if (key->name && strlen(key->name)) {
  470. /* 6 */
  471. if (!append && ini_tmp) {
  472. ret = inifile_filter(dba, ini_tmp, key TSRMLS_CC);
  473. }
  474. /* 7 */
  475. /* important: do not query ret==SUCCESS again: inifile_filter might fail but
  476. * however next operation must be done.
  477. */
  478. if (value) {
  479. if (pos_grp_start == pos_grp_next && key->group && strlen(key->group)) {
  480. php_stream_printf(dba->fp TSRMLS_CC, "[%s]\n", key->group);
  481. }
  482. php_stream_printf(dba->fp TSRMLS_CC, "%s=%s\n", key->name, value->value ? value->value : "");
  483. }
  484. }
  485. /* 8 */
  486. /* important: do not query ret==SUCCESS again: inifile_filter might fail but
  487. * however next operation must be done.
  488. */
  489. if (fp_tmp && php_stream_tell(fp_tmp)) {
  490. php_stream_seek(fp_tmp, 0, SEEK_SET);
  491. php_stream_seek(dba->fp, 0, SEEK_END);
  492. if (SUCCESS != php_stream_copy_to_stream_ex(fp_tmp, dba->fp, PHP_STREAM_COPY_ALL, NULL)) {
  493. php_error_docref(NULL TSRMLS_CC, E_RECOVERABLE_ERROR, "Could not copy from temporary stream - ini file truncated");
  494. ret = FAILURE;
  495. }
  496. }
  497. }
  498. if (ini_tmp) {
  499. php_stream_close(ini_tmp->fp);
  500. inifile_free(ini_tmp, 0);
  501. }
  502. if (fp_tmp) {
  503. php_stream_close(fp_tmp);
  504. }
  505. php_stream_flush(dba->fp);
  506. php_stream_seek(dba->fp, 0, SEEK_SET);
  507. return ret;
  508. }
  509. /* }}} */
  510. /* {{{ inifile_delete
  511. */
  512. int inifile_delete(inifile *dba, const key_type *key TSRMLS_DC)
  513. {
  514. return inifile_delete_replace_append(dba, key, NULL, 0 TSRMLS_CC);
  515. }
  516. /* }}} */
  517. /* {{{ inifile_relace
  518. */
  519. int inifile_replace(inifile *dba, const key_type *key, const val_type *value TSRMLS_DC)
  520. {
  521. return inifile_delete_replace_append(dba, key, value, 0 TSRMLS_CC);
  522. }
  523. /* }}} */
  524. /* {{{ inifile_append
  525. */
  526. int inifile_append(inifile *dba, const key_type *key, const val_type *value TSRMLS_DC)
  527. {
  528. return inifile_delete_replace_append(dba, key, value, 1 TSRMLS_CC);
  529. }
  530. /* }}} */
  531. /*
  532. * Local variables:
  533. * tab-width: 4
  534. * c-basic-offset: 4
  535. * End:
  536. * vim600: sw=4 ts=4 fdm=marker
  537. * vim<600: sw=4 ts=4
  538. */