ini.c 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. /* inih -- simple .INI file parser
  2. SPDX-License-Identifier: BSD-3-Clause
  3. Copyright (C) 2009-2025, Ben Hoyt
  4. inih is released under the New BSD license (see LICENSE.txt). Go to the project
  5. home page for more info:
  6. https://github.com/benhoyt/inih
  7. */
  8. #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
  9. #define _CRT_SECURE_NO_WARNINGS
  10. #endif
  11. #include <stdio.h>
  12. #include <ctype.h>
  13. #include <string.h>
  14. #include <assert.h>
  15. #include "ini.h"
  16. #if !INI_USE_STACK
  17. #if INI_CUSTOM_ALLOCATOR
  18. #include <stddef.h>
  19. void* ini_malloc(size_t size);
  20. void ini_free(void* ptr);
  21. void* ini_realloc(void* ptr, size_t size);
  22. #else
  23. #include <stdlib.h>
  24. #define ini_malloc malloc
  25. #define ini_free free
  26. #define ini_realloc realloc
  27. #endif
  28. #endif
  29. #define MAX_SECTION 50
  30. #define MAX_NAME 50
  31. /* Used by ini_parse_string() to keep track of string parsing state. */
  32. typedef struct {
  33. const char* ptr;
  34. size_t num_left;
  35. } ini_parse_string_ctx;
  36. /* Strip whitespace chars off end of given string, in place. end must be a
  37. pointer to the NUL terminator at the end of the string. Return s. */
  38. static char* ini_rstrip(char* s, char* end)
  39. {
  40. while (end > s && isspace((unsigned char)(*--end)))
  41. *end = '\0';
  42. return s;
  43. }
  44. /* Return pointer to first non-whitespace char in given string. */
  45. static char* ini_lskip(const char* s)
  46. {
  47. while (*s && isspace((unsigned char)(*s)))
  48. s++;
  49. return (char*)s;
  50. }
  51. /* Return pointer to first char (of chars) or inline comment in given string,
  52. or pointer to NUL at end of string if neither found. Inline comment must
  53. be prefixed by a whitespace character to register as a comment. */
  54. static char* ini_find_chars_or_comment(const char* s, const char* chars)
  55. {
  56. #if INI_ALLOW_INLINE_COMMENTS
  57. int was_space = 0;
  58. while (*s && (!chars || !strchr(chars, *s)) &&
  59. !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
  60. was_space = isspace((unsigned char)(*s));
  61. s++;
  62. }
  63. #else
  64. while (*s && (!chars || !strchr(chars, *s))) {
  65. s++;
  66. }
  67. #endif
  68. return (char*)s;
  69. }
  70. /* Similar to strncpy, but ensures dest (size bytes) is
  71. NUL-terminated, and doesn't pad with NULs. */
  72. static char* ini_strncpy0(char* dest, const char* src, size_t size)
  73. {
  74. /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */
  75. size_t i;
  76. for (i = 0; i < size - 1 && src[i]; i++)
  77. dest[i] = src[i];
  78. dest[i] = '\0';
  79. return dest;
  80. }
  81. /* See documentation in header file. */
  82. int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
  83. void* user)
  84. {
  85. /* Uses a fair bit of stack (use heap instead if you need to) */
  86. #if INI_USE_STACK
  87. char line[INI_MAX_LINE];
  88. size_t max_line = INI_MAX_LINE;
  89. #else
  90. char* line;
  91. size_t max_line = INI_INITIAL_ALLOC;
  92. #endif
  93. #if INI_ALLOW_REALLOC && !INI_USE_STACK
  94. char* new_line;
  95. #endif
  96. char section[MAX_SECTION] = "";
  97. #if INI_ALLOW_MULTILINE
  98. char prev_name[MAX_NAME] = "";
  99. #endif
  100. size_t offset;
  101. char* start;
  102. char* end;
  103. char* name;
  104. char* value;
  105. int lineno = 0;
  106. int error = 0;
  107. char abyss[16]; /* Used to consume input when a line is too long. */
  108. size_t abyss_len;
  109. assert(reader != NULL);
  110. assert(stream != NULL);
  111. assert(handler != NULL);
  112. #if !INI_USE_STACK
  113. line = (char*)ini_malloc(INI_INITIAL_ALLOC);
  114. if (!line) {
  115. return -2;
  116. }
  117. #endif
  118. #if INI_HANDLER_LINENO
  119. #define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
  120. #else
  121. #define HANDLER(u, s, n, v) handler(u, s, n, v)
  122. #endif
  123. /* Scan through stream line by line */
  124. while (reader(line, (int)max_line, stream) != NULL) {
  125. offset = strlen(line);
  126. #if INI_ALLOW_REALLOC && !INI_USE_STACK
  127. while (max_line < INI_MAX_LINE &&
  128. offset == max_line - 1 && line[offset - 1] != '\n') {
  129. max_line *= 2;
  130. if (max_line > INI_MAX_LINE)
  131. max_line = INI_MAX_LINE;
  132. new_line = ini_realloc(line, max_line);
  133. if (!new_line) {
  134. ini_free(line);
  135. return -2;
  136. }
  137. line = new_line;
  138. if (reader(line + offset, (int)(max_line - offset), stream) == NULL)
  139. break;
  140. offset += strlen(line + offset);
  141. }
  142. #endif
  143. lineno++;
  144. /* If line exceeded INI_MAX_LINE bytes, discard till end of line. */
  145. if (offset == max_line - 1 && line[offset - 1] != '\n') {
  146. while (reader(abyss, sizeof(abyss), stream) != NULL) {
  147. if (!error)
  148. error = lineno;
  149. abyss_len = strlen(abyss);
  150. if (abyss_len > 0 && abyss[abyss_len - 1] == '\n')
  151. break;
  152. }
  153. }
  154. start = line;
  155. #if INI_ALLOW_BOM
  156. if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
  157. (unsigned char)start[1] == 0xBB &&
  158. (unsigned char)start[2] == 0xBF) {
  159. start += 3;
  160. }
  161. #endif
  162. start = ini_rstrip(ini_lskip(start), line + offset);
  163. if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
  164. /* Start-of-line comment */
  165. }
  166. #if INI_ALLOW_MULTILINE
  167. else if (*prev_name && *start && start > line) {
  168. #if INI_ALLOW_INLINE_COMMENTS
  169. end = ini_find_chars_or_comment(start, NULL);
  170. *end = '\0';
  171. ini_rstrip(start, end);
  172. #endif
  173. /* Non-blank line with leading whitespace, treat as continuation
  174. of previous name's value (as per Python configparser). */
  175. if (!HANDLER(user, section, prev_name, start) && !error)
  176. error = lineno;
  177. }
  178. #endif
  179. else if (*start == '[') {
  180. /* A "[section]" line */
  181. end = ini_find_chars_or_comment(start + 1, "]");
  182. if (*end == ']') {
  183. *end = '\0';
  184. ini_strncpy0(section, start + 1, sizeof(section));
  185. #if INI_ALLOW_MULTILINE
  186. *prev_name = '\0';
  187. #endif
  188. #if INI_CALL_HANDLER_ON_NEW_SECTION
  189. if (!HANDLER(user, section, NULL, NULL) && !error)
  190. error = lineno;
  191. #endif
  192. }
  193. else if (!error) {
  194. /* No ']' found on section line */
  195. error = lineno;
  196. }
  197. }
  198. else if (*start) {
  199. /* Not a comment, must be a name[=:]value pair */
  200. end = ini_find_chars_or_comment(start, "=:");
  201. if (*end == '=' || *end == ':') {
  202. *end = '\0';
  203. name = ini_rstrip(start, end);
  204. value = end + 1;
  205. #if INI_ALLOW_INLINE_COMMENTS
  206. end = ini_find_chars_or_comment(value, NULL);
  207. *end = '\0';
  208. #endif
  209. value = ini_lskip(value);
  210. ini_rstrip(value, end);
  211. #if INI_ALLOW_MULTILINE
  212. ini_strncpy0(prev_name, name, sizeof(prev_name));
  213. #endif
  214. /* Valid name[=:]value pair found, call handler */
  215. if (!HANDLER(user, section, name, value) && !error)
  216. error = lineno;
  217. }
  218. else {
  219. /* No '=' or ':' found on name[=:]value line */
  220. #if INI_ALLOW_NO_VALUE
  221. *end = '\0';
  222. name = ini_rstrip(start, end);
  223. if (!HANDLER(user, section, name, NULL) && !error)
  224. error = lineno;
  225. #else
  226. if (!error)
  227. error = lineno;
  228. #endif
  229. }
  230. }
  231. #if INI_STOP_ON_FIRST_ERROR
  232. if (error)
  233. break;
  234. #endif
  235. }
  236. #if !INI_USE_STACK
  237. ini_free(line);
  238. #endif
  239. return error;
  240. }
  241. /* See documentation in header file. */
  242. int ini_parse_file(FILE* file, ini_handler handler, void* user)
  243. {
  244. return ini_parse_stream((ini_reader)fgets, file, handler, user);
  245. }
  246. /* See documentation in header file. */
  247. int ini_parse(const char* filename, ini_handler handler, void* user)
  248. {
  249. FILE* file;
  250. int error;
  251. file = fopen(filename, "r");
  252. if (!file)
  253. return -1;
  254. error = ini_parse_file(file, handler, user);
  255. fclose(file);
  256. return error;
  257. }
  258. /* An ini_reader function to read the next line from a string buffer. This
  259. is the fgets() equivalent used by ini_parse_string(). */
  260. static char* ini_reader_string(char* str, int num, void* stream) {
  261. ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
  262. const char* ctx_ptr = ctx->ptr;
  263. size_t ctx_num_left = ctx->num_left;
  264. char* strp = str;
  265. char c;
  266. if (ctx_num_left == 0 || num < 2)
  267. return NULL;
  268. while (num > 1 && ctx_num_left != 0) {
  269. c = *ctx_ptr++;
  270. ctx_num_left--;
  271. *strp++ = c;
  272. if (c == '\n')
  273. break;
  274. num--;
  275. }
  276. *strp = '\0';
  277. ctx->ptr = ctx_ptr;
  278. ctx->num_left = ctx_num_left;
  279. return str;
  280. }
  281. /* See documentation in header file. */
  282. int ini_parse_string(const char* string, ini_handler handler, void* user) {
  283. return ini_parse_string_length(string, strlen(string), handler, user);
  284. }
  285. /* See documentation in header file. */
  286. int ini_parse_string_length(const char* string, size_t length,
  287. ini_handler handler, void* user) {
  288. ini_parse_string_ctx ctx;
  289. ctx.ptr = string;
  290. ctx.num_left = length;
  291. return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
  292. user);
  293. }