Skip to content

Commit 2cded85

Browse files
committed
fix regex for non strict header value validation
1 parent ecb8c7f commit 2cded85

3 files changed

Lines changed: 82 additions & 6 deletions

File tree

‎src/main/java/build/buf/protovalidate/StringRulesEvaluator.java‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ private static RuleSite site(int fieldNumber, String ruleId) {
109109
Pattern.compile("^:?[0-9a-zA-Z!#$%&'*+.\\-^_|~`]+$");
110110
private static final Pattern HEADER_VALUE_REGEX =
111111
Pattern.compile("^[^\\x00-\\x08\\x0A-\\x1F\\x7F]*$");
112-
private static final Pattern LOOSE_REGEX = Pattern.compile("^[^\\x00\\x0A\\x0D]+$");
112+
private static final Pattern LOOSE_HEADER_NAME_REGEX = Pattern.compile("^[^\\x00\\x0A\\x0D]+$");
113+
private static final Pattern LOOSE_HEADER_VALUE_REGEX = Pattern.compile("^[^\\x00\\x0A\\x0D]*$");
113114

114115
// --- Well-known string formats ---
115116

@@ -778,19 +779,16 @@ public List<RuleViolation.Builder> evaluate(Value val, boolean failFast) {
778779
return NativeViolations.newViolation(
779780
HEADER_NAME_EMPTY_SITE, null, null, val, knownRegex.getNumber());
780781
}
781-
matcher = HEADER_NAME_REGEX;
782+
matcher = knownRegexStrict ? HEADER_NAME_REGEX : LOOSE_HEADER_NAME_REGEX;
782783
site = HEADER_NAME_SITE;
783784
break;
784785
case KNOWN_REGEX_HTTP_HEADER_VALUE:
785-
matcher = HEADER_VALUE_REGEX;
786+
matcher = knownRegexStrict ? HEADER_VALUE_REGEX : LOOSE_HEADER_VALUE_REGEX;
786787
site = HEADER_VALUE_SITE;
787788
break;
788789
default:
789790
return null;
790791
}
791-
if (!knownRegexStrict) {
792-
matcher = LOOSE_REGEX;
793-
}
794792
if (!matcher.matches(strVal)) {
795793
return NativeViolations.newViolation(site, null, null, val, knownRegex.getNumber());
796794
}

‎src/test/java/build/buf/protovalidate/WellKnownRegexTest.java‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,19 @@
1414

1515
package build.buf.protovalidate;
1616

17+
import static java.util.stream.Collectors.toList;
1718
import static org.assertj.core.api.Assertions.assertThat;
1819

1920
import build.buf.protovalidate.exceptions.ValidationException;
2021
import com.example.noimports.validationtest.HttpHeaderName;
2122
import com.example.noimports.validationtest.HttpHeaderNameLoose;
2223
import com.example.noimports.validationtest.HttpHeaderValue;
24+
import com.example.noimports.validationtest.HttpHeaderValueLoose;
25+
import com.google.protobuf.Message;
26+
import java.util.List;
2327
import org.junit.jupiter.api.Test;
28+
import org.junit.jupiter.params.ParameterizedTest;
29+
import org.junit.jupiter.params.provider.ValueSource;
2430

2531
/**
2632
* Tests for the {@code well_known_regex} oneof case in {@link StringRulesEvaluator}: HTTP header
@@ -32,6 +38,10 @@ class WellKnownRegexTest {
3238
ValidatorFactory.newBuilder()
3339
.withConfig(Config.newBuilder().setEnableNativeRules(true).build())
3440
.build();
41+
private final Validator celValidator =
42+
ValidatorFactory.newBuilder()
43+
.withConfig(Config.newBuilder().setEnableNativeRules(false).build())
44+
.build();
3545

3646
@Test
3747
void headerName_strict_passesValidName() throws ValidationException {
@@ -92,4 +102,65 @@ void headerValue_emptyValueIsValid() throws ValidationException {
92102
HttpHeaderValue msg = HttpHeaderValue.newBuilder().setVal("").build();
93103
assertThat(nativeValidator.validate(msg).isSuccess()).isTrue();
94104
}
105+
106+
@Test
107+
void headerValue_loose_emptyValueIsValid() throws ValidationException {
108+
// The loose header value pattern is also '*', unlike the loose header name pattern.
109+
HttpHeaderValueLoose msg = HttpHeaderValueLoose.newBuilder().setVal("").build();
110+
assertThat(nativeValidator.validate(msg).isSuccess()).isTrue();
111+
}
112+
113+
@Test
114+
void headerValue_loose_failsNullCrLf() throws ValidationException {
115+
for (String val : new String[] {"a\u0000b", "a\nb", "a\rb"}) {
116+
HttpHeaderValueLoose msg = HttpHeaderValueLoose.newBuilder().setVal(val).build();
117+
ValidationResult result = nativeValidator.validate(msg);
118+
assertThat(result.getViolations()).hasSize(1);
119+
build.buf.validate.Violation v = result.getViolations().get(0).toProto();
120+
assertThat(v.getRuleId()).isEqualTo("string.well_known_regex.header_value");
121+
}
122+
}
123+
124+
// Comma in a strict header name is excluded: the bundled validate.proto still allows it via
125+
// the '+-.' range, while native follows upstream's fix (bufbuild/protovalidate#528).
126+
@ParameterizedTest
127+
@ValueSource(
128+
strings = {
129+
"",
130+
"X-Request-Id",
131+
":authority",
132+
"::method",
133+
"not a header",
134+
"text/plain; charset=utf-8",
135+
"tab\there",
136+
"a\u0000b",
137+
"a\u0001b",
138+
"a\u0008b",
139+
"a\nb",
140+
"a\rb",
141+
"a\u001fb",
142+
"a\u007fb",
143+
"a\u0080b",
144+
"naïve",
145+
"内容类型",
146+
"😀",
147+
"trailing\n",
148+
"\n",
149+
})
150+
void nativeMatchesCel(String val) throws ValidationException {
151+
assertSameRuleIds(HttpHeaderName.newBuilder().setVal(val).build());
152+
assertSameRuleIds(HttpHeaderNameLoose.newBuilder().setVal(val).build());
153+
assertSameRuleIds(HttpHeaderValue.newBuilder().setVal(val).build());
154+
assertSameRuleIds(HttpHeaderValueLoose.newBuilder().setVal(val).build());
155+
}
156+
157+
private void assertSameRuleIds(Message msg) throws ValidationException {
158+
assertThat(ruleIds(nativeValidator.validate(msg)))
159+
.as("%s{val=%s}", msg.getDescriptorForType().getName(), msg)
160+
.isEqualTo(ruleIds(celValidator.validate(msg)));
161+
}
162+
163+
private static List<String> ruleIds(ValidationResult result) {
164+
return result.getViolations().stream().map(v -> v.toProto().getRuleId()).collect(toList());
165+
}
95166
}

‎src/test/resources/proto/validationtest/validationtest.proto‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,13 @@ message HttpHeaderValue {
371371
string val = 1 [(buf.validate.field).string.well_known_regex = KNOWN_REGEX_HTTP_HEADER_VALUE];
372372
}
373373

374+
message HttpHeaderValueLoose {
375+
string val = 1 [(buf.validate.field).string = {
376+
well_known_regex: KNOWN_REGEX_HTTP_HEADER_VALUE
377+
strict: false
378+
}];
379+
}
380+
374381
// google.protobuf.*Value wrapper fixtures for WrappedValueEvaluator coverage.
375382
message Int64WrapperConst {
376383
google.protobuf.Int64Value val = 1 [(buf.validate.field).int64.const = 5];

0 commit comments

Comments
 (0)