minIni.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952
  1. /* minIni - Multi-Platform INI file parser, suitable for embedded systems
  2. *
  3. * These routines are in part based on the article "Multiplatform .INI Files"
  4. * by Joseph J. Graf in the March 1994 issue of Dr. Dobb's Journal.
  5. *
  6. * Copyright (c) CompuPhase, 2008-2021
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  9. * use this file except in compliance with the License. You may obtain a copy
  10. * of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  16. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  17. * License for the specific language governing permissions and limitations
  18. * under the License.
  19. *
  20. * Version: $Id: minIni.c 53 2015-01-18 13:35:11Z thiadmer.riemersma@gmail.com $
  21. */
  22. #if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined INI_ANSIONLY
  23. # if !defined UNICODE /* for Windows */
  24. # define UNICODE
  25. # endif
  26. # if !defined _UNICODE /* for C library */
  27. # define _UNICODE
  28. # endif
  29. #endif
  30. #define MININI_IMPLEMENTATION
  31. #include "minIni.h"
  32. #if defined NDEBUG
  33. #define assert(e)
  34. #else
  35. #include <assert.h>
  36. #endif
  37. #if !defined __T || defined INI_ANSIONLY
  38. #include <ctype.h>
  39. #include <string.h>
  40. #include <stdlib.h>
  41. #define TCHAR char
  42. #define __T(s) s
  43. #define _tcscat strcat
  44. #define _tcschr strchr
  45. #define _tcscmp strcmp
  46. #define _tcscpy strcpy
  47. #define _tcsicmp stricmp
  48. #define _tcslen strlen
  49. #define _tcsncmp strncmp
  50. #define _tcsnicmp strnicmp
  51. #define _tcsrchr strrchr
  52. #define _tcstol strtol
  53. #define _tcstod strtod
  54. #define _totupper toupper
  55. #define _stprintf sprintf
  56. #define _tfgets fgets
  57. #define _tfputs fputs
  58. #define _tfopen fopen
  59. #define _tremove remove
  60. #define _trename rename
  61. #endif
  62. #if defined __linux || defined __linux__
  63. #define __LINUX__
  64. #elif defined FREEBSD && !defined __FreeBSD__
  65. #define __FreeBSD__
  66. #elif defined(_MSC_VER)
  67. #pragma warning(disable: 4996) /* for Microsoft Visual C/C++ */
  68. #endif
  69. #if !defined strnicmp && !defined PORTABLE_STRNICMP
  70. #if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ || defined __NetBSD__ || defined __DragonFly__ || defined __GNUC__
  71. #define strnicmp strncasecmp
  72. #endif
  73. #endif
  74. #if !defined _totupper
  75. #define _totupper toupper
  76. #endif
  77. #if !defined INI_LINETERM
  78. #if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ || defined __NetBSD__ || defined __DragonFly__
  79. #define INI_LINETERM __T("\n")
  80. #else
  81. #define INI_LINETERM __T("\r\n")
  82. #endif
  83. #endif
  84. #if !defined INI_FILETYPE
  85. #error Missing definition for INI_FILETYPE.
  86. #endif
  87. #if !defined sizearray
  88. #define sizearray(a) (sizeof(a) / sizeof((a)[0]))
  89. #endif
  90. enum quote_option {
  91. QUOTE_NONE,
  92. QUOTE_ENQUOTE,
  93. QUOTE_DEQUOTE,
  94. };
  95. #if defined PORTABLE_STRNICMP
  96. int strnicmp(const TCHAR *s1, const TCHAR *s2, size_t n)
  97. {
  98. while (n-- != 0 && (*s1 || *s2)) {
  99. register int c1, c2;
  100. c1 = *s1++;
  101. if ('a' <= c1 && c1 <= 'z')
  102. c1 += ('A' - 'a');
  103. c2 = *s2++;
  104. if ('a' <= c2 && c2 <= 'z')
  105. c2 += ('A' - 'a');
  106. if (c1 != c2)
  107. return c1 - c2;
  108. }
  109. return 0;
  110. }
  111. #endif /* PORTABLE_STRNICMP */
  112. static TCHAR *skipleading(const TCHAR *str)
  113. {
  114. assert(str != NULL);
  115. while ('\0' < *str && *str <= ' ')
  116. str++;
  117. return (TCHAR *)str;
  118. }
  119. static TCHAR *skiptrailing(const TCHAR *str, const TCHAR *base)
  120. {
  121. assert(str != NULL);
  122. assert(base != NULL);
  123. while (str > base && '\0' < *(str-1) && *(str-1) <= ' ')
  124. str--;
  125. return (TCHAR *)str;
  126. }
  127. static TCHAR *striptrailing(TCHAR *str)
  128. {
  129. TCHAR *ptr = skiptrailing(_tcschr(str, '\0'), str);
  130. assert(ptr != NULL);
  131. *ptr = '\0';
  132. return str;
  133. }
  134. static TCHAR *ini_strncpy(TCHAR *dest, const TCHAR *source, size_t maxlen, enum quote_option option)
  135. {
  136. size_t d, s;
  137. assert(maxlen>0);
  138. assert(source != NULL && dest != NULL);
  139. assert((dest < source || (dest == source && option != QUOTE_ENQUOTE)) || dest > source + strlen(source));
  140. if (option == QUOTE_ENQUOTE && maxlen < 3)
  141. option = QUOTE_NONE; /* cannot store two quotes and a terminating zero in less than 3 characters */
  142. switch (option) {
  143. case QUOTE_NONE:
  144. for (d = 0; d < maxlen - 1 && source[d] != '\0'; d++)
  145. dest[d] = source[d];
  146. assert(d < maxlen);
  147. dest[d] = '\0';
  148. break;
  149. case QUOTE_ENQUOTE:
  150. d = 0;
  151. dest[d++] = '"';
  152. for (s = 0; source[s] != '\0' && d < maxlen - 2; s++, d++) {
  153. if (source[s] == '"') {
  154. if (d >= maxlen - 3)
  155. break; /* no space to store the escape character plus the one that follows it */
  156. dest[d++] = '\\';
  157. }
  158. dest[d] = source[s];
  159. }
  160. dest[d++] = '"';
  161. dest[d] = '\0';
  162. break;
  163. case QUOTE_DEQUOTE:
  164. for (d = s = 0; source[s] != '\0' && d < maxlen - 1; s++, d++) {
  165. if ((source[s] == '"' || source[s] == '\\') && source[s + 1] == '"')
  166. s++;
  167. dest[d] = source[s];
  168. }
  169. dest[d] = '\0';
  170. break;
  171. default:
  172. assert(0);
  173. }
  174. return dest;
  175. }
  176. static TCHAR *cleanstring(TCHAR *string, enum quote_option *quotes)
  177. {
  178. int isstring;
  179. TCHAR *ep;
  180. assert(string != NULL);
  181. assert(quotes != NULL);
  182. /* Remove a trailing comment */
  183. isstring = 0;
  184. for (ep = string; *ep != '\0' && ((*ep != ';' && *ep != '#') || isstring); ep++) {
  185. if (*ep == '"') {
  186. if (*(ep + 1) == '"')
  187. ep++; /* skip "" (both quotes) */
  188. else
  189. isstring = !isstring; /* single quote, toggle isstring */
  190. } else if (*ep == '\\' && *(ep + 1) == '"') {
  191. ep++; /* skip \" (both quotes */
  192. }
  193. }
  194. assert(ep != NULL && (*ep == '\0' || *ep == ';' || *ep == '#'));
  195. *ep = '\0'; /* terminate at a comment */
  196. striptrailing(string);
  197. /* Remove double quotes surrounding a value */
  198. *quotes = QUOTE_NONE;
  199. if (*string == '"' && (ep = _tcschr(string, '\0')) != NULL && *(ep - 1) == '"') {
  200. string++;
  201. *--ep = '\0';
  202. *quotes = QUOTE_DEQUOTE; /* this is a string, so remove escaped characters */
  203. }
  204. return string;
  205. }
  206. static int getkeystring(INI_FILETYPE *fp, const TCHAR *Section, const TCHAR *Key,
  207. int idxSection, int idxKey, TCHAR *Buffer, int BufferSize,
  208. INI_FILEPOS *mark)
  209. {
  210. TCHAR *sp, *ep;
  211. int len, idx;
  212. enum quote_option quotes;
  213. TCHAR LocalBuffer[INI_BUFFERSIZE];
  214. assert(fp != NULL);
  215. /* Move through file 1 line at a time until a section is matched or EOF. If
  216. * parameter Section is NULL, only look at keys above the first section. If
  217. * idxSection is positive, copy the relevant section name.
  218. */
  219. len = (Section != NULL) ? (int)_tcslen(Section) : 0;
  220. if (len > 0 || idxSection >= 0) {
  221. assert(idxSection >= 0 || Section != NULL);
  222. idx = -1;
  223. do {
  224. do {
  225. if (!ini_read(LocalBuffer, INI_BUFFERSIZE, fp))
  226. return 0;
  227. sp = skipleading(LocalBuffer);
  228. ep = _tcsrchr(sp, ']');
  229. } while (*sp != '[' || ep == NULL);
  230. /* When arrived here, a section was found; now optionally skip leading and
  231. * trailing whitespace.
  232. */
  233. assert(sp != NULL && *sp == '[');
  234. sp = skipleading(sp + 1);
  235. assert(ep != NULL && *ep == ']');
  236. ep = skiptrailing(ep, sp);
  237. } while ((((int)(ep-sp) != len || Section == NULL || _tcsnicmp(sp, Section, len) != 0) && ++idx != idxSection));
  238. if (idxSection >= 0) {
  239. if (idx == idxSection) {
  240. assert(ep != NULL);
  241. *ep = '\0'; /* the end of the section name was found earlier */
  242. ini_strncpy(Buffer, sp, BufferSize, QUOTE_NONE);
  243. return 1;
  244. }
  245. return 0; /* no more section found */
  246. }
  247. }
  248. /* Now that the section has been found, find the entry.
  249. * Stop searching upon leaving the section's area.
  250. */
  251. assert(Key != NULL || idxKey >= 0);
  252. len = (Key != NULL) ? (int)_tcslen(Key) : 0;
  253. idx = -1;
  254. do {
  255. if (mark != NULL)
  256. ini_tell(fp, mark); /* optionally keep the mark to the start of the line */
  257. if (!ini_read(LocalBuffer,INI_BUFFERSIZE,fp) || *(sp = skipleading(LocalBuffer)) == '[')
  258. return 0;
  259. sp = skipleading(LocalBuffer);
  260. ep = _tcschr(sp, '='); /* Parse out the equal sign */
  261. if (ep == NULL)
  262. ep = _tcschr(sp, ':');
  263. } while (*sp == ';' || *sp == '#' || ep == NULL
  264. || ((len == 0 || (int)(skiptrailing(ep,sp)-sp) != len || _tcsnicmp(sp,Key,len) != 0) && ++idx != idxKey));
  265. if (idxKey >= 0) {
  266. if (idx == idxKey) {
  267. assert(ep != NULL);
  268. assert(*ep == '=' || *ep == ':');
  269. *ep = '\0';
  270. striptrailing(sp);
  271. ini_strncpy(Buffer, sp, BufferSize, QUOTE_NONE);
  272. return 1;
  273. }
  274. return 0; /* no more key found (in this section) */
  275. }
  276. /* Copy up to BufferSize chars to buffer */
  277. assert(ep != NULL);
  278. assert(*ep == '=' || *ep == ':');
  279. sp = skipleading(ep + 1);
  280. sp = cleanstring(sp, &quotes); /* Remove a trailing comment */
  281. ini_strncpy(Buffer, sp, BufferSize, quotes);
  282. return 1;
  283. }
  284. /** ini_gets()
  285. * \param Section the name of the section to search for
  286. * \param Key the name of the entry to find the value of
  287. * \param DefValue default string in the event of a failed read
  288. * \param Buffer a pointer to the buffer to copy into
  289. * \param BufferSize the maximum number of characters to copy
  290. * \param Filename the name and full path of the .ini file to read from
  291. *
  292. * \return the number of characters copied into the supplied buffer
  293. */
  294. int ini_gets(const TCHAR *Section, const TCHAR *Key, const TCHAR *DefValue,
  295. TCHAR *Buffer, int BufferSize, const TCHAR *Filename)
  296. {
  297. INI_FILETYPE fp;
  298. int ok = 0;
  299. if (Buffer == NULL || BufferSize <= 0 || Key == NULL)
  300. return 0;
  301. if (ini_openread(Filename, &fp)) {
  302. ok = getkeystring(&fp, Section, Key, -1, -1, Buffer, BufferSize, NULL);
  303. (void)ini_close(&fp);
  304. }
  305. if (!ok)
  306. ini_strncpy(Buffer, (DefValue != NULL) ? DefValue : __T(""), BufferSize, QUOTE_NONE);
  307. return (int)_tcslen(Buffer);
  308. }
  309. /** ini_getl()
  310. * \param Section the name of the section to search for
  311. * \param Key the name of the entry to find the value of
  312. * \param DefValue the default value in the event of a failed read
  313. * \param Filename the name of the .ini file to read from
  314. *
  315. * \return the value located at Key
  316. */
  317. long ini_getl(const TCHAR *Section, const TCHAR *Key, long DefValue, const TCHAR *Filename)
  318. {
  319. TCHAR LocalBuffer[64];
  320. int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename);
  321. return (len == 0) ? DefValue
  322. : ((len >= 2 && _totupper((int)LocalBuffer[1]) == 'X') ? _tcstol(LocalBuffer, NULL, 16)
  323. : _tcstol(LocalBuffer, NULL, 10));
  324. }
  325. #if defined INI_REAL
  326. /** ini_getf()
  327. * \param Section the name of the section to search for
  328. * \param Key the name of the entry to find the value of
  329. * \param DefValue the default value in the event of a failed read
  330. * \param Filename the name of the .ini file to read from
  331. *
  332. * \return the value located at Key
  333. */
  334. INI_REAL ini_getf(const TCHAR *Section, const TCHAR *Key, INI_REAL DefValue, const TCHAR *Filename)
  335. {
  336. TCHAR LocalBuffer[64];
  337. int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename);
  338. return (len == 0) ? DefValue : ini_atof(LocalBuffer);
  339. }
  340. #endif
  341. /** ini_getbool()
  342. * \param Section the name of the section to search for
  343. * \param Key the name of the entry to find the value of
  344. * \param DefValue default value in the event of a failed read; it should
  345. * zero (0) or one (1).
  346. * \param Filename the name and full path of the .ini file to read from
  347. *
  348. * A true boolean is found if one of the following is matched:
  349. * - A string starting with 'y' or 'Y'
  350. * - A string starting with 't' or 'T'
  351. * - A string starting with '1'
  352. *
  353. * A false boolean is found if one of the following is matched:
  354. * - A string starting with 'n' or 'N'
  355. * - A string starting with 'f' or 'F'
  356. * - A string starting with '0'
  357. *
  358. * \return the true/false flag as interpreted at Key
  359. */
  360. int ini_getbool(const TCHAR *Section, const TCHAR *Key, int DefValue, const TCHAR *Filename)
  361. {
  362. TCHAR LocalBuffer[2] = __T("");
  363. int ret;
  364. ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename);
  365. LocalBuffer[0] = (TCHAR)_totupper((int)LocalBuffer[0]);
  366. if (LocalBuffer[0] == 'Y' || LocalBuffer[0] == '1' || LocalBuffer[0] == 'T')
  367. ret = 1;
  368. else if (LocalBuffer[0] == 'N' || LocalBuffer[0] == '0' || LocalBuffer[0] == 'F')
  369. ret = 0;
  370. else
  371. ret = DefValue;
  372. return(ret);
  373. }
  374. /** ini_getsection()
  375. * \param idx the zero-based sequence number of the section to return
  376. * \param Buffer a pointer to the buffer to copy into
  377. * \param BufferSize the maximum number of characters to copy
  378. * \param Filename the name and full path of the .ini file to read from
  379. *
  380. * \return the number of characters copied into the supplied buffer
  381. */
  382. int ini_getsection(int idx, TCHAR *Buffer, int BufferSize, const TCHAR *Filename)
  383. {
  384. INI_FILETYPE fp;
  385. int ok = 0;
  386. if (Buffer == NULL || BufferSize <= 0 || idx < 0)
  387. return 0;
  388. if (ini_openread(Filename, &fp)) {
  389. ok = getkeystring(&fp, NULL, NULL, idx, -1, Buffer, BufferSize, NULL);
  390. (void)ini_close(&fp);
  391. }
  392. if (!ok)
  393. *Buffer = '\0';
  394. return (int)_tcslen(Buffer);
  395. }
  396. /** ini_getkey()
  397. * \param Section the name of the section to browse through, or NULL to
  398. * browse through the keys outside any section
  399. * \param idx the zero-based sequence number of the key to return
  400. * \param Buffer a pointer to the buffer to copy into
  401. * \param BufferSize the maximum number of characters to copy
  402. * \param Filename the name and full path of the .ini file to read from
  403. *
  404. * \return the number of characters copied into the supplied buffer
  405. */
  406. int ini_getkey(const TCHAR *Section, int idx, TCHAR *Buffer, int BufferSize, const TCHAR *Filename)
  407. {
  408. INI_FILETYPE fp;
  409. int ok = 0;
  410. if (Buffer == NULL || BufferSize <= 0 || idx < 0)
  411. return 0;
  412. if (ini_openread(Filename, &fp)) {
  413. ok = getkeystring(&fp, Section, NULL, -1, idx, Buffer, BufferSize, NULL);
  414. (void)ini_close(&fp);
  415. }
  416. if (!ok)
  417. *Buffer = '\0';
  418. return (int)_tcslen(Buffer);
  419. }
  420. /** ini_hassection()
  421. * \param Section the name of the section to search for
  422. * \param Filename the name of the .ini file to read from
  423. *
  424. * \return 1 if the section is found, 0 if not found
  425. */
  426. int ini_hassection(const mTCHAR *Section, const mTCHAR *Filename)
  427. {
  428. TCHAR LocalBuffer[32]; /* dummy buffer */
  429. INI_FILETYPE fp;
  430. int ok = 0;
  431. if (ini_openread(Filename, &fp)) {
  432. ok = getkeystring(&fp, Section, NULL, -1, 0, LocalBuffer, sizearray(LocalBuffer), NULL);
  433. (void)ini_close(&fp);
  434. }
  435. return ok;
  436. }
  437. /** ini_haskey()
  438. * \param Section the name of the section to search for
  439. * \param Key the name of the entry to find the value of
  440. * \param Filename the name of the .ini file to read from
  441. *
  442. * \return 1 if the key is found, 0 if not found
  443. */
  444. int ini_haskey(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Filename)
  445. {
  446. TCHAR LocalBuffer[32]; /* dummy buffer */
  447. INI_FILETYPE fp;
  448. int ok = 0;
  449. if (ini_openread(Filename, &fp)) {
  450. ok = getkeystring(&fp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer), NULL);
  451. (void)ini_close(&fp);
  452. }
  453. return ok;
  454. }
  455. #if !defined INI_NOBROWSE
  456. /** ini_browse()
  457. * \param Callback a pointer to a function that will be called for every
  458. * setting in the INI file.
  459. * \param UserData arbitrary data, which the function passes on the
  460. * \c Callback function
  461. * \param Filename the name and full path of the .ini file to read from
  462. *
  463. * \return 1 on success, 0 on failure (INI file not found)
  464. *
  465. * \note The \c Callback function must return 1 to continue
  466. * browsing through the INI file, or 0 to stop. Even when the
  467. * callback stops the browsing, this function will return 1
  468. * (for success).
  469. */
  470. int ini_browse(INI_CALLBACK Callback, void *UserData, const TCHAR *Filename)
  471. {
  472. TCHAR LocalBuffer[INI_BUFFERSIZE];
  473. int lenSec, lenKey;
  474. enum quote_option quotes;
  475. INI_FILETYPE fp;
  476. if (Callback == NULL)
  477. return 0;
  478. if (!ini_openread(Filename, &fp))
  479. return 0;
  480. LocalBuffer[0] = '\0'; /* copy an empty section in the buffer */
  481. lenSec = (int)_tcslen(LocalBuffer) + 1;
  482. for ( ;; ) {
  483. TCHAR *sp, *ep;
  484. if (!ini_read(LocalBuffer + lenSec, INI_BUFFERSIZE - lenSec, &fp))
  485. break;
  486. sp = skipleading(LocalBuffer + lenSec);
  487. /* ignore empty strings and comments */
  488. if (*sp == '\0' || *sp == ';' || *sp == '#')
  489. continue;
  490. /* see whether we reached a new section */
  491. ep = _tcsrchr(sp, ']');
  492. if (*sp == '[' && ep != NULL) {
  493. sp = skipleading(sp + 1);
  494. ep = skiptrailing(ep, sp);
  495. *ep = '\0';
  496. ini_strncpy(LocalBuffer, sp, INI_BUFFERSIZE, QUOTE_NONE);
  497. lenSec = (int)_tcslen(LocalBuffer) + 1;
  498. continue;
  499. }
  500. /* not a new section, test for a key/value pair */
  501. ep = _tcschr(sp, '='); /* test for the equal sign or colon */
  502. if (ep == NULL)
  503. ep = _tcschr(sp, ':');
  504. if (ep == NULL)
  505. continue; /* invalid line, ignore */
  506. *ep++ = '\0'; /* split the key from the value */
  507. striptrailing(sp);
  508. ini_strncpy(LocalBuffer + lenSec, sp, INI_BUFFERSIZE - lenSec, QUOTE_NONE);
  509. lenKey = (int)_tcslen(LocalBuffer + lenSec) + 1;
  510. /* clean up the value */
  511. sp = skipleading(ep);
  512. sp = cleanstring(sp, &quotes); /* Remove a trailing comment */
  513. ini_strncpy(LocalBuffer + lenSec + lenKey, sp, INI_BUFFERSIZE - lenSec - lenKey, quotes);
  514. /* call the callback */
  515. if (!Callback(LocalBuffer, LocalBuffer + lenSec, LocalBuffer + lenSec + lenKey, UserData))
  516. break;
  517. }
  518. (void)ini_close(&fp);
  519. return 1;
  520. }
  521. #endif /* INI_NOBROWSE */
  522. #if ! defined INI_READONLY
  523. static void ini_tempname(TCHAR *dest, const TCHAR *source, int maxlength)
  524. {
  525. TCHAR *p;
  526. ini_strncpy(dest, source, maxlength, QUOTE_NONE);
  527. p = _tcschr(dest, '\0');
  528. assert(p != NULL);
  529. *(p - 1) = '~';
  530. }
  531. static enum quote_option check_enquote(const TCHAR *Value)
  532. {
  533. const TCHAR *p;
  534. /* run through the value, if it has trailing spaces, or '"', ';' or '#'
  535. * characters, enquote it
  536. */
  537. assert(Value != NULL);
  538. for (p = Value; *p != '\0' && *p != '"' && *p != ';' && *p != '#'; p++)
  539. /* nothing */;
  540. return (*p != '\0' || (p > Value && *(p - 1) == ' ')) ? QUOTE_ENQUOTE : QUOTE_NONE;
  541. }
  542. static void writesection(TCHAR *LocalBuffer, const TCHAR *Section, INI_FILETYPE *fp)
  543. {
  544. if (Section != NULL && _tcslen(Section) > 0) {
  545. TCHAR *p;
  546. LocalBuffer[0] = '[';
  547. ini_strncpy(LocalBuffer + 1, Section, INI_BUFFERSIZE - 4, QUOTE_NONE); /* -1 for '[', -1 for ']', -2 for '\r\n' */
  548. p = _tcschr(LocalBuffer, '\0');
  549. assert(p != NULL);
  550. *p++ = ']';
  551. _tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */
  552. if (fp != NULL)
  553. (void)ini_write(LocalBuffer, fp);
  554. }
  555. }
  556. static void writekey(TCHAR *LocalBuffer, const TCHAR *Key, const TCHAR *Value, INI_FILETYPE *fp)
  557. {
  558. TCHAR *p;
  559. enum quote_option option = check_enquote(Value);
  560. ini_strncpy(LocalBuffer, Key, INI_BUFFERSIZE - 3, QUOTE_NONE); /* -1 for '=', -2 for '\r\n' */
  561. p = _tcschr(LocalBuffer, '\0');
  562. assert(p != NULL);
  563. *p++ = '=';
  564. ini_strncpy(p, Value, INI_BUFFERSIZE - (p - LocalBuffer) - 2, option); /* -2 for '\r\n' */
  565. p = _tcschr(LocalBuffer, '\0');
  566. assert(p != NULL);
  567. _tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */
  568. if (fp != NULL)
  569. (void)ini_write(LocalBuffer, fp);
  570. }
  571. static int cache_accum(const TCHAR *string, int *size, int max)
  572. {
  573. int len = (int)_tcslen(string);
  574. if (*size + len >= max)
  575. return 0;
  576. *size += len;
  577. return 1;
  578. }
  579. static int cache_flush(TCHAR *buffer, int *size,
  580. INI_FILETYPE *rfp, INI_FILETYPE *wfp, INI_FILEPOS *mark)
  581. {
  582. int terminator_len = (int)_tcslen(INI_LINETERM);
  583. int pos = 0, pos_prev = -1;
  584. (void)ini_seek(rfp, mark);
  585. assert(buffer != NULL);
  586. buffer[0] = '\0';
  587. assert(size != NULL);
  588. assert(*size <= INI_BUFFERSIZE);
  589. while (pos < *size && pos != pos_prev) {
  590. pos_prev = pos; /* to guard against zero bytes in the INI file */
  591. (void)ini_read(buffer + pos, INI_BUFFERSIZE - pos, rfp);
  592. while (pos < *size && buffer[pos] != '\0')
  593. pos++; /* cannot use _tcslen() because buffer may not be zero-terminated */
  594. }
  595. if (buffer[0] != '\0') {
  596. assert(pos > 0 && pos <= INI_BUFFERSIZE);
  597. if (pos == INI_BUFFERSIZE)
  598. pos--;
  599. buffer[pos] = '\0'; /* force zero-termination (may be left unterminated in the above while loop) */
  600. (void)ini_write(buffer, wfp);
  601. }
  602. ini_tell(rfp, mark); /* update mark */
  603. *size = 0;
  604. /* return whether the buffer ended with a line termination */
  605. return (pos > terminator_len) && (_tcscmp(buffer + pos - terminator_len, INI_LINETERM) == 0);
  606. }
  607. static int close_rename(INI_FILETYPE *rfp, INI_FILETYPE *wfp, const TCHAR *filename, TCHAR *buffer)
  608. {
  609. (void)ini_close(rfp);
  610. (void)ini_close(wfp);
  611. (void)ini_tempname(buffer, filename, INI_BUFFERSIZE);
  612. #if defined ini_remove || defined INI_REMOVE
  613. (void)ini_remove(filename);
  614. #endif
  615. (void)ini_rename(buffer, filename);
  616. return 1;
  617. }
  618. /** ini_puts()
  619. * \param Section the name of the section to write the string in
  620. * \param Key the name of the entry to write, or NULL to erase all keys in the section
  621. * \param Value a pointer to the buffer the string, or NULL to erase the key
  622. * \param Filename the name and full path of the .ini file to write to
  623. *
  624. * \return 1 if successful, otherwise 0
  625. */
  626. int ini_puts(const TCHAR *Section, const TCHAR *Key, const TCHAR *Value, const TCHAR *Filename)
  627. {
  628. INI_FILETYPE rfp;
  629. INI_FILETYPE wfp;
  630. INI_FILEPOS mark;
  631. INI_FILEPOS head, tail;
  632. TCHAR *sp, *ep;
  633. TCHAR LocalBuffer[INI_BUFFERSIZE];
  634. int len, match, flag, cachelen;
  635. assert(Filename != NULL);
  636. if (!ini_openread(Filename, &rfp)) {
  637. /* If the .ini file doesn't exist, make a new file */
  638. if (Key != NULL && Value != NULL) {
  639. if (!ini_openwrite(Filename, &wfp))
  640. return 0;
  641. writesection(LocalBuffer, Section, &wfp);
  642. writekey(LocalBuffer, Key, Value, &wfp);
  643. (void)ini_close(&wfp);
  644. }
  645. return 1;
  646. }
  647. /* If parameters Key and Value are valid (so this is not an "erase" request)
  648. * and the setting already exists, there are two short-cuts to avoid rewriting
  649. * the INI file.
  650. */
  651. if (Key != NULL && Value != NULL) {
  652. match = getkeystring(&rfp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer), &head);
  653. if (match) {
  654. /* if the current setting is identical to the one to write, there is
  655. * nothing to do.
  656. */
  657. if (_tcscmp(LocalBuffer,Value) == 0) {
  658. (void)ini_close(&rfp);
  659. return 1;
  660. }
  661. /* if the new setting has the same length as the current setting, and the
  662. * glue file permits file read/write access, we can modify in place.
  663. */
  664. #if defined ini_openrewrite || defined INI_OPENREWRITE
  665. /* we already have the start of the (raw) line, get the end too */
  666. ini_tell(&rfp, &tail);
  667. /* create new buffer (without writing it to file) */
  668. writekey(LocalBuffer, Key, Value, NULL);
  669. if (_tcslen(LocalBuffer) == (size_t)(tail - head)) {
  670. /* length matches, close the file & re-open for read/write, then
  671. * write at the correct position
  672. */
  673. (void)ini_close(&rfp);
  674. if (!ini_openrewrite(Filename, &wfp))
  675. return 0;
  676. (void)ini_seek(&wfp, &head);
  677. (void)ini_write(LocalBuffer, &wfp);
  678. (void)ini_close(&wfp);
  679. return 1;
  680. }
  681. #endif
  682. }
  683. /* key not found, or different value & length -> proceed */
  684. } else if (Key != NULL && Value == NULL) {
  685. /* Conversely, for a request to delete a setting; if that setting isn't
  686. present, just return */
  687. match = getkeystring(&rfp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer), NULL);
  688. if (!match) {
  689. (void)ini_close(&rfp);
  690. return 1;
  691. }
  692. /* key found -> proceed to delete it */
  693. }
  694. /* Get a temporary file name to copy to. Use the existing name, but with
  695. * the last character set to a '~'.
  696. */
  697. (void)ini_close(&rfp);
  698. ini_tempname(LocalBuffer, Filename, INI_BUFFERSIZE);
  699. if (!ini_openwrite(LocalBuffer, &wfp))
  700. return 0;
  701. /* In the case of (advisory) file locks, ini_openwrite() may have been blocked
  702. * on the open, and after the block is lifted, the original file may have been
  703. * renamed, which is why the original file was closed and is now reopened */
  704. if (!ini_openread(Filename, &rfp)) {
  705. /* If the .ini file doesn't exist any more, make a new file */
  706. assert(Key != NULL && Value != NULL);
  707. writesection(LocalBuffer, Section, &wfp);
  708. writekey(LocalBuffer, Key, Value, &wfp);
  709. (void)ini_close(&wfp);
  710. return 1;
  711. }
  712. (void)ini_tell(&rfp, &mark);
  713. cachelen = 0;
  714. /* Move through the file one line at a time until a section is
  715. * matched or until EOF. Copy to temp file as it is read.
  716. */
  717. len = (Section != NULL) ? (int)_tcslen(Section) : 0;
  718. if (len > 0) {
  719. do {
  720. if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) {
  721. /* Failed to find section, so add one to the end */
  722. flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
  723. if (Key!=NULL && Value!=NULL) {
  724. if (!flag)
  725. (void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */
  726. writesection(LocalBuffer, Section, &wfp);
  727. writekey(LocalBuffer, Key, Value, &wfp);
  728. }
  729. return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */
  730. }
  731. /* Check whether this line is a section */
  732. sp = skipleading(LocalBuffer);
  733. ep = _tcsrchr(sp, ']');
  734. match = (*sp == '[' && ep != NULL);
  735. if (match) {
  736. /* A section was found, skip leading and trailing whitespace */
  737. assert(sp != NULL && *sp == '[');
  738. sp = skipleading(sp + 1);
  739. assert(ep != NULL && *ep == ']');
  740. ep = skiptrailing(ep, sp);
  741. match = ((int)(ep-sp) == len && _tcsnicmp(sp, Section, len) == 0);
  742. }
  743. /* Copy the line from source to dest, but not if this is the section that
  744. * we are looking for and this section must be removed
  745. */
  746. if (!match || Key != NULL) {
  747. if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) {
  748. cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
  749. (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
  750. cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE);
  751. }
  752. }
  753. } while (!match);
  754. }
  755. cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
  756. /* when deleting a section, the section head that was just found has not been
  757. * copied to the output file, but because this line was not "accumulated" in
  758. * the cache, the position in the input file was reset to the point just
  759. * before the section; this must now be skipped (again)
  760. */
  761. if (Key == NULL) {
  762. (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
  763. (void)ini_tell(&rfp, &mark);
  764. }
  765. /* Now that the section has been found, find the entry. Stop searching
  766. * upon leaving the section's area. Copy the file as it is read
  767. * and create an entry if one is not found.
  768. */
  769. len = (Key != NULL) ? (int)_tcslen(Key) : 0;
  770. for( ;; ) {
  771. if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) {
  772. /* EOF without an entry so make one */
  773. flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
  774. if (Key!=NULL && Value!=NULL) {
  775. if (!flag)
  776. (void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */
  777. writekey(LocalBuffer, Key, Value, &wfp);
  778. }
  779. return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */
  780. }
  781. sp = skipleading(LocalBuffer);
  782. ep = _tcschr(sp, '='); /* Parse out the equal sign */
  783. if (ep == NULL)
  784. ep = _tcschr(sp, ':');
  785. match = (ep != NULL && len > 0 && (int)(skiptrailing(ep,sp)-sp) == len && _tcsnicmp(sp,Key,len) == 0);
  786. if ((Key != NULL && match) || *sp == '[')
  787. break; /* found the key, or found a new section */
  788. /* copy other keys in the section */
  789. if (Key == NULL) {
  790. (void)ini_tell(&rfp, &mark); /* we are deleting the entire section, so update the read position */
  791. } else {
  792. if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) {
  793. cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
  794. (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
  795. cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE);
  796. }
  797. }
  798. }
  799. /* the key was found, or we just dropped on the next section (meaning that it
  800. * wasn't found); in both cases we need to write the key, but in the latter
  801. * case, we also need to write the line starting the new section after writing
  802. * the key
  803. */
  804. flag = (*sp == '[');
  805. cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
  806. if (Key != NULL && Value != NULL)
  807. writekey(LocalBuffer, Key, Value, &wfp);
  808. /* cache_flush() reset the "read pointer" to the start of the line with the
  809. * previous key or the new section; read it again (because writekey() destroyed
  810. * the buffer)
  811. */
  812. (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
  813. if (flag) {
  814. /* the new section heading needs to be copied to the output file */
  815. cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE);
  816. } else {
  817. /* forget the old key line */
  818. (void)ini_tell(&rfp, &mark);
  819. }
  820. /* Copy the rest of the INI file */
  821. while (ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) {
  822. if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) {
  823. cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
  824. (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
  825. cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE);
  826. }
  827. }
  828. cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
  829. return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */
  830. }
  831. /* Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C" book. */
  832. #define ABS(v) ((v) < 0 ? -(v) : (v))
  833. static void strreverse(TCHAR *str)
  834. {
  835. int i, j;
  836. for (i = 0, j = (int)_tcslen(str) - 1; i < j; i++, j--) {
  837. TCHAR t = str[i];
  838. str[i] = str[j];
  839. str[j] = t;
  840. }
  841. }
  842. static void long2str(long value, TCHAR *str)
  843. {
  844. int i = 0;
  845. long sign = value;
  846. /* generate digits in reverse order */
  847. do {
  848. int n = (int)(value % 10); /* get next lowest digit */
  849. str[i++] = (TCHAR)(ABS(n) + '0'); /* handle case of negative digit */
  850. } while (value /= 10); /* delete the lowest digit */
  851. if (sign < 0)
  852. str[i++] = '-';
  853. str[i] = '\0';
  854. strreverse(str);
  855. }
  856. /** ini_putl()
  857. * \param Section the name of the section to write the value in
  858. * \param Key the name of the entry to write
  859. * \param Value the value to write
  860. * \param Filename the name and full path of the .ini file to write to
  861. *
  862. * \return 1 if successful, otherwise 0
  863. */
  864. int ini_putl(const TCHAR *Section, const TCHAR *Key, long Value, const TCHAR *Filename)
  865. {
  866. TCHAR LocalBuffer[32];
  867. long2str(Value, LocalBuffer);
  868. return ini_puts(Section, Key, LocalBuffer, Filename);
  869. }
  870. #if defined INI_REAL
  871. /** ini_putf()
  872. * \param Section the name of the section to write the value in
  873. * \param Key the name of the entry to write
  874. * \param Value the value to write
  875. * \param Filename the name and full path of the .ini file to write to
  876. *
  877. * \return 1 if successful, otherwise 0
  878. */
  879. int ini_putf(const TCHAR *Section, const TCHAR *Key, INI_REAL Value, const TCHAR *Filename)
  880. {
  881. TCHAR LocalBuffer[64];
  882. ini_ftoa(LocalBuffer, Value);
  883. return ini_puts(Section, Key, LocalBuffer, Filename);
  884. }
  885. #endif /* INI_REAL */
  886. #endif /* !INI_READONLY */