all repos — mgba @ 67905d281bfecbb06f51f2ca5ac939df378734a5

mGBA Game Boy Advance Emulator

src/third-party/inih/ini.c (view raw)

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