A terminal-based radio player
git clone git://soophie.de/rx
blob: e8392a0fcf93f7f6aac1c5dc4c5bf99b7c9f6688 (14075 bytes)[doc][blame][raw]
1
/* inih -- simple .INI file parser3
SPDX-License-Identifier: BSD-3-Clause5
Copyright (C) 2009-2020, Ben Hoyt7
inih is released under the New BSD license (see LICENSE.txt). Go to the project8
home page for more info:10
https://github.com/benhoyt/inih12
*/14
#ifndef INI_H15
#define INI_H17
/* Make this header file easier to include in C++ code */18
#ifdef __cplusplus19
extern "C" {20
#endif22
#include <stdio.h>24
/* Nonzero if ini_handler callback should accept lineno parameter. */25
#ifndef INI_HANDLER_LINENO26
#define INI_HANDLER_LINENO 027
#endif29
/* Visibility symbols, required for Windows DLLs */30
#ifndef INI_API31
#if defined _WIN32 || defined __CYGWIN__32
# ifdef INI_SHARED_LIB33
# ifdef INI_SHARED_LIB_BUILDING34
# define INI_API __declspec(dllexport)35
# else36
# define INI_API __declspec(dllimport)37
# endif38
# else39
# define INI_API40
# endif41
#else42
# if defined(__GNUC__) && __GNUC__ >= 443
# define INI_API __attribute__ ((visibility ("default")))44
# else45
# define INI_API46
# endif47
#endif48
#endif50
/* Typedef for prototype of handler function. */51
#if INI_HANDLER_LINENO52
typedef int (*ini_handler)(void* user, const char* section,53
const char* name, const char* value,54
int lineno);55
#else56
typedef int (*ini_handler)(void* user, const char* section,57
const char* name, const char* value);58
#endif60
/* Typedef for prototype of fgets-style reader function. */61
typedef char* (*ini_reader)(char* str, int num, void* stream);63
/* Parse given INI-style file. May have [section]s, name=value pairs64
(whitespace stripped), and comments starting with ';' (semicolon). Section65
is "" if name=value pair parsed before any section heading. name:value66
pairs are also supported as a concession to Python's configparser.68
For each name=value pair parsed, call handler function with given user69
pointer as well as section, name, and value (data only valid for duration70
of handler call). Handler should return nonzero on success, zero on error.72
Returns 0 on success, line number of first error on parse error (doesn't73
stop on first error), -1 on file open error, or -2 on memory allocation74
error (only when INI_USE_STACK is zero).75
*/76
INI_API int ini_parse(const char* filename, ini_handler handler, void* user);78
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't79
close the file when it's finished -- the caller must do that. */80
INI_API int ini_parse_file(FILE* file, ini_handler handler, void* user);82
/* Same as ini_parse(), but takes an ini_reader function pointer instead of83
filename. Used for implementing custom or string-based I/O (see also84
ini_parse_string). */85
INI_API int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,86
void* user);88
/* Same as ini_parse(), but takes a zero-terminated string with the INI data89
instead of a file. Useful for parsing INI data from a network socket or90
already in memory. */91
INI_API int ini_parse_string(const char* string, ini_handler handler, void* user);93
/* Nonzero to allow multi-line value parsing, in the style of Python's94
configparser. If allowed, ini_parse() will call the handler with the same95
name for each subsequent line parsed. */96
#ifndef INI_ALLOW_MULTILINE97
#define INI_ALLOW_MULTILINE 198
#endif100
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of101
the file. See https://github.com/benhoyt/inih/issues/21 */102
#ifndef INI_ALLOW_BOM103
#define INI_ALLOW_BOM 1104
#endif106
/* Chars that begin a start-of-line comment. Per Python configparser, allow107
both ; and # comments at the start of a line by default. */108
#ifndef INI_START_COMMENT_PREFIXES109
#define INI_START_COMMENT_PREFIXES ";#"110
#endif112
/* Nonzero to allow inline comments (with valid inline comment characters113
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match114
Python 3.2+ configparser behaviour. */115
#ifndef INI_ALLOW_INLINE_COMMENTS116
#define INI_ALLOW_INLINE_COMMENTS 1117
#endif118
#ifndef INI_INLINE_COMMENT_PREFIXES119
#define INI_INLINE_COMMENT_PREFIXES ";"120
#endif122
/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */123
#ifndef INI_USE_STACK124
#define INI_USE_STACK 1125
#endif127
/* Maximum line length for any line in INI file (stack or heap). Note that128
this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */129
#ifndef INI_MAX_LINE130
#define INI_MAX_LINE 200131
#endif133
/* Nonzero to allow heap line buffer to grow via realloc(), zero for a134
fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is135
zero. */136
#ifndef INI_ALLOW_REALLOC137
#define INI_ALLOW_REALLOC 0138
#endif140
/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK141
is zero. */142
#ifndef INI_INITIAL_ALLOC143
#define INI_INITIAL_ALLOC 200144
#endif146
/* Stop parsing on first error (default is to keep parsing). */147
#ifndef INI_STOP_ON_FIRST_ERROR148
#define INI_STOP_ON_FIRST_ERROR 0149
#endif151
/* Nonzero to call the handler at the start of each new section (with152
name and value NULL). Default is to only call the handler on153
each name=value pair. */154
#ifndef INI_CALL_HANDLER_ON_NEW_SECTION155
#define INI_CALL_HANDLER_ON_NEW_SECTION 0156
#endif158
/* Nonzero to allow a name without a value (no '=' or ':' on the line) and159
call the handler with value NULL in this case. Default is to treat160
no-value lines as an error. */161
#ifndef INI_ALLOW_NO_VALUE162
#define INI_ALLOW_NO_VALUE 0163
#endif165
/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory166
allocation functions (INI_USE_STACK must also be 0). These functions must167
have the same signatures as malloc/free/realloc and behave in a similar168
way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */169
#ifndef INI_CUSTOM_ALLOCATOR170
#define INI_CUSTOM_ALLOCATOR 0171
#endif174
#ifdef __cplusplus175
}176
#endif178
#ifdef INI_IMPL180
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)181
#define _CRT_SECURE_NO_WARNINGS182
#endif184
#include <stdio.h>185
#include <ctype.h>186
#include <string.h>188
#include "ini.h"190
#if !INI_USE_STACK191
#if INI_CUSTOM_ALLOCATOR192
#include <stddef.h>193
void* ini_malloc(size_t size);194
void ini_free(void* ptr);195
void* ini_realloc(void* ptr, size_t size);196
#else197
#include <stdlib.h>198
#define ini_malloc malloc199
#define ini_free free200
#define ini_realloc realloc201
#endif202
#endif204
#define MAX_SECTION 50205
#define MAX_NAME 50207
/* Used by ini_parse_string() to keep track of string parsing state. */208
typedef struct {209
const char* ptr;210
size_t num_left;211
} ini_parse_string_ctx;213
/* Strip whitespace chars off end of given string, in place. Return s. */214
static char* ini_rstrip(char* s)215
{216
char* p = s + strlen(s);217
while (p > s && isspace((unsigned char)(*--p)))218
*p = '\0';219
return s;220
}222
/* Return pointer to first non-whitespace char in given string. */223
static char* ini_lskip(const char* s)224
{225
while (*s && isspace((unsigned char)(*s)))226
s++;227
return (char*)s;228
}230
/* Return pointer to first char (of chars) or inline comment in given string,231
or pointer to NUL at end of string if neither found. Inline comment must232
be prefixed by a whitespace character to register as a comment. */233
static char* ini_find_chars_or_comment(const char* s, const char* chars)234
{235
#if INI_ALLOW_INLINE_COMMENTS236
int was_space = 0;237
while (*s && (!chars || !strchr(chars, *s)) &&238
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {239
was_space = isspace((unsigned char)(*s));240
s++;241
}242
#else243
while (*s && (!chars || !strchr(chars, *s))) {244
s++;245
}246
#endif247
return (char*)s;248
}250
/* Similar to strncpy, but ensures dest (size bytes) is251
NUL-terminated, and doesn't pad with NULs. */252
static char* ini_strncpy0(char* dest, const char* src, size_t size)253
{254
/* Could use strncpy internally, but it causes gcc warnings (see issue #91) */255
size_t i;256
for (i = 0; i < size - 1 && src[i]; i++)257
dest[i] = src[i];258
dest[i] = '\0';259
return dest;260
}262
/* See documentation in header file. */263
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,264
void* user)265
{266
/* Uses a fair bit of stack (use heap instead if you need to) */267
#if INI_USE_STACK268
char line[INI_MAX_LINE];269
size_t max_line = INI_MAX_LINE;270
#else271
char* line;272
size_t max_line = INI_INITIAL_ALLOC;273
#endif274
#if INI_ALLOW_REALLOC && !INI_USE_STACK275
char* new_line;276
size_t offset;277
#endif278
char section[MAX_SECTION] = "";279
#if INI_ALLOW_MULTILINE280
char prev_name[MAX_NAME] = "";281
#endif283
char* start;284
char* end;285
char* name;286
char* value;287
int lineno = 0;288
int error = 0;290
#if !INI_USE_STACK291
line = (char*)ini_malloc(INI_INITIAL_ALLOC);292
if (!line) {293
return -2;294
}295
#endif297
#if INI_HANDLER_LINENO298
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)299
#else300
#define HANDLER(u, s, n, v) handler(u, s, n, v)301
#endif303
/* Scan through stream line by line */304
while (reader(line, (int)max_line, stream) != NULL) {305
#if INI_ALLOW_REALLOC && !INI_USE_STACK306
offset = strlen(line);307
while (offset == max_line - 1 && line[offset - 1] != '\n') {308
max_line *= 2;309
if (max_line > INI_MAX_LINE)310
max_line = INI_MAX_LINE;311
new_line = ini_realloc(line, max_line);312
if (!new_line) {313
ini_free(line);314
return -2;315
}316
line = new_line;317
if (reader(line + offset, (int)(max_line - offset), stream) == NULL)318
break;319
if (max_line >= INI_MAX_LINE)320
break;321
offset += strlen(line + offset);322
}323
#endif325
lineno++;327
start = line;328
#if INI_ALLOW_BOM329
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&330
(unsigned char)start[1] == 0xBB &&331
(unsigned char)start[2] == 0xBF) {332
start += 3;333
}334
#endif335
start = ini_rstrip(ini_lskip(start));337
if (strchr(INI_START_COMMENT_PREFIXES, *start)) {338
/* Start-of-line comment */339
}340
#if INI_ALLOW_MULTILINE341
else if (*prev_name && *start && start > line) {342
#if INI_ALLOW_INLINE_COMMENTS343
end = ini_find_chars_or_comment(start, NULL);344
if (*end)345
*end = '\0';346
ini_rstrip(start);347
#endif348
/* Non-blank line with leading whitespace, treat as continuation349
of previous name's value (as per Python configparser). */350
if (!HANDLER(user, section, prev_name, start) && !error)351
error = lineno;352
}353
#endif354
else if (*start == '[') {355
/* A "[section]" line */356
end = ini_find_chars_or_comment(start + 1, "]");357
if (*end == ']') {358
*end = '\0';359
ini_strncpy0(section, start + 1, sizeof(section));360
#if INI_ALLOW_MULTILINE361
*prev_name = '\0';362
#endif363
#if INI_CALL_HANDLER_ON_NEW_SECTION364
if (!HANDLER(user, section, NULL, NULL) && !error)365
error = lineno;366
#endif367
}368
else if (!error) {369
/* No ']' found on section line */370
error = lineno;371
}372
}373
else if (*start) {374
/* Not a comment, must be a name[=:]value pair */375
end = ini_find_chars_or_comment(start, "=:");376
if (*end == '=' || *end == ':') {377
*end = '\0';378
name = ini_rstrip(start);379
value = end + 1;380
#if INI_ALLOW_INLINE_COMMENTS381
end = ini_find_chars_or_comment(value, NULL);382
if (*end)383
*end = '\0';384
#endif385
value = ini_lskip(value);386
ini_rstrip(value);388
#if INI_ALLOW_MULTILINE389
ini_strncpy0(prev_name, name, sizeof(prev_name));390
#endif391
/* Valid name[=:]value pair found, call handler */392
if (!HANDLER(user, section, name, value) && !error)393
error = lineno;394
}395
else if (!error) {396
/* No '=' or ':' found on name[=:]value line */397
#if INI_ALLOW_NO_VALUE398
*end = '\0';399
name = ini_rstrip(start);400
if (!HANDLER(user, section, name, NULL) && !error)401
error = lineno;402
#else403
error = lineno;404
#endif405
}406
}408
#if INI_STOP_ON_FIRST_ERROR409
if (error)410
break;411
#endif412
}414
#if !INI_USE_STACK415
ini_free(line);416
#endif418
return error;419
}421
/* See documentation in header file. */422
int ini_parse_file(FILE* file, ini_handler handler, void* user)423
{424
return ini_parse_stream((ini_reader)fgets, file, handler, user);425
}427
/* See documentation in header file. */428
int ini_parse(const char* filename, ini_handler handler, void* user)429
{430
FILE* file;431
int error;433
file = fopen(filename, "r");434
if (!file)435
return -1;436
error = ini_parse_file(file, handler, user);437
fclose(file);438
return error;439
}441
/* An ini_reader function to read the next line from a string buffer. This442
is the fgets() equivalent used by ini_parse_string(). */443
static char* ini_reader_string(char* str, int num, void* stream) {444
ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;445
const char* ctx_ptr = ctx->ptr;446
size_t ctx_num_left = ctx->num_left;447
char* strp = str;448
char c;450
if (ctx_num_left == 0 || num < 2)451
return NULL;453
while (num > 1 && ctx_num_left != 0) {454
c = *ctx_ptr++;455
ctx_num_left--;456
*strp++ = c;457
if (c == '\n')458
break;459
num--;460
}462
*strp = '\0';463
ctx->ptr = ctx_ptr;464
ctx->num_left = ctx_num_left;465
return str;466
}468
/* See documentation in header file. */469
int ini_parse_string(const char* string, ini_handler handler, void* user) {470
ini_parse_string_ctx ctx;472
ctx.ptr = string;473
ctx.num_left = strlen(string);474
return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,475
user);476
}478
#endif /* INI_IMPL */479
#endif /* INI_H */