2
0

ini.c 9.1 KB

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