diff --git a/buf/validate/conformance/expected_failures.yaml b/buf/validate/conformance/expected_failures.yaml index 1046307..7d8e30a 100644 --- a/buf/validate/conformance/expected_failures.yaml +++ b/buf/validate/conformance/expected_failures.yaml @@ -16,721 +16,6 @@ library/is_hostname: # got: validation error (1 violation) # 1. constraint_id: "library.is_hostname" # message: "" -library/is_uri: - - invalid/query_bad_pct-encoded - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com?%2x"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/host_reg-name_bad_pct-encoded/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://foo%"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/userinfo_bad_pct-encoded/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://%@example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/leading_space - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:" https://example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/host_reg-name_bad_pct-encoded/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://foo%2x"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/fragment_bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com#\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/scheme/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"1foo://example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/host_reg-name_pct-encoded_invalid_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://foo%c3x%96"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/scheme/d - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:":foo://example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/trailing_space - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com "} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/scheme/g - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo^bar://example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/scheme/c - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:".foo://example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/port/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com:8a"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/host_ipv6/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://2001:0db8:85a3:0000:0000:8a2e:0370:7334"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/host/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://\x1f.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/fragment_bad_hash - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com##"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/host/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://^.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/host_ipv6_zone-id_unquoted - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[::1%eth0]"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/query_bad_caret - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com?^"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/scheme/f - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo\x1fbar://example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/scheme/e - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo%20bar://example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/userinfo_bad_pct-encoded/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://%2x@example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/port/c - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com: 1"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/userinfo_reserved_at - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://@@example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/fragment_bad_pct-encoded/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com#%2x"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/fragment_bad_pct-encoded/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com#%"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/userinfo_caret - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://^@example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/host/c - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://foo@你好.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/host_ipv6_zone-id_empty - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[::1%25]"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/fragment_bad_caret - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com#^"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/scheme/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"-foo://example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/port/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com:x"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/query_bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://example.com?\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/userinfo_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://\x1f@example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/authority_path-abempty_segment_bad_caret - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo://example.com/^"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/authority_path-abempty_segment_bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo://example.com/\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/authority_path-abempty_segment_bad_pct-encoded - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo://example.com/%x"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: valid - - invalid/host_ipfuture - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[v1x]"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: runtime error: ('return error for overflow', , ('IPvFuture address is invalid',)) - - invalid/host_ipv6/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[2001::0370::7334]"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: runtime error: ('return error for overflow', , ("'2001::0370::7334' does not appear to be an IPv4 or IPv6 address",)) - - invalid/host_ipv6_zone-id_bad_pct-encoded/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[::1%25foo%]"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: runtime error: ('return error for overflow', , ("'::1%25foo%' does not appear to be an IPv4 or IPv6 address",)) - - invalid/host_ipv6_zone-id_bad_pct-encoded/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[::1%25foo%2x]"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: runtime error: ('return error for overflow', , ("'::1%25foo%2x' does not appear to be an IPv4 or IPv6 address",)) - - invalid/host_ipv6_zone-id_pct-encoded_invalid_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[::1%25foo%c3x%96]"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: runtime error: ('return error for overflow', , ("'::1%25foo%c3x%96' does not appear to be an IPv4 or IPv6 address",)) - - invalid/userinfo_reserved_square_bracket_close - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://]@example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: runtime error: ('return error for overflow', , ('Invalid IPv6 URL',)) - - invalid/userinfo_reserved_square_bracket_open - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https://[@example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # got: runtime error: ('return error for overflow', , ('Invalid IPv6 URL',)) - - valid/path-absolute - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_exhaust_segment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz/0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&'()*+,;=:@%20"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_exhaust_segment-nz - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/@%20!$&()*+,;=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~:"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_segment-nz-pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/%61%20%23"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_segment-nz-pct-encoded_invalid_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/%c3x%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_segment-nz-pct-encoded_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/%c3%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_segment_pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz/%61%20%23"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_segment_pct-encoded_invalid_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz/%c3x%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_segment_pct-encoded_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz/%c3%96%c3"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_with_empty_pchar - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_with_query_and_fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz?q#f"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_with_segment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz/a"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-absolute_with_segments - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:/nz//segment//segment/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-empty - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-empty_with_query_and_fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:?q#f"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment-nz_exhaust - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:@%20!$&()*+,;=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~:"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment-nz_pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:%61%20%23"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment-nz_pct-encoded_invalid_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:%c3x%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment-nz_pct-encoded_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:%c3%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment_empty_pchar - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment_exhaust - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz/0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&'()*+,;=:@%20"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment_pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz/%61%20%23"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment_pct-encoded_invalid_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz/%c3x%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_segment_pct-encoded_utf8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz/%c3%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_with_query_and_fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz?q#f"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_with_segment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz/a"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/path-rootless_with_segments - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"foo:nz//segment//segment/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false - - valid/userinfo_reserved_slash_parses_as_path-abempty - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUri]:{val:"https:///@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri" - # message: "" - # for_key: false -library/is_uri_ref: - - invalid/path-noscheme_query_bad_caret - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:".?^"} - # want: validation error (1 violation) - #1. constraint_id: "library.is_uri_ref" - #got: valid - - invalid/path-absolute_query_bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/?\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - valid/path-abempty_exhaust_userinfo - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~!$&'()*+,;=::@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-absolute_segment-nz_matches_colon - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/:"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-noscheme_exhaust_segment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&'()*+,;=:@%20"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-empty - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-absolute_segment-nz_pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/%61%20%23"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-absolute_segment-nz_pct-encoded_utf-8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/%c3%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-noscheme_segment-nz_pct-encoded_invalid_utf-8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"%c3x%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-absolute_segment-nz_pct-encoded_invalid_utf-8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/%c3x%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-noscheme_segment_pct-encoded_invalid_utf-8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"./%c3x%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-absolute_exhaust_segment-nz - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/@%20!$&()*+,;=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~:"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-noscheme_segment_pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"./%61%20%23"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-noscheme_segment_pct-encoded_utf-8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"./%c3%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-abempty_segment_pct-encoded_invalid_utf-8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host/%c3x%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-absolute_segment_pct-encoded_invalid_utf-8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/nz/%c3x%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-noscheme_segment-nz_pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"%61%20%23"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-noscheme_segment-nz_pct-encoded_utf-8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"%c3%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-abempty_segment_pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host/%61%20%23"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-abempty_port - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host:8080"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-abempty_segment_pct-encoded_utf-8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host/%c3%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-absolute_exhaust_segment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/nz/0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&()*+,;=:@"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-abempty_exhaust_segment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host/0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&()*+,;=:@"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/empty_string - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-absolute_segment_pct-encoded_utf-8 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/nz/%c3%96"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-absolute_segment_pct-encoded_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/nz/%61%20%23"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-abempty_ipv6 - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//[::1]"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-noscheme/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"*"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-empty_exhaust_query - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"?0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - valid/path-noscheme_exhaust_segment-nz-nc - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"@%20!$&()*+,;=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - - invalid/path-abempty_query_bad_caret - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host?^"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-abempty_query_bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host?\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-abempty_query_bad_pct-encoded - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host?%"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-absolute_query_bad_caret - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/?^"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-absolute_query_bad_pct-encoded - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/?%"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-noscheme_query_bad_control_character - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:".?\x1f"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - invalid/path-noscheme_query_bad_pct-encoded - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:".?%"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # got: valid - - valid/extreme - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//userinfo0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~!$&'()*+,;=::@host!$&'()*+,;=._~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789/path0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&'()*+,;=:@%20//foo/?query0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?#fragment0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-abempty_exhaust_fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host#0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-abempty_with_fragment/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host#frag"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-abempty_with_fragment/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"//host/foo/bar#frag"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-absolute_exhaust_fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/#0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-absolute_with_fragment/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/#frag"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-absolute_with_fragment/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"/foo/bar#frag"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-empty_exhaust_fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"#0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-noscheme_exhaust_fragment - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:".#0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?/"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-noscheme_with_fragment/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:".#frag"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false - - valid/path-noscheme_with_fragment/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsUriRef]:{val:"./foo/bar#frag"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_uri_ref" - # message: "" - # for_key: false standard_constraints/required: - proto2/scalar/optional/unset # input: [type.googleapis.com/buf.validate.conformance.cases.RequiredProto2ScalarOptional]:{} @@ -746,30 +31,6 @@ standard_constraints/required: # field: "val" elements:{field_number:1 field_name:"val" field_type:TYPE_STRING} # rule: "required" elements:{field_number:25 field_name:"required" field_type:TYPE_BOOL} # got: valid -standard_constraints/string: - - uri_ref/valid/urn - # input: [type.googleapis.com/buf.validate.conformance.cases.StringURI]:{val:"urn:isbn:0451450523"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "string.uri" - # message: "value must be a valid URI" - # field: "val" elements:{field_number:1 field_name:"val" field_type:TYPE_STRING} - # rule: "string.uri" elements:{field_number:14 field_name:"string" field_type:TYPE_MESSAGE} elements:{field_number:17 field_name:"uri" field_type:TYPE_BOOL} - - uri/valid/urn - # input: [type.googleapis.com/buf.validate.conformance.cases.StringURI]:{val:"urn:isbn:0451450523"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "string.uri" - # message: "value must be a valid URI" - # field: "val" elements:{field_number:1 field_name:"val" field_type:TYPE_STRING} - # rule: "string.uri" elements:{field_number:14 field_name:"string" field_type:TYPE_MESSAGE} elements:{field_number:17 field_name:"uri" field_type:TYPE_BOOL} - - uri/invalid/url/absolute/encoding - # input: [type.googleapis.com/buf.validate.conformance.cases.StringURI]:{val:"https://example.com/foo/bar?baz=%x"} - # want: validation error (1 violation) - # 1. constraint_id: "string.uri" - # field: "val" elements:{field_number:1 field_name:"val" field_type:TYPE_STRING} - # rule: "string.uri" elements:{field_number:14 field_name:"string" field_type:TYPE_MESSAGE} elements:{field_number:17 field_name:"uri" field_type:TYPE_BOOL} - # got: valid standard_constraints/enum: - defined_only/invalid/unknown # input: [type.googleapis.com/buf.validate.conformance.cases.EnumDefined]:{val:2147483647} diff --git a/buf/validate/internal/BUILD.bazel b/buf/validate/internal/BUILD.bazel index 7ed747a..937d4f0 100644 --- a/buf/validate/internal/BUILD.bazel +++ b/buf/validate/internal/BUILD.bazel @@ -135,6 +135,7 @@ cc_library( ":string_format", "//buf/validate/internal/lib:ipv4", "//buf/validate/internal/lib:ipv6", + "//buf/validate/internal/lib:uri", "@com_google_absl//absl/status", "@com_google_cel_cpp//eval/public:cel_function_adapter", "@com_google_cel_cpp//eval/public:cel_function_registry", diff --git a/buf/validate/internal/extra_func.cc b/buf/validate/internal/extra_func.cc index 1ed9ecc..a405c27 100644 --- a/buf/validate/internal/extra_func.cc +++ b/buf/validate/internal/extra_func.cc @@ -21,6 +21,7 @@ #include "absl/strings/str_split.h" #include "buf/validate/internal/lib/ipv4.h" #include "buf/validate/internal/lib/ipv6.h" +#include "buf/validate/internal/lib/uri.h" #include "buf/validate/internal/string_format.h" #include "eval/public/cel_function_adapter.h" #include "eval/public/cel_value.h" @@ -316,55 +317,17 @@ cel::CelValue isIpPrefix(google::protobuf::Arena* arena, cel::CelValue::StringHo } /** - * Naive URI validation. + * URI validation. */ cel::CelValue isUri(google::protobuf::Arena* arena, cel::CelValue::StringHolder lhs) { - const std::string_view& ref = lhs.value(); - if (ref.empty()) { - return cel::CelValue::CreateBool(false); - } - std::string_view scheme, host; - if (!absl::StrContains(ref, "://")) { - return cel::CelValue::CreateBool(false); - } - std::vector split = absl::StrSplit(ref, absl::MaxSplits("://", 1)); - scheme = split[0]; - std::vector hostSplit = absl::StrSplit(split[1], absl::MaxSplits('/', 1)); - host = hostSplit[0]; - // Just checking that scheme and host are present. - return cel::CelValue::CreateBool(!scheme.empty() && !host.empty()); + return cel::CelValue::CreateBool(lib::validateUri(lhs.value())); } /** - * Naive URI ref validation. + * URI ref validation. */ cel::CelValue isUriRef(google::protobuf::Arena* arena, cel::CelValue::StringHolder lhs) { - const std::string_view& ref = lhs.value(); - if (ref.empty()) { - return cel::CelValue::CreateBool(false); - } - std::string_view scheme, host, path; - std::string_view remainder = ref; - if (absl::StrContains(ref, "://")) { - std::vector split = absl::StrSplit(ref, absl::MaxSplits("://", 1)); - scheme = split[0]; - std::vector hostSplit = absl::StrSplit(split[1], absl::MaxSplits('/', 1)); - host = hostSplit[0]; - // If hostSplit has a size greater than 1, then a '/' appeared in the string. Set the rest - // to remainder so we can parse any query string. - if (hostSplit.size() > 1) { - remainder = hostSplit[1]; - } - } - std::vector querySplit = absl::StrSplit(remainder, absl::MaxSplits('?', 1)); - path = querySplit[0]; - if (!isPathValid(path)) { - return cel::CelValue::CreateBool(false); - } - // If the scheme and host are invalid, then the input is a URI ref (so make sure path exists). - // If the scheme and host are valid, then the input is a URI. - bool parsedResult = !path.empty() || (!scheme.empty() && !host.empty()); - return cel::CelValue::CreateBool(parsedResult); + return cel::CelValue::CreateBool(lib::validateUriReference(lhs.value())); } absl::Status RegisterExtraFuncs( diff --git a/buf/validate/internal/lib/BUILD.bazel b/buf/validate/internal/lib/BUILD.bazel index c4c6d85..a19951c 100644 --- a/buf/validate/internal/lib/BUILD.bazel +++ b/buf/validate/internal/lib/BUILD.bazel @@ -50,6 +50,24 @@ cc_test( ], ) +cc_library( + name = "uri", + srcs = ["uri.cc"], + hdrs = ["uri.h"], + deps = [ + ":parser_common" + ] +) + +cc_test( + name = "uri_test", + srcs = ["uri_test.cc"], + deps = [ + ":uri", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "parser_common", hdrs = ["parser_common.h"] diff --git a/buf/validate/internal/lib/ipv4.cc b/buf/validate/internal/lib/ipv4.cc index dfc9c2c..a332d17 100644 --- a/buf/validate/internal/lib/ipv4.cc +++ b/buf/validate/internal/lib/ipv4.cc @@ -24,14 +24,14 @@ namespace buf::validate::internal::lib { namespace { struct IPv4Parser : ParserCommon, public IPv4Prefix { - bool consumePrefixLength() { return consumeDecimalNumber(prefixLength); } + bool parsePrefixLength() { return parseDecimalNumber(prefixLength); } - bool consumeAddressPart() { + bool parseAddressPart() { std::array octets; - if (!consumeDecimalOctet(octets[0]) || !consumeDot() || // - !consumeDecimalOctet(octets[1]) || !consumeDot() || // - !consumeDecimalOctet(octets[2]) || !consumeDot() || // - !consumeDecimalOctet(octets[3])) { + if (!parseDecimalOctet(octets[0]) || !consume>() || // + !parseDecimalOctet(octets[1]) || !consume>() || // + !parseDecimalOctet(octets[2]) || !consume>() || // + !parseDecimalOctet(octets[3])) { return false; } bits |= static_cast(octets[0]) << 24; @@ -41,10 +41,10 @@ struct IPv4Parser : ParserCommon, public IPv4Prefix { return true; } - bool parseAddress() { return consumeAddressPart() && str.empty(); } + bool parseAddress() { return parseAddressPart() && str.empty(); } bool parsePrefix() { - return consumeAddressPart() && consumeSlash() && consumePrefixLength() && str.empty(); + return parseAddressPart() && consume>() && parsePrefixLength() && str.empty(); } }; diff --git a/buf/validate/internal/lib/ipv4_test.cc b/buf/validate/internal/lib/ipv4_test.cc index 10d069d..aef8095 100644 --- a/buf/validate/internal/lib/ipv4_test.cc +++ b/buf/validate/internal/lib/ipv4_test.cc @@ -78,10 +78,10 @@ INSTANTIATE_TEST_SUITE_P( IPv4PrefixParseTest, IPv4PrefixParseTestSuite, ::testing::Values( - IPv4PrefixParseTestCase{"127.0.0.1/1", {{0x7f000001, 1}}}, - IPv4PrefixParseTestCase{"100.100.100.100/0", {{0x64646464, 0}}}, - IPv4PrefixParseTestCase{"255.255.255.255/32", {{0xffffffff, 32}}}, - IPv4PrefixParseTestCase{"10.0.0.0/8", {{0x0a000000, 8}}}, + IPv4PrefixParseTestCase{"127.0.0.1/1", {{{0x7f000001}, 1}}}, + IPv4PrefixParseTestCase{"100.100.100.100/0", {{{0x64646464}, 0}}}, + IPv4PrefixParseTestCase{"255.255.255.255/32", {{{0xffffffff}, 32}}}, + IPv4PrefixParseTestCase{"10.0.0.0/8", {{{0x0a000000}, 8}}}, IPv4PrefixParseTestCase{"1.1.1.1//1", std::nullopt}, IPv4PrefixParseTestCase{"1.1.1.1.1", std::nullopt}, IPv4PrefixParseTestCase{"1.1.1.1/33", std::nullopt}, diff --git a/buf/validate/internal/lib/ipv6.cc b/buf/validate/internal/lib/ipv6.cc index e4f34ac..3a5a62b 100644 --- a/buf/validate/internal/lib/ipv6.cc +++ b/buf/validate/internal/lib/ipv6.cc @@ -35,7 +35,7 @@ struct IPv6Parser : ParserCommon, public IPv6Prefix { return false; } str = str.substr(1); - } while(!str.empty()); + } while (!str.empty()); return true; } @@ -49,12 +49,12 @@ struct IPv6Parser : ParserCommon, public IPv6Prefix { return str[1] == '.' || str[2] == '.' || str[3] == '.'; } - bool consumeDotted(int index) { + bool parseDotted() { std::array octets; - if (!consumeDecimalOctet(octets[0]) || !consumeDot() || // - !consumeDecimalOctet(octets[1]) || !consumeDot() || // - !consumeDecimalOctet(octets[2]) || !consumeDot() || // - !consumeDecimalOctet(octets[3])) { + if (!parseDecimalOctet(octets[0]) || !consume>() || // + !parseDecimalOctet(octets[1]) || !consume>() || // + !parseDecimalOctet(octets[2]) || !consume>() || // + !parseDecimalOctet(octets[3])) { return false; } bits |= static_cast(octets[0]) << 24; @@ -64,9 +64,9 @@ struct IPv6Parser : ParserCommon, public IPv6Prefix { return true; } - bool consumePrefixLength() { return consumeDecimalNumber(prefixLength); } + bool consumePrefixLength() { return parseDecimalNumber(prefixLength); } - bool consumeAddressPart() { + bool parseAddressPart() { std::bitset b; int index = 0; bool doubleColonFound = false; @@ -80,18 +80,18 @@ struct IPv6Parser : ParserCommon, public IPv6Prefix { while (index < hexadecatets_count) { if ((state == Separator || state == DoubleColon) && (doubleColonFound || index == hexadecatets_count - 2) && checkDotted()) { - if (!consumeDotted(index)) { + if (!parseDotted()) { return false; } b <<= 32; index += 2; break; - } else if (state != Hexadecatet && consumeHexadecimalHexadecatet(value)) { + } else if (state != Hexadecatet && parseHexadecimalHexadecatet(value)) { state = Hexadecatet; b <<= 16; b |= value; index++; - } else if (state != Separator && consumeDoubleColon()) { + } else if (state != Separator && consumeSequence<':', ':'>()) { state = DoubleColon; if (index > hexadecatets_count - 1 || doubleColonFound) { return false; @@ -102,7 +102,7 @@ struct IPv6Parser : ParserCommon, public IPv6Prefix { // This ensures that we can't have more than 7 hexadecatets when there's // a double-colon, even though we don't actually process a hexadecatet. index++; - } else if (state == Hexadecatet && consumeColon()) { + } else if (state == Hexadecatet && consume>()) { state = Separator; } else { // Unable to match anything: this is the end. @@ -120,11 +120,11 @@ struct IPv6Parser : ParserCommon, public IPv6Prefix { } bool parseAddress() { - return consumeAddressPart() && (!consumePercent() || consumeZoneId()) && str.empty(); + return parseAddressPart() && (!consume>() || consumeZoneId()) && str.empty(); } bool parsePrefix() { - return consumeAddressPart() && consumeSlash() && consumePrefixLength() && str.empty(); + return parseAddressPart() && consume>() && consumePrefixLength() && str.empty(); } }; diff --git a/buf/validate/internal/lib/parser_common.h b/buf/validate/internal/lib/parser_common.h index e5e1395..58976e5 100644 --- a/buf/validate/internal/lib/parser_common.h +++ b/buf/validate/internal/lib/parser_common.h @@ -14,11 +14,75 @@ #include #include +#include +#include +#include namespace buf::validate::internal::lib { namespace { +// These helper templates enable constructing arbitrary character ranges at +// compile-time. + +template +constexpr auto charSeqFromIndices(std::index_sequence /*unused*/) { + return std::integer_sequence(add + n)...>{}; +} + +template +struct MakeCharRange { + static constexpr size_t size = static_cast(end) - static_cast(start) + 1; + using indices = std::make_integer_sequence; + using type = decltype(charSeqFromIndices(indices{})); +}; + +template +using CharRange = typename MakeCharRange::type; + +template +using Char = typename MakeCharRange::type; + +template +struct MakeConcatRangesImpl; + +template +struct MakeConcatRangesImpl< + std::integer_sequence, + std::integer_sequence> { + using type = typename std::integer_sequence; +}; + +template +struct MakeConcatRanges { + using type = typename MakeConcatRangesImpl::type>::type; +}; + +template +struct MakeConcatRanges { + using type = n; +}; + +template +using ConcatRanges = typename MakeConcatRanges::type; + +// Minimal test suite to ensure that our template machinery does what we expect. +static_assert( + std::is_same_v>, std::integer_sequence>); +static_assert(std::is_same_v< + ConcatRanges, CharRange<'0', '2'>>, + std::integer_sequence>); +static_assert(std::is_same_v< + ConcatRanges, CharRange<'0', '1'>, CharRange<'4', '5'>>, + std::integer_sequence>); + +// Common character ranges +using Num = CharRange<'0', '9'>; +using Alpha = ConcatRanges, CharRange<'A', 'Z'>>; +using AlphaNum = ConcatRanges; +using HexDigit = ConcatRanges, CharRange<'A', 'F'>>; + +// Helper functions for parsing decimal and hexadecimal digits. constexpr unsigned calculateDecimalDigits(unsigned value) { unsigned i = 0; for (; value != 0; i++) { @@ -62,25 +126,48 @@ struct ParserCommon { ParserCommon(std::string_view str) : str{str} {} - template - bool consumeCharLiteral() { - if (str.empty() || str[0] != c) { + template + bool charMatchRange(char value) { + if (value == head) { + return true; + } + if constexpr (sizeof...(tail) > 0) { + return charMatchRange(value); + } + return false; + } + + template + bool charMatchRangeSeqRange(char value, std::integer_sequence /*unused*/) { + return charMatchRange(value); + } + + template + bool consume() { + if (str.empty() || !charMatchRangeSeqRange(str[0], seq{})) { return false; } str = str.substr(1); return true; } - bool consumeDot() { return consumeCharLiteral<'.'>(); } - bool consumeSlash() { return consumeCharLiteral<'/'>(); } - bool consumeColon() { return consumeCharLiteral<':'>(); } - bool consumePercent() { return consumeCharLiteral<'%'>(); } + template + bool sequenceMatch(int i = 0) { + if (str[i] != head) { + return false; + } + if constexpr (sizeof...(tail) > 0) { + return sequenceMatch(i + 1); + } + return true; + } - bool consumeDoubleColon() { - if (str.length() < 2 || str[0] != ':' || str[1] != ':') { + template + bool consumeSequence() { + if (str.length() < sizeof...(c) || !sequenceMatch()) { return false; } - str = str.substr(2); + str = str.substr(sizeof...(c)); return true; } @@ -89,7 +176,7 @@ struct ParserCommon { * Leading zeros are disallowed. */ template - bool consumeDecimalNumber(T& outputValue) { + bool parseDecimalNumber(T& outputValue) { constexpr unsigned maxDigits = calculateDecimalDigits(static_cast(maxValue)); int digitValue; if (str.empty() || !decimalDigitValue(str[0], digitValue)) { @@ -121,8 +208,8 @@ struct ParserCommon { /** * Consumes an unsigned hexadecimal number, storing the value in outputValue. */ - template - bool consumeHexadecimalNumber(T& outputValue) { + template + bool parseHexadecimalNumber(T& outputValue) { constexpr unsigned maxDigits = calculateHexadecimalDigits(static_cast(maxValue)); int digitValue; int i = 0; @@ -140,6 +227,10 @@ struct ParserCommon { // Invalid: out of range return false; } + if (i < minDigits) { + // Invalid: not enough digits. + return false; + } str = str.substr(i); outputValue = static_cast(value); return true; @@ -148,15 +239,15 @@ struct ParserCommon { /** * Consumes a decimal octet (0-255) with no leading zeroes. */ - bool consumeDecimalOctet(uint8_t& outputValue) { - return consumeDecimalNumber(outputValue); + bool parseDecimalOctet(uint8_t& outputValue) { + return parseDecimalNumber(outputValue); } /** * Consumes a hexadecimal hexadecatet (0x0 - 0xffff). */ - bool consumeHexadecimalHexadecatet(uint16_t& outputValue) { - return consumeHexadecimalNumber(outputValue); + bool parseHexadecimalHexadecatet(uint16_t& outputValue) { + return parseHexadecimalNumber(outputValue); } }; diff --git a/buf/validate/internal/lib/uri.cc b/buf/validate/internal/lib/uri.cc new file mode 100644 index 0000000..85e8137 --- /dev/null +++ b/buf/validate/internal/lib/uri.cc @@ -0,0 +1,474 @@ +// Copyright 2023-2025 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "buf/validate/internal/lib/uri.h" + +#include +#include + +#include "buf/validate/internal/lib/parser_common.h" + +namespace buf::validate::internal::lib { + +namespace { + +struct UriParser : ParserCommon { + using Unreserved = ConcatRanges< + Alpha, // + Num, // + Char<'-'>, // + Char<'_'>, // + Char<'.'>, // + Char<'~'>>; + + using SubDelims = ConcatRanges< + Char<'!'>, // + Char<'$'>, // + Char<'&'>, // + Char<'\''>, // + Char<'('>, // + Char<')'>, // + Char<'*'>, // + Char<'+'>, // + Char<','>, // + Char<';'>, // + Char<'='>>; + + bool validateUri() { return consumeUri() && str.empty(); } + + bool consumeUri() { + auto start = str; + if (!(consumeScheme() && consume>() && consumeHierPart())) { + str = start; + return false; + } + if (consume>() && !consumeQuery()) { + str = start; + return false; + } + if (consume>() && !consumeFragment()) { + str = start; + return false; + } + return true; + } + + bool consumeHierPart() { + auto start = str; + if (consumeSequence<'/', '/'>() && consumeAuthority() && consumePathAbempty()) { + return true; + } + str = start; + return consumePathAbsolute() || consumePathRootless() || consumePathEmpty(); + } + + bool validateUriReference() { return (consumeUri() || consumeRelativeRef()) && str.empty(); } + + bool consumeRelativeRef() { + auto start = str; + if (!consumeRelativePart()) { + return false; + } + if (consume>() && !consumeQuery()) { + str = start; + return false; + } + if (consume>() && !consumeFragment()) { + str = start; + return false; + } + return true; + } + + bool consumeRelativePart() { + auto start = str; + if (consumeSequence<'/', '/'>() && consumeAuthority() && consumePathAbempty()) { + return true; + } + str = start; + return consumePathAbsolute() || consumePathNoscheme() || consumePathEmpty(); + } + + bool consumeScheme() { + auto start = str; + if (consume()) { + while (consume, Char<'-'>, Char<'.'>>>()) { + // continue + } + if (!str.empty() && str[0] == ':') { + return true; + } + } + str = start; + return false; + } + + bool consumeAuthority() { + auto start = str; + if (consumeUserInfo() && !consume>()) { + str = start; + return false; + } + if (!consumeHost()) { + str = start; + return false; + } + if (consume>() && !consumePort()) { + str = start; + return false; + } + if (!this->isAuthorityEnd()) { + str = start; + return false; + } + return true; + } + + bool isAuthorityEnd() { + return str.empty() || // + str[0] == '?' || // + str[0] == '#' || // + str[0] == '/'; + } + + bool consumeUserInfo() { + auto start = str; + for (;;) { + if (consume>>() || consumePercentEncoded()) { + continue; + } + if (!str.empty() && str[0] == '@') { + return true; + } + str = start; + return false; + } + } + + bool consumeHost() { + if (str.empty()) { + return false; + } + if (str[0] == '[' && consumeIPLiteral()) { + return true; + } + return consumeRegName(); + } + + bool consumePort() { + auto start = str; + for (;;) { + if (consume()) { + continue; + } + if (isAuthorityEnd()) { + return true; + } + str = start; + return false; + } + } + + bool consumeIPLiteral() { + auto start = str; + if (consume>()) { + auto j = str; + if (consumeIPv6Address() && consume>()) { + return true; + } + str = j; + if (consumeIPVFuture() && consume>()) { + return true; + } + } + str = start; + return false; + } + + bool consumeIPv6Address() { + // These is a simplified IPv6 parser, since we don't care about prefixes or + // the actual values. See ipv6.cc for more details and comments. + const size_t hexadecatets_count = 8; + int index = 0; + bool doubleColonFound = false; + enum : uint8_t { Initial, Hexadecatet, Separator, DoubleColon } state = Initial; + while (index < hexadecatets_count) { + if ((state == Separator || state == DoubleColon) && + (doubleColonFound || index == hexadecatets_count - 2) && + str.length() >= std::char_traits::length("0.0.0.0") && + (str[1] == '.' || str[2] == '.' || str[3] == '.')) { + uint8_t _; + return parseDecimalOctet(_) && consume>() && // + parseDecimalOctet(_) && consume>() && // + parseDecimalOctet(_) && consume>() && // + parseDecimalOctet(_); + } else if (uint16_t _; state != Hexadecatet && parseHexadecimalHexadecatet(_)) { + state = Hexadecatet; + index++; + } else if (state != Separator && consumeSequence<':', ':'>()) { + state = DoubleColon; + if (index++ > hexadecatets_count - 1 || doubleColonFound) { + return false; + } + doubleColonFound = true; + } else if (state == Hexadecatet && consume>()) { + state = Separator; + } else { + break; + } + } + return (state == Hexadecatet || state == DoubleColon) && + (doubleColonFound || index == hexadecatets_count) && + (!consumeSequence<'%', '2', '5'>() || consumeZoneId()); + } + + bool consumeZoneId() { + auto start = str; + while (consume() || consumePercentEncodedUTF8CodePoint()) { + // continue + } + return start != str; + } + + bool consumeIPVFuture() { + auto start = str; + if (consume>() && consume()) { + while (consume()) { + // continue + } + if (consume>()) { + int j = 0; + while (consume>>()) { + j++; + } + if (j >= 1) { + return true; + } + } + } + str = start; + return false; + } + + bool consumeRegName() { + auto start = str; + for (;;) { + if (consume>() || consumePercentEncodedUTF8CodePoint()) { + continue; + } + if (!str.empty() && str[0] == ':') { + return true; + } + if (isAuthorityEnd()) { + return true; + } + str = start; + return false; + } + } + + bool isPathEnd() { return str.empty() || str[0] == '?' || str[0] == '#'; } + + bool consumePathAbempty() { + auto start = str; + while (consume>() && consumeSegment()) { + // continue + } + if (isPathEnd()) { + return true; + } + str = start; + return false; + } + + bool consumePathAbsolute() { + auto start = str; + if (consume>()) { + if (consumeSegmentNz()) { + while (consume>() && consumeSegment()) { + // continue + } + } + if (isPathEnd()) { + return true; + } + } + str = start; + return false; + } + + bool consumePathNoscheme() { + auto start = str; + if (consumeSegmentNzNc()) { + while (consume>() && consumeSegment()) { + // continue + } + if (isPathEnd()) { + return true; + } + } + str = start; + return false; + } + + bool consumePathRootless() { + auto start = str; + if (consumeSegmentNz()) { + while (consume>() && consumeSegment()) { + // continue + } + if (isPathEnd()) { + return true; + } + } + str = start; + return false; + } + + bool consumePathEmpty() { return isPathEnd(); } + + bool consumeSegment() { + while (consumePChar()) { + // continue + } + return true; + } + + bool consumeSegmentNz() { + if (!consumePChar()) { + return false; + } + return consumeSegment(); + } + + bool consumeSegmentNzNc() { + auto start = str; + while (consume>>() || consumePercentEncoded()) { + // continue + } + return str != start; + } + + template + bool consumePChar() { + return consume, Char<'@'>, T...>>() || + consumePercentEncoded(); + } + + bool consumeQuery() { + auto start = str; + for (;;) { + if (consumePChar, Char<'?'>>()) { + continue; + } + if (str.empty() || str[0] == '#') { + return true; + } + str = start; + return false; + } + } + + bool consumeFragment() { + auto start = str; + for (;;) { + if (consumePChar, Char<'?'>>()) { + continue; + } + if (str.empty()) { + return true; + } + str = start; + return false; + } + } + + bool consumePercentEncoded() { + auto start = str; + if (consume>() && consume() && consume()) { + return true; + } + str = start; + return false; + } + + bool parsePercentEncoded(uint8_t& outputValue) { + auto start = str; + if (!consume>()) { + return false; + } + if (parseHexadecimalNumber(outputValue)) { + return true; + } + str = start; + return false; + } + + bool consumePercentEncodedUTF8CodePoint() { + auto start = str; + std::array b; + int size; + + // Handle prefix byte + if (!parsePercentEncoded(b[0])) { + return false; + } + if (b[0] <= 0x7f) { + return true; + } else if (0xc2 <= b[0] && b[0] <= 0xdf) { + size = 2; + } else if (0xe0 <= b[0] && b[0] <= 0xef) { + size = 3; + } else if (0xf0 <= b[0] && b[0] <= 0xf4) { + size = 4; + } else { + // Invalid prefix byte. + str = start; + return false; + } + + // Remaining bytes must be percent coded; parse them. + for (int i = 1; i < size; i++) { + if (!parsePercentEncoded(b[i]) || (b[i] & 0b1100'0000) != 0b1000'0000) { + str = start; + return false; + } + } + + // Check for invalid conditions. + if ((b[0] == 0xe0 && b[1] < 0xa0) || // Overlong encoding (3-byte) + (b[0] == 0xf0 && b[1] < 0x90) || // Overlong encoding (4-byte) + (b[0] == 0xf4 && b[1] > 0x90) // Sequence larger than U+10FFFF + ) { + str = start; + return false; + } + + return true; + } +}; + +} // namespace + +bool validateUri(std::string_view str) { + UriParser parser{str}; + return parser.validateUri(); +} + +bool validateUriReference(std::string_view str) { + UriParser parser{str}; + return parser.validateUriReference(); +} + +} // namespace buf::validate::internal::lib diff --git a/buf/validate/internal/lib/uri.h b/buf/validate/internal/lib/uri.h new file mode 100644 index 0000000..0d52e3b --- /dev/null +++ b/buf/validate/internal/lib/uri.h @@ -0,0 +1,25 @@ +// Copyright 2023-2025 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +namespace buf::validate::internal::lib { + +bool validateUri(std::string_view str); + +bool validateUriReference(std::string_view str); + +} // namespace buf::validate::internal::lib diff --git a/buf/validate/internal/lib/uri_test.cc b/buf/validate/internal/lib/uri_test.cc new file mode 100644 index 0000000..7e9031b --- /dev/null +++ b/buf/validate/internal/lib/uri_test.cc @@ -0,0 +1,395 @@ +// Copyright 2023-2025 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "buf/validate/internal/lib/uri.h" + +#include "gtest/gtest.h" + +namespace buf::validate::internal::lib { +namespace { + +struct URIValidateTestCase { + std::string name; + bool valid; + std::string input; +}; + +class URIValidateTestSuite : public ::testing::TestWithParam {}; + +TEST_P(URIValidateTestSuite, Parse) { + auto param = GetParam(); + EXPECT_EQ(validateUri(param.input), param.valid); +} + +using T = URIValidateTestCase; +INSTANTIATE_TEST_SUITE_P( + URIValidateTest, + URIValidateTestSuite, + ::testing::Values( + T{"Example", true, "https://example.com"}, + T{"ExampleWithPathSegment", true, "https://example.com/foo"}, + T{"ExampleWithPathSegments", true, "https://example.com/foo/bar"}, + T{"ExampleWithPathQueryFragment", true, "https://example.com/foo/bar?baz=quux#frag"}, + T{"ExampleWithUserinfo", true, "https://joe@example.com/foo"}, + T{"EmptyString", false, ""}, + T{"Space", false, " "}, + T{"LeadingSpace", false, " https://example.com"}, + T{"TrailingSpace", false, "https://example.com "}, + T{"RelativeRef", false, "./"}, + T{"RelativeRefWithAuthority", false, "//example.com/foo"}, + T{"Extreme", + true, + "scheme0123456789azAZ+-.://userinfo0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~!$&'()*+,;=::@host!$&'()*+,;=._~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789/path0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&'()*+,;=:@%20//foo/?query0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?#fragment0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?/"}, + T{"SchemeFtp", true, "ftp://example.com"}, + T{"SchemeExhaust", true, "foo0123456789azAZ+-.://example.com"}, + T{"SchemeA", false, "1foo://example.com"}, + T{"SchemeB", false, "-foo://example.com"}, + T{"SchemeC", false, ".foo://example.com"}, + T{"SchemeD", false, ":foo://example.com"}, + T{"SchemeE", false, "foo%20bar://example.com"}, + T{"SchemeF", false, "foo\037bar://example.com"}, + T{"SchemeG", false, "foo^bar://example.com"}, + T{"UserinfoName", true, "https://user@example.com"}, + T{"UserinfoNamePassword", true, "https://user:password@example.com"}, + T{"UserinfoPctEncodedAscii", true, "https://%61%20%23@example.com"}, + T{"UserinfoPctEncodedUtf8", true, "https://%c3%963@example.com"}, + T{"UserinfoPctEncodedInvalidUtf8", true, "https://%c3x%963@example.com"}, + T{"UserinfoUnreserved", + true, + "https://0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~@example.com"}, + T{"UserinfoSubDelims", true, "https://!$&'()*+,;=@example.com"}, + T{"UserinfoExtra", true, "https://:@example.com"}, + T{"UserinfoMultipleColons", true, "https://:::@example.com"}, + T{"UserinfoReservedSlashParsesAsPathAbempty", true, "https:///@example.com"}, + T{"UserinfoReservedQuestionmarkParsesAsQuery", true, "https://?@example.com"}, + T{"UserinfoReservedHashParsesAsFragment", true, "https://#@example.com"}, + T{"UserinfoReservedSquareBracketOpen", false, "https://[@example.com"}, + T{"UserinfoReservedSquareBracketClose", false, "https://]@example.com"}, + T{"UserinfoReservedAt", false, "https://@@example.com"}, + T{"UserinfoBadPctEncodedA", false, "https://%@example.com"}, + T{"UserinfoBadPctEncodedB", false, "https://%2x@example.com"}, + T{"UserinfoExhaust", + true, + "https://0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~!$&'()*+,;=::@example.com"}, + T{"UserinfoControlCharacter", false, "https://\x1F@example.com"}, + T{"UserinfoCaret", false, "https://^@example.com"}, + T{"HostRegName", true, "https://foo"}, + T{"HostRegNameExhaust", + true, + "https://!$&'()*+,;=._~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"}, + T{"HostRegNameEmpty", true, "https://:8080"}, + T{"HostRegNamePctEncodedAscii", true, "https://foo%61%20%23"}, + T{"HostRegNamePctEncodedUtf8", true, "https://foo%c3%96"}, + T{"HostRegNamePctEncodedInvalidUtf8", false, "https://foo%c3x%96"}, + T{"HostRegNameBadPctEncodedA", false, "https://foo%"}, + T{"HostRegNameBadPctEncodedB", false, "https://foo%2x"}, + T{"HostIpv4", true, "https://127.0.0.1"}, + T{"HostIp4vBadOctet", true, "https://256.0.0.1"}, + T{"HostIpv6", true, "https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"}, + T{"HostIpv6A", false, "https://2001:0db8:85a3:0000:0000:8a2e:0370:7334"}, + T{"HostIpv6B", false, "https://[2001::0370::7334]"}, + T{"HostIpfutureShort", true, "https://[v1.x]"}, + T{"HostIpfutureLong", true, "https://[v1234AF.x]"}, + T{"HostIpfutureExhaust", + true, + "https://[vF.-!$&'()*+,;=._~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]"}, + T{"HostIpfuture", false, "https://[v1x]"}, + T{"HostA", false, "https://\x1F.com"}, + T{"HostB", false, "https://^.com"}, + T{"HostC", false, "https://foo@你好.com"}, + T{"HostIpv6ZoneId", true, "https://[::1%25eth0]"}, + T{"HostIpv6ZoneIdPctEncodedAscii", true, "https://[::1%25foo%61%20%23]"}, + T{"HostIpv6ZoneIdPctEncodedUtf8", true, "https://[::1%25foo%c3%96]"}, + T{"HostIpv6ZoneIdPctEncodedInvalidUtf8", false, "https://[::1%25foo%c3x%96]"}, + T{"HostIpv6ZoneIdEmpty", false, "https://[::1%25]"}, + T{"HostIpv6ZoneIdUnquoted", false, "https://[::1%eth0]"}, + T{"HostIpv6ZoneIdBadPctEncodedA", false, "https://[::1%25foo%]"}, + T{"HostIpv6ZoneIdBadPctEncodedB", false, "https://[::1%25foo%2x]"}, + T{"Port8080", true, "https://example.com:8080"}, + T{"Port65535", true, "https://example.com:65535"}, + T{"Port65536", true, "https://example.com:65536"}, + T{"Port0", true, "https://example.com:0"}, + T{"Port1", true, "https://example.com:1"}, + T{"PortEmpty", true, "https://example.com:"}, + T{"PortEmptyRegNameEmpty", true, "https://:"}, + T{"PortA", false, "https://example.com:8a"}, + T{"PortB", false, "https://example.com:x"}, + T{"PortC", false, "https://example.com: 1"}, + T{"PathSimple", true, "https://example.com/foo"}, + T{"PathSimpleNested", true, "https://example.com/foo/bar"}, + T{"PathSimpleNestedTrailingSlash", true, "https://example.com/foo/bar/"}, + T{"PathAbsolute", true, "foo:/nz"}, + T{"PathAbsoluteWithSegment", true, "foo:/nz/a"}, + T{"PathAbsoluteWithSegments", true, "foo:/nz//segment//segment/"}, + T{"PathAbsoluteWithEmptyPchar", true, "foo:/nz/"}, + T{"PathAbsoluteSegmentNzBadCaret", false, "foo:/^"}, + T{"PathAbsoluteSegmentNzBadControlCharacter", false, "foo:/\x1F"}, + T{"PathAbsoluteSegmentNzBadPctEncoded", false, "foo:/%x"}, + T{"PathAbsoluteSegmentNzPctEncodedAscii", true, "foo:/%61%20%23"}, + T{"PathAbsoluteSegmentNzPctEncodedUtf8", true, "foo:/%c3%96"}, + T{"PathAbsoluteSegmentNzPctEncodedInvalidUtf8", true, "foo:/%c3x%96"}, + T{"PathAbsoluteExhaustSegment", + true, + "foo:/nz/0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&'()*+,;=:@%20"}, + T{"PathAbsoluteSegmentBadCaret", false, "foo:/nz/^"}, + T{"PathAbsoluteSegmentBadControlCharacter", false, "foo:/nz/\x1F"}, + T{"PathAbsoluteSegmentBadPctEncoded", false, "foo:/nz/%x"}, + T{"PathAbsoluteSegmentPctEncodedAscii", true, "foo:/nz/%61%20%23"}, + T{"PathAbsoluteSegmentPctEncodedUtf8", true, "foo:/nz/%c3%96%c3"}, + T{"PathAbsoluteSegmentPctEncodedInvalidUtf8", true, "foo:/nz/%c3x%96"}, + T{"PathAbsoluteExhaustSegmentNz", + true, + "foo:/@%20!$&()*+,;=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~:"}, + T{"PathAbsoluteWithQueryAndFragment", true, "foo:/nz?q#f"}, + T{"PathRootless", true, "foo:nz"}, + T{"PathRootlessWithSegment", true, "foo:nz/a"}, + T{"PathRootlessWithSegments", true, "foo:nz//segment//segment/"}, + T{"PathRootlessSegmentEmptyPchar", true, "foo:nz/"}, + T{"PathRootlessSegmentNzBadCaret", false, "foo:^"}, + T{"PathRootlessSegmentNzBadControlCharacter", false, "foo:\x1F"}, + T{"PathRootlessSegmentNzBadPctEncoded", false, "foo:%x"}, + T{"PathRootlessSegmentNzPctEncodedAscii", true, "foo:%61%20%23"}, + T{"PathRootlessSegmentNzPctEncodedUtf8", true, "foo:%c3%96"}, + T{"PathRootlessSegmentNzPctEncodedInvalidUtf8", true, "foo:%c3x%96"}, + T{"PathRootlessSegmentNzExhaust", + true, + "foo:@%20!$&()*+,;=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~:"}, + T{"PathRootlessSegmentBadCaret", false, "foo:nz/^"}, + T{"PathRootlessSegmentBadControlCharacter", false, "foo:nz/\x1F"}, + T{"PathRootlessSegmentBadPctEncoded", false, "foo:nz/%x"}, + T{"PathRootlessSegmentPctEncodedAscii", true, "foo:nz/%61%20%23"}, + T{"PathRootlessSegmentPctEncodedUtf8", true, "foo:nz/%c3%96"}, + T{"PathRootlessSegmentPctEncodedInvalidUtf8", true, "foo:nz/%c3x%96"}, + T{"PathRootlessSegmentExhaust", + true, + "foo:nz/0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&'()*+,;=:@%20"}, + T{"PathRootlessWithQueryAndFragment", true, "foo:nz?q#f"}, + T{"PathEmpty", true, "foo:"}, + T{"PathEmptyWithQueryAndFragment", true, "foo:?q#f"}, + T{"AuthorityPathAbempty", true, "foo://example.com"}, + T{"AuthorityPathAbemptyWithSegment", true, "foo://example.com/a"}, + T{"AuthorityPathAbemptyWithSegments", true, "foo://example.com/segment//segment/"}, + T{"AuthorityPathAbemptySegmentEmptyPchar", true, "foo://example.com/"}, + T{"AuthorityPathAbemptySegmentBadCaret", false, "foo://example.com/^"}, + T{"AuthorityPathAbemptySegmentBadControlCharacter", false, "foo://example.com/\x1F"}, + T{"AuthorityPathAbemptySegmentBadPctEncoded", false, "foo://example.com/%x"}, + T{"AuthorityPathAbemptySegmentPctEncodedAscii", true, "foo://example.com/%61%20%23"}, + T{"AuthorityPathAbemptySegmentPctEncodedUtf8", true, "foo://example.com/%c3%96"}, + T{"AuthorityPathAbemptySegmentPctEncodedInvalidUtf8", true, "foo://example.com/%c3x%96"}, + T{"AuthorityPathAbemptySegmentExhaust", + true, + "foo://example.com/0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&'()*+,;=:@%20"}, + T{"AuthorityPathAbemptyWithQueryAndFragment", + true, + "foo://example.com/0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&'()*+,;=:@%20"}, + T{"Query", true, "https://example.com?baz=quux"}, + T{"QueryPctEncodedAscii", true, "https://example.com?%61%20%23"}, + T{"QueryPctEncodedUtf8", true, "https://example.com?%c3%96%c3"}, + T{"QueryPctEncodedInvalidUtf8", true, "https://example.com?%c3x%96"}, + T{"QueryBadPctEncoded", false, "https://example.com?%2x"}, + T{"QueryBadControlCharacter", false, "https://example.com?\x1F"}, + T{"QueryBadCaret", false, "https://example.com?^"}, + T{"QuerySubDelims", true, "https://example.com?!$&'()*+,="}, + T{"QuerySubDelimSemicolon", true, "https://example.com?;"}, + T{"QueryPcharExtra", true, "https://example.com?:@"}, + T{"QueryExtra", true, "https://example.com?/?"}, + T{"QueryUnusualKeyValueStructure", true, "https://example.com?a=b&c&&=1&=="}, + T{"QueryExhaust", + true, + "https://example.com?0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?"}, + T{"Fragment", true, "https://example.com?#frag"}, + T{"FragmentPctEncodedAscii", true, "https://example.com#%61%20%23"}, + T{"FragmentPctEncodedUtf8", true, "https://example.com#%c3%96"}, + T{"FragmentPctEncodedInvalidUtf8", true, "https://example.com#%c3x%96"}, + T{"FragmentBadPctEncodedA", false, "https://example.com#%2x"}, + T{"FragmentBadPctEncodedB", false, "https://example.com#%"}, + T{"FragmentSubDelims", true, "https://example.com#!$&'()*+,;="}, + T{"FragmentPcharExtra", true, "https://example.com#/?"}, + T{"FragmentBadHash", false, "https://example.com##"}, + T{"FragmentBadCaret", false, "https://example.com#^"}, + T{"FragmentBadControlCharacter", false, "https://example.com#\x1F"}, + T{"FragmentExhaust", + true, + "https://example.com/#0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?/"}), + [](const auto& info) { return info.param.name; }); + +struct URIReferenceValidateTestCase { + std::string name; + bool valid; + std::string input; +}; + +class URIReferenceValidateTestSuite + : public ::testing::TestWithParam {}; + +TEST_P(URIReferenceValidateTestSuite, Parse) { + auto param = GetParam(); + EXPECT_EQ(validateUriReference(param.input), param.valid); +} + +using U = URIReferenceValidateTestCase; +INSTANTIATE_TEST_SUITE_P( + URIReferenceValidateTest, + URIReferenceValidateTestSuite, + ::testing::Values( + U{"PathNoschemeWithSegment", true, "./foo"}, + U{"PathNoschemeWithSegmentQueryFragment", true, "./foo/bar?baz=quux#frag"}, + U{"EmptyString", true, ""}, + U{"Extreme", + true, + "//userinfo0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~!$&'()*+,;=::@host!$&'()*+,;=._~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789/path0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&'()*+,;=:@%20//foo/?query0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?#fragment0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?/"}, + U{"Space", false, " "}, + U{"LeadingSpace", false, " ./foo"}, + U{"TrailingSpace", false, "./foo "}, + U{"BadRelativePart", false, ":"}, + U{"UriWithBadScheme", false, "1foo://example.com"}, + U{"AuthorityPathAbempty", true, "//host"}, + U{"AuthorityPathAbemptyWithSegmentQueryFragment", true, "//host/foo?baz=quux#frag"}, + U{"AuthorityPathAbemptySegmentEmptyPchar", true, "//host/"}, + U{"AuthorityPathAbemptySegmentBadControlCharacter", false, "//host/\x1F"}, + U{"PathAbemptySegmentBadPctEncoded", false, "//host/%x"}, + U{"PathAbemptySegmentPctEncodedAscii", true, "//host/%61%20%23"}, + U{"PathAbemptySegmentPctEncodedUtf8", true, "//host/%c3%96"}, + U{"PathAbemptySegmentPctEncodedInvalidUtf8", true, "//host/%c3x%96"}, + U{"PathAbemptyExhaustSegment", + true, + "//host/0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&()*+,;=:@"}, + U{"PathAbemptyMultipleSegmentsA", true, "//host//"}, + U{"PathAbemptyMultipleSegmentsB", true, "//host/a/b/c"}, + U{"PathAbemptyMultipleSegmentsC", true, "//host/a/b/c/"}, + U{"PathAbemptyWithQueryA", true, "//host?baz=quux"}, + U{"PathAbemptyWithQueryB", true, "//host/foo/bar?baz=quux"}, + U{"PathAbemptyWithFragmentA", true, "//host#frag"}, + U{"PathAbemptyWithFragmentB", true, "//host/foo/bar#frag"}, + U{"PathAbemptyExhaustFragment", + true, + "//host#0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?/"}, + U{"PathAbemptyFragmentBadFragment", false, "//host##"}, + U{"PathAbemptyFragmentBadCaret", false, "//host#^"}, + U{"PathAbemptyFragmentBadControlCharacter", false, "//host#\x1F"}, + U{"PathAbemptyFragmentBadPctEncoding", false, "//host#%"}, + U{"PathAbemptyExhaustQuery", + true, + "//host?0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?"}, + U{"PathAbemptyQueryBadCaret", false, "//host?^"}, + U{"PathAbemptyQueryBadPctEncoded", false, "//host?%"}, + U{"PathAbemptyQueryBadControlCharacter", false, "//host?\x1F"}, + U{"PathAbemptyExhaustUserinfo", + true, + "//0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~!$&'()*+,;=::@example.com"}, + U{"PathAbemptyPort", true, "//host:8080"}, + U{"PathAbemptyIpv4", true, "//127.0.0.1"}, + U{"PathAbemptyIpv6", true, "//[::1]"}, + U{"PathAbsolute", true, "/"}, + U{"PathAbsoluteSegmentNz", true, "/nz"}, + U{"PathAbsoluteSegmentNzMatchesColon", true, "/:"}, + U{"PathAbsoluteSegmentNzBadCaret", false, "/^"}, + U{"PathAbsoluteSegmentNzBadControlCharacter", false, "/\x1F"}, + U{"PathAbsoluteSegmentNzBadPctEncoded", false, "/%x"}, + U{"PathAbsoluteSegmentNzPctEncodedAscii", true, "/%61%20%23"}, + U{"PathAbsoluteSegmentNzPctEncodedUtf8", true, "/%c3%96"}, + U{"PathAbsoluteSegmentNzPctEncodedInvalidUtf8", true, "/%c3x%96"}, + U{"PathAbsoluteExhaustSegmentNz", + true, + "/@%20!$&()*+,;=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~:"}, + U{"PathAbsoluteSegmentEmptyPchar", true, "/nz/"}, + U{"PathAbsoluteSegmentBadCaret", false, "/nz/^"}, + U{"PathAbsoluteSegmentBadControlCharacter", false, "/nz/\x1F"}, + U{"PathAbsoluteSegmentBadPctEncoded", false, "/nz/%x"}, + U{"PathAbsoluteSegmentPctEncodedAscii", true, "/nz/%61%20%23"}, + U{"PathAbsoluteSegmentPctEncodedUtf8", true, "/nz/%c3%96"}, + U{"PathAbsoluteSegmentPctEncodedInvalidUtf8", true, "/nz/%c3x%96"}, + U{"PathAbsoluteExhaustSegment", + true, + "/nz/0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&()*+,;=:@"}, + U{"PathAbsoluteWithQueryA", true, "/?baz=quux"}, + U{"PathAbsoluteWithQueryB", true, "/foo/bar?baz=quux"}, + U{"PathAbsoluteWithFragmentA", true, "/#frag"}, + U{"PathAbsoluteWithFragmentB", true, "/foo/bar#frag"}, + U{"PathAbsoluteBadControlCharacter", false, "/foo/\x1F"}, + U{"PathAbsoluteExhaustFragment", + true, + "/#0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?/"}, + U{"PathAbsoluteFragmentBadFragment", false, "/##"}, + U{"PathAbsoluteFragmentBadCaret", false, "/#^"}, + U{"PathAbsoluteFragmentBadControlCharacter", false, "/#\x1F"}, + U{"PathAbsoluteFragmentBadPctEncoding", false, "/#%"}, + U{"PathAbsoluteExhaustQuery", + true, + "/?0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?"}, + U{"PathAbsoluteQueryBadCaret", false, "/?^"}, + U{"PathAbsoluteQueryBadPctEncoded", false, "/?%"}, + U{"PathAbsoluteQueryBadControlCharacter", false, "/?\x1F"}, + U{"PathNoschemeA", true, "./foo/bar"}, + U{"PathNoschemeB", true, "*"}, + U{"PathNoschemeC", true, "./foo"}, + U{"PathNoschemeSegmentNzBadColon", false, ":"}, + U{"PathNoschemeSegmentNzBadCaret", false, "^"}, + U{"PathNoschemeSegmentNzBadControlCharacter", false, "\x1F"}, + U{"PathNoschemeSegmentNzBadPctEncoded", false, "%x"}, + U{"PathNoschemeSegmentNzPctEncodedAscii", true, "%61%20%23"}, + U{"PathNoschemeSegmentNzPctEncodedUtf8", true, "%c3%96"}, + U{"PathNoschemeSegmentNzPctEncodedInvalidUtf8", true, "%c3x%96"}, + U{"PathNoschemeExhaustSegmentNzNc", + true, + "@%20!$&()*+,;=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~"}, + U{"PathNoschemeSegmentEmptyPchar", true, "./"}, + U{"PathNoschemeSegmentBadCaret", false, "./^"}, + U{"PathNoschemeSegmentBadControlCharacter", false, "./\x1F"}, + U{"PathNoschemeSegmentBadPctEncoded", false, "./%x"}, + U{"PathNoschemeSegmentPctEncodedAscii", true, "./%61%20%23"}, + U{"PathNoschemeSegmentPctEncodedUtf8", true, "./%c3%96"}, + U{"PathNoschemeSegmentPctEncodedInvalidUtf8", true, "./%c3x%96"}, + U{"PathNoschemeExhaustSegment", + true, + "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%20!$&'()*+,;=:@%20"}, + U{"PathNoschemeMultipleSegmentsA", true, ".///"}, + U{"PathNoschemeMultipleSegmentsB", true, "./a/b/c"}, + U{"PathNoschemeMultipleSegmentsC", true, "./a/b/c/"}, + U{"PathNoschemeWithQueryA", true, ".?baz=quux"}, + U{"PathNoschemeWithQueryB", true, "./foo/bar?baz=quux"}, + U{"PathNoschemeWithFragmentA", true, ".#frag"}, + U{"PathNoschemeWithFragmentB", true, "./foo/bar#frag"}, + U{"PathNoschemeBadControlCharacter", false, "./foo/\x1F"}, + U{"PathNoschemeExhaustFragment", + true, + ".#0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?/"}, + U{"PathNoschemeFragmentBadFragment", false, ".##"}, + U{"PathNoschemeFragmentBadCaret", false, ".#^"}, + U{"PathNoschemeFragmentBadControlCharacter", false, ".#\x1F"}, + U{"PathNoschemeFragmentBadPctEncoded", false, ".#%"}, + U{"PathNoschemeExhaustQuery", + true, + ".?0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?"}, + U{"PathNoschemeQueryBadCaret", false, ".?^"}, + U{"PathNoschemeQueryBadPctEncoded", false, ".?%"}, + U{"PathNoschemeQueryBadControlCharacter", false, ".?\x1F"}, + U{"PathEmpty", true, ""}, + U{"PathEmptyExhaustFragment", + true, + "#0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?/"}, + U{"PathEmptyFragmentBadFragment", false, "##"}, + U{"PathEmptyFragmentBadCaret", false, "#^"}, + U{"PathEmptyFragmentBadControlCharacter", false, "#\x1F"}, + U{"PathEmptyFragmentBadPctEncoded", false, "#%"}, + U{"PathEmptyExhaustQuery", + true, + "?0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~%20!$&'()*+,=;:@?"}, + U{"PathEmptyQueryBadCaret", false, "?^"}, + U{"PathEmptyQueryBadPctEncoded", false, "?%"}, + U{"PathEmptyQueryBadControlCharacter", false, "?\x1F"}), + [](const auto& info) { return info.param.name; }); + +} // namespace +} // namespace buf::validate::internal::lib