inifile.c 14 KB

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