7 n__pcre_8c
gullradriel edited this page 2026-10-29 20:54:40 +01:00

src/n_pcre.c

PCRE helpers for regex matching. More...

Detailed Description

PCRE helpers for regex matching.

Author: Castagnier Mickael

Version: 2.0

Date: 04/12/2019

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#include "nilorea/n_pcre.h"
#include "nilorea/n_log.h"
#include "nilorea/n_str.h"

#include <string.h>

/*
 * This module was originally written for legacy PCRE (pcre.h).
 * It has been migrated to PCRE2 (pcre2.h) using the 8-bit API.
 */

N_PCRE* npcre_new(char* str, int flags) {
    N_PCRE* pcre = NULL;
    __n_assert(str, return NULL);

    Malloc(pcre, N_PCRE, 1);
    __n_assert(pcre, return NULL);

    pcre->captured = 0;
    pcre->match_list = NULL;
    init_lock(pcre->rwlock);

    pcre->regexp_str = strdup(str);
    __n_assert(pcre->regexp_str, npcre_delete(&pcre); return NULL);

    int errorcode = 0;
    PCRE2_SIZE erroroffset = 0;

    pcre->regexp = pcre2_compile(
        (PCRE2_SPTR)str,
        PCRE2_ZERO_TERMINATED,
        (uint32_t)flags,
        &errorcode,
        &erroroffset,
        NULL);

    if (!pcre->regexp) {
        PCRE2_UCHAR errmsg[256];
        (void)pcre2_get_error_message(errorcode, errmsg, sizeof(errmsg));
        n_log(LOG_ERR, "pcre2 compilation of %s failed at offset %zu : %s", str, (size_t)erroroffset, (char*)errmsg);
        npcre_delete(&pcre);
        return NULL;
    }

    /* Allocate match_data sized for this pattern (captures, etc.) */
    pcre->match_data = pcre2_match_data_create_from_pattern(pcre->regexp, NULL);
    if (!pcre->match_data) {
        n_log(LOG_ERR, "pcre2_match_data_create_from_pattern failed (OOM?)");
        npcre_delete(&pcre);
        return NULL;
    }

    /* Best-effort JIT compilation (optional). Ignore failures. */
    (void)pcre2_jit_compile(pcre->regexp, PCRE2_JIT_COMPLETE);

    return pcre;
} /* npcre_new(...) */

int npcre_delete(N_PCRE** pcre) {
    __n_assert(pcre && (*pcre), return FALSE);

    write_lock((*pcre)->rwlock);
    if ((*pcre)->captured > 0 && (*pcre)->match_list) {
        pcre2_substring_list_free(PCRE2_LIST_FREE_CAST((*pcre)->match_list));
        (*pcre)->match_list = NULL;
        (*pcre)->captured = 0;
    }

    if ((*pcre)->match_data) {
        pcre2_match_data_free((*pcre)->match_data);
        (*pcre)->match_data = NULL;
    }
    if ((*pcre)->regexp) {
        pcre2_code_free((*pcre)->regexp);
        (*pcre)->regexp = NULL;
    }
    FreeNoLog((*pcre)->regexp_str);
    unlock((*pcre)->rwlock);
    rw_lock_destroy((*pcre)->rwlock);
    FreeNoLog((*pcre));
    return TRUE;
} /* npcre_delete (...) */

int npcre_clean_match(N_PCRE* pcre) {
    __n_assert(pcre, return FALSE);

    write_lock(pcre->rwlock);
    if (pcre->match_list) {
        pcre->captured = 0;
        pcre2_substring_list_free(PCRE2_LIST_FREE_CAST(pcre->match_list));
        pcre->match_list = NULL;
    }
    unlock(pcre->rwlock);

    return TRUE;
} /* npcre_clean_match */

int _npcre_print_error(int LOG_LEVEL, const char* file, const char* func, int line, int rc) {
    /*
     * PCRE2 error code names differ from legacy PCRE, and not all historical
     * constants exist in all packaged versions.
     *
     * The portable way is to ask PCRE2 for the human-readable message.
     */

    if (rc > 0) {
        _n_log(LOG_DEBUG, file, func, line, "no pcre error, match: %d elements in ovector", rc);
        return TRUE;
    }

    if (rc == 0) {
        _n_log(LOG_LEVEL, file, func, line, "match OK, but not enough space in ovector to store all elements");
        return FALSE;
    }

    PCRE2_UCHAR errmsg[256];
    PCRE2_SIZE cap = sizeof(errmsg) / sizeof(errmsg[0]);

    int mrc = pcre2_get_error_message(rc, errmsg, cap);
    if (mrc >= 0) {
        _n_log(LOG_LEVEL, file, func, line, "PCRE2 error %d: %s", rc, (const char*)errmsg);
    } else {
        _n_log(LOG_LEVEL, file, func, line, "Unknown PCRE2 error code: %d", rc);
    }

    return FALSE;
}

int npcre_match(char* str, N_PCRE* pcre) {
    __n_assert(str, return FALSE);
    __n_assert(pcre, return FALSE);
    __n_assert(pcre->regexp, return FALSE);

    pcre2_match_data* local_match_data = pcre2_match_data_create_from_pattern(pcre->regexp, NULL);
    if (!local_match_data) {
        n_log(LOG_ERR, "npcre_match: pcre2_match_data_create_from_pattern failed");
        return FALSE;
    }

    int rc = (int)pcre2_match(
        pcre->regexp,
        (PCRE2_SPTR)str,
        (PCRE2_SIZE)strlen(str),
        0,
        0,
        local_match_data,
        NULL);

    pcre2_match_data_free(local_match_data);

    return (rc > 0) ? TRUE : FALSE;
} /* npcre_match(...) */

N_STR* npcre_replace(N_PCRE* pcre, const char* subject, size_t subject_len, const char* replacement) {
    pcre2_match_data* md = NULL;
    PCRE2_UCHAR8* outbuf = NULL;
    PCRE2_SIZE slen, rlen, cap;
    N_STR* result = NULL;

    __n_assert(pcre, return NULL);
    __n_assert(pcre->regexp, return NULL);
    __n_assert(subject, return NULL);
    __n_assert(replacement, return NULL);

    md = pcre2_match_data_create_from_pattern(pcre->regexp, NULL);
    if (!md) {
        n_log(LOG_ERR, "npcre_replace: pcre2_match_data_create_from_pattern failed");
        return NULL;
    }

    slen = (PCRE2_SIZE)subject_len;
    rlen = (PCRE2_SIZE)strlen(replacement);
    cap = slen + 64; /* initial output capacity guess (code units, incl. room for NUL) */

    for (;;) {
        PCRE2_SIZE used = cap; /* in: buffer capacity; out: length used (or required) */
        int rc;
        Malloc(outbuf, PCRE2_UCHAR8, cap);
        if (!outbuf) {
            pcre2_match_data_free(md);
            return NULL;
        }
        rc = pcre2_substitute(
            pcre->regexp,
            (PCRE2_SPTR)subject, slen,
            0,
            PCRE2_SUBSTITUTE_GLOBAL | PCRE2_SUBSTITUTE_OVERFLOW_LENGTH,
            md, NULL,
            (PCRE2_SPTR)replacement, rlen,
            outbuf, &used);
        if (rc == PCRE2_ERROR_NOMEMORY) {
            /* OVERFLOW_LENGTH set used to the required buffer size (incl. NUL) */
            FreeNoLog(outbuf);
            outbuf = NULL;
            cap = used;
            continue;
        }
        if (rc < 0) {
            npcre_print_error(LOG_ERR, rc);
            FreeNoLog(outbuf);
            pcre2_match_data_free(md);
            return NULL;
        }
        /* success: used is the result length, excluding the trailing NUL */
        char_to_nstr_ex((char*)outbuf, (NSTRBYTE)used, &result);
        FreeNoLog(outbuf);
        break;
    }

    pcre2_match_data_free(md);
    return result;
} /* npcre_replace(...) */

int npcre_match_capture(char* str, N_PCRE* pcre) {
    __n_assert(str, return FALSE);
    __n_assert(pcre, return FALSE);
    __n_assert(pcre->regexp_str, return FALSE);
    __n_assert(pcre->regexp, return FALSE);

    write_lock(pcre->rwlock);

    int rc = 0;
    size_t len = strlen(str);

    rc = (int)pcre2_match(
        pcre->regexp,
        (PCRE2_SPTR)str,
        (PCRE2_SIZE)len,
        0,
        0,
        pcre->match_data,
        NULL);

    if (rc < 0) {
        unlock(pcre->rwlock);
        switch (rc) {
            case PCRE2_ERROR_NOMATCH: /* n_log( LOG_DEBUG , "String did not match the pattern");*/
                break;
            case PCRE2_ERROR_NULL:
                n_log(LOG_DEBUG, "Something was null");
                break;
            case PCRE2_ERROR_BADOPTION:
                n_log(LOG_DEBUG, "A bad option was passed");
                break;
            case PCRE2_ERROR_BADMAGIC:
                n_log(LOG_DEBUG, "Magic number bad (compiled re corrupt?)");
                break;
            case PCRE2_ERROR_NOMEMORY:
                n_log(LOG_DEBUG, "Ran out of memory");
                break;
            default:
                n_log(LOG_DEBUG, "Unknown error");
                break;
        }
        return FALSE;
    }

    if (rc > 0) {
        /* clean previous match results (already under lock, call internal logic directly) */
        if (pcre->match_list) {
            pcre->captured = 0;
            pcre2_substring_list_free(PCRE2_LIST_FREE_CAST(pcre->match_list));
            pcre->match_list = NULL;
        }
        /* lengthsptr    Where to put a pointer to the lengths, or NULL */
        PCRE2_SIZE* lens = NULL;
        /* list is NULL-terminated and must be freed with pcre2_substring_list_free() */
        int lrc = pcre2_substring_list_get(pcre->match_data, &pcre->match_list, &lens);
        if (lrc != 0) {
            _npcre_print_error(LOG_ERR, __FILE__, __func__, __LINE__, lrc);
            pcre->match_list = NULL;
            pcre->captured = 0;
            unlock(pcre->rwlock);
            return FALSE;
        }
        pcre->captured = rc;
    }
    unlock(pcre->rwlock);
    return TRUE;
} /* npcre_match_capture(...) */