ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
parsers.c
Go to the documentation of this file.
1
7#include "parsers.h"
8#include <string.h>
9#include <stdlib.h>
10#include <ctype.h>
11#include <stddef.h>
12#include "common.h"
13#include "options/options.h"
14
15// Helper function to convert string to lowercase in-place (non-destructive)
16static void to_lower(const char *src, char *dst, size_t max_len) {
17 size_t i = 0;
18 while (src[i] && i < max_len - 1) {
19 dst[i] = (char)tolower((unsigned char)src[i]);
20 i++;
21 }
22 dst[i] = '\0';
23}
24
40static bool is_session_string(const char *str) {
41 if (!str) {
42 return false;
43 }
44
45 size_t len = strlen(str);
46 if (len == 0 || len > 47) {
47 return false;
48 }
49
50 // Must not start or end with hyphen
51 if (str[0] == '-' || str[len - 1] == '-') {
52 return false;
53 }
54
55 // Count hyphens and validate characters
56 int hyphen_count = 0;
57 for (size_t i = 0; i < len; i++) {
58 char c = str[i];
59 if (c == '-') {
60 hyphen_count++;
61 // No consecutive hyphens
62 if (i > 0 && str[i - 1] == '-') {
63 return false;
64 }
65 } else if (!islower(c)) {
66 // Only lowercase letters and hyphens allowed
67 return false;
68 }
69 }
70
71 // Must have exactly 2 hyphens (3 words)
72 return hyphen_count == 2;
73}
74
75bool parse_color_mode(const char *arg, void *dest, char **error_msg) {
76 if (!arg || !dest) {
77 if (error_msg) {
78 *error_msg = strdup("Internal error: NULL argument or destination");
79 }
80 return false;
81 }
82
83 terminal_color_mode_t *color_mode = (terminal_color_mode_t *)dest;
84 char lower[32];
85 to_lower(arg, lower, sizeof(lower));
86
87 // Auto-detect
88 if (strcmp(lower, "auto") == 0 || strcmp(lower, "a") == 0) {
89 *color_mode = TERM_COLOR_AUTO;
90 return true;
91 }
92
93 // Monochrome/None
94 if (strcmp(lower, "none") == 0 || strcmp(lower, "mono") == 0 || strcmp(lower, "monochrome") == 0 ||
95 strcmp(lower, "0") == 0) {
96 *color_mode = TERM_COLOR_NONE;
97 return true;
98 }
99
100 // 16-color
101 if (strcmp(lower, "16") == 0 || strcmp(lower, "16color") == 0 || strcmp(lower, "ansi") == 0 ||
102 strcmp(lower, "1") == 0) {
103 *color_mode = TERM_COLOR_16;
104 return true;
105 }
106
107 // 256-color
108 if (strcmp(lower, "256") == 0 || strcmp(lower, "256color") == 0 || strcmp(lower, "2") == 0) {
109 *color_mode = TERM_COLOR_256;
110 return true;
111 }
112
113 // Truecolor
114 if (strcmp(lower, "truecolor") == 0 || strcmp(lower, "true") == 0 || strcmp(lower, "tc") == 0 ||
115 strcmp(lower, "rgb") == 0 || strcmp(lower, "24bit") == 0 || strcmp(lower, "3") == 0) {
116 *color_mode = TERM_COLOR_TRUECOLOR;
117 return true;
118 }
119
120 // Invalid value
121 if (error_msg) {
122 char *msg = SAFE_MALLOC(256, char *);
123 if (msg) {
124 snprintf(msg, 256, "Invalid color mode '%s'. Valid values: auto, none, 16, 256, truecolor", arg);
125 *error_msg = msg;
126 }
127 }
128 return false;
129}
130
131bool parse_render_mode(const char *arg, void *dest, char **error_msg) {
132 if (!arg || !dest) {
133 if (error_msg) {
134 *error_msg = strdup("Internal error: NULL argument or destination");
135 }
136 return false;
137 }
138
139 render_mode_t *render_mode = (render_mode_t *)dest;
140 char lower[32];
141 to_lower(arg, lower, sizeof(lower));
142
143 // Foreground mode
144 if (strcmp(lower, "foreground") == 0 || strcmp(lower, "fg") == 0 || strcmp(lower, "0") == 0) {
145 *render_mode = RENDER_MODE_FOREGROUND;
146 return true;
147 }
148
149 // Background mode
150 if (strcmp(lower, "background") == 0 || strcmp(lower, "bg") == 0 || strcmp(lower, "1") == 0) {
151 *render_mode = RENDER_MODE_BACKGROUND;
152 return true;
153 }
154
155 // Half-block mode
156 if (strcmp(lower, "half-block") == 0 || strcmp(lower, "half") == 0 || strcmp(lower, "hb") == 0 ||
157 strcmp(lower, "2") == 0) {
158 *render_mode = RENDER_MODE_HALF_BLOCK;
159 return true;
160 }
161
162 // Invalid value
163 if (error_msg) {
164 char *msg = SAFE_MALLOC(256, char *);
165 if (msg) {
166 snprintf(msg, 256, "Invalid render mode '%s'. Valid values: foreground, background, half-block", arg);
167 *error_msg = msg;
168 }
169 }
170 return false;
171}
172
173bool parse_palette_type(const char *arg, void *dest, char **error_msg) {
174 if (!arg || !dest) {
175 if (error_msg) {
176 *error_msg = strdup("Internal error: NULL argument or destination");
177 }
178 return false;
179 }
180
181 palette_type_t *palette_type = (palette_type_t *)dest;
182 char lower[32];
183 to_lower(arg, lower, sizeof(lower));
184
185 // Standard palette
186 if (strcmp(lower, "standard") == 0 || strcmp(lower, "std") == 0 || strcmp(lower, "0") == 0) {
187 *palette_type = PALETTE_STANDARD;
188 return true;
189 }
190
191 // Blocks palette
192 if (strcmp(lower, "blocks") == 0 || strcmp(lower, "block") == 0 || strcmp(lower, "1") == 0) {
193 *palette_type = PALETTE_BLOCKS;
194 return true;
195 }
196
197 // Digital palette
198 if (strcmp(lower, "digital") == 0 || strcmp(lower, "dig") == 0 || strcmp(lower, "2") == 0) {
199 *palette_type = PALETTE_DIGITAL;
200 return true;
201 }
202
203 // Minimal palette
204 if (strcmp(lower, "minimal") == 0 || strcmp(lower, "min") == 0 || strcmp(lower, "3") == 0) {
205 *palette_type = PALETTE_MINIMAL;
206 return true;
207 }
208
209 // Cool palette
210 if (strcmp(lower, "cool") == 0 || strcmp(lower, "4") == 0) {
211 *palette_type = PALETTE_COOL;
212 return true;
213 }
214
215 // Custom palette
216 if (strcmp(lower, "custom") == 0 || strcmp(lower, "5") == 0) {
217 *palette_type = PALETTE_CUSTOM;
218 return true;
219 }
220
221 // Invalid value
222 if (error_msg) {
223 char *msg = SAFE_MALLOC(256, char *);
224 if (msg) {
225 snprintf(msg, 256, "Invalid palette type '%s'. Valid values: standard, blocks, digital, minimal, cool, custom",
226 arg);
227 *error_msg = msg;
228 }
229 }
230 return false;
231}
232
233bool parse_log_level(const char *arg, void *dest, char **error_msg) {
234 if (!arg || !dest) {
235 if (error_msg) {
236 *error_msg = strdup("Internal error: NULL argument or destination");
237 }
238 return false;
239 }
240
241 log_level_t *log_level = (log_level_t *)dest;
242 char lower[32];
243 to_lower(arg, lower, sizeof(lower));
244
245 // Development level
246 if (strcmp(lower, "dev") == 0 || strcmp(lower, "development") == 0 || strcmp(lower, "0") == 0) {
247 *log_level = LOG_DEV;
248 return true;
249 }
250
251 // Debug level
252 if (strcmp(lower, "debug") == 0 || strcmp(lower, "dbg") == 0 || strcmp(lower, "1") == 0) {
253 *log_level = LOG_DEBUG;
254 return true;
255 }
256
257 // Info level
258 if (strcmp(lower, "info") == 0 || strcmp(lower, "information") == 0 || strcmp(lower, "2") == 0) {
259 *log_level = LOG_INFO;
260 return true;
261 }
262
263 // Warning level
264 if (strcmp(lower, "warn") == 0 || strcmp(lower, "warning") == 0 || strcmp(lower, "3") == 0) {
265 *log_level = LOG_WARN;
266 return true;
267 }
268
269 // Error level
270 if (strcmp(lower, "error") == 0 || strcmp(lower, "err") == 0 || strcmp(lower, "4") == 0) {
271 *log_level = LOG_ERROR;
272 return true;
273 }
274
275 // Fatal level
276 if (strcmp(lower, "fatal") == 0 || strcmp(lower, "5") == 0) {
277 *log_level = LOG_FATAL;
278 return true;
279 }
280
281 // Invalid value
282 if (error_msg) {
283 char *msg = SAFE_MALLOC(256, char *);
284 if (msg) {
285 snprintf(msg, 256, "Invalid log level '%s'. Valid values: dev, debug, info, warn, error, fatal", arg);
286 *error_msg = msg;
287 }
288 }
289 return false;
290}
291
292// ============================================================================
293// Positional Argument Parsers
294// ============================================================================
295
296#include "util/ip.h"
297#include <string.h>
298
306int parse_server_bind_address(const char *arg, void *config, char **remaining, int num_remaining, char **error_msg) {
307 (void)remaining; // Unused - we consume one arg at a time
308 (void)num_remaining;
309
310 if (!arg || !config) {
311 if (error_msg) {
312 *error_msg = strdup("Internal error: NULL argument or config");
313 }
314 return -1;
315 }
316
317 // Assume config struct has address and address6 fields (OPTIONS_BUFF_SIZE each)
318 // This is a simplified version that assumes standard options_state layout
319 char *address = (char *)config + offsetof(struct options_state, address);
320 char *address6 = (char *)config + offsetof(struct options_state, address6);
321
322 // Parse IPv6 address (remove brackets if present)
323 char parsed_addr[OPTIONS_BUFF_SIZE];
324 const char *addr_to_check = arg;
325 if (parse_ipv6_address(arg, parsed_addr, sizeof(parsed_addr)) == 0) {
326 addr_to_check = parsed_addr;
327 }
328
329 // Check if it's IPv4 or IPv6
330 if (is_valid_ipv4(addr_to_check)) {
331 // Check if we already have a non-default IPv4 address
332 // Allow overwriting defaults (127.0.0.1, localhost)
333 if (address[0] != '\0' && strcmp(address, "127.0.0.1") != 0 && strcmp(address, "localhost") != 0) {
334 if (error_msg) {
335 char *msg = SAFE_MALLOC(256, char *);
336 if (msg) {
337 snprintf(msg, 256,
338 "Cannot specify multiple IPv4 addresses.\n"
339 "Already have: %s\n"
340 "Cannot add: %s",
341 address, addr_to_check);
342 *error_msg = msg;
343 }
344 }
345 return -1;
346 }
347 SAFE_SNPRINTF(address, OPTIONS_BUFF_SIZE, "%s", addr_to_check);
348 return 1; // Consumed 1 arg
349 } else if (is_valid_ipv6(addr_to_check)) {
350 // Check if we already have a non-default IPv6 address
351 // Allow overwriting default (::1)
352 if (address6[0] != '\0' && strcmp(address6, "::1") != 0) {
353 if (error_msg) {
354 char *msg = SAFE_MALLOC(256, char *);
355 if (msg) {
356 snprintf(msg, 256,
357 "Cannot specify multiple IPv6 addresses.\n"
358 "Already have: %s\n"
359 "Cannot add: %s",
360 address6, addr_to_check);
361 *error_msg = msg;
362 }
363 }
364 return -1;
365 }
366 SAFE_SNPRINTF(address6, OPTIONS_BUFF_SIZE, "%s", addr_to_check);
367 return 1; // Consumed 1 arg
368 } else {
369 if (error_msg) {
370 char *msg = SAFE_MALLOC(512, char *);
371 if (msg) {
372 snprintf(msg, 512,
373 "Invalid IP address '%s'.\n"
374 "Server bind addresses must be valid IPv4 or IPv6 addresses.\n"
375 "Examples:\n"
376 " ascii-chat server 0.0.0.0\n"
377 " ascii-chat server ::1\n"
378 " ascii-chat server 0.0.0.0 ::1",
379 arg);
380 *error_msg = msg;
381 }
382 }
383 return -1;
384 }
385}
386
393int parse_client_address(const char *arg, void *config, char **remaining, int num_remaining, char **error_msg) {
394 (void)remaining;
395 (void)num_remaining;
396
397 if (!arg || !config) {
398 if (error_msg) {
399 *error_msg = strdup("Internal error: NULL argument or config");
400 }
401 return -1;
402 }
403
404 // Check if this is a session string (format: adjective-noun-noun)
405 // Session strings have exactly 2 hyphens, only lowercase letters, length 1-47
406 if (is_session_string(arg)) {
407 // This is a session string, not a server address
408 char *session_string = (char *)config + offsetof(struct options_state, session_string);
409 SAFE_SNPRINTF(session_string, 64, "%s", arg);
410 log_debug("Detected session string: %s", arg);
411 return 1; // Consumed 1 arg
412 }
413
414 // Not a session string, parse as server address
415 // Access address and port fields from options_state struct
416 char *address = (char *)config + offsetof(struct options_state, address);
417 char *port = (char *)config + offsetof(struct options_state, port);
418
419 // Check for port in address (format: address:port or [ipv6]:port)
420 const char *colon = strrchr(arg, ':');
421
422 if (colon != NULL) {
423 // Check if this is IPv6 with port [::1]:port or plain hostname:port
424 if (arg[0] == '[') {
425 // IPv6 with brackets: [address]:port
426 const char *closing_bracket = strchr(arg, ']');
427 if (closing_bracket && closing_bracket < colon) {
428 // Extract address (remove brackets)
429 size_t addr_len = (size_t)(closing_bracket - arg - 1);
430 if (addr_len >= OPTIONS_BUFF_SIZE) {
431 if (error_msg) {
432 *error_msg = strdup("IPv6 address too long");
433 }
434 return -1;
435 }
436 SAFE_SNPRINTF(address, OPTIONS_BUFF_SIZE, "%.*s", (int)addr_len, arg + 1);
437
438 // Extract and validate port
439 const char *port_str = colon + 1;
440 char *endptr;
441 long port_num = strtol(port_str, &endptr, 10);
442 if (*endptr != '\0' || port_num < 1 || port_num > 65535) {
443 if (error_msg) {
444 char *msg = SAFE_MALLOC(256, char *);
445 if (msg) {
446 snprintf(msg, 256, "Invalid port number '%s'. Must be 1-65535.", port_str);
447 *error_msg = msg;
448 }
449 }
450 return -1;
451 }
452 SAFE_SNPRINTF(port, OPTIONS_BUFF_SIZE, "%s", port_str);
453 }
454 } else {
455 // Check if it's IPv6 without brackets (no port allowed)
456 // or hostname/IPv4:port
457 size_t colon_count = 0;
458 for (const char *p = arg; *p; p++) {
459 if (*p == ':')
460 colon_count++;
461 }
462
463 if (colon_count == 1) {
464 // Likely hostname:port or IPv4:port
465 size_t addr_len = (size_t)(colon - arg);
466 if (addr_len >= OPTIONS_BUFF_SIZE) {
467 if (error_msg) {
468 *error_msg = strdup("Address too long");
469 }
470 return -1;
471 }
472 SAFE_SNPRINTF(address, OPTIONS_BUFF_SIZE, "%.*s", (int)addr_len, arg);
473
474 // Extract and validate port
475 const char *port_str = colon + 1;
476 char *endptr;
477 long port_num = strtol(port_str, &endptr, 10);
478 if (*endptr != '\0' || port_num < 1 || port_num > 65535) {
479 if (error_msg) {
480 char *msg = SAFE_MALLOC(256, char *);
481 if (msg) {
482 snprintf(msg, 256, "Invalid port number '%s'. Must be 1-65535.", port_str);
483 *error_msg = msg;
484 }
485 }
486 return -1;
487 }
488 SAFE_SNPRINTF(port, OPTIONS_BUFF_SIZE, "%s", port_str);
489 } else {
490 // Multiple colons - likely bare IPv6 address
491 SAFE_SNPRINTF(address, OPTIONS_BUFF_SIZE, "%s", arg);
492 }
493 }
494 } else {
495 // No colon - just an address
496 SAFE_SNPRINTF(address, OPTIONS_BUFF_SIZE, "%s", arg);
497 }
498
499 // Validate addresses that contain dots as potential IPv4 addresses
500 // If it has a dot, it's either a valid IPv4 or a hostname with domain
501 bool has_dot = strchr(address, '.') != NULL;
502 bool starts_with_digit = address[0] >= '0' && address[0] <= '9';
503
504 if (has_dot && starts_with_digit) {
505 // Looks like an IPv4 attempt - validate strictly
506 if (!is_valid_ipv4(address)) {
507 if (error_msg) {
508 char *msg = SAFE_MALLOC(512, char *);
509 if (msg) {
510 snprintf(msg, 512,
511 "Invalid IPv4 address '%s'.\n"
512 "IPv4 addresses must have exactly 4 octets (0-255) separated by dots.\n"
513 "Examples: 127.0.0.1, 192.168.1.1\n"
514 "For hostnames, use letters: example.com, localhost",
515 address);
516 *error_msg = msg;
517 }
518 }
519 return -1;
520 }
521 }
522
523 // Note: Port conflict checking would require additional state
524 // (checking if --port flag was used). For now, this is a simplified version.
525 // Full implementation would need to track whether port was set via flag.
526
527 return 1; // Consumed 1 arg
528}
529
530// ============================================================================
531// Palette Characters Parser
532// ============================================================================
533
534bool parse_palette_chars(const char *arg, void *dest, char **error_msg) {
535 if (!arg || !dest) {
536 if (error_msg) {
537 *error_msg = strdup("Internal error: NULL argument or destination");
538 }
539 return false;
540 }
541
542 // The dest pointer points to the palette_custom field in options_t
543 // We need to get the full options_t struct to call parse_palette_chars_option
544 // Since we only have the field pointer, we need to handle this directly
545
546 char *palette_custom = (char *)dest;
547
548 if (strlen(arg) >= 256) {
549 if (error_msg) {
550 char *msg = SAFE_MALLOC(256, char *);
551 if (msg) {
552 snprintf(msg, 256, "Invalid palette-chars: too long (%zu chars, max 255)", strlen(arg));
553 *error_msg = msg;
554 }
555 }
556 return false;
557 }
558
559 // Copy the palette characters
560 SAFE_STRNCPY(palette_custom, arg, 256);
561 palette_custom[255] = '\0';
562
563 // Also set the palette type to custom
564 // Note: This is a simplification - ideally we'd have access to the full options_t struct
565 // to set palette_custom_set and palette_type, but the callback interface doesn't provide that.
566 // The palette_type should be handled separately or via a dependency.
567
568 return true;
569}
bool is_session_string(const char *str)
Check if a string matches session string pattern.
Definition discovery.c:83
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
#define SAFE_MALLOC(size, cast)
Definition common.h:208
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:412
log_level_t
Logging levels enumeration.
Definition log/logging.h:59
#define log_debug(...)
Log a DEBUG message.
@ LOG_ERROR
Definition log/logging.h:64
@ LOG_INFO
Definition log/logging.h:62
@ LOG_DEBUG
Definition log/logging.h:61
@ LOG_FATAL
Definition log/logging.h:65
@ LOG_WARN
Definition log/logging.h:63
@ LOG_DEV
Definition log/logging.h:60
#define OPTIONS_BUFF_SIZE
Buffer size for option string values.
Definition options.h:176
palette_type_t
Built-in palette type enumeration.
Definition palette.h:84
@ PALETTE_BLOCKS
Unicode block characters: " ░░▒▒▓▓██".
Definition palette.h:88
@ PALETTE_CUSTOM
User-defined via –palette-chars.
Definition palette.h:96
@ PALETTE_COOL
Ascending blocks: " ▁▂▃▄▅▆▇█".
Definition palette.h:94
@ PALETTE_STANDARD
Standard ASCII palette: " ...',;:clodxkO0KXNWM".
Definition palette.h:86
@ PALETTE_DIGITAL
Digital/glitch aesthetic: " -=≡≣▰▱◼".
Definition palette.h:90
@ PALETTE_MINIMAL
Simple ASCII: " .-+*#".
Definition palette.h:92
render_mode_t
Render mode preferences.
Definition terminal.h:467
terminal_color_mode_t
Terminal color support levels.
Definition terminal.h:424
@ RENDER_MODE_FOREGROUND
Foreground colors only (text color)
Definition terminal.h:469
@ RENDER_MODE_BACKGROUND
Background colors (block colors)
Definition terminal.h:471
@ RENDER_MODE_HALF_BLOCK
Unicode half-block characters (mixed foreground/background)
Definition terminal.h:473
@ TERM_COLOR_NONE
No color support (monochrome terminal)
Definition terminal.h:428
@ TERM_COLOR_16
16-color support (standard ANSI colors)
Definition terminal.h:430
@ TERM_COLOR_256
256-color support (extended ANSI palette)
Definition terminal.h:432
@ TERM_COLOR_AUTO
Auto-detect color support from terminal capabilities.
Definition terminal.h:426
@ TERM_COLOR_TRUECOLOR
24-bit truecolor support (RGB colors)
Definition terminal.h:434
int is_valid_ipv4(const char *ip)
Check if a string is a valid IPv4 address.
Definition ip.c:22
int is_valid_ipv6(const char *ip)
Check if a string is a valid IPv6 address.
Definition ip.c:77
int parse_ipv6_address(const char *input, char *output, size_t output_size)
Parse IPv6 address, removing brackets if present.
Definition ip.c:167
🌍 IP Address Parsing and Formatting Utilities
⚙️ Command-line options parsing and configuration management for ascii-chat
bool parse_render_mode(const char *arg, void *dest, char **error_msg)
Parse render mode option.
Definition parsers.c:131
int parse_server_bind_address(const char *arg, void *config, char **remaining, int num_remaining, char **error_msg)
Parse server bind address positional argument.
Definition parsers.c:306
bool parse_palette_type(const char *arg, void *dest, char **error_msg)
Parse palette type option.
Definition parsers.c:173
bool parse_log_level(const char *arg, void *dest, char **error_msg)
Parse log level option.
Definition parsers.c:233
int parse_client_address(const char *arg, void *config, char **remaining, int num_remaining, char **error_msg)
Parse client address positional argument.
Definition parsers.c:393
bool parse_color_mode(const char *arg, void *dest, char **error_msg)
Parse terminal color level option.
Definition parsers.c:75
bool parse_palette_chars(const char *arg, void *dest, char **error_msg)
Parse custom palette characters option.
Definition parsers.c:534
Custom option parsers for enum types.
Consolidated options structure.
Definition options.h:439
Common SIMD utilities and structures.