You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

185 lines
4.8 KiB

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