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