1#![forbid(unsafe_code)]
51#![cfg_attr(not(test), no_std)]
52#![cfg_attr(feature = "unstable", feature(error_in_core))]
53
54use core::fmt;
55
56#[derive(Debug, PartialEq, Eq)]
57pub enum ParseError {
58 InvalidConnect,
59 EmptyInput,
60 Whitespace,
61 NoHost,
62 Invalid,
63}
64
65impl fmt::Display for ParseError {
66 #[cfg(not(tarpaulin_include))]
67 #[cfg_attr(feature = "_nopanic", no_panic::no_panic)]
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 match self {
70 ParseError::InvalidConnect => {
71 write!(f, "CONNECT requests can only contain \"hostname:port\"")
72 }
73 ParseError::EmptyInput => write!(f, "Empty input"),
74 ParseError::Whitespace => write!(f, "Whitespace"),
75 ParseError::NoHost => {
76 write!(f, "host must be present if there is a schema")
77 }
78 ParseError::Invalid => write!(f, "Invalid URL"),
79 }
80 }
81}
82
83#[cfg(feature = "unstable")]
84impl core::error::Error for ParseError {}
85
86#[derive(Debug, PartialEq, Eq)]
87enum State {
88 SchemaSlash,
89 SchemaSlashSlash,
90 ServerStart,
91 QueryStringStart,
92 FragmentStart,
93 Schema,
94 ServerWithAt,
95 Server,
96 Path,
97 QueryString,
98 Fragment,
99}
100
101#[derive(Debug, PartialEq, Eq)]
102enum UrlFields {
103 Schema,
104 Host,
105 Path,
106 Query,
107 Fragment,
108}
109
110#[derive(Debug, PartialEq, Eq)]
111pub struct Url<'a> {
113 pub schema: Option<&'a str>,
114 pub host: Option<&'a str>,
115 pub port: Option<&'a str>,
116 pub path: Option<&'a str>,
117 pub query: Option<&'a str>,
118 pub fragment: Option<&'a str>,
119 pub userinfo: Option<&'a str>,
120}
121
122impl<'a> Url<'a> {
123 #[cfg_attr(feature = "_nopanic", no_panic::no_panic)]
124 pub fn parse(buf: &'a str) -> Result<Url<'a>, ParseError> {
139 parse_url(buf, false)
140 }
141
142 pub fn parse_connect(buf: &'a str) -> Result<Url<'a>, ParseError> {
147 parse_url(buf, true)
148 }
149}
150
151#[cfg_attr(feature = "_nopanic", no_panic::no_panic)]
152fn parse_url(buf: &str, is_connect: bool) -> Result<Url, ParseError> {
153 if buf.is_empty() {
154 return Err(ParseError::EmptyInput);
155 }
156
157 let mut url = Url {
158 schema: None,
159 host: None,
160 port: None,
161 path: None,
162 query: None,
163 fragment: None,
164 userinfo: None,
165 };
166
167 let mut state = State::ServerStart;
168 let mut old_uf: Option<UrlFields> = None;
169 let mut found_at = false;
170
171 let mut len = 0;
172 let mut off = 0;
173
174 for (i, p) in buf.chars().enumerate() {
175 let uf: UrlFields;
176
177 if p.is_whitespace() {
178 return Err(ParseError::Whitespace);
179 }
180
181 if i == 0 && !is_connect {
182 state = parse_url_start(p)?;
183 } else {
184 state = parse_url_char(state, p)?;
185 }
186
187 match state {
189 State::SchemaSlash
191 | State::SchemaSlashSlash
192 | State::ServerStart
193 | State::QueryStringStart
194 | State::FragmentStart => {
195 continue;
196 }
197 State::Schema => {
198 uf = UrlFields::Schema;
199 }
200 State::ServerWithAt => {
201 found_at = true;
202 uf = UrlFields::Host;
203 }
204 State::Server => {
205 uf = UrlFields::Host;
206 }
207 State::Path => {
208 uf = UrlFields::Path;
209 }
210 State::QueryString => {
211 uf = UrlFields::Query;
212 }
213 State::Fragment => {
214 uf = UrlFields::Fragment;
215 }
216 }
217
218 off += 1;
219 len += 1;
220
221 if old_uf.as_ref() == Some(&uf) {
223 continue;
224 }
225
226 if let Some(old_uf) = old_uf {
227 let value =
228 Some(buf.get(off - len..off).ok_or(ParseError::Invalid)?);
229 set_url_field(&old_uf, &mut url, value)
230 }
231 old_uf = Some(uf);
232 len = 0;
233 off = i;
234 }
235
236 if let Some(old_uf) = old_uf {
237 let value =
238 Some(buf.get(off - len..off + 1).ok_or(ParseError::Invalid)?);
239 set_url_field(&old_uf, &mut url, value)
240 }
241
242 if url.schema.is_some() && url.host.is_none() {
245 return Err(ParseError::NoHost);
246 }
247
248 if let Some(host_buf) = url.host.take() {
249 url.host = None;
250
251 let mut host_state = if found_at {
252 HttpHostState::UserinfoStart
253 } else {
254 HttpHostState::HostStart
255 };
256
257 let mut off = 0;
258 let mut len = 0;
259
260 for (i, p) in host_buf.chars().enumerate() {
261 let new_host_state = parse_host_char(&host_state, p)?;
262
263 match new_host_state {
264 HttpHostState::Host => {
265 if host_state != HttpHostState::Host {
266 off = i;
267 len = 0;
268 }
269 len += 1;
270 url.host = Some(
271 host_buf
272 .get(off..off + len)
273 .ok_or(ParseError::Invalid)?,
274 );
275 }
276 HttpHostState::Hostv6 => {
277 if host_state != HttpHostState::Hostv6 {
278 off = i;
279 }
280 len += 1;
281 url.host = Some(
282 host_buf
283 .get(off..off + len)
284 .ok_or(ParseError::Invalid)?,
285 );
286 }
287 HttpHostState::Hostv6ZoneStart | HttpHostState::Hostv6Zone => {
288 len += 1;
289 url.host = Some(
290 host_buf
291 .get(off..off + len)
292 .ok_or(ParseError::Invalid)?,
293 );
294 }
295 HttpHostState::HostPort => {
296 if host_state != HttpHostState::HostPort {
297 off = i;
298 len = 0;
299 }
300 len += 1;
301 url.port = Some(
302 host_buf
303 .get(off..off + len)
304 .ok_or(ParseError::Invalid)?,
305 );
306 }
307 HttpHostState::Userinfo => {
308 if host_state != HttpHostState::Userinfo {
309 off = i;
310 len = 0;
311 }
312 len += 1;
313 url.userinfo = Some(
314 host_buf
315 .get(off..off + len)
316 .ok_or(ParseError::Invalid)?,
317 );
318 }
319 _ => {}
320 }
321 host_state = new_host_state;
322 }
323
324 match host_state {
326 HttpHostState::HostStart
327 | HttpHostState::Hostv6Start
328 | HttpHostState::Hostv6
329 | HttpHostState::Hostv6ZoneStart
330 | HttpHostState::Hostv6Zone
331 | HttpHostState::HostPortStart
332 | HttpHostState::Userinfo
333 | HttpHostState::UserinfoStart => {
334 return Err(ParseError::Invalid);
335 }
336 _ => {}
337 }
338 }
339
340 if is_connect
341 && (url.schema.is_some()
342 || url.path.is_some()
343 || url.query.is_some()
344 || url.fragment.is_some()
345 || url.userinfo.is_some())
346 {
347 return Err(ParseError::InvalidConnect);
348 }
349
350 Ok(url)
351}
352
353#[cfg_attr(feature = "_nopanic", no_panic::no_panic)]
354fn set_url_field<'a>(
355 uf: &UrlFields,
356 mut url: &mut Url<'a>,
357 value: Option<&'a str>,
358) {
359 match uf {
360 UrlFields::Schema => url.schema = value,
361 UrlFields::Host => url.host = value,
362 UrlFields::Path => url.path = value,
363 UrlFields::Query => url.query = value,
364 UrlFields::Fragment => url.fragment = value,
365 };
366}
367
368#[cfg_attr(feature = "_nopanic", no_panic::no_panic)]
369fn is_mark(c: char) -> bool {
370 c == '-'
371 || c == '_'
372 || c == '.'
373 || c == '!'
374 || c == '~'
375 || c == '*'
376 || c == '\''
377 || c == '('
378 || c == ')'
379}
380
381#[cfg_attr(feature = "_nopanic", no_panic::no_panic)]
382fn is_userinfo_char(c: char) -> bool {
383 c.is_ascii_alphanumeric()
384 || is_mark(c)
385 || c == '%'
386 || c == ';'
387 || c == ':'
388 || c == '&'
389 || c == '='
390 || c == '+'
391 || c == '$'
392 || c == ','
393}
394
395#[cfg_attr(feature = "_nopanic", no_panic::no_panic)]
396fn is_url_char(c: char) -> bool {
397 !matches!(c, '\0'..='\u{001F}' | '#' | '?' | '\x7F')
398}
399
400#[cfg_attr(feature = "_nopanic", no_panic::no_panic)]
401fn parse_url_start(ch: char) -> Result<State, ParseError> {
402 if ch == '/' || ch == '*' {
405 return Ok(State::Path);
406 }
407
408 if ch.is_ascii_alphabetic() {
409 return Ok(State::Schema);
410 }
411
412 Err(ParseError::Invalid)
413}
414
415#[cfg_attr(feature = "_nopanic", no_panic::no_panic)]
416fn parse_url_char(state: State, ch: char) -> Result<State, ParseError> {
417 match state {
418 State::Schema => {
419 if ch.is_ascii_alphabetic() {
420 return Ok(state);
421 }
422
423 if ch == ':' {
424 return Ok(State::SchemaSlash);
425 }
426 }
427 State::SchemaSlash => {
428 if ch == '/' {
429 return Ok(State::SchemaSlashSlash);
430 }
431 }
432 State::SchemaSlashSlash => {
433 if ch == '/' {
434 return Ok(State::ServerStart);
435 }
436 }
437 State::ServerWithAt | State::ServerStart | State::Server => {
438 if state == State::ServerWithAt && ch == '@' {
439 return Err(ParseError::Invalid);
440 }
441
442 if ch == '/' {
443 return Ok(State::Path);
444 }
445
446 if ch == '?' {
447 return Ok(State::QueryStringStart);
448 }
449
450 if ch == '@' {
451 return Ok(State::ServerWithAt);
452 }
453
454 if is_userinfo_char(ch) || ch == '[' || ch == ']' {
455 return Ok(State::Server);
456 }
457 }
458 State::Path => {
459 if is_url_char(ch) {
460 return Ok(state);
461 }
462
463 if ch == '?' {
464 return Ok(State::QueryStringStart);
465 }
466
467 if ch == '#' {
468 return Ok(State::FragmentStart);
469 }
470 }
471 State::QueryStringStart | State::QueryString => {
472 if is_url_char(ch) {
473 return Ok(State::QueryString);
474 }
475
476 if ch == '?' {
477 return Ok(State::QueryString);
479 }
480
481 if ch == '#' {
482 return Ok(State::FragmentStart);
483 }
484 }
485 State::FragmentStart => {
486 if is_url_char(ch) {
487 return Ok(State::Fragment);
488 }
489 }
490 State::Fragment => {
491 if is_url_char(ch) {
492 return Ok(state);
493 }
494 }
495 };
496
497 Err(ParseError::Invalid)
499}
500
501#[derive(Debug, PartialEq, Eq)]
502enum HttpHostState {
503 UserinfoStart,
504 Userinfo,
505 HostStart,
506 Hostv6Start,
507 Host,
508 Hostv6,
509 Hostv6End,
510 Hostv6ZoneStart,
511 Hostv6Zone,
512 HostPortStart,
513 HostPort,
514}
515
516#[cfg_attr(feature = "_nopanic", no_panic::no_panic)]
517fn is_host_char(c: char) -> bool {
518 c.is_ascii_alphanumeric() || c == '.' || c == '-'
519}
520
521#[cfg_attr(feature = "_nopanic", no_panic::no_panic)]
522fn parse_host_char(
523 s: &HttpHostState,
524 ch: char,
525) -> Result<HttpHostState, ParseError> {
526 match s {
527 HttpHostState::Userinfo | HttpHostState::UserinfoStart => {
528 if ch == '@' {
529 return Ok(HttpHostState::HostStart);
530 }
531
532 if is_userinfo_char(ch) {
533 return Ok(HttpHostState::Userinfo);
534 }
535 }
536 HttpHostState::HostStart => {
537 if ch == '[' {
538 return Ok(HttpHostState::Hostv6Start);
539 }
540
541 if is_host_char(ch) {
542 return Ok(HttpHostState::Host);
543 }
544 }
545 HttpHostState::Host => {
546 if is_host_char(ch) {
547 return Ok(HttpHostState::Host);
548 }
549 if ch == ':' {
550 return Ok(HttpHostState::HostPortStart);
551 }
552 }
553 HttpHostState::Hostv6End => {
554 if ch == ':' {
555 return Ok(HttpHostState::HostPortStart);
556 }
557 }
558 HttpHostState::Hostv6 | HttpHostState::Hostv6Start => {
559 if s == &HttpHostState::Hostv6 && ch == ']' {
560 return Ok(HttpHostState::Hostv6End);
561 }
562
563 if ch.is_ascii_hexdigit() || ch == ':' || ch == '.' {
564 return Ok(HttpHostState::Hostv6);
565 }
566
567 if s == &HttpHostState::Hostv6 && ch == '%' {
568 return Ok(HttpHostState::Hostv6ZoneStart);
569 }
570 }
571 HttpHostState::Hostv6Zone | HttpHostState::Hostv6ZoneStart => {
572 if s == &HttpHostState::Hostv6Zone && ch == ']' {
573 return Ok(HttpHostState::Hostv6End);
574 }
575
576 if ch.is_ascii_alphanumeric()
578 || ch == '%'
579 || ch == '.'
580 || ch == '-'
581 || ch == '_'
582 || ch == '~'
583 {
584 return Ok(HttpHostState::Hostv6Zone);
585 }
586 }
587 HttpHostState::HostPort | HttpHostState::HostPortStart => {
588 if ch.is_ascii_digit() {
589 return Ok(HttpHostState::HostPort);
590 }
591 }
592 }
593
594 Err(ParseError::Invalid)
595}
596
597#[cfg(test)]
598mod tests {
599 use super::*;
600 use alloc_counter::{no_alloc, AllocCounterSystem};
601
602 #[global_allocator]
603 static A: AllocCounterSystem = AllocCounterSystem;
604
605 #[test]
606 #[no_alloc(forbid)]
607 #[should_panic]
608 fn test_no_alloc() {
610 let _ = std::boxed::Box::new(8);
611 }
612
613 #[test]
614 #[no_alloc(forbid)]
615 fn test_proxy_request() {
616 let result = Url::parse("http://hostname/").unwrap();
617 let expected = Url {
618 schema: Some("http"),
619 host: Some("hostname"),
620 port: None,
621 path: Some("/"),
622 query: None,
623 fragment: None,
624 userinfo: None,
625 };
626 assert_eq!(expected, result);
627 }
628
629 #[test]
630 #[no_alloc(forbid)]
631 fn test_proxy_request_with_port() {
632 let result = Url::parse("http://hostname:444/").unwrap();
633 let expected = Url {
634 schema: Some("http"),
635 host: Some("hostname"),
636 port: Some("444"),
637 path: Some("/"),
638 query: None,
639 fragment: None,
640 userinfo: None,
641 };
642 assert_eq!(expected, result);
643 }
644
645 #[test]
646 #[no_alloc(forbid)]
647 fn test_connect_request() {
648 let result = Url::parse_connect("hostname:443").unwrap();
649 let expected = Url {
650 schema: None,
651 host: Some("hostname"),
652 port: Some("443"),
653 path: None,
654 query: None,
655 fragment: None,
656 userinfo: None,
657 };
658 assert_eq!(expected, result);
659 }
660
661 #[test]
662 #[no_alloc(forbid)]
663 fn test_proxy_ipv6_request() {
664 let result = Url::parse("http://[1:2::3:4]/").unwrap();
665 let expected = Url {
666 schema: Some("http"),
667 host: Some("1:2::3:4"),
668 port: None,
669 path: Some("/"),
670 query: None,
671 fragment: None,
672 userinfo: None,
673 };
674 assert_eq!(expected, result);
675 }
676
677 #[test]
678 #[no_alloc(forbid)]
679 fn test_proxy_ipv6_request_with_port() {
680 let result = Url::parse("http://[1:2::3:4]:67/").unwrap();
681 let expected = Url {
682 schema: Some("http"),
683 host: Some("1:2::3:4"),
684 port: Some("67"),
685 path: Some("/"),
686 query: None,
687 fragment: None,
688 userinfo: None,
689 };
690 assert_eq!(expected, result);
691 }
692
693 #[test]
694 #[no_alloc(forbid)]
695 fn test_connect_ipv6_address() {
696 let result = Url::parse_connect("[1:2::3:4]:443").unwrap();
697 let expected = Url {
698 schema: None,
699 host: Some("1:2::3:4"),
700 port: Some("443"),
701 path: None,
702 query: None,
703 fragment: None,
704 userinfo: None,
705 };
706 assert_eq!(expected, result);
707 }
708
709 #[test]
710 #[no_alloc(forbid)]
711 fn test_ipv4_in_ipv6_address() {
712 let result =
713 Url::parse("http://[2001:0000:0000:0000:0000:0000:1.9.1.1]/")
714 .unwrap();
715 let expected = Url {
716 schema: Some("http"),
717 host: Some("2001:0000:0000:0000:0000:0000:1.9.1.1"),
718 port: None,
719 path: Some("/"),
720 query: None,
721 fragment: None,
722 userinfo: None,
723 };
724 assert_eq!(expected, result);
725 }
726
727 #[test]
728 #[no_alloc(forbid)]
729 fn test_extra_question_in_query_string() {
730 let result = Url::parse(
731 "http://a.tbcdn.cn/p/fp/2010c/??fp-header-min\
732 .css,fp-base-min.css,fp-channel-min.css,fp-product-min.css,\
733 fp-mall-min.css,fp-category-min.css,fp-sub-min.css,fp-gdp4p-min\
734 .css,fp-css3-min.css,fp-misc-min.css?t=20101022.css",
735 )
736 .unwrap();
737 let expected = Url {
738 schema: Some("http"),
739 host: Some("a.tbcdn.cn"),
740 port: None,
741 path: Some("/p/fp/2010c/"),
742 query: Some(
743 "?fp-header-min.css,fp-base-min.css,fp-channel-min.css,\
744 fp-product-min.css,fp-mall-min.css,fp-category-min.css,\
745 fp-sub-min.css,fp-gdp4p-min.css,fp-css3-min.css,\
746 fp-misc-min.css?t=20101022.css",
747 ),
748 fragment: None,
749 userinfo: None,
750 };
751 assert_eq!(expected, result);
752 }
753
754 #[test]
755 #[no_alloc(forbid)]
756 fn test_space_url_encoded() {
757 let result = Url::parse("/toto.html?toto=a%20b").unwrap();
758 let expected = Url {
759 schema: None,
760 host: None,
761 port: None,
762 path: Some("/toto.html"),
763 query: Some("toto=a%20b"),
764 fragment: None,
765 userinfo: None,
766 };
767 assert_eq!(expected, result);
768 }
769
770 #[test]
771 #[no_alloc(forbid)]
772 fn test_url_fragment() {
773 let result = Url::parse("/toto.html#titi").unwrap();
774 let expected = Url {
775 schema: None,
776 host: None,
777 port: None,
778 path: Some("/toto.html"),
779 query: None,
780 fragment: Some("titi"),
781 userinfo: None,
782 };
783 assert_eq!(expected, result);
784 }
785
786 #[test]
787 #[no_alloc(forbid)]
788 fn test_complex_url_fragment() {
789 let result = Url::parse(
790 "http://www.webmasterworld.com/r.cgi?f=21&d=8405\
791 &url=http://www.example.com/index.html?foo=bar&hello=world#midpage",
792 )
793 .unwrap();
794 let expected = Url {
795 schema: Some("http"),
796 host: Some("www.webmasterworld.com"),
797 port: None,
798 path: Some("/r.cgi"),
799 query: Some(
800 "f=21&d=8405&url=http://www.example.com/index.html\
801 ?foo=bar&hello=world",
802 ),
803 fragment: Some("midpage"),
804 userinfo: None,
805 };
806 assert_eq!(expected, result);
807 }
808
809 #[test]
810 #[no_alloc(forbid)]
811 fn test_complex_url_from_node_js_url_parser_doc() {
812 let result =
813 Url::parse("http://host.com:8080/p/a/t/h?query=string#hash")
814 .unwrap();
815 let expected = Url {
816 schema: Some("http"),
817 host: Some("host.com"),
818 port: Some("8080"),
819 path: Some("/p/a/t/h"),
820 query: Some("query=string"),
821 fragment: Some("hash"),
822 userinfo: None,
823 };
824 assert_eq!(expected, result);
825 }
826
827 #[test]
828 #[no_alloc(forbid)]
829 fn test_complex_url_with_basic_auth_from_node_js_url_parser_doc() {
830 let result =
831 Url::parse("http://a:b@host.com:8080/p/a/t/h?query=string#hash")
832 .unwrap();
833 let expected = Url {
834 schema: Some("http"),
835 host: Some("host.com"),
836 port: Some("8080"),
837 path: Some("/p/a/t/h"),
838 query: Some("query=string"),
839 fragment: Some("hash"),
840 userinfo: Some("a:b"),
841 };
842 assert_eq!(expected, result);
843 }
844
845 #[test]
846 #[no_alloc(forbid)]
847 fn test_proxy_basic_auth_with_space_url_encoded() {
848 let result = Url::parse("http://a%20:b@host.com/").unwrap();
849 let expected = Url {
850 schema: Some("http"),
851 host: Some("host.com"),
852 port: None,
853 path: Some("/"),
854 query: None,
855 fragment: None,
856 userinfo: Some("a%20:b"),
857 };
858 assert_eq!(expected, result);
859 }
860
861 #[test]
862 #[no_alloc(forbid)]
863 fn test_proxy_basic_auth_with_double_colon() {
864 let result = Url::parse("http://a::b@host.com/").unwrap();
865 let expected = Url {
866 schema: Some("http"),
867 host: Some("host.com"),
868 port: None,
869 path: Some("/"),
870 query: None,
871 fragment: None,
872 userinfo: Some("a::b"),
873 };
874 assert_eq!(expected, result);
875 }
876
877 #[test]
878 #[no_alloc(forbid)]
879 fn test_proxy_empty_basic_auth() {
880 let result = Url::parse("http://@hostname/fo").unwrap();
881 let expected = Url {
882 schema: Some("http"),
883 host: Some("hostname"),
884 port: None,
885 path: Some("/fo"),
886 query: None,
887 fragment: None,
888 userinfo: None,
889 };
890 assert_eq!(expected, result);
891 }
892
893 #[test]
894 #[no_alloc(forbid)]
895 fn test_proxy_basic_auth_with_unreservedchars() {
896 let result = Url::parse("http://a!;-_!=+$@host.com/").unwrap();
897 let expected = Url {
898 schema: Some("http"),
899 host: Some("host.com"),
900 port: None,
901 path: Some("/"),
902 query: None,
903 fragment: None,
904 userinfo: Some("a!;-_!=+$"),
905 };
906 assert_eq!(expected, result);
907 }
908
909 #[test]
910 #[no_alloc(forbid)]
911 fn test_ipv6_address_with_zone_id() {
912 let result = Url::parse("http://[fe80::a%25eth0]/").unwrap();
913 let expected = Url {
914 schema: Some("http"),
915 host: Some("fe80::a%25eth0"),
916 port: None,
917 path: Some("/"),
918 query: None,
919 fragment: None,
920 userinfo: None,
921 };
922 assert_eq!(expected, result);
923 }
924
925 #[test]
926 #[no_alloc(forbid)]
927 fn test_ipv6_address_with_zone_id_but_percent_is_not_percent_encoded() {
928 let result = Url::parse("http://[fe80::a%eth0]/").unwrap();
929 let expected = Url {
930 schema: Some("http"),
931 host: Some("fe80::a%eth0"),
932 port: None,
933 path: Some("/"),
934 query: None,
935 fragment: None,
936 userinfo: None,
937 };
938 assert_eq!(expected, result);
939 }
940
941 #[test]
942 #[no_alloc(forbid)]
943 fn test_double_at() {
944 let error = Url::parse("http://a:b@@hostname:443/").unwrap_err();
945 assert_eq!(error, ParseError::Invalid);
946 }
947
948 #[test]
949 #[no_alloc(forbid)]
950 fn test_proxy_empty_host() {
951 let error = Url::parse("http://:443/").unwrap_err();
952 assert_eq!(error, ParseError::Invalid);
953 }
954
955 #[test]
956 #[no_alloc(forbid)]
957 fn test_proxy_empty_port() {
958 let error = Url::parse("http://hostname:/").unwrap_err();
959 assert_eq!(error, ParseError::Invalid);
960 }
961
962 #[test]
963 #[no_alloc(forbid)]
964 fn test_connect_with_basic_auth() {
965 let error = Url::parse_connect("a:b@hostname:443").unwrap_err();
966 assert_eq!(error, ParseError::InvalidConnect);
967 }
968
969 #[test]
970 #[no_alloc(forbid)]
971 fn test_connect_empty_host() {
972 let error = Url::parse_connect(":443").unwrap_err();
973 assert_eq!(error, ParseError::Invalid);
974 }
975
976 #[test]
977 #[no_alloc(forbid)]
978 fn test_connect_empty_port() {
979 let error = Url::parse_connect("hostname:").unwrap_err();
980 assert_eq!(error, ParseError::Invalid);
981 }
982
983 #[test]
984 #[no_alloc(forbid)]
985 fn test_connect_with_extra_bits() {
986 let error = Url::parse_connect("hostname:443/").unwrap_err();
987 assert_eq!(error, ParseError::InvalidConnect);
988 }
989
990 #[test]
991 #[no_alloc(forbid)]
992 fn test_space_in_url() {
993 let error = Url::parse("/foo bar/").unwrap_err();
994 assert_eq!(error, ParseError::Whitespace);
995 }
996
997 #[test]
998 #[no_alloc(forbid)]
999 fn test_carriage_return_in_url() {
1000 let error = Url::parse("/foo\rbar/").unwrap_err();
1001 assert_eq!(error, ParseError::Whitespace);
1002 }
1003
1004 #[test]
1005 #[no_alloc(forbid)]
1006 fn test_proxy_double_colon_in_url() {
1007 let error = Url::parse("http://hostname::443/").unwrap_err();
1008 assert_eq!(error, ParseError::Invalid);
1009 }
1010
1011 #[test]
1012 #[no_alloc(forbid)]
1013 fn test_line_feed_in_url() {
1014 let error = Url::parse("/foo\nbar/").unwrap_err();
1015 assert_eq!(error, ParseError::Whitespace);
1016 }
1017
1018 #[test]
1019 #[no_alloc(forbid)]
1020 fn test_proxy_line_feed_in_hostname() {
1021 let error = Url::parse("http://host\name/fo").unwrap_err();
1022 assert_eq!(error, ParseError::Whitespace);
1023 }
1024
1025 #[test]
1026 #[no_alloc(forbid)]
1027 fn test_proxy_percentage_in_hostname() {
1028 let error = Url::parse("http://host%name/fo").unwrap_err();
1029 assert_eq!(error, ParseError::Invalid);
1030 }
1031
1032 #[test]
1033 #[no_alloc(forbid)]
1034 fn test_proxy_semicolon_in_hostname() {
1035 let error = Url::parse("http://host;ame/fo").unwrap_err();
1036 assert_eq!(error, ParseError::Invalid);
1037 }
1038
1039 #[test]
1040 #[no_alloc(forbid)]
1041 fn test_proxy_only_empty_basic_auth() {
1042 let error = Url::parse("http://@/fo").unwrap_err();
1043 assert_eq!(error, ParseError::Invalid);
1044 }
1045
1046 #[test]
1047 #[no_alloc(forbid)]
1048 fn test_proxy_only_basic_auth() {
1049 let error = Url::parse("http://toto@/fo").unwrap_err();
1050 assert_eq!(error, ParseError::Invalid);
1051 }
1052
1053 #[test]
1054 #[no_alloc(forbid)]
1055 fn test_proxy_emtpy_hostname() {
1056 let error = Url::parse("http:///fo").unwrap_err();
1057 assert_eq!(error, ParseError::NoHost);
1058 }
1059
1060 #[test]
1061 #[no_alloc(forbid)]
1062 fn test_proxy_equal_in_url() {
1063 let error = Url::parse("http://host=ame/fo").unwrap_err();
1064 assert_eq!(error, ParseError::Invalid);
1065 }
1066
1067 #[test]
1068 #[no_alloc(forbid)]
1069 fn test_ipv6_address_ending_with() {
1070 let error = Url::parse("http://[fe80::a%]/").unwrap_err();
1071 assert_eq!(error, ParseError::Invalid);
1072 }
1073
1074 #[test]
1075 #[no_alloc(forbid)]
1076 fn test_ipv6_address_with_zone_id_including_bad_character() {
1077 let error = Url::parse("http://[fe80::a%$HOME]/").unwrap_err();
1078 assert_eq!(error, ParseError::Invalid);
1079 }
1080
1081 #[test]
1082 #[no_alloc(forbid)]
1083 fn test_just_ipv6_zone_id() {
1084 let error = Url::parse("http://[%eth0]/").unwrap_err();
1085 assert_eq!(error, ParseError::Invalid);
1086 }
1087
1088 #[test]
1089 #[no_alloc(forbid)]
1090 fn test_empty_url() {
1091 let error = Url::parse("").unwrap_err();
1092 assert_eq!(error, ParseError::EmptyInput);
1093 }
1094
1095 #[test]
1096 #[no_alloc(forbid)]
1097 fn test_full_of_spaces_url() {
1098 let error = Url::parse(" ").unwrap_err();
1099 assert_eq!(error, ParseError::Whitespace);
1100 }
1101
1102 #[test]
1103 #[no_alloc(forbid)]
1104 fn test_tab_in_url() {
1105 let error = Url::parse("/foo bar/").unwrap_err();
1106 assert_eq!(error, ParseError::Whitespace);
1107 }
1108
1109 #[test]
1110 #[no_alloc(forbid)]
1111 fn test_form_feed_in_url() {
1112 let error = Url::parse("/foo\x0cbar/").unwrap_err();
1113 assert_eq!(error, ParseError::Whitespace);
1114 }
1115
1116 #[test]
1117 #[no_alloc(forbid)]
1118 fn test_char_boundary_path_1() {
1119 let error = Url::parse("http://www.example.com/你好你好").unwrap_err();
1120 assert_eq!(error, ParseError::Invalid);
1121 let error =
1122 Url::parse("http://www.example.com/?q=你好#foo").unwrap_err();
1123 assert_eq!(error, ParseError::Invalid);
1124 let error = Url::parse("http://www.example.com/FOO/?foo=%A1%C1")
1125 .unwrap_err();
1126 assert_eq!(error, ParseError::Invalid);
1127 let error = Url::parse("http://www.example.com/FOO/?foo=%A1%C1")
1128 .unwrap_err();
1129 assert_eq!(error, ParseError::Invalid);
1130 }
1131
1132 #[test]
1133 #[no_alloc(forbid)]
1134 fn test_query_after_at() {
1135 let error = Url::parse("http://a:b@?foo").unwrap_err();
1136 assert_eq!(error, ParseError::Invalid);
1137 }
1138
1139 #[test]
1140 #[no_alloc(forbid)]
1141 fn test_invalid_fragment() {
1142 let error = Url::parse("http://hostname#你").unwrap_err();
1143 assert_eq!(error, ParseError::Invalid);
1144 }
1145
1146 #[test]
1147 #[no_alloc(forbid)]
1148 fn test_invalid_start() {
1149 let error = Url::parse("#").unwrap_err();
1150 assert_eq!(error, ParseError::Invalid);
1151 }
1152}