1#![cfg_attr(
4 feature = "document-features",
5 cfg_attr(doc, doc = ::document_features::document_features!())
6)]
7#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
8#![deny(rust_2018_idioms, missing_docs)]
9#![forbid(unsafe_code)]
10
11use bstr::{BStr, BString};
12
13pub mod parse;
15#[doc(inline)]
16pub use parse::parse;
17
18pub mod expand_path;
20#[doc(inline)]
21pub use expand_path::expand_path;
22
23mod scheme;
24pub use scheme::Scheme;
25
26#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
35#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
36pub struct Url {
37 pub scheme: Scheme,
39 user: Option<String>,
41 host: Option<String>,
43 serialize_alternative_form: bool,
45 pub port: Option<u16>,
47 pub path: bstr::BString,
49}
50
51impl Url {
53 pub fn from_parts(
55 scheme: Scheme,
56 user: Option<String>,
57 host: Option<String>,
58 port: Option<u16>,
59 path: BString,
60 ) -> Result<Self, parse::Error> {
61 parse(
62 Url {
63 scheme,
64 user,
65 host,
66 port,
67 path,
68 serialize_alternative_form: false,
69 }
70 .to_bstring()
71 .as_ref(),
72 )
73 }
74
75 pub fn from_parts_as_alternative_form(
77 scheme: Scheme,
78 user: Option<String>,
79 host: Option<String>,
80 port: Option<u16>,
81 path: BString,
82 ) -> Result<Self, parse::Error> {
83 parse(
84 Url {
85 scheme,
86 user,
87 host,
88 port,
89 path,
90 serialize_alternative_form: true,
91 }
92 .to_bstring()
93 .as_ref(),
94 )
95 }
96}
97
98impl Url {
100 pub fn set_user(&mut self, user: Option<String>) -> Option<String> {
102 let prev = self.user.take();
103 self.user = user;
104 prev
105 }
106}
107
108impl Url {
110 pub fn serialize_alternate_form(mut self, use_alternate_form: bool) -> Self {
115 self.serialize_alternative_form = use_alternate_form;
116 self
117 }
118
119 pub fn canonicalize(&mut self) -> Result<(), git_path::realpath::Error> {
121 if self.scheme == Scheme::File {
122 let path = git_path::from_bstr(self.path.as_ref());
123 let abs_path = git_path::realpath(path)?;
124 self.path = git_path::into_bstr(abs_path).into_owned();
125 }
126 Ok(())
127 }
128}
129
130impl Url {
132 pub fn user(&self) -> Option<&str> {
134 self.user.as_deref()
135 }
136 pub fn host(&self) -> Option<&str> {
138 self.host.as_deref()
139 }
140 pub fn path_is_root(&self) -> bool {
142 self.path == "/"
143 }
144 pub fn port_or_default(&self) -> Option<u16> {
147 self.port.or_else(|| {
148 use Scheme::*;
149 Some(match self.scheme {
150 Http => 80,
151 Https => 443,
152 Ssh => 22,
153 Git => 9418,
154 File | Ext(_) => return None,
155 })
156 })
157 }
158}
159
160impl Url {
162 pub fn canonicalized(&self) -> Result<Self, git_path::realpath::Error> {
164 let mut res = self.clone();
165 res.canonicalize()?;
166 Ok(res)
167 }
168}
169
170impl Url {
172 pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> {
174 if !(self.serialize_alternative_form && (self.scheme == Scheme::File || self.scheme == Scheme::Ssh)) {
175 out.write_all(self.scheme.as_str().as_bytes())?;
176 out.write_all(b"://")?;
177 }
178 match (&self.user, &self.host) {
179 (Some(user), Some(host)) => {
180 out.write_all(user.as_bytes())?;
181 out.write_all(&[b'@'])?;
182 out.write_all(host.as_bytes())?;
183 }
184 (None, Some(host)) => {
185 out.write_all(host.as_bytes())?;
186 }
187 (None, None) => {}
188 (Some(_user), None) => unreachable!("BUG: should not be possible to have a user but no host"),
189 };
190 if let Some(port) = &self.port {
191 write!(&mut out, ":{port}")?;
192 }
193 if self.serialize_alternative_form && self.scheme == Scheme::Ssh {
194 out.write_all(b":")?;
195 }
196 out.write_all(&self.path)?;
197 Ok(())
198 }
199
200 pub fn to_bstring(&self) -> bstr::BString {
202 let mut buf = Vec::with_capacity(
203 (5 + 3)
204 + self.user.as_ref().map(|n| n.len()).unwrap_or_default()
205 + 1
206 + self.host.as_ref().map(|h| h.len()).unwrap_or_default()
207 + self.port.map(|_| 5).unwrap_or_default()
208 + self.path.len(),
209 );
210 self.write_to(&mut buf).expect("io cannot fail in memory");
211 buf.into()
212 }
213}
214
215impl Url {
217 pub fn from_bytes(bytes: &BStr) -> Result<Self, parse::Error> {
219 parse(bytes)
220 }
221}
222
223mod impls;