ini.c 5.9 KB

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