// SPDX-FileCopyrightText: 2023 Rot127 // SPDX-License-Identifier: LGPL-3.0-only #define PCRE2_STATIC #define PCRE2_CODE_UNIT_WIDTH 0 #include #include #include #include #include #include #include #include // Default JIT stack size is 32KiB. // But the wildcard pattern we have for strings takes more. #define RZ_REGEX_JIT_STACK_MIN (32 * 1024) // 1MiB are documented as reasoable maxima. #define RZ_REGEX_JIT_STACK_MAX (1024 * 1024) typedef pcre2_general_context_8 RzRegexGeneralContext8; ///< General context. // typedef pcre2_compile_context RzRegexCompContext; ///< The context for compiling. typedef pcre2_match_context_8 RzRegexMatchContext8; ///< The context for matching. typedef struct { RzRegexGeneralContext8 *general; RzRegexCompContext *compile; RzRegexMatchContext8 *match; } RzRegexContexts8; static void check_faulty_pcre2_word_width(size_t pcre2_word_width) { if (pcre2_word_width != 8 && pcre2_word_width != 16 && pcre2_word_width != 32) { RZ_LOG_FATAL("PCRE2 word with of %" PFMTSZu " is not supported.\n", pcre2_word_width); abort(); } } static void print_pcre2_err(RZ_NULLABLE const char *utf8_pattern, RzRegexStatus err_num, size_t err_off) { PCRE2_UCHAR8 buffer[256]; pcre2_get_error_message_8(err_num, buffer, sizeof(buffer)); RZ_LOG_ERROR("Regex compilation for '%s' failed at %" PFMTSZu ": %s\n", utf8_pattern ? utf8_pattern : "(null)", err_off, buffer); } static RZ_OWN void *regex_new(RZ_NONNULL const char *pattern, RzRegexFlags cflags, RzRegexFlags jflags, RzRegexCompContext *ccontext, size_t pcre2_word_width) { rz_return_val_if_fail(pattern, NULL); check_faulty_pcre2_word_width(pcre2_word_width); if (jflags == RZ_REGEX_DEFAULT) { jflags = PCRE2_JIT_COMPLETE; } RzRegexStatus err_num; RzRegexSize err_off; ut32 supported = 0; if (pcre2_word_width == 8) { pcre2_config_8(PCRE2_CONFIG_UNICODE, &supported); } else if (pcre2_word_width == 16) { pcre2_config_16(PCRE2_CONFIG_UNICODE, &supported); } else { pcre2_config_32(PCRE2_CONFIG_UNICODE, &supported); } if (supported != 1) { RZ_LOG_ERROR("Unicode not supported by PCRE2 library.\n"); return NULL; } char *fixed_pat = NULL; const char *pat = NULL; if (((cflags & RZ_REGEX_EXTENDED) || (cflags & RZ_REGEX_EXTENDED_MORE))) { // In PCRE2 with the extended flag set, ascii space characters ' ' are skipped. // We need to replace them with \s unfortunately to keep our API stable. if (!strchr(pattern, ' ')) { pat = pattern; } else { fixed_pat = rz_str_replace(rz_str_dup(pattern), " ", "\\s", 1); pat = fixed_pat; } } else { pat = pattern; } if (!(cflags & PCRE2_LITERAL)) { cflags |= PCRE2_UCP; } void *regex; if (pcre2_word_width == 8) { regex = pcre2_compile_8( (PCRE2_SPTR8)pat, PCRE2_ZERO_TERMINATED, cflags | PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MATCH_INVALID_UTF, &err_num, &err_off, ccontext); if (!regex) { print_pcre2_err(pat, err_num, err_off); free(fixed_pat); return NULL; } #ifdef SUPPORTS_PCRE2_JIT RzRegexStatus jit_err = pcre2_jit_compile_8(regex, jflags); if (jit_err < 0) { print_pcre2_err(pat, jit_err, 0); } #endif } else if (pcre2_word_width == 16) { ut16 *utf16_pat = rz_str_utf8_to_utf16(pat, RZ_HOST_IS_BIG_ENDIAN); regex = pcre2_compile_16( (PCRE2_SPTR16)utf16_pat, PCRE2_ZERO_TERMINATED, cflags | PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MATCH_INVALID_UTF, &err_num, &err_off, ccontext); if (!regex) { print_pcre2_err(pat, err_num, err_off); RZ_LOG_WARN("PCRE2 pattern offset might be invalid, due to different encoding.\n"); free(fixed_pat); free(utf16_pat); return NULL; } free(utf16_pat); #ifdef SUPPORTS_PCRE2_JIT RzRegexStatus jit_err = pcre2_jit_compile_16(regex, jflags); if (jit_err < 0) { print_pcre2_err(pat, jit_err, 0); } #endif } else { ut32 *utf32_pat = rz_str_utf8_to_utf32(pat, RZ_HOST_IS_BIG_ENDIAN); regex = pcre2_compile_32( (PCRE2_SPTR32)utf32_pat, PCRE2_ZERO_TERMINATED, cflags | PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MATCH_INVALID_UTF, &err_num, &err_off, ccontext); if (!regex) { print_pcre2_err(pat, err_num, err_off / 4); free(fixed_pat); free(utf32_pat); return NULL; } free(utf32_pat); #ifdef SUPPORTS_PCRE2_JIT RzRegexStatus jit_err = pcre2_jit_compile_32(regex, jflags); if (jit_err < 0) { print_pcre2_err(pat, jit_err, 0); } #endif } free(fixed_pat); return regex; } /** * \brief Compile a regex pattern to a RzRegex and return it. * In case of an error, an error message is printed and NULL is returned. * * NOTE: If RZ_REGEX_EXTENDED is passed, spaces in the pattern will **not** be skipped! * This is contrary to the PCRE2 documentation. But keeps our internal regex usage stable. * * \param pattern The regex pattern string. * \param cflags The compilation flags or zero for default. * PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MATCH_INVALID_UTF and PCRE2_UCP are enforced currently. * \param jflags The compilation flags for the JIT compiler. * You can pass RZ_REGEX_JIT_PARTIAL_SOFT or RZ_REGEX_JIT_PARTIAL_HARD if you * intend to use the pattern for partial matching. Otherwise set it to 0. * \param ccontext A compile context or NULL. * * \return The compiled regex or NULL in case of failure. */ RZ_API RZ_OWN RzRegex *rz_regex_new(RZ_NONNULL const char *pattern, RzRegexFlags cflags, RzRegexFlags jflags, RzRegexCompContext *ccontext) { rz_return_val_if_fail(pattern, NULL); return regex_new(pattern, cflags, jflags, ccontext, 8); } /** * \brief Compile an UTF-16 regex pattern to a RzRegex16 and return it. * In case of an error, an error message is printed and NULL is returned. * * NOTE: The pattern and matching will always be in the host's endianness. * NOTE: If RZ_REGEX_EXTENDED is passed, spaces in the pattern **will** be skipped! * This is the opposite behavior to the default UTF-8 regular expressions. * * \param pattern The regex pattern string. It must be an UTF-8 encoded string. * \param cflags The compilation flags or zero for default. * PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MATCH_INVALID_UTF and PCRE2_UCP are enforced currently. * \param jflags The compilation flags for the JIT compiler. * You can pass RZ_REGEX_JIT_PARTIAL_SOFT or RZ_REGEX_JIT_PARTIAL_HARD if you * intend to use the pattern for partial matching. Otherwise set it to 0. * \param ccontext A compile context or NULL. * * \return The compiled regex or NULL in case of failure. */ RZ_API RZ_OWN RzRegex16 *rz_regex_new_16(RZ_NONNULL const char *pattern, RzRegexFlags cflags, RzRegexFlags jflags, RzRegexCompContext16 *ccontext) { rz_return_val_if_fail(pattern, NULL); return regex_new(pattern, cflags, jflags, ccontext, 16); } /** * \brief Compile an UTF-32 regex pattern to a RzRegex32 and return it. * In case of an error, an error message is printed and NULL is returned. * * NOTE: The pattern and matching will always be in the host's endianness. * * \param pattern The regex pattern string. It must be an UTF-8 encoded string. * \param cflags The compilation flags or zero for default. * PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MATCH_INVALID_UTF and PCRE2_UCP are enforced currently. * \param jflags The compilation flags for the JIT compiler. * You can pass RZ_REGEX_JIT_PARTIAL_SOFT or RZ_REGEX_JIT_PARTIAL_HARD if you * intend to use the pattern for partial matching. Otherwise set it to 0. * \param ccontext A compile context or NULL. * * \return The compiled regex or NULL in case of failure. */ RZ_API RZ_OWN RzRegex32 *rz_regex_new_32(RZ_NONNULL const char *pattern, RzRegexFlags cflags, RzRegexFlags jflags, RzRegexCompContext32 *ccontext) { rz_return_val_if_fail(pattern, NULL); return regex_new(pattern, cflags, jflags, ccontext, 32); } /** * \brief Compile an Regex pattern of \p type. * * NOTE: The pattern and matching will always be in the host's endianness. * * \param pattern The regex pattern string. It must be an UTF-8 encoded string. * \param cflags The compilation flags or zero for default. * PCRE2_UCP | PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MATCH_INVALID_UTF are enforced currently. * \param jflags The compilation flags for the JIT compiler. * You can pass RZ_REGEX_JIT_PARTIAL_SOFT or RZ_REGEX_JIT_PARTIAL_HARD if you * intend to use the pattern for partial matching. Otherwise set it to RZ_REGEX_DEFAULT. * \param ccontext A compile context or NULL. * \param type The string encoding type the pattern should match. * * \return The compiled regex or NULL in case of failure. */ RZ_API RZ_OWN RzRegexMulti *rz_regex_new_multi(RZ_NONNULL const char *pattern, RzRegexFlags cflags, RzRegexFlags jflags, RzRegexCompContext *ccontext, RzRegexType type) { RzRegexMulti *re = RZ_NEW0(RzRegexMulti); if (!re) { return NULL; } if (jflags == RZ_REGEX_DEFAULT) { jflags = PCRE2_JIT_COMPLETE; } re->re_type = type; re->compile_flags_jit = jflags; switch (type) { default: rz_warn_if_reached(); free(re); return NULL; case RZ_REGEX_UTF8: re->re8 = rz_regex_new(pattern, cflags, jflags, ccontext); break; case RZ_REGEX_UTF16: re->re16 = rz_regex_new_16(pattern, cflags, jflags, ccontext); break; case RZ_REGEX_UTF32: re->re32 = rz_regex_new_32(pattern, cflags, jflags, ccontext); break; } if (!re->re8 && !re->re16 && !re->re32) { free(re); return NULL; } #ifdef SUPPORTS_PCRE2_JIT switch (type) { default: rz_warn_if_reached(); return NULL; case RZ_REGEX_UTF8: re->jit_stack = pcre2_jit_stack_create_8(RZ_REGEX_JIT_STACK_MIN, RZ_REGEX_JIT_STACK_MAX, NULL); break; case RZ_REGEX_UTF16: re->jit_stack = pcre2_jit_stack_create_16(RZ_REGEX_JIT_STACK_MIN, RZ_REGEX_JIT_STACK_MAX, NULL); break; case RZ_REGEX_UTF32: re->jit_stack = pcre2_jit_stack_create_32(RZ_REGEX_JIT_STACK_MIN, RZ_REGEX_JIT_STACK_MAX, NULL); break; } if (!re->jit_stack) { rz_warn_if_reached(); rz_regex_free_multi(re); return NULL; } #endif return re; } /** * \brief Compile a regex pattern with raw bytes. * In case of an error, an error message is printed and NULL is returned. * * Unlike `rz_regex_new()` it doesn't compile the pattern with the PCRE2_UTF flag. * So any byte can be part of a pattern, not just UTF-8 compatible ones. * * \param pattern The pattern. Bytes must not be escaped. * \param pattern_len The pattern length. * \param cflags The compilation flags. * \param jflags The compilation flags for the JIT compiler. * You can pass RZ_REGEX_JIT_PARTIAL_SOFT or RZ_REGEX_JIT_PARTIAL_HARD if you * intend to use the pattern for partial matching. Otherwise set it to 0. * \param ccontext A compile context or NULL. * * \return The compiled regex or NULL in case of failure. */ RZ_API RZ_OWN RzRegex *rz_regex_new_bytes(RZ_NONNULL const ut8 *pattern, size_t pattern_len, RzRegexFlags cflags, RzRegexFlags jflags, RzRegexCompContext *ccontext) { rz_return_val_if_fail(pattern, NULL); if (jflags == RZ_REGEX_DEFAULT) { jflags = PCRE2_JIT_COMPLETE; } RzRegexStatus err_num; RzRegexSize err_off; RzRegex *regex = pcre2_compile_8( pattern, pattern_len, cflags, &err_num, &err_off, ccontext); if (!regex) { print_pcre2_err((const char *)pattern, err_num, err_off); return NULL; } #ifdef SUPPORTS_PCRE2_JIT RzRegexStatus jit_err = pcre2_jit_compile_8(regex, jflags); if (jit_err < 0) { print_pcre2_err((const char *)pattern, jit_err, 0); } #endif return regex; } /** * \brief Frees a given RzRegex. * * \param regex The RzRegex to free. */ RZ_API void rz_regex_free(RZ_OWN RzRegex *regex) { pcre2_code_free_8(regex); } /** * \brief Frees a given RzRegex16. * * \param regex The RzRegex16 to free. */ RZ_API void rz_regex_free_16(RZ_OWN RzRegex16 *regex) { pcre2_code_free_8(regex); } /** * \brief Frees a given RzRegex32. * * \param regex The RzRegex32 to free. */ RZ_API void rz_regex_free_32(RZ_OWN RzRegex32 *regex) { pcre2_code_free_8(regex); } /** * \brief Makes a clone of the given multi \p regex object. * * NOTE: Cloning is only performed if the library is build with JIT support. * JIT patterns cannot be used by multiple threads. * Patterns without JIT matching can be used by multiple threads. * So JIT compiled patterns need to be clonedbefore being used * in a thread save manner. * * After usage the returned pointer should always * be freed via rz_regex_free_multi_clone(). * * \param regex The multi regex to clone. * \param clone_jit If set, the clone will also have JIT pattern matching. * If unset it still clones the pattern, but the clone won't support * JIT pattern matching. * * \return The clone for \p regex or NULL in case of failure. */ RZ_API RZ_OWN RzRegexMulti *rz_regex_multi_clone(RZ_NONNULL RzRegexMulti *regex, bool clone_jit) { rz_return_val_if_fail(regex, NULL); #ifndef SUPPORTS_PCRE2_JIT return regex; #else RzRegexMulti *clone = RZ_NEW0(RzRegexMulti); if (!clone) { return NULL; } clone->re_type = regex->re_type; clone->compile_flags_jit = regex->compile_flags_jit; switch (regex->re_type) { case RZ_REGEX_UTF8: clone->re8 = pcre2_code_copy_with_tables_8(regex->re8); if (clone_jit) { pcre2_jit_compile_8(clone->re8, clone->compile_flags_jit); } break; case RZ_REGEX_UTF16: clone->re16 = pcre2_code_copy_with_tables_16(regex->re16); if (clone_jit) { pcre2_jit_compile_16(clone->re16, clone->compile_flags_jit); } break; case RZ_REGEX_UTF32: clone->re32 = pcre2_code_copy_with_tables_32(regex->re32); if (clone_jit) { pcre2_jit_compile_32(clone->re32, clone->compile_flags_jit); } break; } if (!(clone->re8 || clone->re16 || clone->re32)) { free(clone); return NULL; } return clone; #endif } /** * \brief Frees a cloned RzRegexMulti object. * Only RzRegexMulti returned by rz_regex_clone_multi() should be passed here. * * This function behaves differently if Rizin is compiled with or without * JIT support. Without JIT support enabled, it will just return. * With JIT support it will free the \p regex. * * Please see rz_regex_clone_multi() for details. * * \param regex The cloned multi regex to free. */ RZ_API void rz_regex_free_multi_clone(RZ_NULLABLE RZ_OWN RzRegexMulti *regex) { #ifndef SUPPORTS_PCRE2_JIT return; #else rz_regex_free_multi(regex); #endif } /** * \brief Frees a given RzRegexMulti. * * \param regex The RzRegexMulti to free. */ RZ_API void rz_regex_free_multi(RZ_NULLABLE RZ_OWN RzRegexMulti *regex_multi) { if (!regex_multi) { return; } switch (regex_multi->re_type) { case RZ_REGEX_UTF8: rz_regex_free(regex_multi->re8); break; case RZ_REGEX_UTF16: rz_regex_free_16(regex_multi->re16); break; case RZ_REGEX_UTF32: rz_regex_free_32(regex_multi->re32); break; } #ifdef SUPPORTS_PCRE2_JIT switch (regex_multi->re_type) { case RZ_REGEX_UTF8: pcre2_jit_stack_free_8(regex_multi->jit_stack); break; case RZ_REGEX_UTF16: pcre2_jit_stack_free_16(regex_multi->jit_stack); break; case RZ_REGEX_UTF32: pcre2_jit_stack_free_32(regex_multi->jit_stack); break; } #endif free(regex_multi); } static void rz_regex_match_data_free(RZ_OWN RzRegexMatchData *match_data) { pcre2_match_data_free_8(match_data); } /** * \brief Matches the \p regex in the \p text and returns a status code with the result. * * \param regex The regex pattern to match. * \param text The text to search in. * \param text_size The length of the buffer pointed to by \p text. * Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string. * \param text_offset The offset into \p text from where the search starts. * \param mflags Match flags. PCRE2_NO_UTF_CHECK is enforced currently. * * \return A status code which describes the result. */ RZ_API RzRegexStatus rz_regex_match(RZ_NONNULL const RzRegex *regex, RZ_NONNULL const char *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags mflags) { rz_return_val_if_fail(regex && text, RZ_REGEX_ERROR_NOMATCH); pcre2_match_data_8 *mdata = pcre2_match_data_create_from_pattern_8(regex, NULL); RzRegexStatus rc = pcre2_match_8(regex, (PCRE2_SPTR8)text, text_size, text_offset, mflags | PCRE2_NO_UTF_CHECK, mdata, NULL); pcre2_match_data_free_8(mdata); return rc; } /** * \brief Matches the \p regex in the \p text and returns a status code with the result. * * NOTE: This matches against UTF-16 strings (host-endianness). * * \param regex The regex pattern to match. * \param text The text to search in. UTF-16 encoded in the host's endianness. * \param text_size The length of the buffer pointed to by \p text. * Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string. * \param text_offset The offset into \p text from where the search starts. * \param mflags Match flags. PCRE2_NO_UTF_CHECK is enforced currently. * * \return A status code which describes the result. */ RZ_API RzRegexStatus rz_regex_match_16(RZ_NONNULL const RzRegex16 *regex, RZ_NONNULL const ut16 *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags mflags) { rz_return_val_if_fail(regex && text, RZ_REGEX_ERROR_NOMATCH); pcre2_match_data_16 *mdata = pcre2_match_data_create_from_pattern_16(regex, NULL); RzRegexStatus rc = pcre2_match_16(regex, (PCRE2_SPTR16)text, text_size, text_offset, mflags | PCRE2_NO_UTF_CHECK, mdata, NULL); pcre2_match_data_free_16(mdata); return rc; } /** * \brief Matches the \p regex in the \p text and returns a status code with the result. * * NOTE: This matches against UTF-32 strings (host-endianness). * * \param regex The regex pattern to match. * \param text The text to search in. UTF-32 encoded in the host's endianness. * \param text_size The length of the buffer pointed to by \p text. * Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string. * \param text_offset The offset into \p text from where the search starts. * \param mflags Match flags. PCRE2_NO_UTF_CHECK is enforced currently. * * \return A status code which describes the result. */ RZ_API RzRegexStatus rz_regex_match_32(RZ_NONNULL const RzRegex32 *regex, RZ_NONNULL const ut32 *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags mflags) { rz_return_val_if_fail(regex && text, RZ_REGEX_ERROR_NOMATCH); pcre2_match_data_32 *mdata = pcre2_match_data_create_from_pattern_32(regex, NULL); RzRegexStatus rc = pcre2_match_32(regex, (PCRE2_SPTR32)text, text_size, text_offset, mflags | PCRE2_NO_UTF_CHECK, mdata, NULL); pcre2_match_data_free_32(mdata); return rc; } /** * \brief Generates the error message to \p errcode. * * \param errcode The error code. * \param errbuf The error message buffer. * \param errbuf_size The error message buffer size in bytes. */ RZ_API void rz_regex_error_msg(RzRegexStatus errcode, RZ_OUT char *errbuf, RzRegexSize errbuf_size) { pcre2_get_error_message_8(errcode, (PCRE2_UCHAR8 *)errbuf, errbuf_size); } /** * \brief Returns the name of a group. * * \param regex The regex expression with named groups. * \param group_idx The index of the group to get the name for. * * \return The name of the group or NULL in case of failure or non is was set. */ RZ_API const ut8 *rz_regex_get_match_name(RZ_NONNULL const RzRegex *regex, ut32 group_idx) { rz_return_val_if_fail(regex, NULL); ut32 namecount; ut32 name_entry_size; PCRE2_SPTR8 nametable_ptr; pcre2_pattern_info_8( regex, PCRE2_INFO_NAMECOUNT, &namecount); pcre2_pattern_info_8( regex, PCRE2_INFO_NAMETABLE, &nametable_ptr); pcre2_pattern_info_8( regex, PCRE2_INFO_NAMEENTRYSIZE, &name_entry_size); for (size_t i = 0; i < namecount; i++) { int n = (nametable_ptr[0] << 8) | nametable_ptr[1]; if (n == group_idx) { return nametable_ptr + 2; } nametable_ptr += name_entry_size; } return NULL; } /** * \brief Returns the index of a group in the pattern \p regex. * * \param regex The regex expression with named groups. * \param group The group name to get the index for. * * \return The index of the group or RZ_REGEX_ERROR_NOMATCH in case of failure or if no name was given. */ RZ_API RzRegexStatus rz_regex_get_group_idx_by_name(RZ_NONNULL const RzRegex *regex, const char *group) { rz_return_val_if_fail(regex, RZ_REGEX_ERROR_NOMATCH); ut32 namecount; ut32 name_entry_size; PCRE2_SPTR8 nametable_ptr; pcre2_pattern_info_8( regex, PCRE2_INFO_NAMECOUNT, &namecount); pcre2_pattern_info_8( regex, PCRE2_INFO_NAMETABLE, &nametable_ptr); pcre2_pattern_info_8( regex, PCRE2_INFO_NAMEENTRYSIZE, &name_entry_size); for (size_t i = 0; i < namecount; i++) { int n = (nametable_ptr[0] << 8) | nametable_ptr[1]; if (RZ_STR_EQ((const char *)nametable_ptr + 2, group)) { return n; } nametable_ptr += name_entry_size; } return RZ_REGEX_ERROR_NOMATCH; } static RZ_OWN RzPVector /**/ *match_first_8( RZ_NONNULL const void *regex, RZ_NONNULL const ut8 *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags mflags, RZ_NULLABLE void *jit_stack) { rz_return_val_if_fail(regex && text, NULL); RzPVector *matches = NULL; RzRegexMatchData *mdata = NULL; mdata = pcre2_match_data_create_from_pattern_8(regex, NULL); pcre2_match_context_8 *mcontext = NULL; #ifdef SUPPORTS_PCRE2_JIT if (jit_stack) { mcontext = pcre2_match_context_create_8(NULL); if (!mcontext) { goto fini; } pcre2_jit_stack_assign_8(mcontext, NULL, jit_stack); } #endif RzRegexStatus rc = 0; rc = pcre2_match_8(regex, (PCRE2_SPTR8)text, text_size, text_offset, mflags | PCRE2_NO_UTF_CHECK, mdata, mcontext); if (rc == PCRE2_ERROR_NOMATCH) { // Nothing matched return empty vector. goto fini; } if (rc < 0) { // Some error happend. Inform the user. PCRE2_UCHAR8 buffer[256]; pcre2_get_error_message_8(rc, buffer, sizeof(buffer)); RZ_LOG_WARN("Regex matching failed: %s\n", buffer); goto fini; } // Add groups to vector PCRE2_SIZE *ovector; ovector = pcre2_get_ovector_pointer_8(mdata); ut32 name_entry_size; PCRE2_SPTR8 nametable_ptr8; pcre2_pattern_info_8( regex, PCRE2_INFO_NAMETABLE, &nametable_ptr8); pcre2_pattern_info_8( regex, PCRE2_INFO_NAMEENTRYSIZE, &name_entry_size); matches = rz_pvector_new(free); for (size_t i = 0; i < rc; i++) { if (ovector[2 * i] > ovector[2 * i + 1]) { // This happens for \K lookaround. We fail if used. // See pcre2demo.c for details. RZ_LOG_ERROR("Usage of \\K to set start of the pattern later than the end, is not implemented.\n"); goto fini; } // Offset and length of match RzRegexMatch *match = RZ_NEW0(RzRegexMatch); match->start = ovector[2 * i]; match->len = ovector[2 * i + 1] - match->start; match->group_idx = i; nametable_ptr8 += name_entry_size; rz_pvector_push(matches, match); } fini: rz_regex_match_data_free(mdata); pcre2_match_context_free_8(mcontext); return matches; } static RZ_OWN RzPVector /**/ *match_first_16( RZ_NONNULL const void *regex, RZ_NONNULL const ut16 *text, RzRegexSize text_size_code_units, RzRegexSize text_offset_code_units, RzRegexFlags mflags, RZ_NULLABLE void *jit_stack) { rz_return_val_if_fail(regex && text, NULL); RzPVector *matches = NULL; RzRegexMatchData *mdata = NULL; mdata = pcre2_match_data_create_from_pattern_16(regex, NULL); pcre2_match_context_16 *mcontext = NULL; #ifdef SUPPORTS_PCRE2_JIT if (jit_stack) { mcontext = pcre2_match_context_create_16(NULL); if (!mcontext) { goto fini; } pcre2_jit_stack_assign_16(mcontext, NULL, jit_stack); } #endif RzRegexStatus rc = 0; rc = pcre2_match_16(regex, (PCRE2_SPTR16)text, text_size_code_units, text_offset_code_units, mflags | PCRE2_NO_UTF_CHECK, mdata, mcontext); if (rc == PCRE2_ERROR_NOMATCH) { // Nothing matched, return an empty vector goto fini; } if (rc < 0) { // Some error happend. Inform the user. PCRE2_UCHAR8 buffer[256]; pcre2_get_error_message_8(rc, buffer, sizeof(buffer)); RZ_LOG_WARN("Regex matching failed: %s\n", buffer); goto fini; } // Add groups to vector PCRE2_SIZE *ovector; ovector = pcre2_get_ovector_pointer_16(mdata); ut32 name_entry_size; PCRE2_SPTR16 nametable_ptr16; pcre2_pattern_info_16( regex, PCRE2_INFO_NAMETABLE, &nametable_ptr16); pcre2_pattern_info_16( regex, PCRE2_INFO_NAMEENTRYSIZE, &name_entry_size); matches = rz_pvector_new(free); for (size_t i = 0; i < rc; i++) { if (ovector[2 * i] > ovector[2 * i + 1]) { // This happens for \K lookaround. We fail if used. // See pcre2demo.c for details. RZ_LOG_ERROR("Usage of \\K to set start of the pattern later than the end, is not implemented.\n"); goto fini; } // Offset and length of match RzRegexMatch *match = RZ_NEW0(RzRegexMatch); match->start = ovector[2 * i]; match->len = ovector[2 * i + 1] - match->start; match->group_idx = i; nametable_ptr16 += name_entry_size; rz_pvector_push(matches, match); } fini: rz_regex_match_data_free(mdata); pcre2_match_context_free_16(mcontext); return matches; } static RZ_OWN RzPVector /**/ *match_first_32( RZ_NONNULL const void *regex, RZ_NONNULL const ut32 *text, RzRegexSize text_size_code_units, RzRegexSize text_offset_code_units, RzRegexFlags mflags, RZ_NULLABLE void *jit_stack) { rz_return_val_if_fail(regex && text, NULL); RzPVector *matches = NULL; RzRegexMatchData *mdata = NULL; mdata = pcre2_match_data_create_from_pattern_32(regex, NULL); pcre2_match_context_32 *mcontext = NULL; #ifdef SUPPORTS_PCRE2_JIT if (jit_stack) { mcontext = pcre2_match_context_create_32(NULL); if (!mcontext) { goto fini; } pcre2_jit_stack_assign_32(mcontext, NULL, jit_stack); } #endif RzRegexStatus rc = 0; rc = pcre2_match_32(regex, (PCRE2_SPTR32)text, text_size_code_units, text_offset_code_units, mflags | PCRE2_NO_UTF_CHECK, mdata, mcontext); if (rc == PCRE2_ERROR_NOMATCH) { // Nothing matched return empty vector. goto fini; } if (rc < 0) { // Some error happend. Inform the user. PCRE2_UCHAR8 buffer[256]; pcre2_get_error_message_8(rc, buffer, sizeof(buffer)); RZ_LOG_WARN("Regex matching failed: %s\n", buffer); goto fini; } // Add groups to vector PCRE2_SIZE *ovector; ovector = pcre2_get_ovector_pointer_32(mdata); ut32 name_entry_size; PCRE2_SPTR32 nametable_ptr32; pcre2_pattern_info_32( regex, PCRE2_INFO_NAMETABLE, &nametable_ptr32); pcre2_pattern_info_32( regex, PCRE2_INFO_NAMEENTRYSIZE, &name_entry_size); matches = rz_pvector_new(free); for (size_t i = 0; i < rc; i++) { if (ovector[2 * i] > ovector[2 * i + 1]) { // This happens for \K lookaround. We fail if used. // See pcre2demo.c for details. RZ_LOG_ERROR("Usage of \\K to set start of the pattern later than the end, is not implemented.\n"); goto fini; } // Offset and length of match RzRegexMatch *match = RZ_NEW0(RzRegexMatch); match->start = ovector[2 * i]; match->len = ovector[2 * i + 1] - match->start; match->group_idx = i; nametable_ptr32 += name_entry_size; rz_pvector_push(matches, match); } fini: rz_regex_match_data_free(mdata); pcre2_match_context_free_32(mcontext); return matches; } /** * \brief Finds the first match in a text and returns it as a pvector. * First element in the vector is always the whole match, the following possible groups. * * \param regex The regex pattern to match. * \param text The text to search in. * \param text_size The length of the buffer pointed to by \p text. * Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string. * \param text_offset The offset into \p text from where the search starts. * \param mflags Match flags. * * \return The matches as pvector. NULL in case of failure. Empty for no matches or regex related errors. */ RZ_API RZ_OWN RzPVector /**/ *rz_regex_match_first( RZ_NONNULL const RzRegex *regex, RZ_NONNULL const char *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags mflags) { rz_return_val_if_fail(regex && text, NULL); return match_first_8( regex, (ut8 *)text, text_size, text_offset, mflags, NULL); } /** * \brief Finds the first match in a UTF-16 text and returns it as a pvector. * First element in the vector is always the whole match, the following possible groups. * * NOTE: This matches against UTF-16 host's endianness strings. * * \param regex The regex pattern to match. * \param text The text to search in. It should be UTF-16 (host-endianness) encoded. * \param text_size The length of the buffer pointed to by \p text in code units. * Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string. * \param text_offset The offset into \p text from where the search starts in code units. * \param mflags Match flags. * * \return The matches as pvector. NULL in case of failure. Empty for no matches or regex related errors. */ RZ_API RZ_OWN RzPVector /**/ *rz_regex_match_first_16( RZ_NONNULL const RzRegex16 *regex, RZ_NONNULL const ut16 *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags mflags) { rz_return_val_if_fail(regex && text, NULL); return match_first_16( regex, text, text_size, text_offset, mflags, NULL); } /** * \brief Finds the first match in a UTF-32 text and returns it as a pvector. * First element in the vector is always the whole match, the following possible groups. * * NOTE: This matches against UTF-32 host's endianness strings. * * \param regex The regex pattern to match. * \param text The text to search in. It should be UTF-32 (host-endianness) encoded. * \param text_size The length of the buffer pointed to by \p text in code units. * Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string. * \param text_offset The offset into \p text from where the search starts in code units. * \param mflags Match flags. * * \return The matches as pvector. NULL in case of failure. Empty for no matches or regex related errors. */ RZ_API RZ_OWN RzPVector /**/ *rz_regex_match_first_32( RZ_NONNULL const RzRegex32 *regex, RZ_NONNULL const ut32 *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags mflags) { rz_return_val_if_fail(regex && text, NULL); return match_first_32( regex, text, text_size, text_offset, mflags, NULL); } /** * \brief Finds all matches in a text and returns them as vector. * The result is a flat vector of matches. A single match with multiple * groups is simply appeneded to the resulting vector. * * \param regex The regex pattern to match. * \param text The text to search in. * \param text_size The length of the buffer pointed to by \p text. * Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string. * \param text_offset The offset into \p text from where the search starts. * \param mflags Match flags. * * \return A vector of all matches or NULL in case of failure. * Sub-groups of a match are appended after their main match. */ RZ_API RZ_OWN RzPVector /**/ *rz_regex_match_all_not_grouped( RZ_NONNULL const RzRegex *regex, RZ_NONNULL const char *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags mflags) { rz_return_val_if_fail(regex && text, NULL); RzPVector *all_matches = rz_pvector_new(free); RzPVector *matches = rz_regex_match_first(regex, text, text_size, text_offset, mflags); while (matches && rz_pvector_len(matches) > 0) { RzRegexMatch *whole_match = rz_pvector_head(matches); text_offset = whole_match->start + whole_match->len; size_t mlen = rz_pvector_len(matches); for (size_t i = 0; i < mlen; ++i) { RzRegexMatch *m = rz_pvector_pop_front(matches); rz_pvector_push(all_matches, m); } rz_pvector_free(matches); // Search again after the whole first match. matches = rz_regex_match_first(regex, text, text_size, text_offset, mflags); } // Free last vector without matches. rz_pvector_free(matches); return all_matches; } static RZ_OWN RzPVector /* *>*/ *match_all_internal_8( RZ_NONNULL const RzRegex *regex, RZ_NONNULL const ut8 *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags mflags, bool allow_overlap, RZ_NULLABLE void *jit_stack) { rz_return_val_if_fail(regex && text, NULL); RzPVector *all_matches = rz_pvector_new((RzPVectorFree)rz_pvector_free); RzPVector *matches = NULL; matches = match_first_8(regex, text, text_size, text_offset, mflags, jit_stack); while (matches && rz_pvector_len(matches) > 0) { rz_pvector_push(all_matches, matches); RzRegexMatch *m = rz_pvector_head(matches); // Search again after the last match. text_offset = allow_overlap ? m->start + 1 : m->start + m->len; matches = match_first_8(regex, text, text_size, text_offset, mflags, jit_stack); } // Free last vector without matches. rz_pvector_free(matches); return all_matches; } static RZ_OWN RzPVector /* *>*/ *match_all_internal_16( RZ_NONNULL const RzRegex *regex, RZ_NONNULL const ut16 *text, RzRegexSize text_size_code_units, RzRegexSize text_offset_code_units, RzRegexFlags mflags, bool allow_overlap, RZ_NULLABLE void *jit_stack) { rz_return_val_if_fail(regex && text, NULL); RzPVector *all_matches = rz_pvector_new((RzPVectorFree)rz_pvector_free); RzPVector *matches = NULL; matches = match_first_16(regex, text, text_size_code_units, text_offset_code_units, mflags, jit_stack); while (matches && rz_pvector_len(matches) > 0) { rz_pvector_push(all_matches, matches); RzRegexMatch *m = rz_pvector_head(matches); // Search again after the last match. text_offset_code_units = allow_overlap ? m->start + 1 : m->start + m->len; matches = match_first_16(regex, text, text_size_code_units, text_offset_code_units, mflags, jit_stack); } // Free last vector without matches. rz_pvector_free(matches); return all_matches; } static RZ_OWN RzPVector /* *>*/ *match_all_internal_32( RZ_NONNULL const RzRegex *regex, RZ_NONNULL const ut32 *text, RzRegexSize text_size_code_units, RzRegexSize text_offset_code_units, RzRegexFlags mflags, bool allow_overlap, RZ_NULLABLE void *jit_stack) { rz_return_val_if_fail(regex && text, NULL); RzPVector *all_matches = rz_pvector_new((RzPVectorFree)rz_pvector_free); RzPVector *matches = NULL; matches = match_first_32(regex, text, text_size_code_units, text_offset_code_units, mflags, jit_stack); while (matches && rz_pvector_len(matches) > 0) { rz_pvector_push(all_matches, matches); RzRegexMatch *m = rz_pvector_head(matches); // Search again after the last match. text_offset_code_units = allow_overlap ? m->start + 1 : m->start + m->len; matches = match_first_32(regex, text, text_size_code_units, text_offset_code_units, mflags, jit_stack); } // Free last vector without matches. rz_pvector_free(matches); return all_matches; } /** * \brief Finds all matches in a text and returns them as vector of vector matches. * * \param pattern The regex pattern to match. * \param text The text to search in. * \param text_size The length of the buffer pointed to by \p text. * Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string. * \param text_offset The offset into \p text from where the search starts. * \param mflags Match flags. * \param allow_overlap If true it will match also overlapping patterns. * * \return PVector of every match in the given string or NULL in case of failure. * One match with all its groups is again assembled in a pvector. */ RZ_API RZ_OWN RzPVector /* *>*/ *rz_regex_match_all_overlap( RZ_NONNULL const RzRegex *regex, RZ_NONNULL const char *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags mflags) { return match_all_internal_8(regex, (ut8 *)text, text_size, text_offset, mflags, true, NULL); } /** * \brief Finds all matches in a text and returns them as vector of vector matches. * * \param pattern The regex pattern to match. * \param text The text to search in. * \param text_size The length of the buffer pointed to by \p text. * Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string. * \param text_offset The offset into \p text from where the search starts. * \param mflags Match flags. * * \return PVector of every match in the given string or NULL in case of failure. * One match with all its groups is again assembled in a pvector. */ RZ_API RZ_OWN RzPVector /* *>*/ *rz_regex_match_all( RZ_NONNULL const RzRegex *regex, RZ_NONNULL const char *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags mflags) { return match_all_internal_8(regex, (ut8 *)text, text_size, text_offset, mflags, false, NULL); } /** * \brief Finds all matches in a text and returns them as vector of vector matches. * * NOTE: This matches against UTF-16 host's endianness strings. * * \param pattern The regex pattern to match. * \param text The text to search in. It should be UTF-16 host-endianness encoded. * \param text_size The length of the buffer pointed to by \p text in code units.. * Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string. * \param text_offset The offset into \p text from where the search starts in code units.. * \param mflags Match flags. * \param allow_overlap If true it will match also overlapping patterns. * * \return PVector of every match in the given string or NULL in case of failure. * One match with all its groups is again assembled in a pvector. */ RZ_API RZ_OWN RzPVector /* *>*/ *rz_regex_match_all_overlap_16( RZ_NONNULL const RzRegex16 *regex, RZ_NONNULL const ut16 *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags mflags) { return match_all_internal_16(regex, text, text_size, text_offset, mflags, true, NULL); } /** * \brief Finds all matches in a text and returns them as vector of vector matches. * * NOTE: This matches against UTF-16 host's endianness strings. * * \param pattern The regex pattern to match. * \param text The text to search in. It should be UTF-16 host-endianness encoded. * \param text_size The length of the buffer pointed to by \p text in code units.. * Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string. * \param text_offset The offset into \p text from where the search starts in code units.. * \param mflags Match flags. * * \return PVector of every match in the given string or NULL in case of failure. * One match with all its groups is again assembled in a pvector. */ RZ_API RZ_OWN RzPVector /* *>*/ *rz_regex_match_all_16( RZ_NONNULL const RzRegex16 *regex, RZ_NONNULL const ut16 *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags mflags) { return match_all_internal_16(regex, text, text_size, text_offset, mflags, false, NULL); } /** * \brief Finds all matches in a text and returns them as vector of vector matches. * * NOTE: This matches against UTF-32 host's endianness strings. * * \param pattern The regex pattern to match. * \param text The text to search in. It should be UTF-32 host-endianness encoded. * \param text_size The length of the buffer pointed to by \p text in code units.. * Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string. * \param text_offset The offset into \p text from where the search starts in code units.. * \param mflags Match flags. * \param allow_overlap If true it will match also overlapping patterns. * * \return PVector of every match in the given string or NULL in case of failure. * One match with all its groups is again assembled in a pvector. */ RZ_API RZ_OWN RzPVector /* *>*/ *rz_regex_match_all_overlap_32( RZ_NONNULL const RzRegex32 *regex, RZ_NONNULL const ut32 *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags mflags) { return match_all_internal_32(regex, text, text_size, text_offset, mflags, true, NULL); } /** * \brief TODO: Note that size and offset are in bytes. Not code points. */ RZ_API RZ_OWN RzPVector /* *>*/ *rz_regex_match_all_overlap_multi( RZ_NONNULL const RzRegexMulti *regex, RZ_NONNULL const ut8 *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags mflags) { switch (regex->re_type) { case RZ_REGEX_UTF8: return match_all_internal_8(regex->re8, (ut8 *)text, text_size, text_offset, mflags, true, regex->jit_stack); case RZ_REGEX_UTF16: return match_all_internal_16(regex->re16, (ut16 *)text, text_size / RZ_UTF16_CODE_POINT_WIDTH, text_offset / RZ_UTF16_CODE_POINT_WIDTH, mflags, true, regex->jit_stack); case RZ_REGEX_UTF32: return match_all_internal_32(regex->re32, (ut32 *)text, text_size / RZ_UTF32_CODE_POINT_WIDTH, text_offset / RZ_UTF32_CODE_POINT_WIDTH, mflags, true, regex->jit_stack); } rz_warn_if_reached(); return NULL; } RZ_API RZ_OWN RzPVector /* *>*/ *rz_regex_match_all_multi( RZ_NONNULL const RzRegexMulti *regex, RZ_NONNULL const ut8 *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags mflags) { switch (regex->re_type) { case RZ_REGEX_UTF8: return match_all_internal_8(regex->re8, (ut8 *)text, text_size, text_offset, mflags, false, regex->jit_stack); case RZ_REGEX_UTF16: return match_all_internal_16(regex->re16, (ut16 *)text, text_size / RZ_UTF16_CODE_POINT_WIDTH, text_offset / RZ_UTF16_CODE_POINT_WIDTH, mflags, false, regex->jit_stack); case RZ_REGEX_UTF32: return match_all_internal_32(regex->re32, (ut32 *)text, text_size / RZ_UTF32_CODE_POINT_WIDTH, text_offset / RZ_UTF32_CODE_POINT_WIDTH, mflags, false, regex->jit_stack); } rz_warn_if_reached(); return NULL; } /** * \brief Finds all matches in a text and returns them as vector of vector matches. * * NOTE: This matches against UTF-32 host's endianness strings. * * \param pattern The regex pattern to match. * \param text The text to search in. It should be UTF-32 host-endianness encoded. * \param text_size The length of the buffer pointed to by \p text in code units.. * Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string. * \param text_offset The offset into \p text from where the search starts in code units.. * \param mflags Match flags. * * \return PVector of every match in the given string or NULL in case of failure. * One match with all its groups is again assembled in a pvector. */ RZ_API RZ_OWN RzPVector /* *>*/ *rz_regex_match_all_32( RZ_NONNULL const RzRegex32 *regex, RZ_NONNULL const ut32 *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags mflags) { return match_all_internal_32(regex, text, text_size, text_offset, mflags, false, NULL); } /** * \brief Checks if \p pattern can be found in \p text. * * \param pattern The regex pattern to match. * \param text The text to search in. * \param text_size The length of the buffer pointed to by \p text. * Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string. * \param text_offset The offset into \p text from where the search starts. * \param cflags Compile flags. * \param mflags Match flags. * * \return true if the text contains the patterns. * \return false Otherwise */ RZ_API bool rz_regex_contains(RZ_NONNULL const char *pattern, RZ_NONNULL const char *text, RzRegexSize text_size, RzRegexFlags cflags, RzRegexFlags mflags) { RzRegex *re = rz_regex_new(pattern, cflags, 0, NULL); if (!re) { return false; } RzPVector *matches = rz_regex_match_first(re, text, text_size, 0, mflags); bool found = matches != NULL && !rz_pvector_empty(matches); rz_pvector_free(matches); rz_regex_free(re); return found; } /** * \brief Searches for a \p pattern in \p text and returns all matches as concatenated string. * Only complete matches are concatenated. Sub-groups are skipped. * * \param pattern The regex pattern to match. * \param text The text to search in. * \param text_size The length of the buffer pointed to by \p text. * Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string. * \param cflags Compile flags. * \param mflags Match flags. * \param separator A string to separate the matches. * * \return A string with all matches concatenated or NULL in case of failure. */ RZ_API RZ_OWN RzStrBuf *rz_regex_full_match_str(RZ_NONNULL const char *pattern, RZ_NONNULL const char *text, RzRegexSize text_size, RzRegexFlags cflags, RzRegexFlags mflags, RZ_NONNULL const char *separator) { rz_return_val_if_fail(pattern && text && separator, NULL); RzRegex *re = rz_regex_new(pattern, cflags, 0, NULL); RzStrBuf *sbuf = rz_strbuf_new(""); RzPVector *matches = rz_regex_match_all(re, text, text_size, 0, mflags); if (!matches || !sbuf) { goto fini; } size_t i = 1; void **m; rz_pvector_foreach (matches, m) { RzPVector *match_groups = *m; RzRegexMatch *match = rz_pvector_head(match_groups); const char *t = text + match->start; if (((int)match->len) < 0) { goto fini; } // No separator in case of only one match if (i == rz_pvector_len(matches)) { rz_strbuf_appendf(sbuf, "%-.*s", (int)match->len, t); } else if (!rz_strbuf_appendf(sbuf, "%-.*s%s", (int)match->len, t, separator)) { goto fini; } ++i; } fini: rz_regex_free(re); rz_pvector_free(matches); return sbuf; } /** * \brief Searches for the given \p pattern in \p text and * returns an byte offset into the string where the pattern was found. * * \param pattern The regex pattern to match. * \param text The text to search in. * \param text_size The length of the buffer pointed to by \p text. * Can be set to RZ_REGEX_ZERO_TERMINATED if the buffer is a zero terminated string. * \param text_offset The offset into \p text from where the search starts. * \param cflags Compile flags. * \param mflags Match flags. * * \return Offset into the string, or SZT_MAX if pattern was not found. */ RZ_API RzRegexSize rz_regex_find(RZ_NONNULL const char *pattern, RZ_NONNULL RZ_BORROW char *text, RzRegexSize text_size, RzRegexSize text_offset, RzRegexFlags cflags, RzRegexFlags mflags) { rz_return_val_if_fail(pattern && text, SZT_MAX); RzRegex *regex = rz_regex_new(pattern, cflags, RZ_REGEX_DEFAULT, NULL); RzPVector *matches = rz_regex_match_first(regex, text, text_size, text_offset, mflags); if (rz_pvector_empty(matches)) { rz_pvector_free(matches); rz_regex_free(regex); return SZT_MAX; } RzRegexSize off = ((RzRegexMatch *)rz_pvector_head(matches))->start; rz_pvector_free(matches); rz_regex_free(regex); return off; } /** * \brief Returns a compile context. * * \return A PCRE2 compile context, or NULL in case of failure. */ RZ_API RZ_OWN RzRegexCompContext *rz_regex_compile_context_new() { return pcre2_compile_context_create_8(NULL); } /** * \brief Frees a compile context. * * \param A PCRE2 compile context. */ RZ_API void rz_regex_compile_context_free(RzRegexCompContext *ccontext) { pcre2_compile_context_free_8(ccontext); } /** * \brief Sets the newline convention of a compile context to the NUL character (\0). * * \param A PCRE2 compile context. */ RZ_API void rz_regex_set_nul_as_newline(RZ_NONNULL RzRegexCompContext *ccontext) { pcre2_set_newline_8(ccontext, PCRE2_NEWLINE_NUL); } /** * \brief Parses a string with regex flags characters and returns the numerical * value of it. * * - ''/NULL/'r' - Default: RZ_REGEX_DEFAULT * - 'l' - RZ_REGEX_LITERAL * - 'i' - RZ_REGEX_CASELESS * - 'e' - RZ_REGEX_EXTENDED * - 'E' - RZ_REGEX_EXTENDED_MORE * - 'm' - RZ_REGEX_MULTILINE * * \param re_flags_desc The string containing several characters. * * \return The flags as numerical value. Or ~RZ_REGEX_DEFAULT if flags are invalid. * * Examples: * * - "ri" => RZ_REGEX_DEFAULT | RZ_REGEX_CASELESS * - "li" => RZ_REGEX_LITERAL | RZ_REGEX_CASELESS * - "Eim" => RZ_REGEX_EXTENDED | RZ_REGEX_CASELESS | RZ_REGEX_MULTILINE * - "rl" => ~RZ_REGEX_DEFAULT => Error: Flag combination is invalid. * - "xX" => ~RZ_REGEX_DEFAULT => Error: Flag combination is invalid. */ RZ_API RzRegexFlags rz_regex_parse_flag_desc(RZ_NULLABLE const char *re_flags_desc) { RzRegexFlags flags = RZ_REGEX_DEFAULT; if (RZ_STR_ISEMPTY(re_flags_desc)) { return flags; } size_t fcount = 0; if (strchr(re_flags_desc, 'i')) { fcount++; flags |= RZ_REGEX_CASELESS; } if (strchr(re_flags_desc, 'l')) { fcount++; flags |= RZ_REGEX_LITERAL; goto return_flags; } if (strchr(re_flags_desc, 'r')) { fcount++; goto return_flags; } if (strchr(re_flags_desc, 'e')) { fcount++; flags |= RZ_REGEX_EXTENDED; } if (strchr(re_flags_desc, 'E')) { fcount++; flags |= RZ_REGEX_EXTENDED_MORE; } if (strchr(re_flags_desc, 'm')) { fcount++; flags |= RZ_REGEX_MULTILINE; } if (strchr(re_flags_desc, 'd')) { fcount++; flags |= RZ_REGEX_DOTALL; } return_flags: if (fcount != strlen(re_flags_desc)) { RZ_LOG_ERROR("Flag combination '%s' is invalid.\n", re_flags_desc); return ~RZ_REGEX_DEFAULT; } return flags; } /** * \brief The string characters in binary files. * The characters matched with this pattern are assumed to belong to * single string. * Any character not matching this pattern is assumed to end a string. * Note: A new line is a valid character within a string. */ #define RZ_REGEX_STR_WILDARD "[\\w\\e\\pS\\s\\n\\r\\pP]" /** * \brief Returns a Regex pattern of the form "[\w\e\pS\s\n\r\pP]{min_len, max_len}". * * If min_len == max_len == 0, the function returns "[\w\e\pS\s\n\r\pP]*". * If min_len > max_len, the function returns "[\w\e\pS\s\n\r\pP]{min_len,}". * * \param min_len The minimum length of the wildcard regex pattern. * \param max_len The maximum length of the wildcard regex pattern. * * \return The pattern or NULL in case of failure. */ RZ_API RZ_OWN char *rz_regex_create_wildcard_pattern(size_t min_len, size_t max_len) { if (min_len == 0 && max_len == 0) { return rz_str_dup(RZ_REGEX_STR_WILDARD "*"); } else if (min_len > max_len) { return rz_str_newf(RZ_REGEX_STR_WILDARD "{%" PFMTSZd ",}", min_len); } return rz_str_newf(RZ_REGEX_STR_WILDARD "{%" PFMTSZd ",%" PFMTSZd "}", min_len, max_len); }