Path: blob/a-new-beginning/SharedDependencies/Sources/inih/ini.c
2 views
/* inih -- simple .INI file parser12SPDX-License-Identifier: BSD-3-Clause34Copyright (C) 2009-2025, Ben Hoyt56inih is released under the New BSD license (see LICENSE.txt). Go to the project7home page for more info:89https://github.com/benhoyt/inih1011*/1213#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)14#define _CRT_SECURE_NO_WARNINGS15#endif1617#include <stdio.h>18#include <ctype.h>19#include <string.h>2021#include "ini.h"2223#if !INI_USE_STACK24#if INI_CUSTOM_ALLOCATOR25#include <stddef.h>26void* ini_malloc(size_t size);27void ini_free(void* ptr);28void* ini_realloc(void* ptr, size_t size);29#else30#include <stdlib.h>31#define ini_malloc malloc32#define ini_free free33#define ini_realloc realloc34#endif35#endif3637#define MAX_SECTION 5038#define MAX_NAME 503940/* Used by ini_parse_string() to keep track of string parsing state. */41typedef struct {42const char* ptr;43size_t num_left;44} ini_parse_string_ctx;4546/* Strip whitespace chars off end of given string, in place. end must be a47pointer to the NUL terminator at the end of the string. Return s. */48static char* ini_rstrip(char* s, char* end)49{50while (end > s && isspace((unsigned char)(*--end)))51*end = '\0';52return s;53}5455/* Return pointer to first non-whitespace char in given string. */56static char* ini_lskip(const char* s)57{58while (*s && isspace((unsigned char)(*s)))59s++;60return (char*)s;61}6263/* Return pointer to first char (of chars) or inline comment in given string,64or pointer to NUL at end of string if neither found. Inline comment must65be prefixed by a whitespace character to register as a comment. */66static char* ini_find_chars_or_comment(const char* s, const char* chars)67{68#if INI_ALLOW_INLINE_COMMENTS69int was_space = 0;70while (*s && (!chars || !strchr(chars, *s)) &&71!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {72was_space = isspace((unsigned char)(*s));73s++;74}75#else76while (*s && (!chars || !strchr(chars, *s))) {77s++;78}79#endif80return (char*)s;81}8283/* Similar to strncpy, but ensures dest (size bytes) is84NUL-terminated, and doesn't pad with NULs. */85static char* ini_strncpy0(char* dest, const char* src, size_t size)86{87/* Could use strncpy internally, but it causes gcc warnings (see issue #91) */88size_t i;89for (i = 0; i < size - 1 && src[i]; i++)90dest[i] = src[i];91dest[i] = '\0';92return dest;93}9495/* See documentation in header file. */96int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,97void* user)98{99/* Uses a fair bit of stack (use heap instead if you need to) */100#if INI_USE_STACK101char line[INI_MAX_LINE];102size_t max_line = INI_MAX_LINE;103#else104char* line;105size_t max_line = INI_INITIAL_ALLOC;106#endif107#if INI_ALLOW_REALLOC && !INI_USE_STACK108char* new_line;109#endif110char section[MAX_SECTION] = "";111#if INI_ALLOW_MULTILINE112char prev_name[MAX_NAME] = "";113#endif114115size_t offset;116char* start;117char* end;118char* name;119char* value;120int lineno = 0;121int error = 0;122char abyss[16]; /* Used to consume input when a line is too long. */123124#if !INI_USE_STACK125line = (char*)ini_malloc(INI_INITIAL_ALLOC);126if (!line) {127return -2;128}129#endif130131#if INI_HANDLER_LINENO132#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)133#else134#define HANDLER(u, s, n, v) handler(u, s, n, v)135#endif136137/* Scan through stream line by line */138while (reader(line, (int)max_line, stream) != NULL) {139offset = strlen(line);140141#if INI_ALLOW_REALLOC && !INI_USE_STACK142while (max_line < INI_MAX_LINE &&143offset == max_line - 1 && line[offset - 1] != '\n') {144max_line *= 2;145if (max_line > INI_MAX_LINE)146max_line = INI_MAX_LINE;147new_line = ini_realloc(line, max_line);148if (!new_line) {149ini_free(line);150return -2;151}152line = new_line;153if (reader(line + offset, (int)(max_line - offset), stream) == NULL)154break;155offset += strlen(line + offset);156}157#endif158159lineno++;160161/* If line exceeded INI_MAX_LINE bytes, discard till end of line. */162if (offset == max_line - 1 && line[offset - 1] != '\n') {163while (reader(abyss, sizeof(abyss), stream) != NULL) {164if (!error)165error = lineno;166if (abyss[strlen(abyss) - 1] == '\n')167break;168}169}170171start = line;172#if INI_ALLOW_BOM173if (lineno == 1 && (unsigned char)start[0] == 0xEF &&174(unsigned char)start[1] == 0xBB &&175(unsigned char)start[2] == 0xBF) {176start += 3;177}178#endif179start = ini_rstrip(ini_lskip(start), line + offset);180181if (strchr(INI_START_COMMENT_PREFIXES, *start)) {182/* Start-of-line comment */183}184#if INI_ALLOW_MULTILINE185else if (*prev_name && *start && start > line) {186#if INI_ALLOW_INLINE_COMMENTS187end = ini_find_chars_or_comment(start, NULL);188*end = '\0';189ini_rstrip(start, end);190#endif191/* Non-blank line with leading whitespace, treat as continuation192of previous name's value (as per Python configparser). */193if (!HANDLER(user, section, prev_name, start) && !error)194error = lineno;195}196#endif197else if (*start == '[') {198/* A "[section]" line */199end = ini_find_chars_or_comment(start + 1, "]");200if (*end == ']') {201*end = '\0';202ini_strncpy0(section, start + 1, sizeof(section));203#if INI_ALLOW_MULTILINE204*prev_name = '\0';205#endif206#if INI_CALL_HANDLER_ON_NEW_SECTION207if (!HANDLER(user, section, NULL, NULL) && !error)208error = lineno;209#endif210}211else if (!error) {212/* No ']' found on section line */213error = lineno;214}215}216else if (*start) {217/* Not a comment, must be a name[=:]value pair */218end = ini_find_chars_or_comment(start, "=:");219if (*end == '=' || *end == ':') {220*end = '\0';221name = ini_rstrip(start, end);222value = end + 1;223#if INI_ALLOW_INLINE_COMMENTS224end = ini_find_chars_or_comment(value, NULL);225*end = '\0';226#endif227value = ini_lskip(value);228ini_rstrip(value, end);229230#if INI_ALLOW_MULTILINE231ini_strncpy0(prev_name, name, sizeof(prev_name));232#endif233/* Valid name[=:]value pair found, call handler */234if (!HANDLER(user, section, name, value) && !error)235error = lineno;236}237else {238/* No '=' or ':' found on name[=:]value line */239#if INI_ALLOW_NO_VALUE240*end = '\0';241name = ini_rstrip(start, end);242if (!HANDLER(user, section, name, NULL) && !error)243error = lineno;244#else245if (!error)246error = lineno;247#endif248}249}250251#if INI_STOP_ON_FIRST_ERROR252if (error)253break;254#endif255}256257#if !INI_USE_STACK258ini_free(line);259#endif260261return error;262}263264/* See documentation in header file. */265int ini_parse_file(FILE* file, ini_handler handler, void* user)266{267return ini_parse_stream((ini_reader)fgets, file, handler, user);268}269270/* See documentation in header file. */271int ini_parse(const char* filename, ini_handler handler, void* user)272{273FILE* file;274int error;275276file = fopen(filename, "r");277if (!file)278return -1;279error = ini_parse_file(file, handler, user);280fclose(file);281return error;282}283284/* An ini_reader function to read the next line from a string buffer. This285is the fgets() equivalent used by ini_parse_string(). */286static char* ini_reader_string(char* str, int num, void* stream) {287ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;288const char* ctx_ptr = ctx->ptr;289size_t ctx_num_left = ctx->num_left;290char* strp = str;291char c;292293if (ctx_num_left == 0 || num < 2)294return NULL;295296while (num > 1 && ctx_num_left != 0) {297c = *ctx_ptr++;298ctx_num_left--;299*strp++ = c;300if (c == '\n')301break;302num--;303}304305*strp = '\0';306ctx->ptr = ctx_ptr;307ctx->num_left = ctx_num_left;308return str;309}310311/* See documentation in header file. */312int ini_parse_string(const char* string, ini_handler handler, void* user) {313return ini_parse_string_length(string, strlen(string), handler, user);314}315316/* See documentation in header file. */317int ini_parse_string_length(const char* string, size_t length,318ini_handler handler, void* user) {319ini_parse_string_ctx ctx;320321ctx.ptr = string;322ctx.num_left = length;323return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,324user);325}326327328