1use core::fmt::{self, Write as _};
4use core::ops::Range;
5
6#[cfg(all(feature = "alloc", not(feature = "std")))]
7use alloc::borrow::ToOwned;
8#[cfg(feature = "alloc")]
9use alloc::collections::TryReserveError;
10#[cfg(all(feature = "alloc", not(feature = "std")))]
11use alloc::string::String;
12
13use crate::components::AuthorityComponents;
14#[cfg(feature = "alloc")]
15use crate::format::ToDedicatedString;
16use crate::spec::Spec;
17use crate::types::{RiAbsoluteStr, RiReferenceStr, RiRelativeStr, RiStr};
18#[cfg(feature = "alloc")]
19use crate::types::{RiAbsoluteString, RiReferenceString, RiRelativeString, RiString};
20
21pub(crate) fn password_range_to_hide<S: Spec>(iri: &RiReferenceStr<S>) -> Option<Range<usize>> {
23 fn inner(iri: &str, userinfo: &str) -> Option<Range<usize>> {
25 let authority_start = 2 + iri
28 .find("//")
29 .expect("`authority` component must be prefixed with `//`");
30 let end = authority_start + userinfo.len();
31 let start = authority_start + userinfo.find(':').map_or_else(|| userinfo.len(), |v| v + 1);
32 Some(start..end)
33 }
34
35 let authority_components = AuthorityComponents::from_iri(iri)?;
36 let userinfo = authority_components.userinfo()?;
37 inner(iri.as_str(), userinfo)
38}
39
40fn write_with_masked_password<D>(
42 f: &mut fmt::Formatter<'_>,
43 s: &str,
44 pw_range: Range<usize>,
45 alt: &D,
46) -> fmt::Result
47where
48 D: ?Sized + fmt::Display,
49{
50 debug_assert!(
51 s.len() >= pw_range.end,
52 "password range must be inside the IRI"
53 );
54
55 f.write_str(&s[..pw_range.start])?;
56 alt.fmt(f)?;
57 f.write_str(&s[pw_range.end..])?;
58 Ok(())
59}
60
61fn write_trim_password(f: &mut fmt::Formatter<'_>, s: &str, pw_range: Range<usize>) -> fmt::Result {
63 write_with_masked_password(f, s, pw_range, "")
64}
65
66#[derive(Clone, Copy)]
92pub struct PasswordMasked<'a, T: ?Sized> {
93 iri_ref: &'a T,
95}
96
97impl<'a, T: ?Sized> PasswordMasked<'a, T> {
98 #[inline]
100 #[must_use]
101 pub(crate) fn new(iri_ref: &'a T) -> Self {
102 Self { iri_ref }
103 }
104}
105
106macro_rules! impl_mask {
108 ($borrowed:ident, $owned:ident) => {
109 impl<'a, S: Spec> PasswordMasked<'a, $borrowed<S>> {
110 #[inline]
134 #[must_use]
135 pub fn replace_password<D>(&self, alt: D) -> PasswordReplaced<'a, $borrowed<S>, D>
136 where
137 D: fmt::Display,
138 {
139 PasswordReplaced::with_replacer(self.iri_ref, move |_| alt)
140 }
141
142 #[inline]
168 #[must_use]
169 pub fn replace_password_with<F, D>(
170 &self,
171 replace: F,
172 ) -> PasswordReplaced<'a, $borrowed<S>, D>
173 where
174 F: FnOnce(&str) -> D,
175 D: fmt::Display,
176 {
177 PasswordReplaced::with_replacer(self.iri_ref, replace)
178 }
179 }
180
181 impl<S: Spec> fmt::Display for PasswordMasked<'_, $borrowed<S>> {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 match password_range_to_hide(self.iri_ref.as_ref()) {
184 Some(pw_range) => write_trim_password(f, self.iri_ref.as_str(), pw_range),
185 None => self.iri_ref.fmt(f),
186 }
187 }
188 }
189
190 impl<S: Spec> fmt::Debug for PasswordMasked<'_, $borrowed<S>> {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 f.write_char('<')?;
193 fmt::Display::fmt(self, f)?;
194 f.write_char('>')
195 }
196 }
197
198 #[cfg(feature = "alloc")]
199 impl<S: Spec> ToDedicatedString for PasswordMasked<'_, $borrowed<S>> {
200 type Target = $owned<S>;
201
202 fn try_to_dedicated_string(&self) -> Result<Self::Target, TryReserveError> {
203 let pw_range = match password_range_to_hide(self.iri_ref.as_ref()) {
204 Some(pw_range) => pw_range,
205 None => return Ok(self.iri_ref.to_owned()),
206 };
207 let mut s = String::new();
208 let iri_ref = self.iri_ref.as_str();
209 s.try_reserve(iri_ref.len() - (pw_range.end - pw_range.start))?;
210 s.push_str(&iri_ref[..pw_range.start]);
211 s.push_str(&iri_ref[pw_range.end..]);
212 let iri = unsafe {
215 <$owned<S>>::new_unchecked_justified(
216 s,
217 "the IRI string and its type remains valid without password",
218 )
219 };
220 Ok(iri)
221 }
222 }
223 };
224}
225
226impl_mask!(RiReferenceStr, RiReferenceString);
227impl_mask!(RiStr, RiString);
228impl_mask!(RiAbsoluteStr, RiAbsoluteString);
229impl_mask!(RiRelativeStr, RiRelativeString);
230
231#[cfg_attr(
239 feature = "alloc",
240 doc = "Because of this, [`ToDedicatedString`] trait is not implemented for this type."
241)]
242pub struct PasswordReplaced<'a, T: ?Sized, D> {
245 iri_ref: &'a T,
247 password: Option<(Range<usize>, D)>,
249}
250
251impl<'a, T, D> PasswordReplaced<'a, T, D>
252where
253 T: ?Sized,
254 D: fmt::Display,
255{
256 #[inline]
262 #[must_use]
263 pub(crate) fn with_replacer<S, F>(iri_ref: &'a T, replace: F) -> Self
264 where
265 S: Spec,
266 T: AsRef<RiReferenceStr<S>>,
267 F: FnOnce(&str) -> D,
268 {
269 let iri_ref_asref = iri_ref.as_ref();
270 let password = password_range_to_hide(iri_ref_asref)
271 .map(move |pw_range| (pw_range.clone(), replace(&iri_ref_asref.as_str()[pw_range])));
272 Self { iri_ref, password }
273 }
274}
275
276macro_rules! impl_replace {
278 ($borrowed:ident, $owned:ident) => {
279 impl<S: Spec, D: fmt::Display> fmt::Display for PasswordReplaced<'_, $borrowed<S>, D> {
280 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281 match &self.password {
282 Some((pw_range, alt)) => {
283 write_with_masked_password(f, self.iri_ref.as_str(), pw_range.clone(), alt)
284 }
285 None => self.iri_ref.fmt(f),
286 }
287 }
288 }
289
290 impl<S: Spec, D: fmt::Display> fmt::Debug for PasswordReplaced<'_, $borrowed<S>, D> {
291 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292 f.write_char('<')?;
293 fmt::Display::fmt(self, f)?;
294 f.write_char('>')
295 }
296 }
297 };
298}
299
300impl_replace!(RiReferenceStr, RiReferenceString);
301impl_replace!(RiStr, RiString);
302impl_replace!(RiAbsoluteStr, RiAbsoluteString);
303impl_replace!(RiRelativeStr, RiRelativeString);