C · 33959 bytes Raw Blame History
1 /* Minimal, security-focused TOML parser implementation
2 * Built specifically for gitswitch-c with comprehensive input validation
3 */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <ctype.h>
9 #include <errno.h>
10 #include <limits.h>
11 #include <sys/stat.h>
12
13 #include "toml_parser.h"
14 #include "error.h"
15 #include "utils.h"
16
17 /* Internal parsing helper functions */
18 static int parse_section_header(toml_parser_state_t *state, char *section_name);
19 static int parse_key_value_pair(toml_parser_state_t *state, toml_keyvalue_t *kv);
20 static int parse_string_value(toml_parser_state_t *state, char *value, size_t value_size);
21 static int parse_integer_value(toml_parser_state_t *state, int *value);
22 static int parse_boolean_value(toml_parser_state_t *state, bool *value);
23 static void skip_whitespace(toml_parser_state_t *state);
24 static void skip_comment(toml_parser_state_t *state);
25 static bool is_at_end(const toml_parser_state_t *state);
26 static char current_char(const toml_parser_state_t *state);
27 static char advance_char(toml_parser_state_t *state);
28 static bool match_char(toml_parser_state_t *state, char expected);
29 static void set_parser_error(toml_parser_state_t *state, const char *message);
30 static toml_section_t *find_section(toml_document_t *doc, const char *section_name);
31 static toml_section_t *find_or_create_section(toml_document_t *doc, const char *section_name);
32 static toml_keyvalue_t *find_key(toml_section_t *section, const char *key_name);
33
34 /* Initialize TOML document structure */
35 void toml_init_document(toml_document_t *doc) {
36 if (!doc) return;
37
38 memset(doc, 0, sizeof(toml_document_t));
39 doc->is_valid = false;
40 doc->section_count = 0;
41 }
42
43 /* Parse TOML from file with comprehensive security validation */
44 int toml_parse_file(const char *file_path, toml_document_t *doc) {
45 FILE *file;
46 struct stat file_stat;
47 char *buffer = NULL;
48 size_t file_size;
49 size_t bytes_read;
50 int result = -1;
51
52 if (!file_path || !doc) {
53 set_error(ERR_INVALID_ARGS, "NULL arguments to toml_parse_file");
54 return -1;
55 }
56
57 /* Security: Validate file path */
58 if (!toml_validate_file_path(file_path)) {
59 set_error(ERR_CONFIG_INVALID, "Invalid file path: %s", file_path);
60 return -1;
61 }
62
63 /* Get file statistics for security checks */
64 if (stat(file_path, &file_stat) != 0) {
65 set_system_error(ERR_CONFIG_NOT_FOUND, "Cannot access config file: %s", file_path);
66 return -1;
67 }
68
69 /* Security: Check file size limit */
70 if (file_stat.st_size > TOML_MAX_FILE_SIZE) {
71 set_error(ERR_CONFIG_INVALID, "Configuration file too large: %ld bytes (max: %d)",
72 file_stat.st_size, TOML_MAX_FILE_SIZE);
73 return -1;
74 }
75
76 /* Security: Check file permissions (should not be world-readable) */
77 if (file_stat.st_mode & (S_IRGRP | S_IROTH)) {
78 set_error(ERR_PERMISSION_DENIED, "Configuration file has unsafe permissions: %o",
79 file_stat.st_mode & 0777);
80 return -1;
81 }
82
83 file_size = (size_t)file_stat.st_size;
84
85 /* Open file for reading */
86 file = fopen(file_path, "r");
87 if (!file) {
88 set_system_error(ERR_CONFIG_NOT_FOUND, "Failed to open config file: %s", file_path);
89 return -1;
90 }
91
92 /* Allocate buffer for file content */
93 buffer = safe_malloc(file_size + 1);
94 if (!buffer) {
95 fclose(file);
96 return -1;
97 }
98
99 /* Read file content */
100 bytes_read = fread(buffer, 1, file_size, file);
101 if (bytes_read != file_size) {
102 set_system_error(ERR_FILE_IO, "Failed to read complete config file: %s", file_path);
103 goto cleanup;
104 }
105
106 buffer[file_size] = '\0';
107 fclose(file);
108 file = NULL;
109
110 /* Security: Validate character set */
111 if (!toml_validate_safe_characters(buffer, file_size)) {
112 set_error(ERR_CONFIG_INVALID, "Configuration file contains unsafe characters");
113 goto cleanup;
114 }
115
116 /* Security: Check for injection patterns */
117 if (!toml_check_injection_patterns(buffer, file_size)) {
118 set_error(ERR_CONFIG_INVALID, "Configuration file contains potentially malicious patterns");
119 goto cleanup;
120 }
121
122 /* Store file path in document */
123 safe_strncpy(doc->file_path, file_path, sizeof(doc->file_path));
124
125 /* Parse the TOML content */
126 result = toml_parse_string(buffer, file_size, doc);
127
128 cleanup:
129 if (file) fclose(file);
130 if (buffer) {
131 secure_zero_memory(buffer, file_size + 1);
132 free(buffer);
133 }
134
135 return result;
136 }
137
138 /* Parse TOML from string buffer */
139 int toml_parse_string(const char *toml_string, size_t length, toml_document_t *doc) {
140 toml_parser_state_t state;
141 char section_name[TOML_MAX_SECTION_LEN] = ""; /* Default to root section */
142 toml_section_t *current_section = NULL;
143
144 if (!toml_string || !doc || length == 0) {
145 set_error(ERR_INVALID_ARGS, "Invalid arguments to toml_parse_string");
146 return -1;
147 }
148
149 /* Initialize parser state */
150 memset(&state, 0, sizeof(state));
151 state.input = toml_string;
152 state.input_length = length;
153 state.position = 0;
154 state.line_number = 1;
155 state.column_number = 1;
156 state.has_error = false;
157
158 /* Initialize document */
159 toml_init_document(doc);
160
161 /* Parse line by line */
162 while (!is_at_end(&state) && !state.has_error) {
163 skip_whitespace(&state);
164
165 if (is_at_end(&state)) {
166 break;
167 }
168
169 char c = current_char(&state);
170
171 /* Skip comments */
172 if (c == '#') {
173 skip_comment(&state);
174 continue;
175 }
176
177 /* Parse section header */
178 if (c == '[') {
179 if (parse_section_header(&state, section_name) == 0) {
180 current_section = find_or_create_section(doc, section_name);
181 if (!current_section) {
182 set_parser_error(&state, "Failed to create section");
183 break;
184 }
185 }
186 continue;
187 }
188
189 /* Parse key-value pair */
190 if (isalpha(c) || c == '_') {
191 if (!current_section) {
192 /* Create default section if none exists */
193 current_section = find_or_create_section(doc, "");
194 if (!current_section) {
195 set_parser_error(&state, "Failed to create default section");
196 break;
197 }
198 }
199
200 if (current_section->key_count >= TOML_MAX_KEYS_PER_SECTION) {
201 set_parser_error(&state, "Too many keys in section");
202 break;
203 }
204
205 toml_keyvalue_t *kv = &current_section->keys[current_section->key_count];
206 if (parse_key_value_pair(&state, kv) == 0) {
207 current_section->key_count++;
208 }
209 continue;
210 }
211
212 /* Skip empty lines */
213 if (c == '\n' || c == '\r') {
214 advance_char(&state);
215 continue;
216 }
217
218 /* Unknown character */
219 set_parser_error(&state, "Unexpected character");
220 break;
221 }
222
223 if (state.has_error) {
224 set_error(ERR_CONFIG_INVALID, "TOML parsing failed at line %zu, column %zu: %s",
225 state.line_number, state.column_number, state.error_message);
226 return -1;
227 }
228
229 /* Validate the parsed document against our schema */
230 if (toml_validate_gitswitch_schema(doc) != 0) {
231 return -1;
232 }
233
234 doc->is_valid = true;
235 log_debug("TOML document parsed successfully: %zu sections", doc->section_count);
236
237 return 0;
238 }
239
240 /* Get string value from TOML document */
241 int toml_get_string(const toml_document_t *doc, const char *section,
242 const char *key, char *value, size_t value_size) {
243 const toml_section_t *sec;
244 const toml_keyvalue_t *kv;
245
246 if (!doc || !section || !key || !value || value_size == 0) {
247 set_error(ERR_INVALID_ARGS, "Invalid arguments to toml_get_string");
248 return -1;
249 }
250
251 if (!doc->is_valid) {
252 set_error(ERR_CONFIG_INVALID, "TOML document is not valid");
253 return -1;
254 }
255
256 sec = find_section((toml_document_t *)doc, section);
257 if (!sec) {
258 /* Section not found - return silently, caller handles missing data */
259 return -1;
260 }
261
262 kv = find_key((toml_section_t *)sec, key);
263 if (!kv || !kv->is_set) {
264 /* Key not found - return silently, caller handles missing data */
265 return -1;
266 }
267
268 if (kv->type != TOML_TYPE_STRING) {
269 set_error(ERR_CONFIG_INVALID, "Key %s.%s is not a string", section, key);
270 return -1;
271 }
272
273 /* Sanitize the value before returning */
274 return toml_sanitize_string(kv->value, value, value_size);
275 }
276
277 /* Get integer value from TOML document */
278 int toml_get_integer(const toml_document_t *doc, const char *section,
279 const char *key, int *value) {
280 const toml_section_t *sec;
281 const toml_keyvalue_t *kv;
282 char *endptr;
283 long parsed_value;
284
285 if (!doc || !section || !key || !value) {
286 set_error(ERR_INVALID_ARGS, "Invalid arguments to toml_get_integer");
287 return -1;
288 }
289
290 if (!doc->is_valid) {
291 set_error(ERR_CONFIG_INVALID, "TOML document is not valid");
292 return -1;
293 }
294
295 sec = find_section((toml_document_t *)doc, section);
296 if (!sec) {
297 /* Section not found - return silently, caller handles missing data */
298 return -1;
299 }
300
301 kv = find_key((toml_section_t *)sec, key);
302 if (!kv || !kv->is_set) {
303 /* Key not found - return silently, caller handles missing data */
304 return -1;
305 }
306
307 if (kv->type != TOML_TYPE_INTEGER) {
308 set_error(ERR_CONFIG_INVALID, "Key %s.%s is not an integer", section, key);
309 return -1;
310 }
311
312 errno = 0;
313 parsed_value = strtol(kv->value, &endptr, 10);
314
315 if (errno != 0 || *endptr != '\0') {
316 set_error(ERR_CONFIG_INVALID, "Invalid integer value: %s", kv->value);
317 return -1;
318 }
319
320 if (parsed_value < INT_MIN || parsed_value > INT_MAX) {
321 set_error(ERR_CONFIG_INVALID, "Integer value out of range: %ld", parsed_value);
322 return -1;
323 }
324
325 *value = (int)parsed_value;
326 return 0;
327 }
328
329 /* Get boolean value from TOML document */
330 int toml_get_boolean(const toml_document_t *doc, const char *section,
331 const char *key, bool *value) {
332 const toml_section_t *sec;
333 const toml_keyvalue_t *kv;
334
335 if (!doc || !section || !key || !value) {
336 set_error(ERR_INVALID_ARGS, "Invalid arguments to toml_get_boolean");
337 return -1;
338 }
339
340 if (!doc->is_valid) {
341 set_error(ERR_CONFIG_INVALID, "TOML document is not valid");
342 return -1;
343 }
344
345 sec = find_section((toml_document_t *)doc, section);
346 if (!sec) {
347 /* Section not found - return silently, caller handles missing data */
348 return -1;
349 }
350
351 kv = find_key((toml_section_t *)sec, key);
352 if (!kv || !kv->is_set) {
353 /* Key not found - return silently, caller handles missing data */
354 return -1;
355 }
356
357 if (kv->type != TOML_TYPE_BOOLEAN) {
358 set_error(ERR_CONFIG_INVALID, "Key %s.%s is not a boolean", section, key);
359 return -1;
360 }
361
362 *value = (strcmp(kv->value, "true") == 0);
363 return 0;
364 }
365
366 /* Validate TOML document structure for gitswitch schema */
367 int toml_validate_gitswitch_schema(const toml_document_t *doc) {
368 if (!doc) {
369 set_error(ERR_INVALID_ARGS, "NULL document to validate");
370 return -1;
371 }
372
373 /* Check for required sections */
374 bool has_settings = false;
375 bool has_accounts = false;
376
377 for (size_t i = 0; i < doc->section_count; i++) {
378 const toml_section_t *section = &doc->sections[i];
379
380 if (strcmp(section->name, "settings") == 0) {
381 has_settings = true;
382
383 /* Validate settings section */
384 bool has_default_scope = false;
385 for (size_t j = 0; j < section->key_count; j++) {
386 const toml_keyvalue_t *kv = &section->keys[j];
387
388 if (strcmp(kv->key, "default_scope") == 0) {
389 has_default_scope = true;
390 if (kv->type != TOML_TYPE_STRING) {
391 set_error(ERR_CONFIG_INVALID, "default_scope must be a string");
392 return -1;
393 }
394 if (strcmp(kv->value, "local") != 0 && strcmp(kv->value, "global") != 0) {
395 set_error(ERR_CONFIG_INVALID, "default_scope must be 'local' or 'global'");
396 return -1;
397 }
398 }
399 }
400
401 if (!has_default_scope) {
402 set_error(ERR_CONFIG_INVALID, "settings section missing required default_scope");
403 return -1;
404 }
405 }
406
407 if (string_starts_with(section->name, "accounts.")) {
408 has_accounts = true;
409
410 /* Validate account section */
411 bool has_name = false, has_email = false;
412
413 for (size_t j = 0; j < section->key_count; j++) {
414 const toml_keyvalue_t *kv = &section->keys[j];
415
416 if (strcmp(kv->key, "name") == 0) {
417 has_name = true;
418 if (kv->type != TOML_TYPE_STRING || strlen(kv->value) == 0) {
419 set_error(ERR_CONFIG_INVALID, "Account name must be a non-empty string");
420 return -1;
421 }
422 }
423
424 if (strcmp(kv->key, "email") == 0) {
425 has_email = true;
426 if (kv->type != TOML_TYPE_STRING || !validate_email(kv->value)) {
427 set_error(ERR_CONFIG_INVALID, "Account email must be a valid email address");
428 return -1;
429 }
430 }
431
432 if (strcmp(kv->key, "ssh_key") == 0) {
433 if (kv->type != TOML_TYPE_STRING) {
434 set_error(ERR_CONFIG_INVALID, "ssh_key must be a string");
435 return -1;
436 }
437 if (strlen(kv->value) > 0 && !toml_validate_file_path(kv->value)) {
438 set_error(ERR_CONFIG_INVALID, "Invalid SSH key path: %s", kv->value);
439 return -1;
440 }
441 }
442
443 if (strcmp(kv->key, "gpg_key") == 0) {
444 if (kv->type != TOML_TYPE_STRING) {
445 set_error(ERR_CONFIG_INVALID, "gpg_key must be a string");
446 return -1;
447 }
448 if (strlen(kv->value) > 0 && !validate_key_id(kv->value)) {
449 set_error(ERR_CONFIG_INVALID, "Invalid GPG key ID: %s", kv->value);
450 return -1;
451 }
452 }
453 }
454
455 if (!has_name || !has_email) {
456 set_error(ERR_CONFIG_INVALID, "Account section %s missing required name or email",
457 section->name);
458 return -1;
459 }
460 }
461 }
462
463 if (!has_settings) {
464 set_error(ERR_CONFIG_INVALID, "Configuration missing required [settings] section");
465 return -1;
466 }
467
468 if (!has_accounts) {
469 log_info("Configuration has no account sections yet - this is normal for new installations");
470 /* This is not an error - allow empty configurations */
471 }
472
473 log_debug("TOML document schema validation passed");
474 return 0;
475 }
476
477 /* Security validation: Check for safe characters only */
478 bool toml_validate_safe_characters(const char *input, size_t length) {
479 if (!input) return false;
480
481 for (size_t i = 0; i < length; i++) {
482 unsigned char c = (unsigned char)input[i];
483
484 /* Allow printable ASCII, newlines, tabs, and carriage returns */
485 if (!(c >= 32 && c <= 126) && c != '\n' && c != '\r' && c != '\t') {
486 log_warning("Unsafe character found at position %zu: 0x%02x", i, c);
487 return false;
488 }
489 }
490
491 return true;
492 }
493
494 /* Sanitize string value */
495 int toml_sanitize_string(const char *input, char *output, size_t output_size) {
496 size_t input_len, output_pos = 0;
497
498 if (!input || !output || output_size == 0) {
499 set_error(ERR_INVALID_ARGS, "Invalid arguments to toml_sanitize_string");
500 return -1;
501 }
502
503 input_len = strlen(input);
504
505 for (size_t i = 0; i < input_len && output_pos < output_size - 1; i++) {
506 char c = input[i];
507
508 /* Remove or escape potentially dangerous characters */
509 if (c >= 32 && c <= 126 && c != '"' && c != '\\') {
510 output[output_pos++] = c;
511 }
512 /* Allow some whitespace */
513 else if (c == ' ' || c == '\t') {
514 output[output_pos++] = c;
515 }
516 /* Skip other characters */
517 }
518
519 output[output_pos] = '\0';
520 return 0;
521 }
522
523 /* Validate file path for security */
524 bool toml_validate_file_path(const char *path) {
525 if (!path || strlen(path) == 0) return true; /* Empty path is allowed */
526
527 /* Check for directory traversal attempts */
528 if (strstr(path, "..") != NULL) {
529 log_warning("Directory traversal attempt in path: %s", path);
530 return false;
531 }
532
533 /* Check for absolute paths outside user home */
534 if (path[0] == '/' && !string_starts_with(path, "/home/") &&
535 !string_starts_with(path, "/Users/") &&
536 !string_starts_with(path, "/tmp/")) {
537 log_warning("Suspicious absolute path: %s", path);
538 return false;
539 }
540
541 /* Check path length */
542 if (strlen(path) > 256) {
543 log_warning("Path too long: %zu characters", strlen(path));
544 return false;
545 }
546
547 return true;
548 }
549
550 /* Check for TOML injection patterns */
551 bool toml_check_injection_patterns(const char *input, size_t length) {
552 const char *dangerous_patterns[] = {
553 "$(", "`", "${", "\\x", "\\u", NULL
554 };
555
556 if (!input) return false;
557
558 for (int i = 0; dangerous_patterns[i] != NULL; i++) {
559 if (strstr(input, dangerous_patterns[i]) != NULL) {
560 log_warning("Potentially dangerous pattern found: %s", dangerous_patterns[i]);
561 return false;
562 }
563 }
564
565 /* Check for excessive nesting or repetition */
566 size_t bracket_count = 0;
567 for (size_t i = 0; i < length; i++) {
568 if (input[i] == '[') {
569 bracket_count++;
570 if (bracket_count > 32) {
571 log_warning("Excessive bracket nesting detected");
572 return false;
573 }
574 }
575 }
576
577 return true;
578 }
579
580 /* Internal helper functions implementation continues... */
581
582 /* Find section in document */
583 static toml_section_t *find_section(toml_document_t *doc, const char *section_name) {
584 if (!doc || !section_name) return NULL;
585
586 log_debug("Finding section '%s' among %zu sections", section_name, doc->section_count);
587
588 for (size_t i = 0; i < doc->section_count; i++) {
589 log_debug(" Section %zu: '%s'", i, doc->sections[i].name);
590 if (strcmp(doc->sections[i].name, section_name) == 0) {
591 log_debug(" Found matching section!");
592 return &doc->sections[i];
593 }
594 }
595
596 log_debug(" Section '%s' not found", section_name);
597 return NULL;
598 }
599
600 /* Find or create section */
601 static toml_section_t *find_or_create_section(toml_document_t *doc, const char *section_name) {
602 toml_section_t *section;
603
604 if (!doc || !section_name) return NULL;
605
606 /* Try to find existing section */
607 section = find_section(doc, section_name);
608 if (section) return section;
609
610 /* Create new section */
611 if (doc->section_count >= TOML_MAX_SECTIONS) {
612 log_error("Maximum number of sections exceeded: %d", TOML_MAX_SECTIONS);
613 return NULL;
614 }
615
616 section = &doc->sections[doc->section_count];
617 memset(section, 0, sizeof(toml_section_t));
618
619 safe_strncpy(section->name, section_name, sizeof(section->name));
620 section->is_set = true;
621 section->key_count = 0;
622
623 doc->section_count++;
624
625 return section;
626 }
627
628 /* Find key in section */
629 static toml_keyvalue_t *find_key(toml_section_t *section, const char *key_name) {
630 if (!section || !key_name) return NULL;
631
632 log_debug("Finding key '%s' in section with %zu keys", key_name, section->key_count);
633
634 for (size_t i = 0; i < section->key_count; i++) {
635 log_debug(" Key %zu: '%s' = '%s' (is_set=%d)", i, section->keys[i].key, section->keys[i].value, section->keys[i].is_set);
636 if (strcmp(section->keys[i].key, key_name) == 0) {
637 log_debug(" Found matching key! is_set=%d", section->keys[i].is_set);
638 return &section->keys[i];
639 }
640 }
641
642 log_debug(" Key '%s' not found", key_name);
643 return NULL;
644 }
645
646 /* Parse section header [section.name] */
647 static int parse_section_header(toml_parser_state_t *state, char *section_name) {
648 size_t name_pos = 0;
649
650 if (!match_char(state, '[')) {
651 set_parser_error(state, "Expected '[' at start of section");
652 return -1;
653 }
654
655 skip_whitespace(state);
656
657 /* Parse section name */
658 while (!is_at_end(state) && current_char(state) != ']' &&
659 name_pos < TOML_MAX_SECTION_LEN - 1) {
660 char c = advance_char(state);
661
662 if (isalnum(c) || c == '.' || c == '_' || c == '-') {
663 section_name[name_pos++] = c;
664 } else {
665 set_parser_error(state, "Invalid character in section name");
666 return -1;
667 }
668 }
669
670 section_name[name_pos] = '\0';
671
672 skip_whitespace(state);
673
674 if (!match_char(state, ']')) {
675 set_parser_error(state, "Expected ']' at end of section");
676 return -1;
677 }
678
679 return 0;
680 }
681
682 /* Parse key-value pair */
683 static int parse_key_value_pair(toml_parser_state_t *state, toml_keyvalue_t *kv) {
684 size_t key_pos = 0;
685
686 memset(kv, 0, sizeof(toml_keyvalue_t));
687
688 /* Parse key name */
689 while (!is_at_end(state) && current_char(state) != '=' &&
690 key_pos < TOML_MAX_KEY_LEN - 1) {
691 char c = current_char(state);
692
693 if (isalnum(c) || c == '_') {
694 kv->key[key_pos++] = advance_char(state);
695 } else if (isspace(c)) {
696 advance_char(state);
697 break;
698 } else {
699 set_parser_error(state, "Invalid character in key name");
700 return -1;
701 }
702 }
703
704 kv->key[key_pos] = '\0';
705
706 skip_whitespace(state);
707
708 if (!match_char(state, '=')) {
709 set_parser_error(state, "Expected '=' after key name");
710 return -1;
711 }
712
713 skip_whitespace(state);
714
715 /* Determine value type and parse */
716 char c = current_char(state);
717
718 if (c == '"') {
719 /* String value */
720 kv->type = TOML_TYPE_STRING;
721 if (parse_string_value(state, kv->value, sizeof(kv->value)) == 0) {
722 kv->is_set = true;
723 return 0;
724 }
725 return -1;
726 } else if (c == 't' || c == 'f') {
727 /* Boolean value */
728 kv->type = TOML_TYPE_BOOLEAN;
729 bool bool_val;
730 if (parse_boolean_value(state, &bool_val) == 0) {
731 strcpy(kv->value, bool_val ? "true" : "false");
732 kv->is_set = true;
733 return 0;
734 }
735 return -1;
736 } else if (isdigit(c) || c == '-' || c == '+') {
737 /* Integer value */
738 kv->type = TOML_TYPE_INTEGER;
739 int int_val;
740 if (parse_integer_value(state, &int_val) == 0) {
741 snprintf(kv->value, sizeof(kv->value), "%d", int_val);
742 kv->is_set = true;
743 return 0;
744 }
745 return -1;
746 } else {
747 set_parser_error(state, "Invalid value type");
748 return -1;
749 }
750 }
751
752 /* Parse string value "..." */
753 static int parse_string_value(toml_parser_state_t *state, char *value, size_t value_size) {
754 size_t value_pos = 0;
755
756 if (!match_char(state, '"')) {
757 set_parser_error(state, "Expected '\"' at start of string");
758 return -1;
759 }
760
761 while (!is_at_end(state) && current_char(state) != '"' &&
762 value_pos < value_size - 1) {
763 char c = advance_char(state);
764
765 /* Handle escape sequences */
766 if (c == '\\' && !is_at_end(state)) {
767 char next = advance_char(state);
768 switch (next) {
769 case 'n': value[value_pos++] = '\n'; break;
770 case 't': value[value_pos++] = '\t'; break;
771 case 'r': value[value_pos++] = '\r'; break;
772 case '\\': value[value_pos++] = '\\'; break;
773 case '"': value[value_pos++] = '"'; break;
774 default:
775 set_parser_error(state, "Invalid escape sequence");
776 return -1;
777 }
778 } else {
779 value[value_pos++] = c;
780 }
781 }
782
783 value[value_pos] = '\0';
784
785 if (!match_char(state, '"')) {
786 set_parser_error(state, "Expected '\"' at end of string");
787 return -1;
788 }
789
790 return 0;
791 }
792
793 /* Parse boolean value true/false */
794 static int parse_boolean_value(toml_parser_state_t *state, bool *value) {
795 if (strncmp(&state->input[state->position], "true", 4) == 0) {
796 state->position += 4;
797 *value = true;
798 return 0;
799 } else if (strncmp(&state->input[state->position], "false", 5) == 0) {
800 state->position += 5;
801 *value = false;
802 return 0;
803 } else {
804 set_parser_error(state, "Invalid boolean value");
805 return -1;
806 }
807 }
808
809 /* Parse integer value */
810 static int parse_integer_value(toml_parser_state_t *state, int *value) {
811 char num_str[32];
812 size_t num_pos = 0;
813 char *endptr;
814 long parsed_value;
815
816 /* Handle optional sign */
817 char c = current_char(state);
818 if (c == '+' || c == '-') {
819 num_str[num_pos++] = advance_char(state);
820 }
821
822 /* Parse digits */
823 while (!is_at_end(state) && isdigit(current_char(state)) &&
824 num_pos < sizeof(num_str) - 1) {
825 num_str[num_pos++] = advance_char(state);
826 }
827
828 num_str[num_pos] = '\0';
829
830 if (num_pos == 0 || (num_pos == 1 && (num_str[0] == '+' || num_str[0] == '-'))) {
831 set_parser_error(state, "Invalid integer format");
832 return -1;
833 }
834
835 errno = 0;
836 parsed_value = strtol(num_str, &endptr, 10);
837
838 if (errno != 0 || *endptr != '\0') {
839 set_parser_error(state, "Integer parsing error");
840 return -1;
841 }
842
843 if (parsed_value < INT_MIN || parsed_value > INT_MAX) {
844 set_parser_error(state, "Integer out of range");
845 return -1;
846 }
847
848 *value = (int)parsed_value;
849 return 0;
850 }
851
852 /* Parsing helper functions */
853
854 static void skip_whitespace(toml_parser_state_t *state) {
855 while (!is_at_end(state)) {
856 char c = current_char(state);
857 if (c == ' ' || c == '\t') {
858 advance_char(state);
859 } else {
860 break;
861 }
862 }
863 }
864
865 static void skip_comment(toml_parser_state_t *state) {
866 while (!is_at_end(state) && current_char(state) != '\n') {
867 advance_char(state);
868 }
869 if (!is_at_end(state)) {
870 advance_char(state); /* Skip the newline */
871 }
872 }
873
874 static bool is_at_end(const toml_parser_state_t *state) {
875 return state->position >= state->input_length;
876 }
877
878 static char current_char(const toml_parser_state_t *state) {
879 if (is_at_end(state)) return '\0';
880 return state->input[state->position];
881 }
882
883 static char advance_char(toml_parser_state_t *state) {
884 if (is_at_end(state)) return '\0';
885
886 char c = state->input[state->position++];
887
888 if (c == '\n') {
889 state->line_number++;
890 state->column_number = 1;
891 } else {
892 state->column_number++;
893 }
894
895 return c;
896 }
897
898 static bool match_char(toml_parser_state_t *state, char expected) {
899 if (is_at_end(state) || current_char(state) != expected) {
900 return false;
901 }
902 advance_char(state);
903 return true;
904 }
905
906 static void set_parser_error(toml_parser_state_t *state, const char *message) {
907 state->has_error = true;
908 safe_strncpy(state->error_message, message, sizeof(state->error_message));
909 }
910
911 /* Get all sections from document */
912 int toml_get_sections(const toml_document_t *doc, char sections[][TOML_MAX_SECTION_LEN],
913 size_t max_sections, size_t *section_count) {
914 if (!doc || !sections || !section_count) {
915 set_error(ERR_INVALID_ARGS, "Invalid arguments to toml_get_sections");
916 return -1;
917 }
918
919 *section_count = 0;
920
921 for (size_t i = 0; i < doc->section_count && *section_count < max_sections; i++) {
922 safe_strncpy(sections[*section_count], doc->sections[i].name, TOML_MAX_SECTION_LEN);
923 (*section_count)++;
924 }
925
926 return 0;
927 }
928
929 /* Set string value in document */
930 int toml_set_string(toml_document_t *doc, const char *section_name,
931 const char *key_name, const char *value) {
932 toml_section_t *section;
933 toml_keyvalue_t *kv;
934
935 if (!doc || !section_name || !key_name || !value) {
936 set_error(ERR_INVALID_ARGS, "Invalid arguments to toml_set_string");
937 return -1;
938 }
939
940 /* Find or create section */
941 section = find_or_create_section(doc, section_name);
942 if (!section) {
943 return -1;
944 }
945
946 /* Find or create key */
947 kv = find_key(section, key_name);
948 if (!kv) {
949 /* Create new key-value pair */
950 if (section->key_count >= TOML_MAX_KEYS_PER_SECTION) {
951 set_error(ERR_CONFIG_INVALID, "Too many key-value pairs in section: %s", section_name);
952 return -1;
953 }
954
955 kv = &section->keys[section->key_count];
956 safe_strncpy(kv->key, key_name, sizeof(kv->key));
957 section->key_count++;
958 }
959
960 /* Set string value */
961 kv->type = TOML_TYPE_STRING;
962 safe_strncpy(kv->value, value, sizeof(kv->value));
963 kv->is_set = true;
964
965 return 0;
966 }
967
968 /* Set boolean value in document */
969 int toml_set_boolean(toml_document_t *doc, const char *section_name,
970 const char *key_name, bool value) {
971 toml_section_t *section;
972 toml_keyvalue_t *kv;
973
974 if (!doc || !section_name || !key_name) {
975 set_error(ERR_INVALID_ARGS, "Invalid arguments to toml_set_boolean");
976 return -1;
977 }
978
979 /* Find or create section */
980 section = find_or_create_section(doc, section_name);
981 if (!section) {
982 return -1;
983 }
984
985 /* Find or create key */
986 kv = find_key(section, key_name);
987 if (!kv) {
988 /* Create new key-value pair */
989 if (section->key_count >= TOML_MAX_KEYS_PER_SECTION) {
990 set_error(ERR_CONFIG_INVALID, "Too many key-value pairs in section: %s", section_name);
991 return -1;
992 }
993
994 kv = &section->keys[section->key_count];
995 safe_strncpy(kv->key, key_name, sizeof(kv->key));
996 section->key_count++;
997 }
998
999 /* Set boolean value */
1000 kv->type = TOML_TYPE_BOOLEAN;
1001 safe_strncpy(kv->value, value ? "true" : "false", sizeof(kv->value));
1002 kv->is_set = true;
1003
1004 return 0;
1005 }
1006
1007 /* Write document to file */
1008 int toml_write_file(const toml_document_t *doc, const char *file_path) {
1009 FILE *file;
1010
1011 if (!doc || !file_path) {
1012 set_error(ERR_INVALID_ARGS, "Invalid arguments to toml_write_file");
1013 return -1;
1014 }
1015
1016 file = fopen(file_path, "w");
1017 if (!file) {
1018 set_system_error(ERR_CONFIG_WRITE_FAILED, "Failed to open file for writing: %s", file_path);
1019 return -1;
1020 }
1021
1022 /* Write sections */
1023 for (size_t i = 0; i < doc->section_count; i++) {
1024 const toml_section_t *section = &doc->sections[i];
1025
1026 /* Write section header */
1027 if (fprintf(file, "[%s]\n", section->name) < 0) {
1028 fclose(file);
1029 set_system_error(ERR_CONFIG_WRITE_FAILED, "Failed to write section header");
1030 return -1;
1031 }
1032
1033 /* Write key-value pairs */
1034 for (size_t j = 0; j < section->key_count; j++) {
1035 const toml_keyvalue_t *kv = &section->keys[j];
1036
1037 if (!kv->is_set) continue;
1038
1039 switch (kv->type) {
1040 case TOML_TYPE_STRING:
1041 if (fprintf(file, "%s = \"%s\"\n", kv->key, kv->value) < 0) {
1042 fclose(file);
1043 set_system_error(ERR_CONFIG_WRITE_FAILED, "Failed to write string value");
1044 return -1;
1045 }
1046 break;
1047
1048 case TOML_TYPE_INTEGER:
1049 case TOML_TYPE_BOOLEAN:
1050 if (fprintf(file, "%s = %s\n", kv->key, kv->value) < 0) {
1051 fclose(file);
1052 set_system_error(ERR_CONFIG_WRITE_FAILED, "Failed to write value");
1053 return -1;
1054 }
1055 break;
1056
1057 case TOML_TYPE_INVALID:
1058 default:
1059 break;
1060 }
1061 }
1062
1063 /* Add blank line between sections */
1064 if (i < doc->section_count - 1) {
1065 fprintf(file, "\n");
1066 }
1067 }
1068
1069 fclose(file);
1070 return 0;
1071 }
1072
1073 /* Cleanup TOML document */
1074 void toml_cleanup_document(toml_document_t *doc) {
1075 if (!doc) return;
1076
1077 /* Clear sensitive data */
1078 secure_zero_memory(doc, sizeof(toml_document_t));
1079 }