Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_pcre.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
28#include "nilorea/n_pcre.h"
29#include "nilorea/n_log.h"
30#include "nilorea/n_str.h"
31
32#include <string.h>
33
34/*
35 * This module was originally written for legacy PCRE (pcre.h).
36 * It has been migrated to PCRE2 (pcre2.h) using the 8-bit API.
37 */
38
82N_PCRE* npcre_new(char* str, int flags) {
83 N_PCRE* pcre = NULL;
84 __n_assert(str, return NULL);
85
86 Malloc(pcre, N_PCRE, 1);
87 __n_assert(pcre, return NULL);
88
89 pcre->captured = 0;
90 pcre->match_list = NULL;
91 init_lock(pcre->rwlock);
92
93 pcre->regexp_str = strdup(str);
94 __n_assert(pcre->regexp_str, npcre_delete(&pcre); return NULL);
95
96 int errorcode = 0;
97 PCRE2_SIZE erroroffset = 0;
98
99 pcre->regexp = pcre2_compile(
100 (PCRE2_SPTR)str,
101 PCRE2_ZERO_TERMINATED,
102 (uint32_t)flags,
103 &errorcode,
104 &erroroffset,
105 NULL);
106
107 if (!pcre->regexp) {
108 PCRE2_UCHAR errmsg[256];
109 (void)pcre2_get_error_message(errorcode, errmsg, sizeof(errmsg));
110 n_log(LOG_ERR, "pcre2 compilation of %s failed at offset %zu : %s", str, (size_t)erroroffset, (char*)errmsg);
111 npcre_delete(&pcre);
112 return NULL;
113 }
114
115 /* Allocate match_data sized for this pattern (captures, etc.) */
116 pcre->match_data = pcre2_match_data_create_from_pattern(pcre->regexp, NULL);
117 if (!pcre->match_data) {
118 n_log(LOG_ERR, "pcre2_match_data_create_from_pattern failed (OOM?)");
119 npcre_delete(&pcre);
120 return NULL;
121 }
122
123 /* Best-effort JIT compilation (optional). Ignore failures. */
124 (void)pcre2_jit_compile(pcre->regexp, PCRE2_JIT_COMPLETE);
125
126 return pcre;
127} /* npcre_new(...) */
128
136int npcre_delete(N_PCRE** pcre) {
137 __n_assert(pcre && (*pcre), return FALSE);
138
139 write_lock((*pcre)->rwlock);
140 if ((*pcre)->captured > 0 && (*pcre)->match_list) {
141 pcre2_substring_list_free(PCRE2_LIST_FREE_CAST((*pcre)->match_list));
142 (*pcre)->match_list = NULL;
143 (*pcre)->captured = 0;
144 }
145
146 if ((*pcre)->match_data) {
147 pcre2_match_data_free((*pcre)->match_data);
148 (*pcre)->match_data = NULL;
149 }
150 if ((*pcre)->regexp) {
151 pcre2_code_free((*pcre)->regexp);
152 (*pcre)->regexp = NULL;
153 }
154 FreeNoLog((*pcre)->regexp_str);
155 unlock((*pcre)->rwlock);
156 rw_lock_destroy((*pcre)->rwlock);
157 FreeNoLog((*pcre));
158 return TRUE;
159} /* npcre_delete (...) */
160
167 __n_assert(pcre, return FALSE);
168
169 write_lock(pcre->rwlock);
170 if (pcre->match_list) {
171 pcre->captured = 0;
172 pcre2_substring_list_free(PCRE2_LIST_FREE_CAST(pcre->match_list));
173 pcre->match_list = NULL;
174 }
175 unlock(pcre->rwlock);
176
177 return TRUE;
178} /* npcre_clean_match */
179
189int _npcre_print_error(int LOG_LEVEL, const char* file, const char* func, int line, int rc) {
190 /*
191 * PCRE2 error code names differ from legacy PCRE, and not all historical
192 * constants exist in all packaged versions.
193 *
194 * The portable way is to ask PCRE2 for the human-readable message.
195 */
196
197 if (rc > 0) {
198 _n_log(LOG_DEBUG, file, func, line, "no pcre error, match: %d elements in ovector", rc);
199 return TRUE;
200 }
201
202 if (rc == 0) {
203 _n_log(LOG_LEVEL, file, func, line, "match OK, but not enough space in ovector to store all elements");
204 return FALSE;
205 }
206
207 PCRE2_UCHAR errmsg[256];
208 PCRE2_SIZE cap = sizeof(errmsg) / sizeof(errmsg[0]);
209
210 int mrc = pcre2_get_error_message(rc, errmsg, cap);
211 if (mrc >= 0) {
212 _n_log(LOG_LEVEL, file, func, line, "PCRE2 error %d: %s", rc, (const char*)errmsg);
213 } else {
214 _n_log(LOG_LEVEL, file, func, line, "Unknown PCRE2 error code: %d", rc);
215 }
216
217 return FALSE;
218}
219
228int npcre_match(char* str, N_PCRE* pcre) {
229 __n_assert(str, return FALSE);
230 __n_assert(pcre, return FALSE);
231 __n_assert(pcre->regexp, return FALSE);
232
233 pcre2_match_data* local_match_data = pcre2_match_data_create_from_pattern(pcre->regexp, NULL);
234 if (!local_match_data) {
235 n_log(LOG_ERR, "npcre_match: pcre2_match_data_create_from_pattern failed");
236 return FALSE;
237 }
238
239 int rc = (int)pcre2_match(
240 pcre->regexp,
241 (PCRE2_SPTR)str,
242 (PCRE2_SIZE)strlen(str),
243 0,
244 0,
245 local_match_data,
246 NULL);
247
248 pcre2_match_data_free(local_match_data);
249
250 return (rc > 0) ? TRUE : FALSE;
251} /* npcre_match(...) */
252
253N_STR* npcre_replace(N_PCRE* pcre, const char* subject, size_t subject_len, const char* replacement) {
254 pcre2_match_data* md = NULL;
255 PCRE2_UCHAR8* outbuf = NULL;
256 PCRE2_SIZE slen, rlen, cap;
257 N_STR* result = NULL;
258
259 __n_assert(pcre, return NULL);
260 __n_assert(pcre->regexp, return NULL);
261 __n_assert(subject, return NULL);
262 __n_assert(replacement, return NULL);
263
264 md = pcre2_match_data_create_from_pattern(pcre->regexp, NULL);
265 if (!md) {
266 n_log(LOG_ERR, "npcre_replace: pcre2_match_data_create_from_pattern failed");
267 return NULL;
268 }
269
270 slen = (PCRE2_SIZE)subject_len;
271 rlen = (PCRE2_SIZE)strlen(replacement);
272 cap = slen + 64; /* initial output capacity guess (code units, incl. room for NUL) */
273
274 for (;;) {
275 PCRE2_SIZE used = cap; /* in: buffer capacity; out: length used (or required) */
276 int rc;
277 Malloc(outbuf, PCRE2_UCHAR8, cap);
278 if (!outbuf) {
279 pcre2_match_data_free(md);
280 return NULL;
281 }
282 rc = pcre2_substitute(
283 pcre->regexp,
284 (PCRE2_SPTR)subject, slen,
285 0,
286 PCRE2_SUBSTITUTE_GLOBAL | PCRE2_SUBSTITUTE_OVERFLOW_LENGTH,
287 md, NULL,
288 (PCRE2_SPTR)replacement, rlen,
289 outbuf, &used);
290 if (rc == PCRE2_ERROR_NOMEMORY) {
291 /* OVERFLOW_LENGTH set used to the required buffer size (incl. NUL) */
292 FreeNoLog(outbuf);
293 outbuf = NULL;
294 cap = used;
295 continue;
296 }
297 if (rc < 0) {
299 FreeNoLog(outbuf);
300 pcre2_match_data_free(md);
301 return NULL;
302 }
303 /* success: used is the result length, excluding the trailing NUL */
304 char_to_nstr_ex((char*)outbuf, (NSTRBYTE)used, &result);
305 FreeNoLog(outbuf);
306 break;
307 }
308
309 pcre2_match_data_free(md);
310 return result;
311} /* npcre_replace(...) */
312
323int npcre_match_capture(char* str, N_PCRE* pcre) {
324 __n_assert(str, return FALSE);
325 __n_assert(pcre, return FALSE);
326 __n_assert(pcre->regexp_str, return FALSE);
327 __n_assert(pcre->regexp, return FALSE);
328
329 write_lock(pcre->rwlock);
330
331 int rc = 0;
332 size_t len = strlen(str);
333
334 rc = (int)pcre2_match(
335 pcre->regexp,
336 (PCRE2_SPTR)str,
337 (PCRE2_SIZE)len,
338 0,
339 0,
340 pcre->match_data,
341 NULL);
342
343 if (rc < 0) {
344 unlock(pcre->rwlock);
345 switch (rc) {
346 case PCRE2_ERROR_NOMATCH: /* n_log( LOG_DEBUG , "String did not match the pattern");*/
347 break;
348 case PCRE2_ERROR_NULL:
349 n_log(LOG_DEBUG, "Something was null");
350 break;
351 case PCRE2_ERROR_BADOPTION:
352 n_log(LOG_DEBUG, "A bad option was passed");
353 break;
354 case PCRE2_ERROR_BADMAGIC:
355 n_log(LOG_DEBUG, "Magic number bad (compiled re corrupt?)");
356 break;
357 case PCRE2_ERROR_NOMEMORY:
358 n_log(LOG_DEBUG, "Ran out of memory");
359 break;
360 default:
361 n_log(LOG_DEBUG, "Unknown error");
362 break;
363 }
364 return FALSE;
365 }
366
367 if (rc > 0) {
368 /* clean previous match results (already under lock, call internal logic directly) */
369 if (pcre->match_list) {
370 pcre->captured = 0;
371 pcre2_substring_list_free(PCRE2_LIST_FREE_CAST(pcre->match_list));
372 pcre->match_list = NULL;
373 }
374 /* lengthsptr Where to put a pointer to the lengths, or NULL */
375 PCRE2_SIZE* lens = NULL;
376 /* list is NULL-terminated and must be freed with pcre2_substring_list_free() */
377 int lrc = pcre2_substring_list_get(pcre->match_data, &pcre->match_list, &lens);
378 if (lrc != 0) {
379 _npcre_print_error(LOG_ERR, __FILE__, __func__, __LINE__, lrc);
380 pcre->match_list = NULL;
381 pcre->captured = 0;
382 unlock(pcre->rwlock);
383 return FALSE;
384 }
385 pcre->captured = rc;
386 }
387 unlock(pcre->rwlock);
388 return TRUE;
389} /* npcre_match_capture(...) */
#define init_lock(__rwlock_mutex)
Macro for initializing a rwlock.
Definition n_common.h:350
#define FreeNoLog(__ptr)
Free Handler without log.
Definition n_common.h:272
#define Malloc(__ptr, __struct, __size)
Malloc Handler to get errors and set to 0.
Definition n_common.h:204
#define __n_assert(__ptr, __ret)
macro to assert things
Definition n_common.h:279
#define rw_lock_destroy(__rwlock_mutex)
Macro to destroy rwlock mutex.
Definition n_common.h:409
#define unlock(__rwlock_mutex)
Macro for releasing read/write lock a rwlock mutex.
Definition n_common.h:396
#define write_lock(__rwlock_mutex)
Macro for acquiring a write lock on a rwlock mutex.
Definition n_common.h:382
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:89
#define LOG_DEBUG
debug-level messages
Definition n_log.h:84
#define LOG_ERR
error conditions
Definition n_log.h:76
void _n_log(int level, const char *file, const char *func, int line, const char *format,...)
Logging function.
Definition n_log.c:268
size_t NSTRBYTE
N_STR base unit.
Definition n_str.h:58
int char_to_nstr_ex(const char *from, NSTRBYTE nboct, N_STR **to)
Convert a char into a N_STR, extended version.
Definition n_str.c:232
A box including a string and his lenght.
Definition n_str.h:61
PCRE2_UCHAR8 ** match_list
populated match list (NULL-terminated) after npcre_match_capture() Allocated by PCRE2 via pcre2_subst...
Definition n_pcre.h:104
pcre2_code * regexp
regexp
Definition n_pcre.h:97
pcre2_match_data * match_data
match data storage (ovector, etc.)
Definition n_pcre.h:99
char * regexp_str
original regexp string
Definition n_pcre.h:95
pthread_rwlock_t rwlock
rwlock protecting match_data, match_list, and captured
Definition n_pcre.h:108
int captured
flag for match_list cleaning
Definition n_pcre.h:106
N_PCRE * npcre_new(char *str, int flags)
From pcre doc, the flag bits are: PCRE_ANCHORED Force pattern anchoring PCRE_AUTO_CALLOUT Compile aut...
Definition n_pcre.c:82
int _npcre_print_error(int LOG_LEVEL, const char *file, const char *func, int line, int rc)
print error associated to pcre error code
Definition n_pcre.c:189
int npcre_match_capture(char *str, N_PCRE *pcre)
Return TRUE if str matches regexp, and make captures.
Definition n_pcre.c:323
int npcre_match(char *str, N_PCRE *pcre)
Thread-safe match: returns TRUE/FALSE without modifying the N_PCRE struct.
Definition n_pcre.c:228
int npcre_clean_match(N_PCRE *pcre)
clean the match list of the last capture, if any
Definition n_pcre.c:166
N_STR * npcre_replace(N_PCRE *pcre, const char *subject, size_t subject_len, const char *replacement)
thread-safe regex substitute: replace every match in subject (binary-safe, subject_len bytes) using a...
Definition n_pcre.c:253
int npcre_delete(N_PCRE **pcre)
Free a N_PCRE pointer.
Definition n_pcre.c:136
N_PCRE structure.
Definition n_pcre.h:93
static int LOG_LEVEL
static global maximum wanted log level value
Definition n_log.c:78
Generic log system.
PCRE helpers for regex matching.
#define PCRE2_LIST_FREE_CAST(x)
Definition n_pcre.h:78
#define npcre_print_error(__LEVEL__, __rc__)
PCRE error logging function wrapper to get line and func.
Definition n_pcre.h:82
N_STR and string function declaration.