@@ -103,15 +103,20 @@ impl Authority {
103103 }
104104 b':' => {
105105 colon_cnt += 1 ;
106- } ,
106+ }
107107 b'[' => {
108108 start_bracket = true ;
109+ if has_percent {
110+ // Something other than the userinfo has a `%`, so reject it.
111+ return Err ( ErrorKind :: InvalidAuthority . into ( ) ) ;
112+ }
109113 }
110114 b']' => {
111115 end_bracket = true ;
112116
113117 // Those were part of an IPv6 hostname, so forget them...
114118 colon_cnt = 0 ;
119+ has_percent = false ;
115120 }
116121 b'@' => {
117122 at_sign_pos = Some ( i) ;
@@ -127,8 +132,11 @@ impl Authority {
127132 // the userinfo can have a percent-encoded username and password,
128133 // so record that a `%` was found. If this turns out to be
129134 // part of the userinfo, this flag will be cleared.
135+ // Also per https://tools.ietf.org/html/rfc6874, percent-encoding can
136+ // be used to indicate a zone identifier.
130137 // If the flag hasn't been cleared at the end, that means this
131- // was part of the hostname, and will fail with an error.
138+ // was part of the hostname (and not part of an IPv6 address), and
139+ // will fail with an error.
132140 has_percent = true ;
133141 }
134142 0 => {
@@ -612,4 +620,20 @@ mod tests {
612620 let err = Authority :: parse_non_empty ( b"a%2f:b%2f@example%2f.com" ) . unwrap_err ( ) ;
613621 assert_eq ! ( err. 0 , ErrorKind :: InvalidAuthority ) ;
614622 }
623+
624+ #[ test]
625+ fn allows_percent_in_ipv6_address ( ) {
626+ let authority_str = "[fe80::1:2:3:4%25eth0]" ;
627+ let result: Authority = authority_str. parse ( ) . unwrap ( ) ;
628+ assert_eq ! ( result, authority_str) ;
629+ }
630+
631+ #[ test]
632+ fn rejects_percent_outside_ipv6_address ( ) {
633+ let err = Authority :: parse_non_empty ( b"1234%20[fe80::1:2:3:4]" ) . unwrap_err ( ) ;
634+ assert_eq ! ( err. 0 , ErrorKind :: InvalidAuthority ) ;
635+
636+ let err = Authority :: parse_non_empty ( b"[fe80::1:2:3:4]%20" ) . unwrap_err ( ) ;
637+ assert_eq ! ( err. 0 , ErrorKind :: InvalidAuthority ) ;
638+ }
615639}
0 commit comments