1#![cfg_attr(docsrs, feature(doc_cfg))]
5
6use core::{
7 net::{Ipv4Addr, Ipv6Addr},
8 str,
9};
10
11use winnow::{
12 combinator::{alt, opt, preceded, repeat, terminated},
13 prelude::*,
14 stream::{AsChar, Compare, Stream, StreamIsPartial},
15 token::{literal, one_of, take_while},
16};
17
18#[inline]
43pub fn parse_path<I>(input: &mut I) -> ModalResult<()>
44where
45 I: Stream + StreamIsPartial + Compare<u8>,
46 I::Token: Clone + AsChar,
47{
48 parse_path_absolute.parse_next(input)
49}
50
51#[inline]
73pub fn parse_query<I>(input: &mut I) -> ModalResult<()>
74where
75 I: Stream + StreamIsPartial + Compare<u8>,
76 I::Token: Clone + AsChar,
77{
78 repeat::<_, _, (), _, _>(.., parse_query_or_fragment_item)
79 .void()
80 .parse_next(input)
81}
82
83#[inline]
105pub fn parse_fragment<I>(input: &mut I) -> ModalResult<()>
106where
107 I: Stream + StreamIsPartial + Compare<u8>,
108 I::Token: Clone + AsChar,
109{
110 parse_query.parse_next(input)
111}
112
113#[inline]
115pub fn is_unreserved(char: impl AsChar) -> bool {
116 matches!(
117 char.as_char(),
118 '0'..='9' | 'A'..='Z' | 'a'..='z' | '-' | '.' | '_' | '~'
119 )
120}
121
122#[inline]
124pub fn is_sub_delim(char: impl AsChar) -> bool {
125 matches!(
126 char.as_char(),
127 '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | ';' | '='
128 )
129}
130
131#[inline]
133pub fn is_gen_delim(char: impl AsChar) -> bool {
134 matches!(char.as_char(), ':' | '/' | '?' | '#' | '[' | ']' | '@')
135}
136
137#[inline]
139pub fn is_reserved(char: impl AsChar) -> bool {
140 let char = char.as_char();
141
142 is_gen_delim(char) || is_sub_delim(char)
143}
144
145#[inline]
147pub fn is_hexdig(char: impl AsChar) -> bool {
148 char.as_char().is_ascii_hexdigit()
149}
150
151#[inline]
153pub fn is_pchar(char: impl AsChar) -> bool {
154 let char = char.as_char();
155
156 is_unreserved(char) || is_sub_delim(char) || matches!(char, '%' | ':' | '@')
157}
158
159#[inline]
161pub fn is_pchar_nc(char: impl AsChar) -> bool {
162 let char = char.as_char();
163
164 is_unreserved(char) || is_sub_delim(char) || matches!(char, '%' | '@')
165}
166
167#[inline]
169pub fn is_reg_name_char(char: impl AsChar) -> bool {
170 let char = char.as_char();
171
172 is_unreserved(char) || is_sub_delim(char) || matches!(char, '%')
173}
174
175#[inline]
177pub fn is_userinfo_char(char: impl AsChar) -> bool {
178 let char = char.as_char();
179
180 is_unreserved(char) || is_sub_delim(char) || matches!(char, '%' | ':')
181}
182
183#[inline]
185pub fn is_ipvfuture_char(char: impl AsChar) -> bool {
186 let char = char.as_char();
187
188 is_unreserved(char) || is_sub_delim(char) || matches!(char, ':')
189}
190
191#[inline]
193pub fn is_ip_literal_char(char: impl AsChar) -> bool {
194 let char = char.as_char();
195
196 char.is_ascii_hexdigit() || is_ipvfuture_char(char) || matches!(char, '.')
197}
198
199#[inline]
203pub fn is_ip_literal_body(bytes: &[u8]) -> bool {
204 parse_ipv6address
205 .parse_peek(bytes)
206 .is_ok_and(|(rest, ())| rest.is_empty())
207 || parse_ipvfuture
208 .parse_peek(bytes)
209 .is_ok_and(|(rest, ())| rest.is_empty())
210}
211
212#[inline]
234pub fn parse_pct_encoded<I>(input: &mut I) -> ModalResult<()>
235where
236 I: Stream + StreamIsPartial + Compare<u8>,
237 I::Token: Clone + AsChar,
238{
239 (b'%', take_while(2..=2, is_hexdig))
240 .void()
241 .parse_next(input)
242}
243
244#[inline]
266pub fn parse_userinfo<I>(input: &mut I) -> ModalResult<()>
267where
268 I: Stream + StreamIsPartial + Compare<u8>,
269 I::Token: Clone + AsChar,
270{
271 repeat::<_, _, (), _, _>(.., parse_userinfo_item)
272 .void()
273 .parse_next(input)
274}
275
276#[inline]
298pub fn parse_host<I>(input: &mut I) -> ModalResult<()>
299where
300 I: Stream + StreamIsPartial + Compare<u8>,
301 I::Token: Clone + AsChar,
302 I::Slice: AsRef<[u8]>,
303{
304 alt((parse_ip_literal, parse_ipv4address, parse_reg_name))
305 .void()
306 .parse_next(input)
307}
308
309#[inline]
334pub fn parse_uri_host<I>(input: &mut I) -> ModalResult<()>
335where
336 I: Stream + StreamIsPartial + Compare<u8>,
337 I::Token: Clone + AsChar,
338 I::Slice: AsRef<[u8]>,
339{
340 alt((parse_ip_literal, parse_ipv4address, parse_reg_name_nz))
341 .void()
342 .parse_next(input)
343}
344
345#[inline]
367pub fn parse_ip_literal<I>(input: &mut I) -> ModalResult<()>
368where
369 I: Stream + StreamIsPartial + Compare<u8>,
370 I::Token: Clone + AsChar,
371 I::Slice: AsRef<[u8]>,
372{
373 (b'[', alt((parse_ipv6address, parse_ipvfuture)), b']')
374 .void()
375 .parse_next(input)
376}
377
378#[inline]
400pub fn parse_ipvfuture<I>(input: &mut I) -> ModalResult<()>
401where
402 I: Stream + StreamIsPartial + Compare<u8>,
403 I::Token: Clone + AsChar,
404{
405 (
406 one_of([b'v', b'V']),
407 take_while(1.., is_hexdig),
408 b'.',
409 repeat::<_, _, (), _, _>(1.., parse_ipvfuture_item),
410 )
411 .void()
412 .parse_next(input)
413}
414
415#[inline]
435pub fn parse_ipv6address<I>(input: &mut I) -> ModalResult<()>
436where
437 I: Stream + StreamIsPartial,
438 I::Token: Clone + AsChar,
439 I::Slice: AsRef<[u8]>,
440{
441 take_while(1.., is_ipv6address_char)
442 .verify(|slice: &I::Slice| parse_ascii::<Ipv6Addr>(slice.as_ref()).is_some())
443 .void()
444 .parse_next(input)
445}
446
447#[inline]
469pub fn parse_ipv4address<I>(input: &mut I) -> ModalResult<()>
470where
471 I: Stream + StreamIsPartial,
472 I::Token: Clone + AsChar,
473 I::Slice: AsRef<[u8]>,
474{
475 take_while(1.., is_ipv4address_char)
476 .verify(|slice: &I::Slice| parse_ascii::<Ipv4Addr>(slice.as_ref()).is_some())
477 .void()
478 .parse_next(input)
479}
480
481#[inline]
497pub fn parse_dec_octet<I>(input: &mut I) -> ModalResult<()>
498where
499 I: Stream + StreamIsPartial,
500 I::Token: Clone + AsChar,
501 I::Slice: AsRef<[u8]>,
502{
503 take_while(1..=3, |char: I::Token| char.as_char().is_ascii_digit())
504 .verify(|slice: &I::Slice| is_dec_octet(slice.as_ref()))
505 .void()
506 .parse_next(input)
507}
508
509#[inline]
531pub fn parse_reg_name<I>(input: &mut I) -> ModalResult<()>
532where
533 I: Stream + StreamIsPartial + Compare<u8>,
534 I::Token: Clone + AsChar,
535{
536 repeat::<_, _, (), _, _>(.., parse_reg_name_item)
537 .void()
538 .parse_next(input)
539}
540
541#[inline]
563pub fn parse_port<I>(input: &mut I) -> ModalResult<()>
564where
565 I: Stream + StreamIsPartial,
566 I::Token: Clone + AsChar,
567{
568 take_while(.., |char: I::Token| char.as_char().is_ascii_digit())
569 .void()
570 .parse_next(input)
571}
572
573#[inline]
575pub fn is_scheme_start(char: impl AsChar) -> bool {
576 char.is_alpha()
577}
578
579#[inline]
581pub fn is_scheme_char(char: impl AsChar) -> bool {
582 let char = char.as_char();
583 char.is_ascii_alphanumeric() || matches!(char, '+' | '-' | '.')
584}
585
586#[inline]
608pub fn parse_scheme<I>(input: &mut I) -> ModalResult<()>
609where
610 I: Stream + StreamIsPartial,
611 I::Token: Clone + AsChar,
612{
613 preceded(one_of(is_scheme_start), take_while(.., is_scheme_char))
614 .void()
615 .parse_next(input)
616}
617
618#[inline]
644pub fn parse_authority<I>(input: &mut I) -> ModalResult<()>
645where
646 I: Stream + StreamIsPartial + Compare<u8>,
647 I::Token: Clone + AsChar,
648 I::Slice: AsRef<[u8]>,
649{
650 parse_authority_parts.void().parse_next(input)
651}
652
653#[inline]
680pub fn parse_authority_parts<I>(input: &mut I) -> ModalResult<()>
681where
682 I: Stream + StreamIsPartial + Compare<u8>,
683 I::Token: Clone + AsChar,
684 I::Slice: AsRef<[u8]>,
685{
686 (
687 opt(terminated(parse_userinfo_nz, b'@')),
688 parse_uri_host,
689 opt((b':', parse_port)),
690 )
691 .void()
692 .parse_next(input)
693}
694
695#[inline]
717pub fn parse_path_abempty<I>(input: &mut I) -> ModalResult<()>
718where
719 I: Stream + StreamIsPartial + Compare<u8>,
720 I::Token: Clone + AsChar,
721{
722 repeat::<_, _, (), _, _>(.., (b'/', parse_segment))
723 .void()
724 .parse_next(input)
725}
726
727#[inline]
749pub fn parse_path_absolute<I>(input: &mut I) -> ModalResult<()>
750where
751 I: Stream + StreamIsPartial + Compare<u8>,
752 I::Token: Clone + AsChar,
753{
754 (
755 b'/',
756 opt((
757 parse_segment_nz,
758 repeat::<_, _, (), _, _>(.., (b'/', parse_segment)),
759 )),
760 )
761 .void()
762 .parse_next(input)
763}
764
765#[inline]
787pub fn parse_path_noscheme<I>(input: &mut I) -> ModalResult<()>
788where
789 I: Stream + StreamIsPartial + Compare<u8>,
790 I::Token: Clone + AsChar,
791{
792 (
793 parse_segment_nz_nc,
794 repeat::<_, _, (), _, _>(.., (b'/', parse_segment)),
795 )
796 .void()
797 .parse_next(input)
798}
799
800#[inline]
822pub fn parse_path_rootless<I>(input: &mut I) -> ModalResult<()>
823where
824 I: Stream + StreamIsPartial + Compare<u8>,
825 I::Token: Clone + AsChar,
826{
827 (
828 parse_segment_nz,
829 repeat::<_, _, (), _, _>(.., (b'/', parse_segment)),
830 )
831 .void()
832 .parse_next(input)
833}
834
835#[inline]
857pub fn parse_path_empty<I>(_input: &mut I) -> ModalResult<()>
858where
859 I: Stream + StreamIsPartial,
860{
861 Ok(())
862}
863
864#[inline]
874pub fn parse_segment<I>(input: &mut I) -> ModalResult<()>
875where
876 I: Stream + StreamIsPartial + Compare<u8>,
877 I::Token: Clone + AsChar,
878{
879 repeat::<_, _, (), _, _>(.., parse_pchar_item)
880 .void()
881 .parse_next(input)
882}
883
884#[inline]
894pub fn parse_segment_nz<I>(input: &mut I) -> ModalResult<()>
895where
896 I: Stream + StreamIsPartial + Compare<u8>,
897 I::Token: Clone + AsChar,
898{
899 repeat::<_, _, (), _, _>(1.., parse_pchar_item)
900 .void()
901 .parse_next(input)
902}
903
904#[inline]
914pub fn parse_segment_nz_nc<I>(input: &mut I) -> ModalResult<()>
915where
916 I: Stream + StreamIsPartial + Compare<u8>,
917 I::Token: Clone + AsChar,
918{
919 repeat::<_, _, (), _, _>(1.., parse_pchar_nc_item)
920 .void()
921 .parse_next(input)
922}
923
924#[inline]
949pub fn parse_hier_part<I>(input: &mut I) -> ModalResult<()>
950where
951 I: Stream + StreamIsPartial + Compare<u8>,
952 I::Token: Clone + AsChar,
953 I::Slice: AsRef<[u8]>,
954{
955 alt((
956 ((b'/', b'/'), parse_generic_authority, parse_path_abempty).void(),
957 parse_path_absolute,
958 parse_path_rootless,
959 parse_path_empty,
960 ))
961 .parse_next(input)
962}
963
964#[inline]
989pub fn parse_relative_part<I>(input: &mut I) -> ModalResult<()>
990where
991 I: Stream + StreamIsPartial + Compare<u8>,
992 I::Token: Clone + AsChar,
993 I::Slice: AsRef<[u8]>,
994{
995 alt((
996 ((b'/', b'/'), parse_generic_authority, parse_path_abempty).void(),
997 parse_path_absolute,
998 parse_path_noscheme,
999 parse_path_empty,
1000 ))
1001 .parse_next(input)
1002}
1003
1004#[inline]
1028pub fn parse_absolute_uri<I>(input: &mut I) -> ModalResult<()>
1029where
1030 I: Stream + StreamIsPartial + Compare<u8>,
1031 I::Token: Clone + AsChar,
1032 I::Slice: AsRef<[u8]>,
1033{
1034 (
1035 parse_scheme,
1036 b':',
1037 parse_hier_part,
1038 opt((b'?', parse_query)),
1039 )
1040 .void()
1041 .parse_next(input)
1042}
1043
1044#[inline]
1068pub fn parse_relative_ref<I>(input: &mut I) -> ModalResult<()>
1069where
1070 I: Stream + StreamIsPartial + Compare<u8>,
1071 I::Token: Clone + AsChar,
1072 I::Slice: AsRef<[u8]>,
1073{
1074 (
1075 parse_relative_part,
1076 opt((b'?', parse_query)),
1077 opt((b'#', parse_fragment)),
1078 )
1079 .void()
1080 .parse_next(input)
1081}
1082
1083#[inline]
1107pub fn parse_uri<I>(input: &mut I) -> ModalResult<()>
1108where
1109 I: Stream + StreamIsPartial + Compare<u8>,
1110 I::Token: Clone + AsChar,
1111 I::Slice: AsRef<[u8]>,
1112{
1113 (
1114 parse_scheme,
1115 b':',
1116 parse_hier_part,
1117 opt((b'?', parse_query)),
1118 opt((b'#', parse_fragment)),
1119 )
1120 .void()
1121 .parse_next(input)
1122}
1123
1124#[inline]
1146pub fn parse_uri_reference<I>(input: &mut I) -> ModalResult<()>
1147where
1148 I: Stream + StreamIsPartial + Compare<u8>,
1149 I::Token: Clone + AsChar,
1150 I::Slice: AsRef<[u8]>,
1151{
1152 alt((parse_uri, parse_relative_ref)).parse_next(input)
1153}
1154
1155#[inline]
1165pub fn parse_h16<I>(input: &mut I) -> ModalResult<()>
1166where
1167 I: Stream + StreamIsPartial,
1168 I::Token: Clone + AsChar,
1169{
1170 take_while(1..=4, is_hexdig).void().parse_next(input)
1171}
1172
1173#[inline]
1183pub fn parse_ls32<I>(input: &mut I) -> ModalResult<()>
1184where
1185 I: Stream + StreamIsPartial + Compare<u8>,
1186 I::Token: Clone + AsChar,
1187 I::Slice: AsRef<[u8]>,
1188{
1189 alt((parse_ipv4address, (parse_h16, b':', parse_h16).void())).parse_next(input)
1190}
1191
1192#[inline]
1193fn parse_unreserved_item<I>(input: &mut I) -> ModalResult<()>
1194where
1195 I: Stream + StreamIsPartial,
1196 I::Token: Clone + AsChar,
1197{
1198 take_while(1..=1, is_unreserved).void().parse_next(input)
1199}
1200
1201#[inline]
1202fn parse_sub_delim_item<I>(input: &mut I) -> ModalResult<()>
1203where
1204 I: Stream + StreamIsPartial,
1205 I::Token: Clone + AsChar,
1206{
1207 take_while(1..=1, is_sub_delim).void().parse_next(input)
1208}
1209
1210#[inline]
1211fn parse_reg_name_item<I>(input: &mut I) -> ModalResult<()>
1212where
1213 I: Stream + StreamIsPartial + Compare<u8>,
1214 I::Token: Clone + AsChar,
1215{
1216 alt((
1217 parse_pct_encoded,
1218 parse_unreserved_item,
1219 parse_sub_delim_item,
1220 ))
1221 .parse_next(input)
1222}
1223
1224#[inline]
1225fn parse_reg_name_nz<I>(input: &mut I) -> ModalResult<()>
1226where
1227 I: Stream + StreamIsPartial + Compare<u8>,
1228 I::Token: Clone + AsChar,
1229{
1230 repeat::<_, _, (), _, _>(1.., parse_reg_name_item)
1231 .void()
1232 .parse_next(input)
1233}
1234
1235#[inline]
1236fn parse_userinfo_item<I>(input: &mut I) -> ModalResult<()>
1237where
1238 I: Stream + StreamIsPartial + Compare<u8>,
1239 I::Token: Clone + AsChar,
1240{
1241 alt((
1242 parse_pct_encoded,
1243 parse_unreserved_item,
1244 parse_sub_delim_item,
1245 literal(b':').void(),
1246 ))
1247 .parse_next(input)
1248}
1249
1250#[inline]
1251fn parse_userinfo_nz<I>(input: &mut I) -> ModalResult<()>
1252where
1253 I: Stream + StreamIsPartial + Compare<u8>,
1254 I::Token: Clone + AsChar,
1255{
1256 repeat::<_, _, (), _, _>(1.., parse_userinfo_item)
1257 .void()
1258 .parse_next(input)
1259}
1260
1261#[inline]
1262fn parse_pchar_item<I>(input: &mut I) -> ModalResult<()>
1263where
1264 I: Stream + StreamIsPartial + Compare<u8>,
1265 I::Token: Clone + AsChar,
1266{
1267 alt((
1268 parse_pct_encoded,
1269 parse_unreserved_item,
1270 parse_sub_delim_item,
1271 one_of([b':', b'@']).void(),
1272 ))
1273 .parse_next(input)
1274}
1275
1276#[inline]
1277fn parse_pchar_nc_item<I>(input: &mut I) -> ModalResult<()>
1278where
1279 I: Stream + StreamIsPartial + Compare<u8>,
1280 I::Token: Clone + AsChar,
1281{
1282 alt((
1283 parse_pct_encoded,
1284 parse_unreserved_item,
1285 parse_sub_delim_item,
1286 literal(b'@').void(),
1287 ))
1288 .parse_next(input)
1289}
1290
1291#[inline]
1292fn parse_query_or_fragment_item<I>(input: &mut I) -> ModalResult<()>
1293where
1294 I: Stream + StreamIsPartial + Compare<u8>,
1295 I::Token: Clone + AsChar,
1296{
1297 alt((parse_pchar_item, one_of([b'/', b'?']).void())).parse_next(input)
1298}
1299
1300#[inline]
1301fn parse_ipvfuture_item<I>(input: &mut I) -> ModalResult<()>
1302where
1303 I: Stream + StreamIsPartial,
1304 I::Token: Clone + AsChar,
1305{
1306 take_while(1..=1, is_ipvfuture_char)
1307 .void()
1308 .parse_next(input)
1309}
1310
1311#[inline]
1312fn parse_generic_authority<I>(input: &mut I) -> ModalResult<()>
1313where
1314 I: Stream + StreamIsPartial + Compare<u8>,
1315 I::Token: Clone + AsChar,
1316 I::Slice: AsRef<[u8]>,
1317{
1318 (
1319 opt(terminated(parse_userinfo, b'@')),
1320 parse_host,
1321 opt((b':', parse_port)),
1322 )
1323 .void()
1324 .parse_next(input)
1325}
1326
1327#[inline]
1328fn is_ipv4address_char(char: impl AsChar) -> bool {
1329 matches!(char.as_char(), '0'..='9' | '.')
1330}
1331
1332#[inline]
1333fn is_ipv6address_char(char: impl AsChar) -> bool {
1334 let char = char.as_char();
1335
1336 char.is_ascii_hexdigit() || matches!(char, ':' | '.')
1337}
1338
1339#[inline]
1340fn parse_ascii<T>(bytes: &[u8]) -> Option<T>
1341where
1342 T: str::FromStr,
1343{
1344 str::from_utf8(bytes).ok()?.parse().ok()
1345}
1346
1347#[inline]
1348fn is_dec_octet(bytes: &[u8]) -> bool {
1349 matches!(bytes, [b'0'..=b'9'])
1350 || matches!(bytes, [b'1'..=b'9', b'0'..=b'9'])
1351 || matches!(bytes, [b'1', b'0'..=b'9', b'0'..=b'9'])
1352 || matches!(bytes, [b'2', b'0'..=b'4', b'0'..=b'9'])
1353 || matches!(bytes, [b'2', b'5', b'0'..=b'5'])
1354}
1355
1356#[cfg(test)]
1357mod tests {
1358 use winnow::{
1359 BStr, Partial,
1360 error::{ErrMode, Needed},
1361 };
1362
1363 use super::*;
1364
1365 macro_rules! assert_backtrack {
1366 ($parser:expr, $input:expr $(,)?) => {
1367 assert!(
1368 matches!(
1369 $parser.parse_peek(BStr::new($input)),
1370 Err(ErrMode::Backtrack(_))
1371 ),
1372 "assertion failed: parser did not backtrack for input {:?}: {:?}",
1373 $input,
1374 $parser.parse_peek(BStr::new($input)),
1375 );
1376 };
1377 }
1378
1379 macro_rules! assert_ok_remaining {
1380 ($parser:expr, $input:expr, $remaining:expr $(,)?) => {
1381 assert!(
1382 matches!(
1383 $parser.parse_peek(BStr::new($input)),
1384 Ok((remaining, _)) if remaining == BStr::new($remaining)
1385 ),
1386 "assertion failed: parser did not leave expected remaining input {:?}: {:?}",
1387 $remaining,
1388 $parser.parse_peek(BStr::new($input)),
1389 );
1390 };
1391 }
1392
1393 macro_rules! assert_partial_incomplete {
1394 ($parser:expr, $input:expr, $needed:expr $(,)?) => {
1395 assert_eq!(
1396 $parser.parse_peek(Partial::new(BStr::new($input))),
1397 Err(ErrMode::Incomplete($needed)),
1398 );
1399 };
1400 }
1401
1402 #[test]
1403 fn validates_char_groups() {
1404 assert!(is_unreserved('a'));
1405 assert!(!is_unreserved('/'));
1406 assert!(is_sub_delim('!'));
1407 assert!(!is_sub_delim('/'));
1408 assert!(is_gen_delim('/'));
1409 assert!(is_reserved('/'));
1410 assert!(is_hexdig('F'));
1411 assert!(!is_hexdig('g'));
1412 assert!(is_pchar('%'));
1413 assert!(is_pchar_nc('@'));
1414 assert!(!is_pchar_nc(':'));
1415 assert!(is_reg_name_char('%'));
1416 assert!(is_userinfo_char(':'));
1417 assert!(is_ipvfuture_char(':'));
1418 assert!(is_ip_literal_char('.'));
1419 }
1420
1421 #[test]
1422 fn validates_ip_literal_bodies() {
1423 assert!(!is_ip_literal_body(b""));
1424 assert!(!is_ip_literal_body(b"localhost"));
1425 assert!(!is_ip_literal_body(b"v1"));
1426 assert!(is_ip_literal_body(b"::1"));
1427 assert!(is_ip_literal_body(b"2001:db8::1"));
1428 assert!(is_ip_literal_body(b"v1.future-host"));
1429 }
1430
1431 #[test]
1432 fn parses_pct_encoded() {
1433 assert_backtrack!(parse_pct_encoded, b"");
1434 assert_backtrack!(parse_pct_encoded, b"%");
1435 assert_backtrack!(parse_pct_encoded, b"%2G");
1436 assert_partial_incomplete!(parse_pct_encoded, b"%2", Needed::new(1));
1437
1438 assert_ok_remaining!(parse_pct_encoded, b"%20rest", b"rest");
1439 }
1440
1441 #[test]
1442 fn parses_userinfo_and_reg_name() {
1443 assert_ok_remaining!(parse_userinfo, b"", b"");
1444 assert_ok_remaining!(parse_userinfo, b"user:pass@", b"@");
1445 assert_ok_remaining!(parse_reg_name, b"", b"");
1446 assert_ok_remaining!(parse_reg_name, b"example.com:80", b":80");
1447 assert_backtrack!(parse_uri_host, b"");
1448 assert_ok_remaining!(parse_uri_host, b"example.com:80", b":80");
1449 }
1450
1451 #[test]
1452 fn parses_ipv4_address_and_dec_octet() {
1453 assert_ok_remaining!(parse_dec_octet, b"0.", b".");
1454 assert_ok_remaining!(parse_dec_octet, b"255.", b".");
1455 assert_backtrack!(parse_dec_octet, b"256");
1456 assert_backtrack!(parse_dec_octet, b"01");
1457
1458 assert_ok_remaining!(parse_ipv4address, b"127.0.0.1:80", b":80");
1459 assert_backtrack!(parse_ipv4address, b"256.0.0.1");
1460 assert_backtrack!(parse_ipv4address, b"127.0.0");
1461 }
1462
1463 #[test]
1464 fn parses_ip_literal_variants() {
1465 assert_ok_remaining!(parse_h16, b"abcd:", b":");
1466 assert_ok_remaining!(parse_ls32, b"abcd:ef01", b"");
1467 assert_ok_remaining!(parse_ls32, b"192.0.2.1", b"");
1468
1469 assert_ok_remaining!(parse_ipv6address, b"2001:db8::1]", b"]");
1470 assert_ok_remaining!(parse_ipvfuture, b"vF.token:part]", b"]");
1471 assert_backtrack!(parse_ipvfuture, b"v.");
1472 assert_ok_remaining!(parse_ip_literal, b"[::1]:443", b":443");
1473 assert_ok_remaining!(parse_ip_literal, b"[vF.token:part]/", b"/");
1474 assert_backtrack!(parse_ip_literal, b"[localhost]");
1475 }
1476
1477 #[test]
1478 fn parses_host_and_authority() {
1479 assert_ok_remaining!(parse_host, b":80", b":80");
1480 assert_ok_remaining!(parse_host, b"127.0.0.1:80", b":80");
1481 assert_ok_remaining!(parse_host, b"[::1]:80", b":80");
1482
1483 assert_backtrack!(parse_authority, b"");
1484 assert_ok_remaining!(parse_authority, b"user:pass@example.com:443/path", b"/path");
1485 assert_ok_remaining!(parse_authority, b"[::1]/path", b"/path");
1486 }
1487
1488 #[test]
1489 fn parses_path_variants() {
1490 assert_ok_remaining!(parse_segment, b":@", b"");
1491 assert_backtrack!(parse_segment_nz, b"");
1492 assert_backtrack!(parse_segment_nz_nc, b":");
1493 assert_ok_remaining!(parse_segment_nz_nc, b"abc@/rest", b"/rest");
1494
1495 assert_ok_remaining!(parse_path_abempty, b"/a/b?x=1", b"?x=1");
1496 assert_ok_remaining!(parse_path_absolute, b"/a/b?x=1", b"?x=1");
1497 assert_backtrack!(parse_path_absolute, b"foo/bar");
1498 assert_ok_remaining!(parse_path_noscheme, b"docs/latest?q=1", b"?q=1");
1499 assert_ok_remaining!(parse_path_noscheme, b"urn:ietf", b":ietf");
1500 assert_ok_remaining!(parse_path_rootless, b"urn:ietf:rfc:3986#frag", b"#frag");
1501 assert_ok_remaining!(parse_path_empty, b"?q=1", b"?q=1");
1502 assert_ok_remaining!(parse_path, b"/foo%20bar?baz", b"?baz");
1503 assert_ok_remaining!(parse_path, b"/foo%", b"%");
1504 }
1505
1506 #[test]
1507 fn parses_query_and_fragment() {
1508 assert_ok_remaining!(parse_query, b"foo=bar/baz?x#frag", b"#frag");
1509 assert_ok_remaining!(parse_query, b"foo%", b"%");
1510 assert_ok_remaining!(parse_fragment, b"section-2/part?a", b"");
1511 }
1512
1513 #[test]
1514 fn parses_hier_part_and_relative_part() {
1515 assert_ok_remaining!(parse_hier_part, b"//example.com/a?b", b"?b");
1516 assert_ok_remaining!(parse_hier_part, b"/a/b?c", b"?c");
1517 assert_ok_remaining!(parse_hier_part, b"mailto:John.Doe@example.com", b"");
1518
1519 assert_ok_remaining!(parse_relative_part, b"//example.com/a?b", b"?b");
1520 assert_ok_remaining!(parse_relative_part, b"/a/b?b", b"?b");
1521 assert_ok_remaining!(parse_relative_part, b"guides/setup#frag", b"#frag");
1522 }
1523
1524 #[test]
1525 fn parses_uri_forms() {
1526 assert_partial_incomplete!(parse_scheme, b"", Needed::new(1));
1527
1528 assert_ok_remaining!(
1529 parse_absolute_uri,
1530 b"mailto:John.Doe@example.com?subject=Hi#frag",
1531 b"#frag"
1532 );
1533 assert_ok_remaining!(parse_relative_ref, b"../images/logo.svg?v=2#hero", b"");
1534 assert_ok_remaining!(parse_uri, b"https://example.com/a/b?q=1#frag", b"");
1535 assert_ok_remaining!(parse_uri_reference, b"//example.com/path", b"");
1536 assert_ok_remaining!(parse_uri_reference, b"urn:ietf:rfc:3986", b"");
1537
1538 assert_backtrack!(parse_uri, b"//example.com/path");
1539 assert_ok_remaining!(parse_uri_reference, b"http://example.com/%", b"%");
1540 }
1541}