ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
https_keys.c
Go to the documentation of this file.
1
8#include <ascii-chat/common.h>
13#include <ascii-chat/util/url.h> // For url_parse()
15#include <string.h>
16#include <stdlib.h>
17#include <stdio.h>
18#ifndef _WIN32
19#include <unistd.h>
20#endif
21#include <fcntl.h>
22
23// =============================================================================
24// Helper Functions
25// =============================================================================
26
31static asciichat_error_t https_fetch_keys(const char *url, char **response_text, size_t *response_len) {
32 if (!url || !response_text || !response_len) {
33 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
35 }
36
37 // Parse URL to extract hostname and path
38 url_parts_t url_parts = {0};
39 asciichat_error_t parse_result = url_parse(url, &url_parts);
40 if (parse_result != ASCIICHAT_OK) {
41 return parse_result;
42 }
43
44 // Use https_get from http_client
45 char *response = https_get(url_parts.host, url_parts.path);
46 url_parts_destroy(&url_parts);
47
48 if (!response) {
49 SET_ERRNO(ERROR_NETWORK, "Failed to fetch from %s", url);
50 return ERROR_NETWORK;
51 }
52
53 *response_text = response;
54 *response_len = strlen(response);
55 return ASCIICHAT_OK;
56}
57
58// =============================================================================
59// URL Construction
60// =============================================================================
61
62asciichat_error_t build_github_ssh_url(const char *username, char *url_out, size_t url_size) {
63 if (!username || !url_out) {
64 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: username=%p, url_out=%p", username, url_out);
66 }
67
68 if (url_size < 64) {
69 SET_ERRNO(ERROR_INVALID_PARAM, "URL buffer too small: %zu (minimum 64)", url_size);
71 }
72
73 // Construct GitHub SSH keys URL: https://github.com/username.keys
74 int result = safe_snprintf(url_out, url_size, "https://github.com/%s.keys", username);
75 if (result < 0 || result >= (int)url_size) {
76 SET_ERRNO(ERROR_STRING, "Failed to construct GitHub SSH URL");
77 return ERROR_STRING;
78 }
79
80 return ASCIICHAT_OK;
81}
82
83asciichat_error_t build_gitlab_ssh_url(const char *username, char *url_out, size_t url_size) {
84 if (!username || !url_out) {
85 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: username=%p, url_out=%p", username, url_out);
87 }
88
89 if (url_size < 64) {
90 SET_ERRNO(ERROR_INVALID_PARAM, "URL buffer too small: %zu (minimum 64)", url_size);
92 }
93
94 // Construct GitLab SSH keys URL: https://gitlab.com/username.keys
95 int result = safe_snprintf(url_out, url_size, "https://gitlab.com/%s.keys", username);
96 if (result < 0 || result >= (int)url_size) {
97 SET_ERRNO(ERROR_STRING, "Failed to construct GitLab SSH URL");
98 return ERROR_STRING;
99 }
100
101 return ASCIICHAT_OK;
102}
103
104asciichat_error_t build_github_gpg_url(const char *username, char *url_out, size_t url_size) {
105 if (!username || !url_out) {
106 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: username=%p, url_out=%p", username, url_out);
107 return ERROR_INVALID_PARAM;
108 }
109
110 if (url_size < 64) {
111 SET_ERRNO(ERROR_INVALID_PARAM, "URL buffer too small: %zu (minimum 64)", url_size);
112 return ERROR_INVALID_PARAM;
113 }
114
115 // Strip .gpg suffix if user already included it (e.g., "github:zfogg.gpg")
116 char clean_username[BUFFER_SIZE_SMALL];
117 SAFE_STRNCPY(clean_username, username, sizeof(clean_username) - 1);
118 size_t len = strlen(clean_username);
119 if (len > 4 && strcmp(clean_username + len - 4, ".gpg") == 0) {
120 clean_username[len - 4] = '\0'; // Remove .gpg suffix
121 }
122
123 // Construct GitHub GPG keys URL: https://github.com/username.gpg
124 int result = safe_snprintf(url_out, url_size, "https://github.com/%s.gpg", clean_username);
125 if (result < 0 || result >= (int)url_size) {
126 SET_ERRNO(ERROR_STRING, "Failed to construct GitHub GPG URL");
127 return ERROR_STRING;
128 }
129
130 return ASCIICHAT_OK;
131}
132
133asciichat_error_t build_gitlab_gpg_url(const char *username, char *url_out, size_t url_size) {
134 if (!username || !url_out) {
135 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: username=%p, url_out=%p", username, url_out);
136 return ERROR_INVALID_PARAM;
137 }
138
139 if (url_size < 64) {
140 SET_ERRNO(ERROR_INVALID_PARAM, "URL buffer too small: %zu (minimum 64)", url_size);
141 return ERROR_INVALID_PARAM;
142 }
143
144 // Strip .gpg suffix if user already included it (e.g., "gitlab:zfogg.gpg")
145 char clean_username[BUFFER_SIZE_SMALL];
146 SAFE_STRNCPY(clean_username, username, sizeof(clean_username) - 1);
147 size_t len = strlen(clean_username);
148 if (len > 4 && strcmp(clean_username + len - 4, ".gpg") == 0) {
149 clean_username[len - 4] = '\0'; // Remove .gpg suffix
150 }
151
152 // Construct GitLab GPG keys URL: https://gitlab.com/username.gpg
153 int result = safe_snprintf(url_out, url_size, "https://gitlab.com/%s.gpg", clean_username);
154 if (result < 0 || result >= (int)url_size) {
155 SET_ERRNO(ERROR_STRING, "Failed to construct GitLab GPG URL");
156 return ERROR_STRING;
157 }
158
159 return ASCIICHAT_OK;
160}
161
162// =============================================================================
163// HTTPS Key Fetching Implementation
164// =============================================================================
165
166asciichat_error_t fetch_github_ssh_keys(const char *username, char ***keys_out, size_t *num_keys) {
167 if (!username || !keys_out || !num_keys) {
168 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: username=%p, keys_out=%p, num_keys=%p", username, keys_out,
169 num_keys);
170 return ERROR_INVALID_PARAM;
171 }
172
173 // Build the GitHub SSH keys URL
174 char url[BUFFER_SIZE_SMALL];
175 asciichat_error_t url_result = build_github_ssh_url(username, url, sizeof(url));
176 if (url_result != ASCIICHAT_OK) {
177 return url_result;
178 }
179
180 // Fetch the keys using HTTPS
181 char *response_text = NULL;
182 size_t response_len = 0;
183 asciichat_error_t fetch_result = https_fetch_keys(url, &response_text, &response_len);
184 if (fetch_result != ASCIICHAT_OK) {
185 return fetch_result;
186 }
187
188 // Parse SSH keys from the response
189 asciichat_error_t parse_result =
190 parse_ssh_keys_from_response(response_text, response_len, keys_out, num_keys, MAX_CLIENTS);
191
192 // Clean up response text
193 SAFE_FREE(response_text);
194
195 return parse_result;
196}
197
198asciichat_error_t fetch_gitlab_ssh_keys(const char *username, char ***keys_out, size_t *num_keys) {
199 if (!username || !keys_out || !num_keys) {
200 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: username=%p, keys_out=%p, num_keys=%p", username, keys_out,
201 num_keys);
202 return ERROR_INVALID_PARAM;
203 }
204
205 // Build the GitLab SSH keys URL
206 char url[BUFFER_SIZE_SMALL];
207 asciichat_error_t url_result = build_gitlab_ssh_url(username, url, sizeof(url));
208 if (url_result != ASCIICHAT_OK) {
209 return url_result;
210 }
211
212 // Fetch the keys using HTTPS
213 char *response_text = NULL;
214 size_t response_len = 0;
215 asciichat_error_t fetch_result = https_fetch_keys(url, &response_text, &response_len);
216 if (fetch_result != ASCIICHAT_OK) {
217 return fetch_result;
218 }
219
220 // Parse SSH keys from the response
221 asciichat_error_t parse_result =
222 parse_ssh_keys_from_response(response_text, response_len, keys_out, num_keys, MAX_CLIENTS);
223
224 // Clean up response text
225 SAFE_FREE(response_text);
226
227 return parse_result;
228}
229
230asciichat_error_t fetch_github_gpg_keys(const char *username, char ***keys_out, size_t *num_keys) {
231 if (!username || !keys_out || !num_keys) {
232 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: username=%p, keys_out=%p, num_keys=%p", username, keys_out,
233 num_keys);
234 return ERROR_INVALID_PARAM;
235 }
236
237 // Build the GitHub GPG keys URL
238 char url[BUFFER_SIZE_SMALL];
239 asciichat_error_t url_result = build_github_gpg_url(username, url, sizeof(url));
240 if (url_result != ASCIICHAT_OK) {
241 return url_result;
242 }
243
244 // Fetch the keys using HTTPS
245 char *response_text = NULL;
246 size_t response_len = 0;
247 asciichat_error_t fetch_result = https_fetch_keys(url, &response_text, &response_len);
248 if (fetch_result != ASCIICHAT_OK) {
249 return fetch_result;
250 }
251
252 // Parse GPG keys from the response
253 asciichat_error_t parse_result =
254 parse_gpg_keys_from_response(response_text, response_len, keys_out, num_keys, MAX_CLIENTS);
255
256 // Clean up response text
257 SAFE_FREE(response_text);
258
259 return parse_result;
260}
261
262asciichat_error_t fetch_gitlab_gpg_keys(const char *username, char ***keys_out, size_t *num_keys) {
263 if (!username || !keys_out || !num_keys) {
264 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: username=%p, keys_out=%p, num_keys=%p", username, keys_out,
265 num_keys);
266 return ERROR_INVALID_PARAM;
267 }
268
269 // Build the GitLab GPG keys URL
270 char url[BUFFER_SIZE_SMALL];
271 asciichat_error_t url_result = build_gitlab_gpg_url(username, url, sizeof(url));
272 if (url_result != ASCIICHAT_OK) {
273 return url_result;
274 }
275
276 // Fetch the keys using HTTPS
277 char *response_text = NULL;
278 size_t response_len = 0;
279 asciichat_error_t fetch_result = https_fetch_keys(url, &response_text, &response_len);
280 if (fetch_result != ASCIICHAT_OK) {
281 return fetch_result;
282 }
283
284 // Parse GPG keys from the response
285 asciichat_error_t parse_result =
286 parse_gpg_keys_from_response(response_text, response_len, keys_out, num_keys, MAX_CLIENTS);
287
288 // Clean up response text
289 SAFE_FREE(response_text);
290
291 return parse_result;
292}
293
294// =============================================================================
295// Key Parsing from HTTPS Responses
296// =============================================================================
297
298asciichat_error_t parse_ssh_keys_from_response(const char *response_text, size_t response_len, char ***keys_out,
299 size_t *num_keys, size_t max_keys) {
300 if (!response_text || !keys_out || !num_keys) {
301 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for SSH key parsing");
302 return ERROR_INVALID_PARAM;
303 }
304
305 *num_keys = 0;
306 *keys_out = NULL;
307
308 // Count the number of SSH keys in the response
309 size_t key_count = 0;
310 const char *line_start = response_text;
311 const char *line_end;
312
313 while ((line_end = strchr(line_start, '\n')) != NULL) {
314 size_t line_len = line_end - line_start;
315 if (line_len > 0 && line_start[0] != '\r' && line_start[0] != '\n') {
316 key_count++;
317 }
318 line_start = line_end + 1;
319 }
320
321 // Handle last line if it doesn't end with newline
322 if (line_start < response_text + response_len) {
323 key_count++;
324 }
325
326 if (key_count == 0) {
327 SET_ERRNO(ERROR_CRYPTO_KEY, "No SSH keys found in response");
328 return ERROR_CRYPTO_KEY;
329 }
330
331 if (key_count > max_keys) {
332 key_count = max_keys;
333 }
334
335 // Allocate array for key strings
336 *keys_out = SAFE_MALLOC(sizeof(char *) * key_count, char **);
337 if (!*keys_out) {
338 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate SSH keys array");
339 }
340
341 // Parse each SSH key line
342 line_start = response_text;
343 size_t parsed_keys = 0;
344
345 while (parsed_keys < key_count && (line_end = strchr(line_start, '\n')) != NULL) {
346 size_t line_len = line_end - line_start;
347
348 // Skip empty lines
349 if (line_len > 0 && line_start[0] != '\r' && line_start[0] != '\n') {
350 // Allocate space for this key line
351 (*keys_out)[parsed_keys] = SAFE_MALLOC(line_len + 1, char *);
352 if (!(*keys_out)[parsed_keys]) {
353 // Cleanup previously allocated keys
354 for (size_t i = 0; i < parsed_keys; i++) {
355 SAFE_FREE((*keys_out)[i]);
356 }
357 SAFE_FREE(*keys_out);
358 *keys_out = NULL;
359 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate SSH key string");
360 }
361
362 // Copy the key line
363 memcpy((*keys_out)[parsed_keys], line_start, line_len);
364 (*keys_out)[parsed_keys][line_len] = '\0';
365
366 parsed_keys++;
367 }
368
369 line_start = line_end + 1;
370 }
371
372 // Handle last line if it doesn't end with newline
373 if (parsed_keys < key_count && line_start < response_text + response_len) {
374 size_t line_len = (response_text + response_len) - line_start;
375 if (line_len > 0) {
376 (*keys_out)[parsed_keys] = SAFE_MALLOC(line_len + 1, char *);
377 if (!(*keys_out)[parsed_keys]) {
378 // Cleanup previously allocated keys
379 for (size_t i = 0; i < parsed_keys; i++) {
380 SAFE_FREE((*keys_out)[i]);
381 }
382 SAFE_FREE(*keys_out);
383 *keys_out = NULL;
384 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate SSH key string");
385 }
386 memcpy((*keys_out)[parsed_keys], line_start, line_len);
387 (*keys_out)[parsed_keys][line_len] = '\0';
388 parsed_keys++;
389 }
390 }
391
392 *num_keys = parsed_keys;
393 return ASCIICHAT_OK;
394}
395
396asciichat_error_t parse_gpg_keys_from_response(const char *response_text, size_t response_len, char ***keys_out,
397 size_t *num_keys, size_t max_keys) {
398 (void)max_keys;
399 if (!response_text || !keys_out || !num_keys) {
400 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for GPG key parsing");
401 return ERROR_INVALID_PARAM;
402 }
403
404 *num_keys = 0;
405 *keys_out = NULL;
406
407 // Check if this looks like a GPG key (starts with -----BEGIN PGP)
408 if (strncmp(response_text, "-----BEGIN PGP", 14) != 0) {
409 SET_ERRNO(ERROR_CRYPTO_KEY, "Response does not contain a valid GPG key");
410 return ERROR_CRYPTO_KEY;
411 }
412
413 // Write armored block to temp file
414 char temp_file[PLATFORM_MAX_PATH_LENGTH];
415 int fd = -1;
416 if (platform_create_temp_file(temp_file, sizeof(temp_file), "asc_gpg_import", &fd) != 0) {
417 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to create temp file for GPG import");
418 }
419
420#ifdef _WIN32
421 // On Windows, platform_create_temp_file returns fd=-1, need to open separately
422 fd = platform_open("https_key_temp", temp_file, O_WRONLY | O_BINARY);
423 if (fd < 0) {
424 platform_delete_temp_file(temp_file);
425 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to open temp file for writing");
426 }
427#endif
428
429 ssize_t written = write(fd, response_text, response_len);
430 close(fd);
431
432 if (written != (ssize_t)response_len) {
433 platform_unlink(temp_file);
434 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to write GPG key to temp file");
435 }
436
437 // Import the key using gpg --import
438 char import_cmd[BUFFER_SIZE_MEDIUM];
439 safe_snprintf(import_cmd, sizeof(import_cmd), "gpg --import '%s' 2>&1", temp_file);
440 FILE *import_fp = NULL;
441 if (platform_popen("gpg_https_import", import_cmd, "r", &import_fp) != ASCIICHAT_OK || !import_fp) {
442 platform_unlink(temp_file);
443 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to run gpg --import");
444 }
445
446 char import_output[2048];
447 size_t import_len = fread(import_output, 1, sizeof(import_output) - 1, import_fp);
448 import_output[import_len] = '\0';
449 platform_pclose(&import_fp);
450 platform_unlink(temp_file);
451
452 // Extract ALL key IDs from import output (format: "gpg: key KEYID: ...")
453 // GitHub often returns multiple keys in one armored block
454 const char *key_marker = "gpg: key ";
455 char key_ids[16][17]; // Support up to 16 keys
456 size_t key_count = 0;
457
458 log_debug("GPG import output:\n%s", import_output);
459
460 char *search_pos = import_output;
461 while (key_count < 16) {
462 char *key_line = strstr(search_pos, key_marker);
463 if (!key_line)
464 break;
465
466 key_line += strlen(key_marker);
467 int i = 0;
468 while (i < 16 && key_line[i] != ':' && key_line[i] != ' ' && key_line[i] != '\n') {
469 key_ids[key_count][i] = key_line[i];
470 i++;
471 }
472 key_ids[key_count][i] = '\0';
473
474 if (i > 0) {
475 log_debug("Extracted GPG key ID #%zu: %s", key_count, key_ids[key_count]);
476 key_count++;
477 }
478 search_pos = key_line + i;
479 }
480
481 if (key_count == 0) {
482 return SET_ERRNO(ERROR_CRYPTO_KEY, "Failed to extract any key IDs from GPG import output");
483 }
484
485 log_debug("Total GPG keys extracted from import: %zu", key_count);
486
487 // Allocate array for results
488 *keys_out = SAFE_MALLOC(sizeof(char *) * key_count, char **);
489 if (!*keys_out) {
490 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate GPG keys array");
491 }
492
493 // Process each key ID to get full fingerprint
494 size_t valid_keys = 0;
495 for (size_t k = 0; k < key_count; k++) {
496 // Get full fingerprint from gpg --list-keys output
497 char list_cmd[BUFFER_SIZE_SMALL];
498 safe_snprintf(list_cmd, sizeof(list_cmd),
499 "gpg --list-keys --with-colons --fingerprint '%s' " PLATFORM_SHELL_NULL_REDIRECT, key_ids[k]);
500 FILE *list_fp = NULL;
501 if (platform_popen("gpg_https_list", list_cmd, "r", &list_fp) != ASCIICHAT_OK || !list_fp) {
502 continue; // Skip this key if we can't list it
503 }
504
505 char list_output[4096];
506 size_t list_len = fread(list_output, 1, sizeof(list_output) - 1, list_fp);
507 list_output[list_len] = '\0';
508 platform_pclose(&list_fp);
509
510 // Check if it contains an Ed25519 key (algorithm 22)
511 if (!strstr(list_output, ":22:") && !strstr(list_output, "ed25519")) {
512 continue; // Skip non-Ed25519 keys
513 }
514
515 // Extract full 40-character fingerprint from "fpr:" line
516 // Format: fpr:::::::::FINGERPRINT:
517 // The fingerprint is field 10 (after 9 colons)
518 char fingerprint[41] = {0};
519 const char *fpr_marker = "\nfpr:";
520 char *fpr_line = strstr(list_output, fpr_marker);
521 if (fpr_line) {
522 fpr_line += 1; // Skip the newline
523 // Count 9 colons from the start of the line
524 int colon_count = 0;
525 while (*fpr_line && colon_count < 9) {
526 if (*fpr_line == ':')
527 colon_count++;
528 fpr_line++;
529 }
530 // Now we should be at the start of the fingerprint
531 // Extract up to 40 hex characters
532 int fpr_len = 0;
533 while (fpr_len < 40 && fpr_line[fpr_len] && fpr_line[fpr_len] != ':' && fpr_line[fpr_len] != '\n') {
534 fingerprint[fpr_len] = fpr_line[fpr_len];
535 fpr_len++;
536 }
537 fingerprint[fpr_len] = '\0';
538 }
539
540 // If fingerprint extraction failed, use the short key ID
541 if (strlen(fingerprint) == 0) {
542 log_warn("Failed to extract fingerprint for key %s, using short key ID", key_ids[k]);
543 SAFE_STRNCPY(fingerprint, key_ids[k], sizeof(fingerprint));
544 }
545
546 log_debug("Key %s -> fingerprint: %s (length: %zu)", key_ids[k], fingerprint, strlen(fingerprint));
547
548 // Allocate and store this key in gpg:KEYID format
549 size_t gpg_key_len = strlen("gpg:") + strlen(fingerprint) + 1;
550 (*keys_out)[valid_keys] = SAFE_MALLOC(gpg_key_len, char *);
551 if (!(*keys_out)[valid_keys]) {
552 // Cleanup on allocation failure
553 for (size_t cleanup = 0; cleanup < valid_keys; cleanup++) {
554 SAFE_FREE((*keys_out)[cleanup]);
555 }
556 SAFE_FREE(*keys_out);
557 *keys_out = NULL;
558 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate GPG key string");
559 }
560
561 safe_snprintf((*keys_out)[valid_keys], gpg_key_len, "gpg:%s", fingerprint);
562 log_debug("Added valid Ed25519 key #%zu: %s", valid_keys, (*keys_out)[valid_keys]);
563 valid_keys++;
564 }
565
566 if (valid_keys == 0) {
567 SAFE_FREE(*keys_out);
568 *keys_out = NULL;
569 return SET_ERRNO(ERROR_CRYPTO_KEY, "No valid Ed25519 keys found in imported GPG keys");
570 }
571
572 *num_keys = valid_keys;
573 return ASCIICHAT_OK;
574}
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
⚙️ Common definitions, error codes, macros, and types shared throughout the application
Cross-platform filesystem operations.
#define BUFFER_SIZE_SMALL
Small buffer size (256 bytes)
#define BUFFER_SIZE_MEDIUM
Medium buffer size (512 bytes)
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_CRYPTO_KEY
Definition error_codes.h:97
@ ERROR_NETWORK
Definition error_codes.h:77
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_STRING
@ ERROR_INVALID_PARAM
asciichat_error_t build_github_ssh_url(const char *username, char *url_out, size_t url_size)
Construct GitHub SSH keys URL.
Definition https_keys.c:62
asciichat_error_t fetch_gitlab_gpg_keys(const char *username, char ***keys_out, size_t *num_keys)
Fetch GPG keys from GitLab using HTTPS.
Definition https_keys.c:262
asciichat_error_t build_github_gpg_url(const char *username, char *url_out, size_t url_size)
Construct GitHub GPG keys URL.
Definition https_keys.c:104
asciichat_error_t parse_ssh_keys_from_response(const char *response_text, size_t response_len, char ***keys_out, size_t *num_keys, size_t max_keys)
Parse SSH keys from HTTPS response text.
Definition https_keys.c:298
asciichat_error_t fetch_github_ssh_keys(const char *username, char ***keys_out, size_t *num_keys)
Fetch SSH keys from GitHub using HTTPS.
Definition https_keys.c:166
asciichat_error_t fetch_github_gpg_keys(const char *username, char ***keys_out, size_t *num_keys)
Fetch GPG keys from GitHub using HTTPS.
Definition https_keys.c:230
asciichat_error_t parse_gpg_keys_from_response(const char *response_text, size_t response_len, char ***keys_out, size_t *num_keys, size_t max_keys)
Parse GPG keys from HTTPS response text.
Definition https_keys.c:396
asciichat_error_t fetch_gitlab_ssh_keys(const char *username, char ***keys_out, size_t *num_keys)
Fetch SSH keys from GitLab using HTTPS.
Definition https_keys.c:198
asciichat_error_t build_gitlab_ssh_url(const char *username, char *url_out, size_t url_size)
Construct GitLab SSH keys URL.
Definition https_keys.c:83
asciichat_error_t build_gitlab_gpg_url(const char *username, char *url_out, size_t url_size)
Construct GitLab GPG keys URL.
Definition https_keys.c:133
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
Definition limits.h:26
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
char * https_get(const char *hostname, const char *path)
Perform HTTPS GET request.
asciichat_error_t platform_popen(const char *name, const char *command, const char *mode, FILE **out_stream)
Execute a command and return a file stream for reading/writing.
int platform_create_temp_file(char *path_out, size_t path_size, const char *prefix, int *fd)
Create a temporary file with a given prefix.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
int platform_unlink(const char *pathname)
Delete/unlink file.
int platform_open(const char *name, const char *pathname, int flags,...)
Safe file open (open replacement)
#define PLATFORM_SHELL_NULL_REDIRECT
Shell null device/error redirect for platform.
asciichat_error_t platform_pclose(FILE **stream_ptr)
Close a process stream opened with platform_popen()
int platform_delete_temp_file(const char *path)
Delete a temporary file.
void url_parts_destroy(url_parts_t *parts)
Free parsed URL components.
Definition url.c:281
asciichat_error_t url_parse(const char *url, url_parts_t *parts_out)
Parse HTTP(S), WebSocket, and TCP URL into components.
Definition url.c:166
Simple HTTPS client for fetching public keys from GitHub/GitLab.
Cross-platform string operations.
Cross-platform process types and execution utilities.
Structure containing parsed URL components.
Definition url.h:57
char * path
Extracted path, query, and fragment (allocated if present, may be NULL)
Definition url.h:63
char * host
Extracted hostname or IP address (allocated)
Definition url.h:60
#define PLATFORM_MAX_PATH_LENGTH
Definition system.c:69
🌐 Production-Grade URL Validation and Parsing
🔤 String Manipulation and Shell Escaping Utilities