From b7a4a86b910c7ac5282d7aa460f535888bc8f991 Mon Sep 17 00:00:00 2001 From: chuuminggg Date: Fri, 6 Nov 2026 00:15:09 +0900 Subject: [PATCH] Handle replacePattern matches in which the first group does not participate Previously, when a pattern with a capturing group was used with replacePattern and a match occurred in which the first group did not participate, for example because the group was optional or in an alternative branch, a StringIndexOutOfBoundsException was thrown. Such matches are now left unchanged. Signed-off-by: chuuminggg --- .../PatternReplacingContentModifier.java | 3 +++ .../PatternReplacingContentModifierTests.java | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifier.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifier.java index d0b9f4a24..1c70d3a34 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifier.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifier.java @@ -65,6 +65,9 @@ public byte[] modifyContent(byte[] content, @Nullable MediaType contentType) { while (matcher.find()) { String prefix; if (matcher.groupCount() > 0) { + if (matcher.start(1) == -1) { + continue; + } prefix = original.substring(previous, matcher.start(1)); previous = matcher.end(1); } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifierTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifierTests.java index 6865eeec9..d6399f58b 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifierTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifierTests.java @@ -40,6 +40,30 @@ void contentThatDoesNotMatchIsUnchanged() { .isEqualTo("{\"id\" : \"CA76-ED42-11CE-BACD\"}".getBytes()); } + @Test + void whenPatternHasAGroupOnlyTheFirstGroupIsReplaced() { + Pattern pattern = Pattern.compile("\"id\":\"([^\"]+)\""); + PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<>"); + assertThat(contentModifier.modifyContent("{\"id\":\"abc\",\"name\":\"alpha\"}".getBytes(), null)) + .isEqualTo("{\"id\":\"<>\",\"name\":\"alpha\"}".getBytes()); + } + + @Test + void matchInWhichTheFirstGroupDoesNotParticipateIsUnchanged() { + Pattern pattern = Pattern.compile("\"id\":\"([^\"]+)\"|\"created\":\"[^\"]+\""); + PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<>"); + assertThat(contentModifier.modifyContent("{\"created\":\"2026-10-16\",\"id\":\"abc\"}".getBytes(), null)) + .isEqualTo("{\"created\":\"2026-10-16\",\"id\":\"<>\"}".getBytes()); + } + + @Test + void matchInWhichAnOptionalFirstGroupIsAbsentIsUnchanged() { + Pattern pattern = Pattern.compile("[0-9]+(-SNAPSHOT)?"); + PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "-RELEASE"); + assertThat(contentModifier.modifyContent("1.0 2.0-SNAPSHOT".getBytes(), null)) + .isEqualTo("1.0 2.0-RELEASE".getBytes()); + } + @Test void encodingIsPreservedUsingCharsetFromContentType() { String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4";