1use core::fmt;
4
5#[cfg(feature = "alloc")]
6use alloc::collections::TryReserveError;
7#[cfg(all(feature = "alloc", not(feature = "std")))]
8use alloc::string::String;
9
10#[cfg(feature = "alloc")]
11use crate::format::{ToDedicatedString, ToStringFallible};
12use crate::spec::Spec;
13use crate::types::{
14 RiAbsoluteStr, RiFragmentStr, RiQueryStr, RiReferenceStr, RiRelativeStr, RiStr,
15};
16#[cfg(feature = "alloc")]
17use crate::types::{
18 RiAbsoluteString, RiFragmentString, RiQueryString, RiReferenceString, RiRelativeString,
19 RiString,
20};
21#[cfg(feature = "alloc")]
22use crate::types::{
23 UriAbsoluteString, UriFragmentString, UriQueryString, UriReferenceString, UriRelativeString,
24 UriString,
25};
26
27const HEXDIGITS: [u8; 16] = [
29 b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D', b'E', b'F',
30];
31
32#[derive(Debug, Clone, Copy)]
69pub struct MappedToUri<'a, Src: ?Sized>(&'a Src);
70
71macro_rules! impl_for_iri {
73 ($borrowed:ident, $owned:ident, $owned_uri:ident) => {
74 impl<S: Spec> fmt::Display for MappedToUri<'_, $borrowed<S>> {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 write_percent_encoded(f, self.0.as_str())
77 }
78 }
79
80 #[cfg(feature = "alloc")]
81 impl<S: Spec> ToDedicatedString for MappedToUri<'_, $borrowed<S>> {
82 type Target = $owned_uri;
83
84 fn try_to_dedicated_string(&self) -> Result<Self::Target, TryReserveError> {
85 let s = self.try_to_string()?;
86 Ok(TryFrom::try_from(s)
87 .expect("[validity] the IRI must be encoded into a valid URI"))
88 }
89 }
90
91 impl<'a, S: Spec> From<&'a $borrowed<S>> for MappedToUri<'a, $borrowed<S>> {
92 #[inline]
93 fn from(iri: &'a $borrowed<S>) -> Self {
94 Self(iri)
95 }
96 }
97
98 #[cfg(feature = "alloc")]
99 impl<'a, S: Spec> From<&'a $owned<S>> for MappedToUri<'a, $borrowed<S>> {
100 #[inline]
101 fn from(iri: &'a $owned<S>) -> Self {
102 Self(iri.as_slice())
103 }
104 }
105 };
106}
107
108impl_for_iri!(RiReferenceStr, RiReferenceString, UriReferenceString);
109impl_for_iri!(RiStr, RiString, UriString);
110impl_for_iri!(RiAbsoluteStr, RiAbsoluteString, UriAbsoluteString);
111impl_for_iri!(RiRelativeStr, RiRelativeString, UriRelativeString);
112impl_for_iri!(RiQueryStr, RiQueryString, UriQueryString);
113impl_for_iri!(RiFragmentStr, RiFragmentString, UriFragmentString);
114
115fn write_percent_encoded(f: &mut fmt::Formatter<'_>, mut s: &str) -> fmt::Result {
117 while !s.is_empty() {
118 let non_ascii_pos = s.bytes().position(|b| !b.is_ascii()).unwrap_or(s.len());
120 let (ascii, rest) = s.split_at(non_ascii_pos);
121 if !ascii.is_empty() {
122 f.write_str(ascii)?;
123 s = rest;
124 }
125
126 if s.is_empty() {
127 return Ok(());
128 }
129
130 let nonascii_end = s.bytes().position(|b| b.is_ascii()).unwrap_or(s.len());
132 let (nonasciis, rest) = s.split_at(nonascii_end);
133 debug_assert!(
134 !nonasciis.is_empty(),
135 "string without non-ASCII characters should have caused early return"
136 );
137 s = rest;
138
139 const NUM_BYTES_AT_ONCE: usize = 21;
147 percent_encode_bytes(f, nonasciis, &mut [0_u8; NUM_BYTES_AT_ONCE * 3])?;
148 }
149
150 Ok(())
151}
152
153fn percent_encode_bytes(f: &mut fmt::Formatter<'_>, s: &str, buf: &mut [u8]) -> fmt::Result {
162 fn fill_by_percent_encoded<'a>(buf: &'a mut [u8], bytes: &mut core::str::Bytes<'_>) -> &'a str {
174 let src_len = bytes.len();
175 for (dest, byte) in buf.chunks_exact_mut(3).zip(bytes.by_ref()) {
177 debug_assert_eq!(
178 dest.len(),
179 3,
180 "[validity] `chunks_exact()` must return a slice with the exact length"
181 );
182 debug_assert_eq!(
183 dest[0], b'%',
184 "[precondition] the buffer must be properly initialized"
185 );
186
187 let upper = byte >> 4;
188 let lower = byte & 0b1111;
189 dest[1] = HEXDIGITS[usize::from(upper)];
190 dest[2] = HEXDIGITS[usize::from(lower)];
191 }
192 let num_dest_written = (src_len - bytes.len()) * 3;
193 let buf_filled = &buf[..num_dest_written];
194 unsafe {
197 debug_assert!(core::str::from_utf8(buf_filled).is_ok());
198 core::str::from_utf8_unchecked(buf_filled)
199 }
200 }
201
202 assert!(
203 buf.len() >= 3,
204 "[precondition] length of `buf` must be 3 bytes or more"
205 );
206
207 let buf_len = buf.len() / 3 * 3;
210 let buf = &mut buf[..buf_len];
211
212 buf.fill(b'%');
216
217 let mut bytes = s.bytes();
218 while bytes.len() != 0 {
220 let encoded = fill_by_percent_encoded(buf, &mut bytes);
221 f.write_str(encoded)?;
222 }
223
224 Ok(())
225}
226
227#[cfg(feature = "alloc")]
229pub(crate) fn try_percent_encode_iri_inline(
230 iri: &mut String,
231) -> Result<(), alloc::collections::TryReserveError> {
232 let num_nonascii = count_nonascii(iri);
234 if num_nonascii == 0 {
235 return Ok(());
237 }
238 let additional = num_nonascii * 2;
239 iri.try_reserve(additional)?;
240 let src_len = iri.len();
241
242 let mut buf = core::mem::take(iri).into_bytes();
244 buf.extend(core::iter::repeat(b'\0').take(additional));
247
248 let mut dest_end = buf.len();
250 let mut src_end = src_len;
251 let mut rest_nonascii = num_nonascii;
252 while rest_nonascii > 0 {
253 debug_assert!(
254 src_end > 0,
255 "[validity] the source position should not overrun"
256 );
257 debug_assert!(
258 dest_end > 0,
259 "[validity] the destination position should not overrun"
260 );
261 src_end -= 1;
262 dest_end -= 1;
263 let byte = buf[src_end];
264 if byte.is_ascii() {
265 buf[dest_end] = byte;
266 } else {
268 dest_end -= 2;
270 buf[dest_end] = b'%';
271 let upper = byte >> 4;
272 let lower = byte & 0b1111;
273 buf[dest_end + 1] = HEXDIGITS[usize::from(upper)];
274 buf[dest_end + 2] = HEXDIGITS[usize::from(lower)];
275 rest_nonascii -= 1;
276 }
277 }
278
279 let s = String::from_utf8(buf).expect("[consistency] the encoding result is an ASCII string");
281 *iri = s;
282 Ok(())
283}
284
285#[cfg(feature = "alloc")]
287#[inline]
288#[must_use]
289fn count_nonascii(s: &str) -> usize {
290 s.bytes().filter(|b| !b.is_ascii()).count()
291}