ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
ip.c
Go to the documentation of this file.
1
7#include "ip.h"
8#include "parsing.h"
9#include "common.h"
10#include <string.h>
11#include <stdio.h>
12#include <stdlib.h>
13#ifdef _WIN32
14#include <winsock2.h>
15#include <ws2tcpip.h>
16#else
17#include <netinet/in.h>
18#include <arpa/inet.h>
19#endif
20
21// Helper function to validate IPv4 address format
22int is_valid_ipv4(const char *ip) {
23 if (!ip) {
24 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IP format: %s", ip);
25 return 0; // Invalid
26 }
27
28 if (strlen(ip) == 0) {
29 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IP format: %s", ip);
30 return 0; // Invalid
31 }
32
33 if (strlen(ip) > 256) {
34 char ip_buffer[256];
35 SAFE_STRNCPY(ip_buffer, ip, sizeof(ip_buffer));
36 SET_ERRNO(ERROR_INVALID_PARAM, "Suspiciously long ip: %s", ip_buffer);
37 return 0; // Invalid
38 }
39
40 int segments = 0;
41 int value = 0;
42 int digits = 0;
43 int has_leading_zero = 0;
44
45 for (const char *p = ip; *p != '\0'; p++) {
46 if (*p == '.') {
47 // Single '0' is valid, but '01', '001', etc. are not
48 if (digits == 0 || value > 255 || (has_leading_zero && digits > 1))
49 return 0;
50 segments++;
51 value = 0;
52 digits = 0;
53 has_leading_zero = 0;
54 } else if (*p >= '0' && *p <= '9') {
55 // Check for leading zero
56 if (digits == 0 && *p == '0') {
57 has_leading_zero = 1;
58 }
59
60 value = value * 10 + (*p - '0');
61 digits++;
62 if (digits > 3 || value > 255)
63 return 0;
64 } else {
65 return 0; // Invalid character
66 }
67 }
68
69 // Check last segment - single '0' is valid
70 if (digits == 0 || value > 255 || (has_leading_zero && digits > 1))
71 return 0;
72
73 return segments == 3; // Should have exactly 3 dots (4 segments)
74}
75
76// Helper function to validate IPv6 address format
77int is_valid_ipv6(const char *ip) {
78 if (!ip) {
79 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IP format: %s", ip);
80 return 0; // Invalid
81 }
82
83 if (strlen(ip) == 0) {
84 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IP format: %s", ip);
85 return 0; // Invalid
86 }
87
88 if (strlen(ip) > 256) {
89 char ip_buffer[256];
90 SAFE_STRNCPY(ip_buffer, ip, sizeof(ip_buffer));
91 SET_ERRNO(ERROR_INVALID_PARAM, "Suspiciously long ip: %s", ip_buffer);
92 return 0; // Invalid
93 }
94
95 // Special case: "::" is valid (all zeros)
96 if (strcmp(ip, "::") == 0)
97 return 1;
98
99 // Check if it contains at least one colon (basic IPv6 requirement)
100 if (strchr(ip, ':') == NULL)
101 return 0;
102
103 // Reject leading or trailing single colons
104 size_t len = strlen(ip);
105 if (len > 0 && ip[0] == ':' && (len < 2 || ip[1] != ':'))
106 return 0; // Leading single colon (not :: which is handled above)
107 if (len > 0 && ip[len - 1] == ':' && (len < 2 || ip[len - 2] != ':'))
108 return 0; // Trailing single colon
109
110 // Check for IPv4-mapped address (contains dots)
111 int has_ipv4_mapped = (strchr(ip, '.') != NULL);
112
113 // Check for valid characters and count double colons
114 const char *p = ip;
115 int double_colon_count = 0;
116 int segment_len = 0;
117 int segment_count = 0;
118 int last_was_colon = 0;
119
120 while (*p) {
121 if (*p == ':') {
122 if (last_was_colon) {
123 double_colon_count++;
124 if (double_colon_count > 1)
125 return 0; // Multiple :: not allowed
126 }
127 if (segment_len > 0) {
128 segment_count++;
129 if (segment_len > 4)
130 return 0; // Hex segment too long
131 }
132 segment_len = 0;
133 last_was_colon = 1;
134 } else if (*p == '.') {
135 // Dots are allowed for IPv4-mapped addresses
136 // Don't count this as part of hex segment
137 last_was_colon = 0;
138 } else if ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F')) {
139 segment_len++;
140 last_was_colon = 0;
141 } else {
142 return 0; // Invalid character for IPv6
143 }
144 p++;
145 }
146
147 // Count last segment if exists and not ending with colon
148 if (segment_len > 0) {
149 if (!has_ipv4_mapped && segment_len > 4)
150 return 0; // Last hex segment too long
151 segment_count++;
152 }
153
154 // For IPv4-mapped addresses, we have fewer hex segments (typically 6 or 7)
155 // For regular IPv6, we need exactly 8 segments (if no ::) or fewer (if :: present)
156 if (!has_ipv4_mapped) {
157 if (double_colon_count == 0 && segment_count != 8)
158 return 0; // Need exactly 8 segments without ::
159 if (segment_count > 8)
160 return 0; // Too many segments
161 }
162
163 return 1;
164}
165
166// Parse IPv6 address, removing brackets if present
167int parse_ipv6_address(const char *input, char *output, size_t output_size) {
168 if (!input || !output || output_size == 0)
169 return -1;
170
171 size_t input_len = strlen(input);
172 if (input_len == 0)
173 return -1; // Empty string
174
175 const char *start = input;
176 const char *end = input + input_len;
177
178 // Remove brackets if present - but brackets must be matched
179 if (*start == '[') {
180 if (input_len < 2 || *(end - 1) != ']')
181 return -1; // Malformed brackets
182 start++;
183 end--;
184
185 // Check for double brackets [[...]]
186 if (start < end && *start == '[')
187 return -1; // Double opening bracket
188 } else if (*(end - 1) == ']') {
189 // Closing bracket without opening bracket
190 return -1;
191 }
192
193 size_t len = (size_t)(end - start);
194 if (len == 0)
195 return -1; // Empty content after removing brackets
196 if (len >= output_size)
197 return -1; // Buffer too small
198
199 memcpy(output, start, len);
200 output[len] = '\0';
201 return 0;
202}
203
204// Format IP address from socket address structure
205asciichat_error_t format_ip_address(int family, const struct sockaddr *addr, char *output, size_t output_size) {
206 if (!addr || !output || output_size == 0) {
207 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid arguments to format_ip_address");
208 }
209
210 const void *ip_ptr = NULL;
211
212 if (family == AF_INET) {
213 const struct sockaddr_in *addr_in = (const struct sockaddr_in *)addr;
214 ip_ptr = &addr_in->sin_addr;
215 } else if (family == AF_INET6) {
216 const struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *)addr;
217 ip_ptr = &addr_in6->sin6_addr;
218 } else {
219 return SET_ERRNO(ERROR_INVALID_PARAM, "Unsupported address family: %d", family);
220 }
221
222 if (inet_ntop(family, ip_ptr, output, (socklen_t)output_size) == NULL) {
223 return SET_ERRNO(ERROR_NETWORK, "Failed to format IP address");
224 }
225
226 return ASCIICHAT_OK;
227}
228
229// Format IP address with port number
230asciichat_error_t format_ip_with_port(const char *ip, uint16_t port, char *output, size_t output_size) {
231 if (!ip || !output || output_size == 0) {
232 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IP format: %s", ip);
233 }
234
235 if (strlen(ip) == 0) {
236 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IP format: %s", ip);
237 }
238
239 if (strlen(ip) > 256) {
240 char ip_buffer[256];
241 SAFE_STRNCPY(ip_buffer, ip, sizeof(ip_buffer));
242 return SET_ERRNO(ERROR_INVALID_PARAM, "Suspiciously long ip: %s", ip_buffer);
243 }
244
245 // Check if it's IPv6 (contains ':')
246 if (strchr(ip, ':') != NULL) {
247 // IPv6 - use bracket notation
248 size_t written = SAFE_SNPRINTF(output, output_size, "[%s]:%u", ip, port);
249 if (written >= output_size) {
250 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid ip=%s or port=%u", ip, port);
251 }
252 } else {
253 // IPv4 - no brackets
254 size_t written = SAFE_SNPRINTF(output, output_size, "%s:%u", ip, port);
255 if (written >= output_size) {
256 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid ip=%s or port=%u", ip, port);
257 }
258 }
259
260 return ASCIICHAT_OK;
261}
262
263// Helper function to validate hostname format
264// Returns 1 if valid hostname, 0 otherwise
265static int is_valid_hostname(const char *hostname) {
266 if (!hostname || strlen(hostname) == 0 || strlen(hostname) > 253) {
267 return 0;
268 }
269
270 // Hostname must not start or end with hyphen or dot
271 size_t len = strlen(hostname);
272 if (hostname[0] == '-' || hostname[0] == '.' || hostname[len - 1] == '-' || hostname[len - 1] == '.') {
273 return 0;
274 }
275
276 // Check each character: alphanumeric, hyphen, or dot
277 int label_len = 0;
278 for (size_t i = 0; i < len; i++) {
279 char c = hostname[i];
280 if (c == '.') {
281 // Label cannot be empty or exceed 63 characters
282 if (label_len == 0 || label_len > 63) {
283 return 0;
284 }
285 label_len = 0;
286 } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-') {
287 label_len++;
288 if (label_len > 63) {
289 return 0;
290 }
291 } else {
292 return 0; // Invalid character
293 }
294 }
295
296 // Check last label
297 if (label_len == 0 || label_len > 63) {
298 return 0;
299 }
300
301 return 1;
302}
303
304// Parse IP address and port from string
305int parse_ip_with_port(const char *input, char *ip_output, size_t ip_output_size, uint16_t *port_output) {
306 if (!input || !ip_output || !port_output || ip_output_size == 0) {
307 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IP format: %s", input);
308 return -1;
309 }
310
311 if (strlen(input) == 0) {
312 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IP format: %s", input);
313 return -1;
314 }
315
316 if (strlen(input) > 256) {
317 char input_buffer[256];
318 SAFE_STRNCPY(input_buffer, input, sizeof(input_buffer));
319 SET_ERRNO(ERROR_INVALID_PARAM, "Suspiciously long ip: %s", input_buffer);
320 return -1;
321 }
322
323 // Check for IPv6 bracket notation: [2001:db8::1]:8080
324 if (input[0] == '[') {
325 // Find closing bracket
326 const char *bracket_end = strchr(input, ']');
327 if (!bracket_end)
328 return -1; // Malformed - opening bracket but no closing bracket
329
330 // Extract IP address (without brackets)
331 size_t ip_len = (size_t)(bracket_end - input - 1);
332 if (ip_len >= ip_output_size)
333 return -1; // IP too long
334
335 memcpy(ip_output, input + 1, ip_len);
336 ip_output[ip_len] = '\0';
337
338 // Check for port after closing bracket
339 if (bracket_end[1] == ':') {
340 // Parse port using safe integer parsing
341 if (parse_port(bracket_end + 2, port_output) != ASCIICHAT_OK) {
342 return -1; // Invalid port
343 }
344 } else {
345 return -1; // Expected ':' after closing bracket
346 }
347 } else {
348 // IPv4 or hostname: "192.0.2.1:8080" or "example.com:8080"
349 // Find last ':' for port separation
350 const char *colon = strrchr(input, ':');
351 if (!colon)
352 return -1; // No port separator
353
354 // Extract IP/hostname
355 size_t ip_len = (size_t)(colon - input);
356 if (ip_len >= ip_output_size)
357 return -1; // IP too long
358
359 memcpy(ip_output, input, ip_len);
360 ip_output[ip_len] = '\0';
361
362 // Reject IPv6 without brackets (check if extracted IP contains ':')
363 // This catches cases like "::1:8080" which should be "[::1]:8080"
364 if (strchr(ip_output, ':') != NULL)
365 return -1; // IPv6 address without brackets - invalid format
366
367 // Parse port using safe integer parsing
368 if (parse_port(colon + 1, port_output) != ASCIICHAT_OK) {
369 return -1; // Invalid port
370 }
371 }
372
373 return 0;
374}
375
376// Parse address with optional port from string
377// Handles IPv4, IPv6 (with or without brackets), and hostnames
378// If no port is specified, uses default_port
379int parse_address_with_optional_port(const char *input, char *address_output, size_t address_output_size,
380 uint16_t *port_output, uint16_t default_port) {
381 if (!input || !address_output || !port_output || address_output_size == 0) {
382 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid arguments to parse_address_with_optional_port");
383 return -1;
384 }
385
386 size_t input_len = strlen(input);
387 if (input_len == 0) {
388 SET_ERRNO(ERROR_INVALID_PARAM, "Empty address string");
389 return -1;
390 }
391
392 if (input_len > 256) {
393 SET_ERRNO(ERROR_INVALID_PARAM, "Address string too long");
394 return -1;
395 }
396
397 // Case 1: Bracketed IPv6 address with or without port
398 // Examples: "[::1]" or "[::1]:8080" or "[2001:db8::1]:443"
399 if (input[0] == '[') {
400 const char *bracket_end = strchr(input, ']');
401 if (!bracket_end) {
402 SET_ERRNO(ERROR_INVALID_PARAM, "Malformed bracketed address (missing ']'): %s", input);
403 return -1;
404 }
405
406 // Extract address without brackets
407 size_t addr_len = (size_t)(bracket_end - input - 1);
408 if (addr_len == 0) {
409 SET_ERRNO(ERROR_INVALID_PARAM, "Empty bracketed address: %s", input);
410 return -1;
411 }
412 if (addr_len >= address_output_size) {
413 SET_ERRNO(ERROR_INVALID_PARAM, "Address too long for buffer");
414 return -1;
415 }
416
417 memcpy(address_output, input + 1, addr_len);
418 address_output[addr_len] = '\0';
419
420 // Validate it's a valid IPv6 address
421 if (!is_valid_ipv6(address_output)) {
422 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid IPv6 address: %s", address_output);
423 return -1;
424 }
425
426 // Check for port after closing bracket
427 if (bracket_end[1] == '\0') {
428 // No port specified, use default
429 *port_output = default_port;
430 } else if (bracket_end[1] == ':') {
431 // Port specified
432 if (parse_port(bracket_end + 2, port_output) != ASCIICHAT_OK) {
433 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid port in: %s", input);
434 return -1;
435 }
436 } else {
437 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid format after ']' (expected ':' or end): %s", input);
438 return -1;
439 }
440 return 0;
441 }
442
443 // Case 2: Check if it's a valid IPv6 address without brackets (no port allowed in this case)
444 // Examples: "::1" or "2001:db8::1" or "fe80::1"
445 // Note: IPv6 addresses contain colons, so we can't use ':' as port separator without brackets
446 if (is_valid_ipv6(input)) {
447 if (strlen(input) >= address_output_size) {
448 SET_ERRNO(ERROR_INVALID_PARAM, "Address too long for buffer");
449 return -1;
450 }
451 SAFE_STRNCPY(address_output, input, address_output_size);
452 *port_output = default_port;
453 return 0;
454 }
455
456 // Case 3: IPv4 or hostname, possibly with port
457 // Examples: "192.168.1.1" or "192.168.1.1:8080" or "localhost" or "example.com:443"
458 const char *last_colon = strrchr(input, ':');
459
460 if (last_colon == NULL) {
461 // No colon - just address/hostname, no port
462 if (strlen(input) >= address_output_size) {
463 SET_ERRNO(ERROR_INVALID_PARAM, "Address too long for buffer");
464 return -1;
465 }
466
467 // Validate it's a valid IPv4 or hostname
468 if (!is_valid_ipv4(input) && !is_valid_hostname(input)) {
469 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid address format: %s", input);
470 return -1;
471 }
472
473 SAFE_STRNCPY(address_output, input, address_output_size);
474 *port_output = default_port;
475 return 0;
476 }
477
478 // Has colon - extract address and port
479 size_t addr_len = (size_t)(last_colon - input);
480 if (addr_len == 0) {
481 SET_ERRNO(ERROR_INVALID_PARAM, "Empty address before port: %s", input);
482 return -1;
483 }
484 if (addr_len >= address_output_size) {
485 SET_ERRNO(ERROR_INVALID_PARAM, "Address too long for buffer");
486 return -1;
487 }
488
489 memcpy(address_output, input, addr_len);
490 address_output[addr_len] = '\0';
491
492 // Check if extracted address contains ':' - that would be IPv6 without brackets (invalid with port)
493 if (strchr(address_output, ':') != NULL) {
494 SET_ERRNO(ERROR_INVALID_PARAM, "IPv6 addresses must use brackets when specifying port: [%s]:%s instead of %s",
495 address_output, last_colon + 1, input);
496 return -1;
497 }
498
499 // Validate address part is valid IPv4 or hostname
500 if (!is_valid_ipv4(address_output) && !is_valid_hostname(address_output)) {
501 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid address format: %s", address_output);
502 return -1;
503 }
504
505 // Parse port
506 if (parse_port(last_colon + 1, port_output) != ASCIICHAT_OK) {
507 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid port in: %s", input);
508 return -1;
509 }
510
511 return 0;
512}
unsigned short uint16_t
Definition common.h:57
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:412
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ERROR_NETWORK
Definition error_codes.h:69
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_INVALID_PARAM
asciichat_error_t format_ip_with_port(const char *ip, uint16_t port, char *output, size_t output_size)
Format IP address with port number.
Definition ip.c:230
int is_valid_ipv4(const char *ip)
Check if a string is a valid IPv4 address.
Definition ip.c:22
int parse_ip_with_port(const char *input, char *ip_output, size_t ip_output_size, uint16_t *port_output)
Parse IP address and port from string.
Definition ip.c:305
int is_valid_ipv6(const char *ip)
Check if a string is a valid IPv6 address.
Definition ip.c:77
int parse_address_with_optional_port(const char *input, char *address_output, size_t address_output_size, uint16_t *port_output, uint16_t default_port)
Parse address with optional port from string.
Definition ip.c:379
int parse_ipv6_address(const char *input, char *output, size_t output_size)
Parse IPv6 address, removing brackets if present.
Definition ip.c:167
asciichat_error_t format_ip_address(int family, const struct sockaddr *addr, char *output, size_t output_size)
Format IP address from socket address structure.
Definition ip.c:205
🌍 IP Address Parsing and Formatting Utilities
asciichat_error_t parse_port(const char *str, uint16_t *out_port)
Parse port number (1-65535) from string.
Definition parsing.c:221
🔍 Safe Parsing Utilities
🔤 String Manipulation and Shell Escaping Utilities