167 lines
7.2 KiB
C
167 lines
7.2 KiB
C
/* Внутренний разбор INI для config_parser и config_updater: одинаковые пробелы,
|
|
* секции, key=value и строгий HEX. Буфер принадлежит вызывающему; trim/key_value
|
|
* меняют его на месте. read выделяет текст через u_malloc; освобождение — u_free.
|
|
* Строка содержит не более 1022 байтов до LF, встроенный NUL запрещён. */
|
|
#ifndef CONFIG_INI_H
|
|
#define CONFIG_INI_H
|
|
|
|
#include "../lib/platform_compat.h"
|
|
#include "../lib/debug_config.h"
|
|
#include "../lib/mem.h"
|
|
#include "../lib/strbuf.h"
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#define CONFIG_INI_LINE_SIZE 1024
|
|
|
|
/* Удалить пробелы с обеих сторон, сохранив исходный адрес строки. */
|
|
static inline char *config_ini_trim(char *text) {
|
|
if (!text) return NULL;
|
|
char *start = text;
|
|
while (isspace((unsigned char)*start)) start++;
|
|
size_t length = strlen(start);
|
|
while (length && isspace((unsigned char)start[length - 1])) length--;
|
|
memmove(text, start, length);
|
|
text[length] = '\0';
|
|
return text;
|
|
}
|
|
|
|
/* Полный заголовок [name], без префиксного сопоставления; 0/-1. */
|
|
static inline int config_ini_section(const char *line, char *name, size_t capacity) {
|
|
size_t length = strlen(line);
|
|
if (length < 2 || line[0] != '[' || line[length - 1] != ']' || length - 2 >= capacity) return -1;
|
|
memcpy(name, line + 1, length - 2);
|
|
name[length - 2] = '\0';
|
|
config_ini_trim(name);
|
|
return 0;
|
|
}
|
|
|
|
/* Разобрать key=value, обрезать пробелы и комментарий # в значении; 0/-1. */
|
|
static inline int config_ini_key_value(const char *line, char *key, size_t key_cap, char *value, size_t value_cap) {
|
|
const char *equal = strchr(line, '=');
|
|
if (!equal || (size_t)(equal - line) >= key_cap || strlen(equal + 1) >= value_cap) return -1;
|
|
memcpy(key, line, (size_t)(equal - line));
|
|
key[equal - line] = '\0';
|
|
config_ini_trim(key);
|
|
strcpy(value, equal + 1);
|
|
char *comment = strchr(value, '#');
|
|
if (comment) *comment = '\0';
|
|
config_ini_trim(value);
|
|
return key[0] ? 0 : -1;
|
|
}
|
|
|
|
/* Проверить физические строки до разбора, чтобы не принять хвост длинной строки за новую опцию. */
|
|
static inline int config_ini_validate(const char *text, size_t length, const char *filename) {
|
|
size_t column = 0, line = 1;
|
|
if (!text && length) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s: NULL configuration buffer", filename); errno = EINVAL; return -1; }
|
|
for (size_t i = 0; i < length; i++) {
|
|
if (!text[i]) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%zu: embedded NUL at byte %zu", filename, line, i);
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
if (text[i] != '\n' && ++column >= CONFIG_INI_LINE_SIZE - 1) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%zu: INI line exceeds 1022 bytes", filename, line);
|
|
errno = EOVERFLOW;
|
|
return -1;
|
|
}
|
|
if (text[i] == '\n') { column = 0; line++; }
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Прочитать весь поток, проверяя ошибки чтения/роста. Поток закрывает вызывающий; 0/-1. */
|
|
static inline int config_ini_read(FILE *file, const char *filename, char **text, size_t *length) {
|
|
struct strbuf output = strbuf_new();
|
|
char chunk[4096];
|
|
size_t count;
|
|
*text = NULL;
|
|
*length = 0;
|
|
while ((count = fread(chunk, 1, sizeof(chunk), file)) != 0) {
|
|
const char *nul = memchr(chunk, '\0', count);
|
|
if (nul) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s: embedded NUL at byte %zu", filename, output.len + (size_t)(nul - chunk));
|
|
errno = EINVAL;
|
|
goto failed;
|
|
}
|
|
if (output.len > UINT32_MAX / 2 - count - 1) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s: INI text exceeds allocator size limit", filename);
|
|
errno = EFBIG;
|
|
goto failed;
|
|
}
|
|
if (strbuf_addf(&output, "%.*s", (int)count, chunk) < 0) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s: allocation failed while reading INI at byte %zu", filename, output.len);
|
|
errno = ENOMEM;
|
|
goto failed;
|
|
}
|
|
}
|
|
if (ferror(file)) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s: read failed at byte %zu: %s", filename, output.len, strerror(errno));
|
|
goto failed;
|
|
}
|
|
if (config_ini_validate(strbuf_str(&output), output.len, filename)) goto failed;
|
|
if (!output.buf && strbuf_addf(&output, "%s", "") < 0) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s: cannot allocate empty configuration buffer", filename);
|
|
errno = ENOMEM;
|
|
goto failed;
|
|
}
|
|
*length = output.len;
|
|
*text = strbuf_detach(&output);
|
|
return 0;
|
|
failed:
|
|
strbuf_free(&output);
|
|
return -1;
|
|
}
|
|
|
|
static inline int config_ini_nibble(unsigned char ch) {
|
|
if (ch >= '0' && ch <= '9') return ch - '0';
|
|
if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
|
|
if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10;
|
|
return -1;
|
|
}
|
|
|
|
/* Проверить весь HEX-фрагмент до записи. binary=NULL — только проверка; 0/-1. */
|
|
static inline int config_ini_hex_span(const char *text, size_t length, uint8_t *binary, size_t size) {
|
|
if (!text || size > SIZE_MAX / 2 || length != size * 2) return -1;
|
|
for (size_t i = 0; i < length; i++) if (config_ini_nibble((unsigned char)text[i]) < 0) return -1;
|
|
if (binary) {
|
|
for (size_t i = 0; i < size; i++) {
|
|
binary[i] = (uint8_t)((config_ini_nibble((unsigned char)text[i * 2]) << 4) |
|
|
config_ini_nibble((unsigned char)text[i * 2 + 1]));
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int config_ini_hex(const char *text, uint8_t *binary, size_t size) {
|
|
return text ? config_ini_hex_span(text, strlen(text), binary, size) : -1;
|
|
}
|
|
|
|
/* Список HEX через запятую: пустой допустим; непустые элементы имеют item_size байтов.
|
|
* Только полностью корректный список копируется в binary; при ошибке *count=0. */
|
|
static inline int config_ini_hex_list(const char *text, uint8_t *binary, size_t item_size, int max_count, int *count) {
|
|
*count = 0;
|
|
for (int pass = 0; pass < 2; pass++) {
|
|
const char *cursor = text;
|
|
int items = 0;
|
|
if (!*cursor) return 0;
|
|
do {
|
|
const char *end = strchr(cursor, ',');
|
|
const char *next = end ? end + 1 : NULL;
|
|
if (!end) end = cursor + strlen(cursor);
|
|
while (cursor < end && isspace((unsigned char)*cursor)) cursor++;
|
|
while (end > cursor && isspace((unsigned char)end[-1])) end--;
|
|
if (items >= max_count || config_ini_hex_span(cursor, (size_t)(end - cursor),
|
|
pass && binary ? binary + (size_t)items * item_size : NULL, item_size)) return -1;
|
|
items++;
|
|
cursor = next;
|
|
} while (cursor);
|
|
if (pass) *count = items;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif
|