1414
1515package build .buf .protovalidate ;
1616
17+ import static java .util .stream .Collectors .toList ;
1718import static org .assertj .core .api .Assertions .assertThat ;
1819
1920import build .buf .protovalidate .exceptions .ValidationException ;
2021import com .example .noimports .validationtest .HttpHeaderName ;
2122import com .example .noimports .validationtest .HttpHeaderNameLoose ;
2223import com .example .noimports .validationtest .HttpHeaderValue ;
24+ import com .example .noimports .validationtest .HttpHeaderValueLoose ;
25+ import com .google .protobuf .Message ;
26+ import java .util .List ;
2327import 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\u0000 b" , "a\n b" , "a\r b" }) {
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\t here" ,
136+ "a\u0000 b" ,
137+ "a\u0001 b" ,
138+ "a\u0008 b" ,
139+ "a\n b" ,
140+ "a\r b" ,
141+ "a\u001f b" ,
142+ "a\u007f b" ,
143+ "a\u0080 b" ,
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}
0 commit comments