Skip to content

Consecutive escape sequences inside conditional substitutions are mishandled #854

@kirschju

Description

@kirschju

Consecutive escape sequences in ${name:+...} conditional substitution are mishandled after commit bf50eeef.
The removal of ptr -= 1 in find_text_end() causes the for loop's ptr++ to skip a character after check_escape() advances ptr, breaking consecutive escapes like \:\: inside conditional substitution text.

Example:

Pattern:     foo(?<Bar>BAR)?
Replacement: X${Bar:+\:\:text}Y

Test 1: Subject 'foo' (Bar does NOT match)
  Expected: 'XY'
  Got:      'XtextY' (rc=1)
  FAILED!

Test 2: Subject 'fooBAR' (Bar DOES match)
  Expected: 'X::textY'
  Got:      'X:extY' (rc=-57)
  FAILED!

Program generating faulty output above:

#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    int errorcode;
    PCRE2_SIZE erroffset;
    PCRE2_UCHAR output[256];
    PCRE2_SIZE outlength;
    int rc;
    int failed = 0;

    const char *pattern = "foo(?<Bar>BAR)?";
    const char *replacement = "X${Bar:+\\:\\:text}Y";
    /*                              ^^^^
     * Two consecutive escaped colons - the bug causes the second
     * escape to be skipped when parsing the conditional text bounds.
     */

    pcre2_code *re = pcre2_compile(
        (PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, 0,
        &errorcode, &erroffset, NULL);
    if (re == NULL) {
        fprintf(stderr, "Compile error %d at offset %zu\n", errorcode, erroffset);
        return 1;
    }

    pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL);

    printf("Pattern:     %s\n", pattern);
    printf("Replacement: %s\n\n", replacement);

    /* Test 1: Group does NOT match - should output "XY" */
    printf("Test 1: Subject 'foo' (Bar does NOT match)\n");
    outlength = sizeof(output);
    rc = pcre2_substitute(re, (PCRE2_SPTR)"foo", PCRE2_ZERO_TERMINATED, 0,
        PCRE2_SUBSTITUTE_EXTENDED, match_data, NULL,
        (PCRE2_SPTR)replacement, PCRE2_ZERO_TERMINATED, output, &outlength);
    printf("  Expected: 'XY'\n");
    printf("  Got:      '%s' (rc=%d)\n", output, rc);
    if (strcmp((char*)output, "XY") != 0) {
        printf("  FAILED!\n");
        failed = 1;
    }
    printf("\n");

    /* Test 2: Group DOES match - should output "X::textY" */
    printf("Test 2: Subject 'fooBAR' (Bar DOES match)\n");
    outlength = sizeof(output);
    rc = pcre2_substitute(re, (PCRE2_SPTR)"fooBAR", PCRE2_ZERO_TERMINATED, 0,
        PCRE2_SUBSTITUTE_EXTENDED, match_data, NULL,
        (PCRE2_SPTR)replacement, PCRE2_ZERO_TERMINATED, output, &outlength);
    printf("  Expected: 'X::textY'\n");
    printf("  Got:      '%s' (rc=%d)\n", output, rc);
    if (rc < 0 || strcmp((char*)output, "X::textY") != 0) {
        printf("  FAILED!\n");
        failed = 1;
    }
    printf("\n");

    pcre2_match_data_free(match_data);
    pcre2_code_free(re);

    return failed;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    backportbugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions