911 lines
32 KiB
C
911 lines
32 KiB
C
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
|
|
|
#define PCRE2_CODE_UNIT_WIDTH 8
|
|
|
|
#include "pcre2_bridge.h"
|
|
|
|
#include <pcre2.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#define REGEX_PCRE2_COMPILE_FLAGS \
|
|
(REGEX_PCRE2_CASELESS | REGEX_PCRE2_MULTILINE | REGEX_PCRE2_DOTALL | \
|
|
REGEX_PCRE2_EXTENDED | REGEX_PCRE2_UNGREEDY | REGEX_PCRE2_UTF | \
|
|
REGEX_PCRE2_UCP | REGEX_PCRE2_DUPNAMES)
|
|
#define REGEX_PCRE2_ALL_FLAGS \
|
|
(REGEX_PCRE2_COMPILE_FLAGS | REGEX_PCRE2_GLOBAL)
|
|
|
|
_Static_assert(sizeof(regex_pcre2_compile_result) == 20,
|
|
"Compile result is part of ABI version 3.");
|
|
_Static_assert(sizeof(regex_pcre2_limits) == 20,
|
|
"Limits record is part of ABI version 3.");
|
|
_Static_assert(sizeof(regex_pcre2_match_record) == 16,
|
|
"Match record is part of ABI version 3.");
|
|
_Static_assert(sizeof(regex_pcre2_name_record) == 12,
|
|
"Name record is part of ABI version 3.");
|
|
_Static_assert(sizeof(regex_pcre2_run_result) == 60,
|
|
"Run result is part of ABI version 3.");
|
|
_Static_assert(sizeof(regex_pcre2_trace_limits) == 20,
|
|
"Trace limits are part of ABI version 3.");
|
|
_Static_assert(sizeof(regex_pcre2_trace_event) == 32,
|
|
"Trace event is part of ABI version 3.");
|
|
_Static_assert(sizeof(regex_pcre2_trace_result) == 52,
|
|
"Trace result is part of ABI version 3.");
|
|
|
|
typedef struct regex_pcre2_run_buffers {
|
|
regex_pcre2_match_record *records;
|
|
uint32_t record_capacity;
|
|
regex_pcre2_name_record *name_records;
|
|
uint32_t name_record_capacity;
|
|
uint8_t *name_bytes;
|
|
uint32_t name_bytes_capacity;
|
|
uint8_t *output;
|
|
uint32_t output_capacity;
|
|
} regex_pcre2_run_buffers;
|
|
|
|
typedef struct regex_pcre2_trace_state {
|
|
regex_pcre2_trace_event *events;
|
|
uint32_t event_capacity;
|
|
uint8_t *mark_bytes;
|
|
uint32_t mark_bytes_capacity;
|
|
uint32_t maximum_events;
|
|
uint32_t maximum_trace_bytes;
|
|
uint32_t event_count;
|
|
uint32_t total_event_count;
|
|
uint32_t mark_bytes_length;
|
|
uint32_t events_truncated;
|
|
uint32_t marks_truncated;
|
|
} regex_pcre2_trace_state;
|
|
|
|
static const uint8_t empty_bytes[1] = {0};
|
|
|
|
static uint32_t to_pcre2_options(uint32_t application_flags) {
|
|
uint32_t options = 0;
|
|
if ((application_flags & REGEX_PCRE2_CASELESS) != 0) {
|
|
options |= PCRE2_CASELESS;
|
|
}
|
|
if ((application_flags & REGEX_PCRE2_MULTILINE) != 0) {
|
|
options |= PCRE2_MULTILINE;
|
|
}
|
|
if ((application_flags & REGEX_PCRE2_DOTALL) != 0) {
|
|
options |= PCRE2_DOTALL;
|
|
}
|
|
if ((application_flags & REGEX_PCRE2_EXTENDED) != 0) {
|
|
options |= PCRE2_EXTENDED;
|
|
}
|
|
if ((application_flags & REGEX_PCRE2_UNGREEDY) != 0) {
|
|
options |= PCRE2_UNGREEDY;
|
|
}
|
|
if ((application_flags & REGEX_PCRE2_UTF) != 0) {
|
|
options |= PCRE2_UTF;
|
|
}
|
|
if ((application_flags & REGEX_PCRE2_UCP) != 0) {
|
|
options |= PCRE2_UCP;
|
|
}
|
|
if ((application_flags & REGEX_PCRE2_DUPNAMES) != 0) {
|
|
options |= PCRE2_DUPNAMES;
|
|
}
|
|
return options;
|
|
}
|
|
|
|
static int32_t fail_compile(regex_pcre2_compile_result *result,
|
|
int32_t status) {
|
|
if (result != NULL) {
|
|
memset(result, 0, sizeof(*result));
|
|
result->status = status;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
static int32_t fail_run(regex_pcre2_run_result *result,
|
|
int32_t status,
|
|
uint32_t phase,
|
|
uint32_t offset) {
|
|
if (result != NULL) {
|
|
memset(result, 0, sizeof(*result));
|
|
result->status = status;
|
|
result->error_phase = phase;
|
|
result->error_offset = offset;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
static int32_t fail_trace(regex_pcre2_trace_result *result,
|
|
int32_t status,
|
|
uint32_t phase,
|
|
uint32_t offset) {
|
|
if (result != NULL) {
|
|
memset(result, 0, sizeof(*result));
|
|
result->status = status;
|
|
result->error_phase = phase;
|
|
result->error_offset = offset;
|
|
result->last_complete_event = UINT32_MAX;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
static int input_pointer_valid(const uint8_t *value, uint32_t length) {
|
|
return value != NULL || length == 0;
|
|
}
|
|
|
|
static const uint8_t *nonnull_input(const uint8_t *value) {
|
|
return value == NULL ? empty_bytes : value;
|
|
}
|
|
|
|
static int limits_valid(const regex_pcre2_limits *limits) {
|
|
return limits != NULL && limits->maximum_matches > 0 &&
|
|
limits->maximum_matches <= REGEX_PCRE2_MAX_MATCHES &&
|
|
limits->maximum_capture_rows > 0 &&
|
|
limits->maximum_capture_rows <= REGEX_PCRE2_MAX_CAPTURE_ROWS &&
|
|
limits->match_limit > 0 &&
|
|
limits->match_limit <= REGEX_PCRE2_MAX_MATCH_LIMIT &&
|
|
limits->depth_limit > 0 &&
|
|
limits->depth_limit <= REGEX_PCRE2_MAX_DEPTH_LIMIT &&
|
|
limits->heap_limit_kib > 0 &&
|
|
limits->heap_limit_kib <= REGEX_PCRE2_MAX_HEAP_LIMIT_KIB;
|
|
}
|
|
|
|
static int buffers_valid(const regex_pcre2_run_buffers *buffers,
|
|
int substitution) {
|
|
if (buffers == NULL ||
|
|
(buffers->records == NULL && buffers->record_capacity != 0) ||
|
|
(buffers->name_records == NULL &&
|
|
buffers->name_record_capacity != 0) ||
|
|
(buffers->name_bytes == NULL && buffers->name_bytes_capacity != 0)) {
|
|
return 0;
|
|
}
|
|
/*
|
|
* PCRE2 writes a trailing NUL even when the semantic output capacity is
|
|
* zero, so the bridge contract always requires the advertised +1 byte.
|
|
*/
|
|
if (substitution && buffers->output == NULL) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static uint32_t bounded_offset(PCRE2_SIZE value) {
|
|
return value > UINT32_MAX ? UINT32_MAX : (uint32_t)value;
|
|
}
|
|
|
|
static int32_t copy_names(const pcre2_code *code,
|
|
regex_pcre2_run_buffers *buffers,
|
|
regex_pcre2_run_result *result) {
|
|
uint32_t entry_size = 0;
|
|
PCRE2_SPTR table = NULL;
|
|
uint32_t index = 0;
|
|
uint32_t name_offset = 0;
|
|
|
|
if (result->name_count == 0) {
|
|
return 0;
|
|
}
|
|
if (pcre2_pattern_info(code, PCRE2_INFO_NAMEENTRYSIZE, &entry_size) != 0 ||
|
|
pcre2_pattern_info(code, PCRE2_INFO_NAMETABLE, &table) != 0 ||
|
|
entry_size < 3 || table == NULL) {
|
|
return REGEX_PCRE2_ERROR_CONFIG;
|
|
}
|
|
for (index = 0; index < result->name_count; index += 1) {
|
|
const uint8_t *entry = table + (index * entry_size);
|
|
uint32_t group_number =
|
|
((uint32_t)entry[0] << 8) | (uint32_t)entry[1];
|
|
uint32_t maximum_name = entry_size - 2;
|
|
uint32_t name_length = 0;
|
|
while (name_length < maximum_name && entry[2 + name_length] != 0) {
|
|
name_length += 1;
|
|
}
|
|
if (result->name_record_count >= buffers->name_record_capacity ||
|
|
name_length > buffers->name_bytes_capacity - name_offset) {
|
|
result->names_truncated = 1;
|
|
break;
|
|
}
|
|
buffers->name_records[result->name_record_count].group_number =
|
|
group_number;
|
|
buffers->name_records[result->name_record_count].name_offset =
|
|
name_offset;
|
|
buffers->name_records[result->name_record_count].name_length =
|
|
name_length;
|
|
if (name_length > 0) {
|
|
memcpy(buffers->name_bytes + name_offset, entry + 2, name_length);
|
|
}
|
|
result->name_record_count += 1;
|
|
name_offset += name_length;
|
|
}
|
|
result->name_bytes_length = name_offset;
|
|
return 0;
|
|
}
|
|
|
|
static int append_utf8_prefix(regex_pcre2_run_buffers *buffers,
|
|
regex_pcre2_run_result *result,
|
|
const uint8_t *source,
|
|
uint32_t length) {
|
|
uint32_t remaining = buffers->output_capacity - result->output_length;
|
|
uint32_t copied = length < remaining ? length : remaining;
|
|
|
|
if (copied < length) {
|
|
while (copied > 0 && (source[copied] & 0xc0u) == 0x80u) {
|
|
copied -= 1;
|
|
}
|
|
result->output_truncated = 1;
|
|
}
|
|
if (copied > 0) {
|
|
memcpy(buffers->output + result->output_length, source, copied);
|
|
result->output_length += copied;
|
|
}
|
|
return copied == length;
|
|
}
|
|
|
|
static int32_t configure_match_context_values(pcre2_match_context *context,
|
|
uint32_t match_limit,
|
|
uint32_t depth_limit,
|
|
uint32_t heap_limit_kib) {
|
|
if (pcre2_set_match_limit(context, match_limit) != 0 ||
|
|
pcre2_set_depth_limit(context, depth_limit) != 0 ||
|
|
pcre2_set_heap_limit(context, heap_limit_kib) != 0) {
|
|
return REGEX_PCRE2_ERROR_CONFIG;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int32_t configure_match_context(pcre2_match_context *context,
|
|
const regex_pcre2_limits *limits) {
|
|
return configure_match_context_values(
|
|
context, limits->match_limit, limits->depth_limit,
|
|
limits->heap_limit_kib);
|
|
}
|
|
|
|
static int trace_limits_valid(const regex_pcre2_trace_limits *limits) {
|
|
return limits != NULL && limits->maximum_events > 0 &&
|
|
limits->maximum_events <= REGEX_PCRE2_MAX_TRACE_EVENTS &&
|
|
limits->maximum_trace_bytes >= sizeof(regex_pcre2_trace_event) &&
|
|
limits->maximum_trace_bytes <= REGEX_PCRE2_MAX_TRACE_BYTES &&
|
|
limits->match_limit > 0 &&
|
|
limits->match_limit <= REGEX_PCRE2_MAX_MATCH_LIMIT &&
|
|
limits->depth_limit > 0 &&
|
|
limits->depth_limit <= REGEX_PCRE2_MAX_DEPTH_LIMIT &&
|
|
limits->heap_limit_kib > 0 &&
|
|
limits->heap_limit_kib <= REGEX_PCRE2_MAX_HEAP_LIMIT_KIB;
|
|
}
|
|
|
|
static uint32_t bounded_mark_length(PCRE2_SPTR mark, uint32_t *truncated) {
|
|
uint32_t length = 0;
|
|
if (mark == NULL) {
|
|
return 0;
|
|
}
|
|
while (length < REGEX_PCRE2_MAX_TRACE_MARK_BYTES && mark[length] != 0) {
|
|
length += 1;
|
|
}
|
|
if (length == REGEX_PCRE2_MAX_TRACE_MARK_BYTES && mark[length] != 0) {
|
|
*truncated = 1;
|
|
while (length > 0 && (mark[length] & 0xc0u) == 0x80u) {
|
|
length -= 1;
|
|
}
|
|
}
|
|
return length;
|
|
}
|
|
|
|
static int trace_callout(pcre2_callout_block *block, void *data) {
|
|
regex_pcre2_trace_state *state = (regex_pcre2_trace_state *)data;
|
|
regex_pcre2_trace_event *event = NULL;
|
|
uint32_t mark_length = 0;
|
|
uint32_t mark_was_truncated = 0;
|
|
uint64_t required_trace_bytes = 0;
|
|
|
|
if (block == NULL || state == NULL) {
|
|
return PCRE2_ERROR_CALLOUT;
|
|
}
|
|
if (state->total_event_count < UINT32_MAX) {
|
|
state->total_event_count += 1;
|
|
}
|
|
mark_length = bounded_mark_length(block->mark, &mark_was_truncated);
|
|
required_trace_bytes =
|
|
((uint64_t)state->event_count + 1u) *
|
|
sizeof(regex_pcre2_trace_event) +
|
|
state->mark_bytes_length + mark_length;
|
|
if (state->event_count >= state->maximum_events ||
|
|
state->event_count >= state->event_capacity ||
|
|
required_trace_bytes > state->maximum_trace_bytes ||
|
|
mark_length > state->mark_bytes_capacity - state->mark_bytes_length) {
|
|
state->events_truncated = 1;
|
|
return PCRE2_ERROR_CALLOUT;
|
|
}
|
|
|
|
event = &state->events[state->event_count];
|
|
event->callout_number = block->callout_number;
|
|
event->pattern_position_byte = bounded_offset(block->pattern_position);
|
|
event->next_item_length_byte = bounded_offset(block->next_item_length);
|
|
event->subject_position_byte = bounded_offset(block->current_position);
|
|
event->capture_top = block->capture_top;
|
|
event->capture_last = block->capture_last;
|
|
event->mark_offset = state->mark_bytes_length;
|
|
event->mark_length = mark_length;
|
|
if (mark_length > 0) {
|
|
memcpy(state->mark_bytes + state->mark_bytes_length, block->mark,
|
|
mark_length);
|
|
state->mark_bytes_length += mark_length;
|
|
}
|
|
state->marks_truncated |= mark_was_truncated;
|
|
state->event_count += 1;
|
|
return 0;
|
|
}
|
|
|
|
static int32_t run_bounded(const uint8_t *pattern,
|
|
uint32_t pattern_length,
|
|
const uint8_t *subject,
|
|
uint32_t subject_length,
|
|
const uint8_t *replacement,
|
|
uint32_t replacement_length,
|
|
uint32_t application_flags,
|
|
const regex_pcre2_limits *limits,
|
|
regex_pcre2_run_buffers *buffers,
|
|
int substitution,
|
|
regex_pcre2_run_result *result) {
|
|
pcre2_code *code = NULL;
|
|
pcre2_match_data *match_data = NULL;
|
|
pcre2_match_context *match_context = NULL;
|
|
int compile_error = 0;
|
|
PCRE2_SIZE compile_offset = 0;
|
|
uint32_t capture_count = 0;
|
|
uint32_t name_count = 0;
|
|
uint32_t options = 0;
|
|
uint32_t search_offset = 0;
|
|
uint32_t match_options = 0;
|
|
uint32_t copied_until = 0;
|
|
uint32_t capture_rows = 0;
|
|
int32_t status = 0;
|
|
const uint8_t *safe_pattern = nonnull_input(pattern);
|
|
const uint8_t *safe_subject = nonnull_input(subject);
|
|
const uint8_t *safe_replacement = nonnull_input(replacement);
|
|
|
|
if (result == NULL || !input_pointer_valid(pattern, pattern_length) ||
|
|
!input_pointer_valid(subject, subject_length) ||
|
|
(substitution &&
|
|
!input_pointer_valid(replacement, replacement_length)) ||
|
|
!buffers_valid(buffers, substitution)) {
|
|
return fail_run(result, REGEX_PCRE2_ERROR_INVALID_ARGUMENT,
|
|
REGEX_PCRE2_PHASE_BRIDGE, 0);
|
|
}
|
|
memset(result, 0, sizeof(*result));
|
|
|
|
if ((application_flags & ~REGEX_PCRE2_ALL_FLAGS) != 0) {
|
|
return fail_run(result, REGEX_PCRE2_ERROR_UNSUPPORTED_FLAGS,
|
|
REGEX_PCRE2_PHASE_BRIDGE, 0);
|
|
}
|
|
if (pattern_length > REGEX_PCRE2_MAX_PATTERN_BYTES) {
|
|
return fail_run(result, REGEX_PCRE2_ERROR_PATTERN_TOO_LARGE,
|
|
REGEX_PCRE2_PHASE_BRIDGE, 0);
|
|
}
|
|
if (subject_length > REGEX_PCRE2_MAX_SUBJECT_BYTES) {
|
|
return fail_run(result, REGEX_PCRE2_ERROR_SUBJECT_TOO_LARGE,
|
|
REGEX_PCRE2_PHASE_BRIDGE, 0);
|
|
}
|
|
if (substitution &&
|
|
replacement_length > REGEX_PCRE2_MAX_REPLACEMENT_BYTES) {
|
|
return fail_run(result, REGEX_PCRE2_ERROR_REPLACEMENT_TOO_LARGE,
|
|
REGEX_PCRE2_PHASE_BRIDGE, 0);
|
|
}
|
|
if (!limits_valid(limits)) {
|
|
return fail_run(result, REGEX_PCRE2_ERROR_LIMIT_OUT_OF_RANGE,
|
|
REGEX_PCRE2_PHASE_BRIDGE, 0);
|
|
}
|
|
|
|
options = to_pcre2_options(application_flags);
|
|
result->effective_options = options;
|
|
code = pcre2_compile(safe_pattern, pattern_length, options, &compile_error,
|
|
&compile_offset, NULL);
|
|
if (code == NULL) {
|
|
status = compile_error;
|
|
result->status = status;
|
|
result->error_phase = REGEX_PCRE2_PHASE_COMPILE;
|
|
result->error_offset = bounded_offset(compile_offset);
|
|
goto cleanup;
|
|
}
|
|
if (pcre2_pattern_info(code, PCRE2_INFO_CAPTURECOUNT, &capture_count) != 0 ||
|
|
pcre2_pattern_info(code, PCRE2_INFO_NAMECOUNT, &name_count) != 0) {
|
|
status = REGEX_PCRE2_ERROR_CONFIG;
|
|
result->status = status;
|
|
result->error_phase = REGEX_PCRE2_PHASE_BRIDGE;
|
|
goto cleanup;
|
|
}
|
|
result->capture_count = capture_count;
|
|
result->name_count = name_count;
|
|
if (capture_count > REGEX_PCRE2_MAX_CAPTURE_GROUPS) {
|
|
status = REGEX_PCRE2_ERROR_CAPTURE_LIMIT;
|
|
result->status = status;
|
|
result->error_phase = REGEX_PCRE2_PHASE_BRIDGE;
|
|
goto cleanup;
|
|
}
|
|
status = copy_names(code, buffers, result);
|
|
if (status != 0) {
|
|
result->status = status;
|
|
result->error_phase = REGEX_PCRE2_PHASE_BRIDGE;
|
|
goto cleanup;
|
|
}
|
|
|
|
match_data = pcre2_match_data_create_from_pattern(code, NULL);
|
|
match_context = pcre2_match_context_create(NULL);
|
|
if (match_data == NULL || match_context == NULL) {
|
|
status = PCRE2_ERROR_NOMEMORY;
|
|
result->status = status;
|
|
result->error_phase = REGEX_PCRE2_PHASE_BRIDGE;
|
|
goto cleanup;
|
|
}
|
|
status = configure_match_context(match_context, limits);
|
|
if (status != 0) {
|
|
result->status = status;
|
|
result->error_phase = REGEX_PCRE2_PHASE_BRIDGE;
|
|
goto cleanup;
|
|
}
|
|
|
|
while (search_offset <= subject_length) {
|
|
int match_status =
|
|
pcre2_match(code, safe_subject, subject_length, search_offset,
|
|
match_options, match_data, match_context);
|
|
PCRE2_SIZE *ovector = NULL;
|
|
uint32_t required_records = capture_count + 1;
|
|
uint32_t required_capture_rows = capture_count == 0 ? 1 : capture_count;
|
|
uint32_t group = 0;
|
|
uint32_t start = 0;
|
|
uint32_t end = 0;
|
|
|
|
if (match_status == PCRE2_ERROR_NOMATCH) {
|
|
break;
|
|
}
|
|
if (match_status < 0) {
|
|
status = match_status;
|
|
result->status = status;
|
|
result->error_phase = REGEX_PCRE2_PHASE_MATCH;
|
|
result->error_offset = search_offset;
|
|
goto cleanup;
|
|
}
|
|
if (match_status == 0) {
|
|
status = REGEX_PCRE2_ERROR_CONFIG;
|
|
result->status = status;
|
|
result->error_phase = REGEX_PCRE2_PHASE_BRIDGE;
|
|
goto cleanup;
|
|
}
|
|
|
|
ovector = pcre2_get_ovector_pointer(match_data);
|
|
start = bounded_offset(ovector[0]);
|
|
end = bounded_offset(ovector[1]);
|
|
if (start > end || end > subject_length) {
|
|
status = REGEX_PCRE2_ERROR_CONFIG;
|
|
result->status = status;
|
|
result->error_phase = REGEX_PCRE2_PHASE_BRIDGE;
|
|
goto cleanup;
|
|
}
|
|
if (result->match_count >= limits->maximum_matches ||
|
|
capture_rows + required_capture_rows >
|
|
limits->maximum_capture_rows ||
|
|
required_records > buffers->record_capacity - result->record_count) {
|
|
result->results_truncated = 1;
|
|
break;
|
|
}
|
|
|
|
for (group = 0; group <= capture_count; group += 1) {
|
|
regex_pcre2_match_record *record =
|
|
&buffers->records[result->record_count];
|
|
PCRE2_SIZE native_start = ovector[group * 2];
|
|
PCRE2_SIZE native_end = ovector[group * 2 + 1];
|
|
record->match_number = result->match_count + 1;
|
|
record->group_number = group;
|
|
if (native_start == PCRE2_UNSET || native_end == PCRE2_UNSET) {
|
|
record->start_byte = REGEX_PCRE2_UNSET_OFFSET;
|
|
record->end_byte = REGEX_PCRE2_UNSET_OFFSET;
|
|
} else {
|
|
record->start_byte = bounded_offset(native_start);
|
|
record->end_byte = bounded_offset(native_end);
|
|
}
|
|
result->record_count += 1;
|
|
}
|
|
result->match_count += 1;
|
|
capture_rows += required_capture_rows;
|
|
|
|
if (substitution && result->output_truncated == 0) {
|
|
PCRE2_SIZE replacement_output_length =
|
|
buffers->output_capacity - result->output_length + 1;
|
|
int substitute_status = 0;
|
|
if (!append_utf8_prefix(buffers, result, safe_subject + copied_until,
|
|
start - copied_until)) {
|
|
break;
|
|
}
|
|
replacement_output_length =
|
|
buffers->output_capacity - result->output_length + 1;
|
|
substitute_status = pcre2_substitute(
|
|
code, safe_subject, subject_length, search_offset,
|
|
match_options | PCRE2_SUBSTITUTE_MATCHED |
|
|
PCRE2_SUBSTITUTE_REPLACEMENT_ONLY |
|
|
PCRE2_SUBSTITUTE_UNSET_EMPTY,
|
|
match_data, match_context, safe_replacement, replacement_length,
|
|
buffers->output + result->output_length, &replacement_output_length);
|
|
if (substitute_status == PCRE2_ERROR_NOMEMORY) {
|
|
result->output_truncated = 1;
|
|
break;
|
|
}
|
|
if (substitute_status < 0) {
|
|
status = substitute_status;
|
|
result->status = status;
|
|
result->error_phase = REGEX_PCRE2_PHASE_SUBSTITUTE;
|
|
result->error_offset = bounded_offset(replacement_output_length);
|
|
goto cleanup;
|
|
}
|
|
result->output_length += bounded_offset(replacement_output_length);
|
|
result->substitution_count += (uint32_t)substitute_status;
|
|
copied_until = end;
|
|
}
|
|
|
|
if ((application_flags & REGEX_PCRE2_GLOBAL) == 0) {
|
|
break;
|
|
}
|
|
{
|
|
PCRE2_SIZE next_offset = search_offset;
|
|
uint32_t next_options = 0;
|
|
if (!pcre2_next_match(match_data, &next_offset, &next_options)) {
|
|
break;
|
|
}
|
|
search_offset = bounded_offset(next_offset);
|
|
match_options = next_options;
|
|
}
|
|
}
|
|
|
|
if (substitution && result->output_truncated == 0) {
|
|
(void)append_utf8_prefix(buffers, result, safe_subject + copied_until,
|
|
subject_length - copied_until);
|
|
}
|
|
|
|
cleanup:
|
|
if (match_context != NULL) {
|
|
pcre2_match_context_free(match_context);
|
|
}
|
|
if (match_data != NULL) {
|
|
pcre2_match_data_free(match_data);
|
|
}
|
|
if (code != NULL) {
|
|
pcre2_code_free(code);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
uint32_t regex_pcre2_bridge_abi_version(void) {
|
|
return REGEX_PCRE2_BRIDGE_ABI_VERSION;
|
|
}
|
|
|
|
uint32_t regex_pcre2_config_flags(void) {
|
|
uint32_t unicode = 0;
|
|
uint32_t jit = 0;
|
|
uint32_t flags = 0;
|
|
|
|
if (pcre2_config(PCRE2_CONFIG_UNICODE, &unicode) != 0 ||
|
|
pcre2_config(PCRE2_CONFIG_JIT, &jit) != 0) {
|
|
return 0;
|
|
}
|
|
if (unicode != 0) {
|
|
flags |= REGEX_PCRE2_CONFIG_UNICODE;
|
|
}
|
|
if (jit != 0) {
|
|
flags |= REGEX_PCRE2_CONFIG_JIT;
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
const uint8_t *regex_pcre2_version(void) {
|
|
static uint8_t version[64];
|
|
if (pcre2_config(PCRE2_CONFIG_VERSION, version) <= 0) {
|
|
version[0] = '\0';
|
|
}
|
|
return version;
|
|
}
|
|
|
|
int32_t regex_pcre2_error_message(int32_t error_code,
|
|
uint8_t *output,
|
|
uint32_t output_capacity) {
|
|
int status = 0;
|
|
if (output == NULL || output_capacity == 0) {
|
|
return REGEX_PCRE2_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
status = pcre2_get_error_message(error_code, output, output_capacity);
|
|
if (status < 0) {
|
|
output[0] = '\0';
|
|
}
|
|
return status;
|
|
}
|
|
|
|
int32_t regex_pcre2_compile_probe(const uint8_t *pattern,
|
|
uint32_t pattern_length,
|
|
uint32_t application_flags,
|
|
regex_pcre2_compile_result *result) {
|
|
pcre2_code *code = NULL;
|
|
int error_code = 0;
|
|
PCRE2_SIZE error_offset = 0;
|
|
uint32_t capture_count = 0;
|
|
uint32_t name_count = 0;
|
|
uint32_t options = 0;
|
|
|
|
if (result == NULL || !input_pointer_valid(pattern, pattern_length)) {
|
|
return fail_compile(result, REGEX_PCRE2_ERROR_INVALID_ARGUMENT);
|
|
}
|
|
memset(result, 0, sizeof(*result));
|
|
|
|
if ((application_flags & ~REGEX_PCRE2_COMPILE_FLAGS) != 0) {
|
|
return fail_compile(result, REGEX_PCRE2_ERROR_UNSUPPORTED_FLAGS);
|
|
}
|
|
if (pattern_length > REGEX_PCRE2_MAX_PATTERN_BYTES) {
|
|
return fail_compile(result, REGEX_PCRE2_ERROR_PATTERN_TOO_LARGE);
|
|
}
|
|
|
|
options = to_pcre2_options(application_flags);
|
|
result->effective_options = options;
|
|
code = pcre2_compile(nonnull_input(pattern), pattern_length, options,
|
|
&error_code, &error_offset, NULL);
|
|
if (code == NULL) {
|
|
result->status = error_code;
|
|
result->error_offset = bounded_offset(error_offset);
|
|
return error_code;
|
|
}
|
|
|
|
if (pcre2_pattern_info(code, PCRE2_INFO_CAPTURECOUNT, &capture_count) != 0 ||
|
|
pcre2_pattern_info(code, PCRE2_INFO_NAMECOUNT, &name_count) != 0) {
|
|
pcre2_code_free(code);
|
|
return fail_compile(result, REGEX_PCRE2_ERROR_CONFIG);
|
|
}
|
|
|
|
result->capture_count = capture_count;
|
|
result->name_count = name_count;
|
|
pcre2_code_free(code);
|
|
return 0;
|
|
}
|
|
|
|
int32_t regex_pcre2_execute(
|
|
const uint8_t *pattern,
|
|
uint32_t pattern_length,
|
|
const uint8_t *subject,
|
|
uint32_t subject_length,
|
|
uint32_t application_flags,
|
|
const regex_pcre2_limits *limits,
|
|
regex_pcre2_match_record *records,
|
|
uint32_t record_capacity,
|
|
regex_pcre2_name_record *name_records,
|
|
uint32_t name_record_capacity,
|
|
uint8_t *name_bytes,
|
|
uint32_t name_bytes_capacity,
|
|
regex_pcre2_run_result *result) {
|
|
regex_pcre2_run_buffers buffers = {
|
|
records, record_capacity, name_records, name_record_capacity,
|
|
name_bytes, name_bytes_capacity, NULL, 0};
|
|
return run_bounded(pattern, pattern_length, subject, subject_length, NULL, 0,
|
|
application_flags, limits, &buffers, 0, result);
|
|
}
|
|
|
|
int32_t regex_pcre2_substitute(
|
|
const uint8_t *pattern,
|
|
uint32_t pattern_length,
|
|
const uint8_t *subject,
|
|
uint32_t subject_length,
|
|
const uint8_t *replacement,
|
|
uint32_t replacement_length,
|
|
uint32_t application_flags,
|
|
const regex_pcre2_limits *limits,
|
|
regex_pcre2_match_record *records,
|
|
uint32_t record_capacity,
|
|
regex_pcre2_name_record *name_records,
|
|
uint32_t name_record_capacity,
|
|
uint8_t *name_bytes,
|
|
uint32_t name_bytes_capacity,
|
|
uint8_t *output,
|
|
uint32_t output_capacity,
|
|
regex_pcre2_run_result *result) {
|
|
regex_pcre2_run_buffers buffers = {
|
|
records, record_capacity, name_records, name_record_capacity,
|
|
name_bytes, name_bytes_capacity, output, output_capacity};
|
|
return run_bounded(pattern, pattern_length, subject, subject_length,
|
|
replacement, replacement_length, application_flags,
|
|
limits, &buffers, 1, result);
|
|
}
|
|
|
|
int32_t regex_pcre2_trace(
|
|
const uint8_t *pattern,
|
|
uint32_t pattern_length,
|
|
const uint8_t *subject,
|
|
uint32_t subject_length,
|
|
uint32_t application_flags,
|
|
const regex_pcre2_trace_limits *limits,
|
|
regex_pcre2_trace_event *events,
|
|
uint32_t event_capacity,
|
|
uint8_t *mark_bytes,
|
|
uint32_t mark_bytes_capacity,
|
|
regex_pcre2_trace_result *result) {
|
|
pcre2_code *code = NULL;
|
|
pcre2_match_data *match_data = NULL;
|
|
pcre2_match_context *match_context = NULL;
|
|
regex_pcre2_trace_state state;
|
|
int compile_error = 0;
|
|
PCRE2_SIZE compile_offset = 0;
|
|
uint32_t capture_count = 0;
|
|
uint32_t options = 0;
|
|
int32_t match_status = 0;
|
|
int32_t status = 0;
|
|
|
|
if (result == NULL || !input_pointer_valid(pattern, pattern_length) ||
|
|
!input_pointer_valid(subject, subject_length) || events == NULL ||
|
|
event_capacity == 0 ||
|
|
(mark_bytes == NULL && mark_bytes_capacity != 0)) {
|
|
return fail_trace(result, REGEX_PCRE2_ERROR_INVALID_ARGUMENT,
|
|
REGEX_PCRE2_PHASE_BRIDGE, 0);
|
|
}
|
|
memset(result, 0, sizeof(*result));
|
|
result->last_complete_event = UINT32_MAX;
|
|
if ((application_flags & ~REGEX_PCRE2_ALL_FLAGS) != 0) {
|
|
return fail_trace(result, REGEX_PCRE2_ERROR_UNSUPPORTED_FLAGS,
|
|
REGEX_PCRE2_PHASE_BRIDGE, 0);
|
|
}
|
|
if (pattern_length > REGEX_PCRE2_MAX_PATTERN_BYTES) {
|
|
return fail_trace(result, REGEX_PCRE2_ERROR_PATTERN_TOO_LARGE,
|
|
REGEX_PCRE2_PHASE_BRIDGE, 0);
|
|
}
|
|
if (subject_length > REGEX_PCRE2_MAX_SUBJECT_BYTES) {
|
|
return fail_trace(result, REGEX_PCRE2_ERROR_SUBJECT_TOO_LARGE,
|
|
REGEX_PCRE2_PHASE_BRIDGE, 0);
|
|
}
|
|
if (!trace_limits_valid(limits) ||
|
|
event_capacity < limits->maximum_events ||
|
|
event_capacity > REGEX_PCRE2_MAX_TRACE_EVENTS ||
|
|
mark_bytes_capacity > REGEX_PCRE2_MAX_TRACE_BYTES) {
|
|
return fail_trace(result, REGEX_PCRE2_ERROR_LIMIT_OUT_OF_RANGE,
|
|
REGEX_PCRE2_PHASE_BRIDGE, 0);
|
|
}
|
|
|
|
options = to_pcre2_options(application_flags) | PCRE2_AUTO_CALLOUT;
|
|
result->effective_options = options;
|
|
code = pcre2_compile(nonnull_input(pattern), pattern_length, options,
|
|
&compile_error, &compile_offset, NULL);
|
|
if (code == NULL) {
|
|
status = compile_error;
|
|
result->status = status;
|
|
result->error_phase = REGEX_PCRE2_PHASE_COMPILE;
|
|
result->error_offset = bounded_offset(compile_offset);
|
|
goto cleanup;
|
|
}
|
|
if (pcre2_pattern_info(code, PCRE2_INFO_CAPTURECOUNT, &capture_count) != 0) {
|
|
status = REGEX_PCRE2_ERROR_CONFIG;
|
|
result->status = status;
|
|
result->error_phase = REGEX_PCRE2_PHASE_BRIDGE;
|
|
goto cleanup;
|
|
}
|
|
if (capture_count > REGEX_PCRE2_MAX_CAPTURE_GROUPS) {
|
|
status = REGEX_PCRE2_ERROR_CAPTURE_LIMIT;
|
|
result->status = status;
|
|
result->error_phase = REGEX_PCRE2_PHASE_BRIDGE;
|
|
goto cleanup;
|
|
}
|
|
|
|
match_data = pcre2_match_data_create_from_pattern(code, NULL);
|
|
match_context = pcre2_match_context_create(NULL);
|
|
if (match_data == NULL || match_context == NULL) {
|
|
status = PCRE2_ERROR_NOMEMORY;
|
|
result->status = status;
|
|
result->error_phase = REGEX_PCRE2_PHASE_BRIDGE;
|
|
goto cleanup;
|
|
}
|
|
memset(&state, 0, sizeof(state));
|
|
state.events = events;
|
|
state.event_capacity = event_capacity;
|
|
state.mark_bytes = mark_bytes;
|
|
state.mark_bytes_capacity = mark_bytes_capacity;
|
|
state.maximum_events = limits->maximum_events;
|
|
state.maximum_trace_bytes = limits->maximum_trace_bytes;
|
|
status = configure_match_context_values(
|
|
match_context, limits->match_limit, limits->depth_limit,
|
|
limits->heap_limit_kib);
|
|
if (status != 0 ||
|
|
pcre2_set_callout(match_context, trace_callout, &state) != 0) {
|
|
status = REGEX_PCRE2_ERROR_CONFIG;
|
|
result->status = status;
|
|
result->error_phase = REGEX_PCRE2_PHASE_BRIDGE;
|
|
goto cleanup;
|
|
}
|
|
/*
|
|
* The global flag is intentionally application-level for normal execution.
|
|
* A trace represents one exact pcre2_match() invocation.
|
|
*/
|
|
match_status = pcre2_match(code, nonnull_input(subject), subject_length, 0, 0,
|
|
match_data, match_context);
|
|
result->native_match_status = match_status;
|
|
result->event_count = state.event_count;
|
|
result->total_event_count = state.total_event_count;
|
|
result->mark_bytes_length = state.mark_bytes_length;
|
|
result->trace_bytes_length =
|
|
state.event_count * (uint32_t)sizeof(regex_pcre2_trace_event) +
|
|
state.mark_bytes_length;
|
|
result->events_truncated = state.events_truncated;
|
|
result->marks_truncated = state.marks_truncated;
|
|
result->last_complete_event =
|
|
state.event_count == 0 ? UINT32_MAX : state.event_count - 1;
|
|
if (match_status >= 0) {
|
|
result->matched = 1;
|
|
} else if (match_status == PCRE2_ERROR_NOMATCH ||
|
|
(match_status == PCRE2_ERROR_CALLOUT &&
|
|
state.events_truncated != 0)) {
|
|
status = 0;
|
|
} else {
|
|
status = match_status;
|
|
result->status = status;
|
|
result->error_phase = REGEX_PCRE2_PHASE_MATCH;
|
|
if (state.event_count > 0) {
|
|
result->error_offset =
|
|
events[state.event_count - 1].subject_position_byte;
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
if (match_context != NULL) {
|
|
pcre2_match_context_free(match_context);
|
|
}
|
|
if (match_data != NULL) {
|
|
pcre2_match_data_free(match_data);
|
|
}
|
|
if (code != NULL) {
|
|
pcre2_code_free(code);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
int32_t regex_pcre2_self_test(void) {
|
|
static const uint8_t pattern[] = "(?<word>\\p{L}+)-(?<number>\\d+)";
|
|
static const uint8_t subject[] = "x Grüße-42 y";
|
|
static const uint8_t replacement[] = "$<number>:$<word>";
|
|
static const uint8_t trace_pattern[] = "a+b";
|
|
static const uint8_t trace_subject[] = "aaab";
|
|
regex_pcre2_compile_result compile_result;
|
|
regex_pcre2_run_result run_result;
|
|
regex_pcre2_trace_result trace_result;
|
|
regex_pcre2_limits limits = {10, 100, 1000000, 1000, 32768};
|
|
regex_pcre2_trace_limits trace_limits = {100, 4096, 1000000, 1000,
|
|
32768};
|
|
regex_pcre2_match_record records[3];
|
|
regex_pcre2_name_record names[2];
|
|
regex_pcre2_trace_event trace_events[100];
|
|
uint8_t name_bytes[64];
|
|
uint8_t trace_marks[64];
|
|
uint8_t output[65];
|
|
int32_t status = regex_pcre2_compile_probe(
|
|
pattern, (uint32_t)(sizeof(pattern) - 1),
|
|
REGEX_PCRE2_UTF | REGEX_PCRE2_UCP, &compile_result);
|
|
|
|
if (status != 0 || compile_result.capture_count != 2 ||
|
|
compile_result.name_count != 2) {
|
|
return 1;
|
|
}
|
|
status = regex_pcre2_substitute(
|
|
pattern, (uint32_t)(sizeof(pattern) - 1), subject,
|
|
(uint32_t)(sizeof(subject) - 1), replacement,
|
|
(uint32_t)(sizeof(replacement) - 1),
|
|
REGEX_PCRE2_UTF | REGEX_PCRE2_UCP, &limits, records, 3, names, 2,
|
|
name_bytes, sizeof(name_bytes), output, sizeof(output) - 1, &run_result);
|
|
if (status != 0 || run_result.match_count != 1 ||
|
|
run_result.record_count != 3 || run_result.substitution_count != 1 ||
|
|
run_result.output_truncated != 0 ||
|
|
run_result.output_length != sizeof("x 42:Grüße y") - 1 ||
|
|
memcmp(output, "x 42:Grüße y", sizeof("x 42:Grüße y") - 1) != 0) {
|
|
return 2;
|
|
}
|
|
status = regex_pcre2_trace(
|
|
trace_pattern, (uint32_t)(sizeof(trace_pattern) - 1), trace_subject,
|
|
(uint32_t)(sizeof(trace_subject) - 1),
|
|
REGEX_PCRE2_UTF | REGEX_PCRE2_UCP, &trace_limits, trace_events, 100,
|
|
trace_marks, sizeof(trace_marks), &trace_result);
|
|
if (status != 0 || trace_result.status != 0 ||
|
|
trace_result.native_match_status <= 0 || trace_result.matched != 1 ||
|
|
trace_result.event_count == 0 || trace_result.events_truncated != 0 ||
|
|
trace_result.last_complete_event != trace_result.event_count - 1) {
|
|
return 3;
|
|
}
|
|
if (regex_pcre2_bridge_abi_version() != REGEX_PCRE2_BRIDGE_ABI_VERSION ||
|
|
regex_pcre2_config_flags() != REGEX_PCRE2_CONFIG_UNICODE) {
|
|
return 4;
|
|
}
|
|
if (strcmp((const char *)regex_pcre2_version(), "10.47 2025-10-21") != 0) {
|
|
return 5;
|
|
}
|
|
return 0;
|
|
}
|