ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
regex.c
Go to the documentation of this file.
1
10#include "ascii-chat/asciichat_errno.h"
11#include <ascii-chat/crypto/regex.h>
12#include <ascii-chat/common.h>
13#include <ascii-chat/util/pcre2.h>
14#include <pcre2.h>
15#include <string.h>
16#include <stdlib.h>
17
18/* ═══════════════════════════════════════════════════════════════════════════
19 * PCRE2 REGEX PATTERNS
20 * ═══════════════════════════════════════════════════════════════════════════ */
21
32static const char *KNOWN_HOSTS_REGEX_PATTERN = "^(?<ip_port>\\S+)" // IP:port (non-whitespace)
33 "\\s+" // Whitespace separator
34 "(?<key_type>\\S+)" // Key type (x25519 or no-identity)
35 "(?:" // Optional key and comment group
36 "\\s+" // Whitespace separator
37 "(?<hex_key>[0-9a-fA-F]{64})?" // Optional 64-char hex key
38 "(?:\\s+(?<comment>.*))?)" // Optional comment
39 "\\s*$"; // Optional trailing whitespace
40
49static const char *SSH_PUBLIC_KEY_REGEX_PATTERN = "ssh-ed25519" // Literal prefix
50 "\\s+" // Whitespace separator
51 "(?<base64_key>[A-Za-z0-9+/]+=*)" // Base64 key with optional padding
52 "(?:\\s+(?<comment>.*))?"; // Optional comment
53
63static const char *OPENSSH_PEM_REGEX_PATTERN =
64 "-----BEGIN OPENSSH PRIVATE KEY-----\\s*" // Header with optional whitespace
65 "(?<base64_data>[A-Za-z0-9+/=\\s]+?)" // Multiline base64 (lazy match)
66 "\\s*-----END OPENSSH PRIVATE KEY-----"; // Footer with optional whitespace
67
75static const char *GPG_KEYGRIP_REGEX_PATTERN = "^grp:(?:[^:]*:){8}(?<keygrip>[A-Fa-f0-9]{40}):"; // GPG keygrip format
76
77/* ═══════════════════════════════════════════════════════════════════════════
78 * PCRE2 REGEX SINGLETONS
79 *
80 * Individual lazy-initialized singletons for each regex pattern.
81 * Compiled regex is read-only after initialization, safe for concurrent reads.
82 * ═══════════════════════════════════════════════════════════════════════════ */
83
84static pcre2_singleton_t *g_known_hosts_regex = NULL;
85static pcre2_singleton_t *g_ssh_public_key_regex = NULL;
86static pcre2_singleton_t *g_openssh_pem_regex = NULL;
87static pcre2_singleton_t *g_gpg_keygrip_regex = NULL;
88
92static pcre2_code *crypto_regex_get_known_hosts(void) {
93 if (g_known_hosts_regex == NULL) {
94 g_known_hosts_regex =
95 asciichat_pcre2_singleton_compile(KNOWN_HOSTS_REGEX_PATTERN, PCRE2_MULTILINE | PCRE2_UCP | PCRE2_UTF);
96 }
97 return asciichat_pcre2_singleton_get_code(g_known_hosts_regex);
98}
99
103static pcre2_code *crypto_regex_get_ssh_public_key(void) {
104 if (g_ssh_public_key_regex == NULL) {
105 g_ssh_public_key_regex =
106 asciichat_pcre2_singleton_compile(SSH_PUBLIC_KEY_REGEX_PATTERN, PCRE2_CASELESS | PCRE2_UCP | PCRE2_UTF);
107 }
108 return asciichat_pcre2_singleton_get_code(g_ssh_public_key_regex);
109}
110
114static pcre2_code *crypto_regex_get_openssh_pem(void) {
115 if (g_openssh_pem_regex == NULL) {
116 g_openssh_pem_regex = asciichat_pcre2_singleton_compile(OPENSSH_PEM_REGEX_PATTERN,
117 PCRE2_MULTILINE | PCRE2_DOTALL | PCRE2_UCP | PCRE2_UTF);
118 }
119 return asciichat_pcre2_singleton_get_code(g_openssh_pem_regex);
120}
121
125static pcre2_code *crypto_regex_get_gpg_keygrip(void) {
126 if (g_gpg_keygrip_regex == NULL) {
127 g_gpg_keygrip_regex = asciichat_pcre2_singleton_compile(GPG_KEYGRIP_REGEX_PATTERN, PCRE2_UCP | PCRE2_UTF);
128 }
129 return asciichat_pcre2_singleton_get_code(g_gpg_keygrip_regex);
130}
131
132/* ═══════════════════════════════════════════════════════════════════════════
133 * PUBLIC API IMPLEMENTATION
134 * ═══════════════════════════════════════════════════════════════════════════ */
135
136bool crypto_regex_match_known_hosts(const char *line, char **ip_port_out, char **key_type_out, char **hex_key_out,
137 char **comment_out) {
138 if (!line || !ip_port_out || !key_type_out || !hex_key_out || !comment_out) {
139 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
140 return false;
141 }
142
143 pcre2_code *regex = crypto_regex_get_known_hosts();
144 if (!regex) {
145 SET_ERRNO(ERROR_INVALID_STATE, "Invalid validator state");
146 return false;
147 }
148
149 pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regex, NULL);
150 if (!match_data) {
151 return false;
152 }
153
154 /* Perform JIT match (falls back to interpreted if JIT unavailable) */
155 int rc = pcre2_jit_match(regex, (PCRE2_SPTR)line, strlen(line), 0, /* startoffset */
156 0, /* options */
157 match_data, NULL); /* mcontext */
158
159 if (rc < 0) {
160 pcre2_match_data_free(match_data);
161 return false;
162 }
163
164 /* Extract named groups */
165 *ip_port_out = asciichat_pcre2_extract_named_group(regex, match_data, "ip_port", line);
166 *key_type_out = asciichat_pcre2_extract_named_group(regex, match_data, "key_type", line);
167 *hex_key_out = asciichat_pcre2_extract_named_group(regex, match_data, "hex_key", line);
168 *comment_out = asciichat_pcre2_extract_named_group(regex, match_data, "comment", line);
169
170 pcre2_match_data_free(match_data);
171
172 /* Verify we got required fields */
173 if (!*ip_port_out || !*key_type_out) {
174 SAFE_FREE(*ip_port_out);
175 SAFE_FREE(*key_type_out);
176 SAFE_FREE(*hex_key_out);
177 SAFE_FREE(*comment_out);
178 return false;
179 }
180
181 return true;
182}
183
184bool crypto_regex_match_public_key(const char *line, char **base64_key_out, char **comment_out) {
185 if (!line || !base64_key_out || !comment_out) {
186 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
187 return false;
188 }
189
190 pcre2_code *regex = crypto_regex_get_ssh_public_key();
191 if (!regex) {
192 SET_ERRNO(ERROR_INVALID_STATE, "Invalid validator state");
193 return false;
194 }
195
196 pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regex, NULL);
197 if (!match_data) {
198 return false;
199 }
200
201 /* Perform JIT match (falls back to interpreted if JIT unavailable) */
202 int rc = pcre2_jit_match(regex, (PCRE2_SPTR)line, strlen(line), 0, /* startoffset */
203 0, /* options */
204 match_data, NULL); /* mcontext */
205
206 if (rc < 0) {
207 pcre2_match_data_free(match_data);
208 return false;
209 }
210
211 /* Extract named groups */
212 *base64_key_out = asciichat_pcre2_extract_named_group(regex, match_data, "base64_key", line);
213 *comment_out = asciichat_pcre2_extract_named_group(regex, match_data, "comment", line);
214
215 pcre2_match_data_free(match_data);
216
217 /* Verify we got required field */
218 if (!*base64_key_out) {
219 SAFE_FREE(*base64_key_out);
220 SAFE_FREE(*comment_out);
221 return false;
222 }
223
224 return true;
225}
226
227bool crypto_regex_extract_pem_base64(const char *file_content, char **base64_data_out) {
228 if (!file_content || !base64_data_out) {
229 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
230 return false;
231 }
232
233 pcre2_code *regex = crypto_regex_get_openssh_pem();
234 if (!regex) {
235 SET_ERRNO(ERROR_INVALID_STATE, "Invalid validator state");
236 return false;
237 }
238
239 pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regex, NULL);
240 if (!match_data) {
241 return false;
242 }
243
244 /* Perform JIT match (falls back to interpreted if JIT unavailable) */
245 int rc = pcre2_jit_match(regex, (PCRE2_SPTR)file_content, strlen(file_content), 0, /* startoffset */
246 0, /* options */
247 match_data, NULL); /* mcontext */
248
249 if (rc < 0) {
250 pcre2_match_data_free(match_data);
251 return false;
252 }
253
254 /* Extract base64 data */
255 *base64_data_out = asciichat_pcre2_extract_named_group(regex, match_data, "base64_data", file_content);
256
257 pcre2_match_data_free(match_data);
258
259 /* Verify we got the base64 data */
260 if (!*base64_data_out) {
261 return false;
262 }
263
264 return true;
265}
266
267bool crypto_regex_extract_gpg_keygrip(const char *line, char **keygrip_out) {
268 if (!line || !keygrip_out) {
269 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
270 return false;
271 }
272
273 pcre2_code *regex = crypto_regex_get_gpg_keygrip();
274 if (!regex) {
275 /* Regex not available - caller should use fallback manual parsing */
276 return false;
277 }
278
279 pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regex, NULL);
280 if (!match_data) {
281 return false;
282 }
283
284 /* Perform JIT match (falls back to interpreted if JIT unavailable) */
285 int rc = pcre2_jit_match(regex, (PCRE2_SPTR)line, strlen(line), 0, /* startoffset */
286 0, /* options */
287 match_data, NULL); /* mcontext */
288
289 if (rc < 0) {
290 pcre2_match_data_free(match_data);
291 return false;
292 }
293
294 /* Extract keygrip from named group */
295 *keygrip_out = asciichat_pcre2_extract_named_group(regex, match_data, "keygrip", line);
296
297 pcre2_match_data_free(match_data);
298
299 /* Verify we got the keygrip */
300 if (!*keygrip_out) {
301 return false;
302 }
303
304 return true;
305}
pcre2_code * asciichat_pcre2_singleton_get_code(pcre2_singleton_t *singleton)
Get the compiled pcre2_code from a singleton handle.
Definition pcre2.c:95
char * asciichat_pcre2_extract_named_group(pcre2_code *regex, pcre2_match_data *match_data, const char *group_name, const char *subject)
Extract named substring from PCRE2 match data.
Definition pcre2.c:252
bool crypto_regex_match_public_key(const char *line, char **base64_key_out, char **comment_out)
Definition regex.c:184
bool crypto_regex_extract_gpg_keygrip(const char *line, char **keygrip_out)
Definition regex.c:267
bool crypto_regex_match_known_hosts(const char *line, char **ip_port_out, char **key_type_out, char **hex_key_out, char **comment_out)
Definition regex.c:136
bool crypto_regex_extract_pem_base64(const char *file_content, char **base64_data_out)
Definition regex.c:227
Represents a thread-safe compiled PCRE2 regex singleton.
Definition pcre2.c:21