1use std::any::Any;
2use std::borrow::{Borrow, Cow};
3use std::ops::{Add, AddAssign, Div};
4use std::str::FromStr;
5
6use ntex_bytes::{ByteString, Bytes, BytesMut};
7use simdutf8::compat::{Utf8Error, from_utf8};
8
9use crate::authority::{self, Authority, Port, UserInfo};
10use crate::error::{ErrorKind, InvalidUrl};
11use crate::path::{Path, PathAndQuery};
12use crate::query::{self, Fragment, Query, split_pair};
13use crate::quoting::{Component, quote, requote, unquote};
14use crate::{chars::lowercase, host::normalize_host, parse, scheme::Scheme};
15
16#[derive(Clone)]
48pub struct Url {
49 data: ByteString,
50 scheme_end: u16,
52 auth_start: u16,
54 path_start: u16,
55 path_end: u16,
56 query_end: u16,
57 host_start: u16,
59 host_end: u16,
60}
61
62#[derive(Copy, Clone, Default)]
64pub(crate) struct Components<'a> {
65 pub(crate) scheme: Option<&'a str>,
66 pub(crate) authority: Option<&'a str>,
67 pub(crate) path: &'a str,
68 pub(crate) query: Option<&'a str>,
69 pub(crate) fragment: Option<&'a str>,
70}
71
72pub(crate) fn assemble(c: &Components<'_>, orig: Option<&ByteString>) -> Result<Url, InvalidUrl> {
77 let prefix = if c.authority.is_some() {
78 if c.path.is_empty() {
79 let special = c
80 .scheme
81 .is_some_and(|s| Scheme::from_str_unchecked(s).is_special());
82 if special { "/" } else { "" }
83 } else if c.path.starts_with('/') {
84 ""
85 } else {
86 "/"
87 }
88 } else if c.path.starts_with("//") {
89 "/."
90 } else if c.scheme.is_none() && c.path.split('/').next().is_some_and(|s| s.contains(':')) {
91 "./"
92 } else {
93 ""
94 };
95
96 let pieces = [
97 c.scheme.unwrap_or(""),
98 if c.scheme.is_some() { ":" } else { "" },
99 if c.authority.is_some() { "//" } else { "" },
100 c.authority.unwrap_or(""),
101 prefix,
102 c.path,
103 if c.query.is_some() { "?" } else { "" },
104 c.query.unwrap_or(""),
105 if c.fragment.is_some() { "#" } else { "" },
106 c.fragment.unwrap_or(""),
107 ];
108 let len: usize = pieces.iter().map(|p| p.len()).sum();
109 if len > parse::MAX_LEN {
110 return Err(InvalidUrl::new(ErrorKind::TooLong));
111 }
112
113 let reuse = orig.filter(|orig| {
114 orig.len() == len && {
115 let orig = orig.as_str().as_bytes();
118 let mut pos = 0;
119 pieces.iter().all(|p| {
120 let start = pos;
121 pos += p.len();
122 p.is_empty() || orig[start..pos] == *p.as_bytes()
123 })
124 }
125 });
126 let data = match reuse {
127 Some(orig) => orig.clone(),
128 None => concat(&pieces, len),
129 };
130
131 let lens = pieces.map(|p| p.len() as u16);
133 let scheme_end = lens[0];
134 let auth_start = if c.authority.is_some() {
135 lens[0] + lens[1] + lens[2]
136 } else {
137 0
138 };
139 let hidden = if prefix == "/." { lens[4] } else { 0 };
141 let path_start = lens[..4].iter().sum::<u16>() + hidden;
142 let path_end = lens[..6].iter().sum::<u16>();
143 let query_end = path_end + lens[6] + lens[7];
144 let (host_start, host_end) = match c.authority {
145 Some(a) => {
146 let host = authority::split(a).1;
147 let start = auth_start + authority::offset(a, host) as u16;
148 (start, start + host.len() as u16)
149 }
150 None => (0, 0),
151 };
152 Ok(Url {
153 data,
154 scheme_end,
155 auth_start,
156 path_start,
157 path_end,
158 query_end,
159 host_start,
160 host_end,
161 })
162}
163
164fn concat(pieces: &[&str], len: usize) -> ByteString {
166 const INLINE: usize = 23;
168
169 let bytes = if len <= INLINE {
170 let mut buf = [0u8; INLINE];
171 let mut pos = 0;
172 for p in pieces {
173 buf[pos..pos + p.len()].copy_from_slice(p.as_bytes());
174 pos += p.len();
175 }
176 Bytes::copy_from_slice(&buf[..len])
177 } else {
178 let mut buf = BytesMut::with_capacity(len);
179 for p in pieces {
180 buf.extend_from_slice(p.as_bytes());
181 }
182 buf.freeze()
183 };
184 unsafe { ByteString::from_bytes_unchecked(bytes) }
186}
187
188fn too_long<T>(res: Result<T, InvalidUrl>) -> T {
189 match res {
190 Ok(v) => v,
191 Err(e) => panic!("{e}"),
192 }
193}
194
195impl Url {
196 pub const fn new() -> Url {
198 Url {
199 data: ByteString::from_static("/"),
200 scheme_end: 0,
201 auth_start: 0,
202 path_start: 0,
203 path_end: 1,
204 query_end: 1,
205 host_start: 0,
206 host_end: 0,
207 }
208 }
209
210 pub fn parse<T: AsRef<str>>(src: T) -> Result<Url, InvalidUrl> {
230 parse::parse_authority(src.as_ref())
231 }
232
233 pub fn parse_ref<T: AsRef<str>>(src: T) -> Result<Url, InvalidUrl> {
237 parse::parse(src.as_ref(), None)
238 }
239
240 pub fn validate<T: AsRef<[u8]>>(src: T) -> Result<(), InvalidUrl> {
263 let src = src.as_ref();
264 if let Ok(s) = from_utf8(src) {
265 return parse::validate(s);
266 }
267 let i = src.iter().position(|b| !b.is_ascii()).unwrap_or_default();
268 let c = src[i..]
269 .utf8_chunks()
270 .next()
271 .and_then(|chunk| chunk.valid().chars().next())
272 .unwrap_or(char::REPLACEMENT_CHARACTER);
273 Err(InvalidUrl::at(ErrorKind::InvalidChar(c), i))
274 }
275
276 pub fn from_static(src: &'static str) -> Url {
283 match parse::parse(src, Some(&ByteString::from_static(src))) {
284 Ok(url) => url,
285 Err(e) => panic!("invalid static url {src:?}: {e}"),
286 }
287 }
288
289 pub fn from_maybe_shared<T>(src: T) -> Result<Url, InvalidUrl>
292 where
293 T: AsRef<[u8]> + 'static,
294 {
295 let mut src = Some(src);
296 let any = &mut src as &mut dyn Any;
297 if let Some(src) = any.downcast_mut::<Option<Bytes>>() {
298 return Url::try_from(src.take().unwrap());
299 }
300 if let Some(src) = any.downcast_mut::<Option<String>>() {
301 return Url::try_from(src.take().unwrap());
302 }
303 if let Some(src) = any.downcast_mut::<Option<Vec<u8>>>() {
304 return Url::try_from(Bytes::from(src.take().unwrap()));
305 }
306 Url::try_from(src.unwrap().as_ref())
307 }
308
309 pub fn builder() -> crate::Builder {
311 crate::Builder::new()
312 }
313
314 pub(crate) fn empty() -> Url {
315 Url {
316 data: ByteString::new(),
317 scheme_end: 0,
318 auth_start: 0,
319 path_start: 0,
320 path_end: 0,
321 query_end: 0,
322 host_start: 0,
323 host_end: 0,
324 }
325 }
326
327 fn components(&self) -> Components<'_> {
328 Components {
329 scheme: self.scheme_str(),
330 authority: self.authority().map(Authority::as_str),
331 path: self.path().as_str(),
332 query: self.query().map(Query::as_str),
333 fragment: self.fragment().map(Fragment::as_str),
334 }
335 }
336
337 fn range(&self, start: u16, end: u16) -> &str {
338 &self.data[start as usize..end as usize]
339 }
340
341 pub fn as_str(&self) -> &str {
345 &self.data
346 }
347
348 pub fn as_bytes(&self) -> &[u8] {
350 self.data.as_bytes()
351 }
352
353 pub fn as_byte_string(&self) -> &ByteString {
355 &self.data
356 }
357
358 pub fn scheme(&self) -> Option<&Scheme> {
360 self.scheme_str().map(Scheme::from_str_unchecked)
361 }
362
363 pub fn scheme_str(&self) -> Option<&str> {
365 (self.scheme_end > 0).then(|| self.range(0, self.scheme_end))
366 }
367
368 pub fn authority(&self) -> Option<&Authority> {
370 (self.auth_start > 0)
371 .then(|| Authority::from_str_unchecked(self.range(self.auth_start, self.path_start)))
372 }
373
374 pub fn userinfo(&self) -> Option<&UserInfo> {
376 (self.host_start > self.auth_start)
377 .then(|| UserInfo::from_str_unchecked(self.range(self.auth_start, self.host_start - 1)))
378 }
379
380 pub fn username(&self) -> Option<Cow<'_, str>> {
382 self.userinfo().map(UserInfo::decoded_username)
383 }
384
385 pub fn password(&self) -> Option<Cow<'_, str>> {
387 self.userinfo()?.decoded_password()
388 }
389
390 pub fn host(&self) -> Option<&str> {
393 (self.host_end > self.host_start).then(|| self.range(self.host_start, self.host_end))
394 }
395
396 pub fn host_parsed(&self) -> Option<crate::Host<'_>> {
410 self.host().map(crate::Host::classify)
411 }
412
413 pub fn port(&self) -> Option<Port<&str>> {
415 if self.auth_start > 0 && self.host_end < self.path_start {
416 Port::parse(self.range(self.host_end + 1, self.path_start))
417 } else {
418 None
419 }
420 }
421
422 pub fn port_u16(&self) -> Option<u16> {
424 self.port().map(|p| p.as_u16())
425 }
426
427 pub fn port_or_known_default(&self) -> Option<u16> {
429 self.port_u16().or_else(|| self.scheme()?.default_port())
430 }
431
432 pub fn path(&self) -> &Path {
445 Path::from_str_unchecked(self.range(self.path_start, self.path_end))
446 }
447
448 pub fn query(&self) -> Option<&Query> {
450 (self.query_end > self.path_end)
451 .then(|| Query::from_str_unchecked(self.range(self.path_end + 1, self.query_end)))
452 }
453
454 pub fn path_and_query(&self) -> &PathAndQuery {
456 PathAndQuery::from_str_unchecked(self.range(self.path_start, self.query_end))
457 }
458
459 pub fn fragment(&self) -> Option<&Fragment> {
461 ((self.query_end as usize) < self.data.len())
462 .then(|| Fragment::from_str_unchecked(&self.data[self.query_end as usize + 1..]))
463 }
464
465 pub fn is_absolute(&self) -> bool {
467 self.scheme_end > 0
468 }
469
470 pub fn is_default_port(&self) -> bool {
472 match self.port_u16() {
473 None => true,
474 Some(port) => self.scheme().and_then(Scheme::default_port) == Some(port),
475 }
476 }
477
478 pub fn origin(&self) -> Option<Url> {
490 let scheme = self.scheme_str()?;
491 let authority = self.authority()?;
492 if authority.host().is_empty() {
493 return None;
494 }
495 Some(self.derive(|c| {
496 *c = Components {
497 scheme: Some(scheme),
498 authority: Some(authority.host_port()),
499 ..Components::default()
500 };
501 }))
502 }
503
504 pub fn relative(&self) -> Url {
513 self.derive(|c| {
514 c.scheme = None;
515 c.authority = None;
516 })
517 }
518
519 pub fn parent(&self) -> Url {
531 let path = self.path().as_str();
532 let trimmed = path.trim_end_matches('/');
533 let parent = match trimmed.rfind('/') {
534 Some(0) => "/",
535 Some(i) => &trimmed[..i],
536 None if path.starts_with('/') => "/",
537 None => "",
538 };
539 self.with_path(parent)
540 }
541
542 pub fn push_segments<I, S>(&self, segments: I) -> Url
555 where
556 I: IntoIterator<Item = S>,
557 S: AsRef<str>,
558 {
559 let mut path = self.path().as_str().to_string();
560 for segment in segments {
561 let segment = segment.as_ref();
562 let segment = segment.strip_prefix('/').unwrap_or(segment);
563 if !path.is_empty() && !path.ends_with('/') {
564 path.push('/');
565 }
566 path.push_str("e(segment, Component::Path));
567 }
568 if path.starts_with('/') && parse::has_dot_segments(&path) {
569 path = parse::remove_dot_segments(&path);
570 }
571 self.with_path(&path)
572 }
573
574 pub fn join(&self, reference: &str) -> Result<Url, InvalidUrl> {
585 if reference.trim_matches(|c: char| c <= ' ').is_empty() {
586 return self.rebuild(|c| c.fragment = None);
587 }
588 Ok(self.join_url(&Url::parse_ref(reference)?))
589 }
590
591 pub fn join_url(&self, r: &Url) -> Url {
608 let base = self.components();
609 let r = r.components();
610 let resolved = |path| Cow::Owned(parse::remove_dot_segments(path));
611 let (scheme, authority, path, query) = if r.scheme.is_some() {
612 (r.scheme, r.authority, resolved(r.path), r.query)
613 } else if r.authority.is_some() {
614 (base.scheme, r.authority, resolved(r.path), r.query)
615 } else if r.path.is_empty() {
616 let query = r.query.or(base.query);
617 (base.scheme, base.authority, Cow::Borrowed(base.path), query)
618 } else if r.path.starts_with('/') {
619 (base.scheme, base.authority, resolved(r.path), r.query)
620 } else {
621 let dir = if base.authority.is_some() && base.path.is_empty() {
622 "/"
623 } else {
624 dir(base.path)
625 };
626 let path = resolved(&format!("{dir}{}", r.path));
627 (base.scheme, base.authority, path, r.query)
628 };
629 self.derive(|c| {
630 *c = Components {
631 scheme,
632 authority,
633 path: &path,
634 query,
635 fragment: r.fragment,
636 };
637 })
638 }
639
640 fn rebuild<'a>(&'a self, f: impl FnOnce(&mut Components<'a>)) -> Result<Url, InvalidUrl> {
645 let mut c = self.components();
646 f(&mut c);
647 assemble(&c, Some(&self.data))
648 }
649
650 fn derive<'a>(&'a self, f: impl FnOnce(&mut Components<'a>)) -> Url {
651 too_long(self.rebuild(f))
652 }
653
654 fn with_path(&self, path: &str) -> Url {
656 self.derive(|c| {
657 c.path = path;
658 c.query = None;
659 c.fragment = None;
660 })
661 }
662
663 pub fn set_scheme(&mut self, scheme: &str) -> Result<(), InvalidUrl> {
665 let scheme = lowercase(Scheme::new(scheme)?.as_str().into());
666 self.rebuild(|c| c.scheme = Some(&scheme))
667 .map(|url| *self = url)
668 }
669
670 pub fn set_authority(&mut self, authority: Option<&str>) -> Result<(), InvalidUrl> {
672 let authority = authority.map(parse::normalize_authority).transpose()?;
673 self.rebuild(|c| c.authority = authority.as_deref())
674 .map(|url| *self = url)
675 }
676
677 fn with_authority(
678 &self,
679 userinfo: Option<&str>,
680 host: &str,
681 port: Option<u16>,
682 ) -> Result<Url, InvalidUrl> {
683 let authority = parse::join_authority(userinfo, host, port);
684 self.rebuild(|c| c.authority = Some(&authority))
685 }
686
687 fn host_parts(&self) -> Result<(Option<&str>, &str, Option<u16>), InvalidUrl> {
688 let authority = self
689 .authority()
690 .ok_or(InvalidUrl::new(ErrorKind::AuthorityMissing))?;
691 let (userinfo, host, _) = authority::split(authority.as_str());
692 if host.is_empty() {
693 return Err(InvalidUrl::new(ErrorKind::InvalidHost));
694 }
695 Ok((userinfo, host, authority.port_u16()))
696 }
697
698 pub fn set_userinfo(
709 &mut self,
710 username: Option<&str>,
711 password: Option<&str>,
712 ) -> Result<(), InvalidUrl> {
713 let (_, host, port) = self.host_parts()?;
714 let userinfo = match (username, password) {
715 (None, None) => None,
716 (user, None) => Some(quote(user.unwrap_or(""), Component::UserInfo)),
717 (user, Some(password)) => Some(Cow::Owned(format!(
718 "{}:{}",
719 quote(user.unwrap_or(""), Component::UserInfo),
720 quote(password, Component::UserInfo)
721 ))),
722 };
723 self.with_authority(userinfo.as_deref(), host, port)
724 .map(|url| *self = url)
725 }
726
727 pub fn set_host(&mut self, host: &str) -> Result<(), InvalidUrl> {
742 let bracketed;
743 let host = if host.contains(':') && !host.starts_with('[') {
744 bracketed = format!("[{host}]");
745 &bracketed
746 } else {
747 host
748 };
749 let host = normalize_host(host)?;
750 if host.is_empty() {
751 return Err(InvalidUrl::new(ErrorKind::InvalidHost));
752 }
753 let (userinfo, port) = match self.authority() {
754 Some(a) => (a.userinfo().map(UserInfo::as_str), a.port_u16()),
755 None => (None, None),
756 };
757 self.with_authority(userinfo, &host, port)
758 .map(|url| *self = url)
759 }
760
761 pub fn set_port(&mut self, port: Option<u16>) -> Result<(), InvalidUrl> {
763 let (userinfo, host, _) = self.host_parts()?;
764 self.with_authority(userinfo, host, port)
765 .map(|url| *self = url)
766 }
767
768 pub fn set_path(&mut self, path: &str) {
780 let prefixed;
781 let path = if self.auth_start != 0 && !path.is_empty() && !path.starts_with('/') {
782 prefixed = format!("/{path}");
783 &prefixed
784 } else {
785 path
786 };
787 let path = parse::normalize_path(path);
788 *self = self.derive(|c| c.path = &path);
789 }
790
791 pub fn set_file_name(&mut self, name: &str) -> Result<(), InvalidUrl> {
801 if name.contains('/') || name == "." || name == ".." {
802 return Err(InvalidUrl::new(ErrorKind::InvalidPath));
803 }
804 self.with_file_name("e(name, Component::Path))
805 .map(|url| *self = url)
806 }
807
808 fn with_file_name(&self, name: &str) -> Result<Url, InvalidUrl> {
810 let path = format!("{}{name}", dir(self.path().as_str()));
811 self.rebuild(|c| c.path = &path)
812 }
813
814 pub fn set_extension(&mut self, extension: &str) -> Result<(), InvalidUrl> {
826 let stem = self
827 .path()
828 .file_stem()
829 .ok_or(InvalidUrl::new(ErrorKind::InvalidPath))?;
830 if extension.contains('/') {
831 return Err(InvalidUrl::new(ErrorKind::InvalidPath));
832 }
833 let name = if extension.is_empty() {
834 Cow::Borrowed(stem)
835 } else {
836 let extension = quote(extension, Component::Path);
837 Cow::Owned(format!("{stem}.{extension}"))
838 };
839 self.with_file_name(&name).map(|url| *self = url)
840 }
841
842 pub fn set_query(&mut self, query: Option<&str>) {
852 let query = query.map(|q| requote(q, Component::Query));
853 *self = self.derive(|c| c.query = query.as_deref());
854 }
855
856 fn edit_query_pairs<I, K, V>(&mut self, keep: impl Fn(&str) -> bool, pairs: I)
859 where
860 I: IntoIterator<Item = (K, V)>,
861 K: AsRef<str>,
862 V: AsRef<str>,
863 {
864 let existing = self.query().map_or("", Query::as_str);
865 let pieces: Vec<Cow<'_, str>> = query::pieces(existing)
866 .filter(|piece| keep(&unquote(split_pair(piece).0, Component::QueryPart)))
867 .map(Cow::Borrowed)
868 .chain(pairs.into_iter().map(|(k, v)| {
869 let (k, v) = (k.as_ref(), v.as_ref());
870 let (k, v) = (
871 quote(k, Component::QueryPart),
872 quote(v, Component::QueryPart),
873 );
874 Cow::Owned(format!("{k}={v}"))
875 }))
876 .collect();
877 let query = pieces.join("&");
878 *self = self.derive(|c| c.query = (!query.is_empty()).then_some(query.as_str()));
879 }
880
881 pub fn set_query_pairs<I, K, V>(&mut self, pairs: I)
891 where
892 I: IntoIterator<Item = (K, V)>,
893 K: AsRef<str>,
894 V: AsRef<str>,
895 {
896 self.edit_query_pairs(|_| false, pairs);
897 }
898
899 pub fn extend_query_pairs<I, K, V>(&mut self, pairs: I)
901 where
902 I: IntoIterator<Item = (K, V)>,
903 K: AsRef<str>,
904 V: AsRef<str>,
905 {
906 self.edit_query_pairs(|_| true, pairs);
907 }
908
909 pub fn update_query_pairs<I, K, V>(&mut self, pairs: I)
919 where
920 I: IntoIterator<Item = (K, V)>,
921 K: AsRef<str>,
922 V: AsRef<str>,
923 {
924 let pairs: Vec<(K, V)> = pairs.into_iter().collect();
925 let keep = |key: &str| !pairs.iter().any(|(k, _)| k.as_ref() == key);
926 self.edit_query_pairs(keep, pairs.iter().map(|(k, v)| (k.as_ref(), v.as_ref())));
927 }
928
929 pub fn remove_query_params<I, K>(&mut self, keys: I)
941 where
942 I: IntoIterator<Item = K>,
943 K: AsRef<str>,
944 {
945 let keys: Vec<K> = keys.into_iter().collect();
946 let keep = |key: &str| !keys.iter().any(|k| k.as_ref() == key);
947 self.edit_query_pairs(keep, std::iter::empty::<(&str, &str)>());
948 }
949
950 pub fn set_fragment(&mut self, fragment: Option<&str>) {
952 let fragment = fragment.map(|f| requote(f, Component::Fragment));
953 *self = self.derive(|c| c.fragment = fragment.as_deref());
954 }
955
956 pub fn into_parts(self) -> crate::Parts {
958 let slice = |start: u16, end: usize| self.data.slice(start as usize..end);
959 crate::Parts {
960 scheme: self.scheme().map(|_| slice(0, self.scheme_end as usize)),
961 authority: self
962 .authority()
963 .map(|_| slice(self.auth_start, self.path_start as usize)),
964 path_and_query: slice(self.path_start, self.query_end as usize),
965 fragment: self
966 .fragment()
967 .map(|_| slice(self.query_end + 1, self.data.len())),
968 }
969 }
970
971 pub fn into_byte_string(self) -> ByteString {
973 self.data
974 }
975}
976
977fn dir(path: &str) -> &str {
979 path.rfind('/').map_or("", |i| &path[..=i])
980}
981
982str_fmt!(Url);
985str_eq!(Url);
986
987impl Default for Url {
989 fn default() -> Url {
990 Url::new()
991 }
992}
993
994impl FromStr for Url {
995 type Err = InvalidUrl;
996
997 fn from_str(s: &str) -> Result<Url, InvalidUrl> {
998 parse::parse(s, None)
999 }
1000}
1001
1002macro_rules! try_from {
1003 ($($ty:ty => |$s:ident| $conv:expr;)*) => {$(
1004 impl TryFrom<$ty> for Url {
1005 type Error = InvalidUrl;
1006
1007 fn try_from($s: $ty) -> Result<Url, InvalidUrl> {
1008 $conv
1009 }
1010 }
1011 )*};
1012}
1013
1014try_from! {
1015 &str => |s| parse::parse(s, None);
1016 &String => |s| parse::parse(s, None);
1017 String => |s| parse::parse(&s, None);
1019 ByteString => |s| parse::parse(&s, Some(&s));
1020 &ByteString => |s| parse::parse(s, Some(s));
1021 &[u8] => |s| parse::parse(from_utf8(s).map_err(utf8_error)?, None);
1022 Bytes => |s| {
1023 from_utf8(&s).map_err(utf8_error)?;
1024 Url::try_from(unsafe { ByteString::from_bytes_unchecked(s) })
1026 };
1027}
1028
1029#[allow(clippy::needless_pass_by_value)]
1030fn utf8_error(e: Utf8Error) -> InvalidUrl {
1031 InvalidUrl::at(ErrorKind::InvalidChar('\u{FFFD}'), e.valid_up_to())
1032}
1033
1034impl From<Url> for ByteString {
1035 fn from(url: Url) -> ByteString {
1036 url.data
1037 }
1038}
1039
1040impl From<Url> for String {
1041 fn from(url: Url) -> String {
1042 url.as_str().to_string()
1043 }
1044}
1045
1046impl<S: AsRef<str>> Div<S> for &Url {
1047 type Output = Url;
1048
1049 fn div(self, segment: S) -> Url {
1050 self.push_segments([segment])
1051 }
1052}
1053
1054impl<S: AsRef<str>> Div<S> for Url {
1055 type Output = Url;
1056
1057 fn div(self, segment: S) -> Url {
1058 self.push_segments([segment])
1059 }
1060}
1061
1062impl<U: Borrow<Url>> Add<U> for &Url {
1063 type Output = Url;
1064
1065 fn add(self, reference: U) -> Url {
1066 self.join_url(reference.borrow())
1067 }
1068}
1069
1070impl<U: Borrow<Url>> Add<U> for Url {
1071 type Output = Url;
1072
1073 fn add(self, reference: U) -> Url {
1074 self.join_url(reference.borrow())
1075 }
1076}
1077
1078impl<U: Borrow<Url>> AddAssign<U> for Url {
1079 fn add_assign(&mut self, reference: U) {
1080 *self = self.join_url(reference.borrow());
1081 }
1082}
1083
1084#[cfg(feature = "http")]
1085mod http_impls {
1086 use http::uri::{InvalidUri, Uri};
1087
1088 use super::Url;
1089 use crate::error::InvalidUrl;
1090
1091 impl TryFrom<&Uri> for Url {
1092 type Error = InvalidUrl;
1093
1094 fn try_from(uri: &Uri) -> Result<Url, InvalidUrl> {
1095 if uri.scheme().is_none()
1096 && let Some(authority) = uri.authority()
1097 {
1098 return Url::parse(authority.as_str());
1099 }
1100 let uri = uri.to_string();
1101 if uri.starts_with("//") {
1103 Url::try_from(format!("/.{uri}"))
1104 } else {
1105 Url::try_from(uri)
1106 }
1107 }
1108 }
1109
1110 impl TryFrom<Uri> for Url {
1111 type Error = InvalidUrl;
1112
1113 fn try_from(uri: Uri) -> Result<Url, InvalidUrl> {
1114 Url::try_from(&uri)
1115 }
1116 }
1117
1118 impl TryFrom<&Url> for Uri {
1122 type Error = InvalidUri;
1123
1124 fn try_from(url: &Url) -> Result<Uri, InvalidUri> {
1125 let start = match (url.scheme_end, url.auth_start) {
1128 (0, 0) => url.path_start,
1129 (0, auth_start) => auth_start,
1130 _ => 0,
1131 };
1132 Uri::try_from(url.range(start, url.query_end))
1133 }
1134 }
1135
1136 impl TryFrom<Url> for Uri {
1138 type Error = InvalidUri;
1139
1140 fn try_from(url: Url) -> Result<Uri, InvalidUri> {
1141 Uri::try_from(&url)
1142 }
1143 }
1144}
1145
1146#[cfg(feature = "serde")]
1147mod serde_impls {
1148 use std::fmt;
1149
1150 use serde::de::{self, Deserialize, Deserializer, Visitor};
1151 use serde::{Serialize, Serializer};
1152
1153 use super::Url;
1154
1155 impl Serialize for Url {
1156 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1157 serializer.serialize_str(self.as_str())
1158 }
1159 }
1160
1161 struct UrlVisitor;
1162
1163 impl Visitor<'_> for UrlVisitor {
1164 type Value = Url;
1165
1166 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1167 f.write_str("a url")
1168 }
1169
1170 fn visit_str<E: de::Error>(self, v: &str) -> Result<Url, E> {
1171 Url::try_from(v).map_err(E::custom)
1172 }
1173
1174 fn visit_string<E: de::Error>(self, v: String) -> Result<Url, E> {
1175 Url::try_from(v).map_err(E::custom)
1176 }
1177 }
1178
1179 impl<'de> Deserialize<'de> for Url {
1180 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Url, D::Error> {
1181 deserializer.deserialize_str(UrlVisitor)
1182 }
1183 }
1184}
1185
1186#[cfg(test)]
1187mod tests {
1188 use super::*;
1189
1190 #[test]
1191 fn default() {
1192 const URL: Url = Url::new();
1193 assert_eq!(URL, Url::default());
1194 let (url, p) = (Url::default(), Url::from_static("/"));
1195 assert_eq!(url.scheme_end, p.scheme_end);
1196 assert_eq!(url.auth_start, p.auth_start);
1197 assert_eq!(url.path_start, p.path_start);
1198 assert_eq!(url.path_end, p.path_end);
1199 assert_eq!(url.query_end, p.query_end);
1200 assert_eq!((url.host_start, url.host_end), (p.host_start, p.host_end));
1201 assert_eq!(url, "/");
1202 assert_eq!(url.path(), "/");
1203 assert!(url.query().is_none());
1204 assert!(!url.is_absolute());
1205 }
1206
1207 #[test]
1208 fn cached_host() {
1209 let cases = [
1210 (
1211 "http://u:p@[::1]:8080/a",
1212 Some("u:p"),
1213 Some("[::1]"),
1214 Some(8080),
1215 ),
1216 ("http://@h/", Some(""), Some("h"), None),
1217 ("http://h:0/", None, Some("h"), Some(0)),
1218 ("file:///etc", None, None, None),
1219 ("//h", None, Some("h"), None),
1220 ("/a", None, None, None),
1221 ];
1222 for (src, userinfo, host, port) in cases {
1223 let url = Url::from_static(src);
1224 let auth = url.authority();
1225 assert_eq!(url.userinfo().map(UserInfo::as_str), userinfo, "{src}");
1226 assert_eq!(url.host(), host, "{src}");
1227 assert_eq!(url.port_u16(), port, "{src}");
1228 assert_eq!(
1229 auth.and_then(|a| a.userinfo()).map(UserInfo::as_str),
1230 userinfo
1231 );
1232 assert_eq!(auth.map(Authority::host).filter(|h| !h.is_empty()), host);
1233 assert_eq!(auth.and_then(Authority::port_u16), port);
1234 let mut copy = url.clone();
1235 copy.set_path("/x");
1236 assert_eq!((copy.host(), copy.port_u16()), (host, port), "{src}");
1237 }
1238 }
1239
1240 #[test]
1241 fn double_slash_path() {
1242 let url = Url::from_static("/.//a/../b?q#f");
1243 assert_eq!(url, "/.//b?q#f");
1244 assert_eq!(url.path(), "//b");
1245 assert_eq!(url.path_and_query(), "//b?q");
1246 assert!(url.authority().is_none());
1247 assert_eq!(Url::from_parts(url.clone().into_parts()).unwrap(), url);
1248 assert_eq!(url.join("c").unwrap().path(), "//c");
1249
1250 let mut url = Url::default();
1251 url.set_path("//p");
1252 assert_eq!(url, "/.//p");
1253 assert_eq!(url.path(), "//p");
1254
1255 let mut url = Url::from_static("http://h//p?q");
1256 assert_eq!(url.path(), "//p");
1257 url.set_authority(None).unwrap();
1258 assert_eq!(url, "http:/.//p?q");
1259 assert_eq!(url.path(), "//p");
1260 url.set_authority(Some("h")).unwrap();
1261 assert_eq!(url, "http://h//p?q");
1262
1263 let parts = crate::Parts {
1264 path_and_query: "//p?q".into(),
1265 ..crate::Parts::default()
1266 };
1267 assert_eq!(Url::from_parts(parts).unwrap().path(), "//p");
1268 }
1269}