ini.c 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. /* inih -- simple .INI file parser
  2. inih is released under the New BSD license (see LICENSE.txt). Go to the project
  3. home page for more info:
  4. https://github.com/benhoyt/inih
  5. */
  6. #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
  7. #define _CRT_SECURE_NO_WARNINGS
  8. #endif
  9. #include <stdio.h>
  10. #include <ctype.h>
  11. #include <string.h>
  12. #include "ini.h"
  13. #if !INI_USE_STACK
  14. #include <stdlib.h>
  15. #endif
  16. #define MAX_SECTION 50
  17. #define MAX_NAME 50
  18. /* Strip whitespace chars off end of given string, in place. Return s. */
  19. static char* rstrip(char* s)
  20. {
  21. char* p = s + strlen(s);
  22. while (p > s && isspace((unsigned char)(*--p)))
  23. *p = '\0';
  24. return s;
  25. }
  26. /* Return pointer to first non-whitespace char in given string. */
  27. static char* lskip(const char* s)
  28. {
  29. while (*s && isspace((unsigned char)(*s)))
  30. s++;
  31. return (char*)s;
  32. }
  33. /* Return pointer to first char (of chars) or inline comment in given string,
  34. or pointer to null at end of string if neither found. Inline comment must
  35. be prefixed by a whitespace character to register as a comment. */
  36. static char* find_chars_or_comment(const char* s, const char* chars)
  37. {
  38. #if INI_ALLOW_INLINE_COMMENTS
  39. int was_space = 0;
  40. while (*s && (!chars || !strchr(chars, *s)) &&
  41. !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
  42. was_space = isspace((unsigned char)(*s));
  43. s++;
  44. }
  45. #else
  46. while (*s && (!chars || !strchr(chars, *s))) {
  47. s++;
  48. }
  49. #endif
  50. return (char*)s;
  51. }
  52. /* Version of strncpy that ensures dest (size bytes) is null-terminated. */
  53. static char* strncpy0(char* dest, const char* src, size_t size)
  54. {
  55. strncpy(dest, src, size-1);
  56. dest[size - 1] = '\0';
  57. return dest;
  58. }
  59. /* See documentation in header file. */
  60. int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
  61. void* user)
  62. {
  63. /* Uses a fair bit of stack (use heap instead if you need to) */
  64. #if INI_USE_STACK
  65. char line[INI_MAX_LINE];
  66. #else
  67. char* line;
  68. #endif
  69. char section[MAX_SECTION] = "";
  70. char prev_name[MAX_NAME] = "";
  71. char* start;
  72. char* end;
  73. char* name;
  74. char* value;
  75. int lineno = 0;
  76. int error = 0;
  77. #if !INI_USE_STACK
  78. line = (char*)malloc(INI_MAX_LINE);
  79. if (!line) {
  80. return -2;
  81. }
  82. #endif
  83. /* Scan through stream line by line */
  84. while (reader(line, INI_MAX_LINE, stream) != NULL) {
  85. lineno++;
  86. start = line;
  87. #if INI_ALLOW_BOM
  88. if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
  89. (unsigned char)start[1] == 0xBB &&
  90. (unsigned char)start[2] == 0xBF) {
  91. start += 3;
  92. }
  93. #endif
  94. start = lskip(rstrip(start));
  95. if (*start == ';' || *start == '#') {
  96. /* Per Python configparser, allow both ; and # comments at the
  97. start of a line */
  98. }
  99. #if INI_ALLOW_MULTILINE
  100. else if (*prev_name && *start && start > line) {
  101. /* Non-blank line with leading whitespace, treat as continuation
  102. of previous name's value (as per Python configparser). */
  103. if (!handler(user, section, prev_name, start) && !error)
  104. error = lineno;
  105. }
  106. #endif
  107. else if (*start == '[') {
  108. /* A "[section]" line */
  109. end = find_chars_or_comment(start + 1, "]");
  110. if (*end == ']') {
  111. *end = '\0';
  112. strncpy0(section, start + 1, sizeof(section));
  113. *prev_name = '\0';
  114. }
  115. else if (!error) {
  116. /* No ']' found on section line */
  117. error = lineno;
  118. }
  119. }
  120. else if (*start) {
  121. /* Not a comment, must be a name[=:]value pair */
  122. end = find_chars_or_comment(start, "=:");
  123. if (*end == '=' || *end == ':') {
  124. *end = '\0';
  125. name = rstrip(start);
  126. value = lskip(end + 1);
  127. #if INI_ALLOW_INLINE_COMMENTS
  128. end = find_chars_or_comment(value, NULL);
  129. if (*end)
  130. *end = '\0';
  131. #endif
  132. rstrip(value);
  133. /* Valid name[=:]value pair found, call handler */
  134. strncpy0(prev_name, name, sizeof(prev_name));
  135. if (!handler(user, section, name, value) && !error)
  136. error = lineno;
  137. memset(value, 0, strlen(value));
  138. }
  139. else if (!error) {
  140. /* No '=' or ':' found on name[=:]value line */
  141. error = lineno;
  142. }
  143. }
  144. #if INI_STOP_ON_FIRST_ERROR
  145. if (error)
  146. break;
  147. #endif
  148. }
  149. #if !INI_USE_STACK
  150. free(line);
  151. #endif
  152. return error;
  153. }
  154. /* See documentation in header file. */
  155. int ini_parse_file(FILE* file, ini_handler handler, void* user)
  156. {
  157. return ini_parse_stream((ini_reader)fgets, file, handler, user);
  158. }
  159. /* See documentation in header file. */
  160. int ini_parse(const char* filename, ini_handler handler, void* user)
  161. {
  162. FILE* file;
  163. int error;
  164. file = fopen(filename, "r");
  165. if (!file)
  166. return -1;
  167. error = ini_parse_file(file, handler, user);
  168. fclose(file);
  169. return error;
  170. }