fluent_uri/
normalize.rs

1//! Module for normalization.
2
3use crate::{
4    component::Scheme,
5    imp::{HostMeta, Meta, RiMaybeRef, RmrRef},
6    parse,
7    pct_enc::{
8        decode_octet, encode_byte,
9        table::{is_iprivate, is_ucschar, UNRESERVED},
10    },
11    resolve,
12    utf8::{self, Utf8Chunks},
13};
14use alloc::{string::String, vec::Vec};
15use borrow_or_share::Bos;
16use core::{
17    fmt::{self, Write},
18    num::NonZeroUsize,
19};
20
21/// An error occurred when normalizing a URI/IRI (reference).
22#[derive(Clone, Copy, Debug, Eq, PartialEq)]
23pub enum NormalizeError {
24    /// An underflow occurred in path normalization.
25    ///
26    /// Used only when [`Normalizer::allow_path_underflow`] is set to `false`.
27    PathUnderflow,
28}
29
30impl fmt::Display for NormalizeError {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        let msg = match self {
33            Self::PathUnderflow => "underflow occurred in path resolution",
34        };
35        f.write_str(msg)
36    }
37}
38
39#[cfg(feature = "impl-error")]
40impl crate::Error for NormalizeError {}
41
42/// A configurable URI/IRI (reference) normalizer.
43#[derive(Clone, Copy)]
44#[allow(missing_debug_implementations)]
45#[must_use]
46pub struct Normalizer {
47    allow_path_underflow: bool,
48    default_port_f: fn(&Scheme) -> Option<u16>,
49}
50
51impl Normalizer {
52    /// Creates a new `Normalizer` with default configuration.
53    pub fn new() -> Self {
54        Self {
55            allow_path_underflow: true,
56            default_port_f: Scheme::default_port,
57        }
58    }
59
60    /// Sets whether to allow underflow in path normalization.
61    ///
62    /// This defaults to `true`. A value of `false` is a deviation from the
63    /// normalization methods described in
64    /// [Section 6 of RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986/#section-6).
65    ///
66    /// # Examples
67    ///
68    /// ```
69    /// use fluent_uri::{normalize::{Normalizer, NormalizeError}, Uri};
70    ///
71    /// let normalizer = Normalizer::new().allow_path_underflow(false);
72    /// let uri = Uri::parse("http://example.com/..")?;
73    ///
74    /// assert_eq!(normalizer.normalize(&uri).unwrap_err(), NormalizeError::PathUnderflow);
75    /// # Ok::<_, fluent_uri::ParseError>(())
76    /// ```
77    pub fn allow_path_underflow(mut self, value: bool) -> Self {
78        self.allow_path_underflow = value;
79        self
80    }
81
82    /// Sets the function with which to get the default port of a scheme.
83    ///
84    /// This defaults to [`Scheme::default_port`].
85    ///
86    /// # Examples
87    ///
88    /// ```
89    /// use fluent_uri::{component::Scheme, normalize::Normalizer, Uri};
90    ///
91    /// const SCHEME_FOO: &Scheme = Scheme::new_or_panic("foo");
92    ///
93    /// let normalizer = Normalizer::new().default_port_with(|scheme| {
94    ///     if scheme == SCHEME_FOO {
95    ///         Some(4673)
96    ///     } else {
97    ///         scheme.default_port()
98    ///     }
99    /// });
100    /// let uri = Uri::parse("foo://localhost:4673")?;
101    ///
102    /// assert_eq!(normalizer.normalize(&uri).unwrap(), "foo://localhost");
103    /// # Ok::<_, fluent_uri::ParseError>(())
104    /// ```
105    pub fn default_port_with(mut self, f: fn(&Scheme) -> Option<u16>) -> Self {
106        self.default_port_f = f;
107        self
108    }
109
110    /// Normalizes the given URI/IRI (reference).
111    ///
112    /// See [`Uri::normalize`][crate::Uri::normalize] for the exact behavior of this method.
113    ///
114    /// # Errors
115    ///
116    /// Returns `Err` if an underflow occurred in path normalization
117    /// when [`allow_path_underflow`] is set to `false`.
118    ///
119    /// [`allow_path_underflow`]: Self::allow_path_underflow
120    pub fn normalize<R: RiMaybeRef>(&self, r: &R) -> Result<R::WithVal<String>, NormalizeError>
121    where
122        R::Val: Bos<str>,
123    {
124        normalize(
125            r.make_ref(),
126            R::CONSTRAINTS.ascii_only,
127            self.allow_path_underflow,
128            self.default_port_f,
129        )
130        .map(RiMaybeRef::from_pair)
131    }
132}
133
134impl Default for Normalizer {
135    fn default() -> Self {
136        Self::new()
137    }
138}
139
140pub(crate) fn normalize(
141    r: RmrRef<'_, '_>,
142    ascii_only: bool,
143    allow_path_underflow: bool,
144    default_port_f: fn(&Scheme) -> Option<u16>,
145) -> Result<(String, Meta), NormalizeError> {
146    // For "a://[::ffff:5:9]/" the capacity is not enough,
147    // but it's fine since this rarely happens.
148    let mut buf = String::with_capacity(r.as_str().len());
149
150    let path = r.path().as_str();
151    let mut path_buf = String::with_capacity(path.len());
152
153    if r.has_scheme() && path.starts_with('/') {
154        normalize_estr(&mut buf, path, false, ascii_only, false);
155
156        let underflow_occurred = resolve::remove_dot_segments(&mut path_buf, 0, &[&buf]);
157        if underflow_occurred && !allow_path_underflow {
158            return Err(NormalizeError::PathUnderflow);
159        }
160
161        buf.clear();
162    } else {
163        // Don't remove dot segments from relative reference or rootless path.
164        normalize_estr(&mut path_buf, path, false, ascii_only, false);
165    }
166
167    let mut meta = Meta::default();
168
169    if let Some(scheme) = r.scheme_opt() {
170        buf.push_str(scheme.as_str());
171        buf.make_ascii_lowercase();
172        meta.scheme_end = NonZeroUsize::new(buf.len());
173        buf.push(':');
174    }
175
176    if let Some(auth) = r.authority() {
177        buf.push_str("//");
178
179        if let Some(userinfo) = auth.userinfo() {
180            normalize_estr(&mut buf, userinfo.as_str(), false, ascii_only, false);
181            buf.push('@');
182        }
183
184        let mut auth_meta = auth.meta();
185        auth_meta.host_bounds.0 = buf.len();
186        match auth_meta.host_meta {
187            // An IPv4 address is always canonical.
188            HostMeta::Ipv4(..) => buf.push_str(auth.host()),
189            #[cfg(feature = "net")]
190            HostMeta::Ipv6(addr) => write!(buf, "[{addr}]").unwrap(),
191            #[cfg(not(feature = "net"))]
192            HostMeta::Ipv6() => {
193                buf.push('[');
194                write_v6(&mut buf, parse::parse_v6(&auth.host().as_bytes()[1..]));
195                buf.push(']');
196            }
197            HostMeta::IpvFuture => {
198                let start = buf.len();
199                buf.push_str(auth.host());
200
201                buf[start..].make_ascii_lowercase();
202            }
203            HostMeta::RegName => {
204                let start = buf.len();
205                let host = auth.host();
206                normalize_estr(&mut buf, host, true, ascii_only, false);
207
208                if buf.len() < start + host.len() {
209                    // Only reparse when the length is less than before.
210                    auth_meta.host_meta = parse::parse_v4_or_reg_name(&buf.as_bytes()[start..]);
211                }
212            }
213        }
214        auth_meta.host_bounds.1 = buf.len();
215        meta.auth_meta = Some(auth_meta);
216
217        if let Some(port) = auth.port() {
218            if !port.is_empty() {
219                let mut eq_default = false;
220                if let Some(scheme) = r.scheme_opt() {
221                    if let Some(default) = default_port_f(scheme) {
222                        eq_default = port.as_str().parse().ok() == Some(default);
223                    }
224                }
225                if !eq_default {
226                    buf.push(':');
227                    buf.push_str(port.as_str());
228                }
229            }
230        }
231    }
232
233    meta.path_bounds.0 = buf.len();
234    // Make sure that the output is a valid URI/IRI reference.
235    if r.has_scheme() && !r.has_authority() && path_buf.starts_with("//") {
236        buf.push_str("/.");
237    }
238    buf.push_str(&path_buf);
239    meta.path_bounds.1 = buf.len();
240
241    if let Some(query) = r.query() {
242        buf.push('?');
243        normalize_estr(&mut buf, query.as_str(), false, ascii_only, true);
244        meta.query_end = NonZeroUsize::new(buf.len());
245    }
246
247    if let Some(fragment) = r.fragment() {
248        buf.push('#');
249        normalize_estr(&mut buf, fragment.as_str(), false, ascii_only, false);
250    }
251
252    Ok((buf, meta))
253}
254
255fn normalize_estr(
256    buf: &mut String,
257    s: &str,
258    to_ascii_lowercase: bool,
259    ascii_only: bool,
260    is_query: bool,
261) {
262    let s = s.as_bytes();
263    let mut i = 0;
264
265    if ascii_only {
266        while i < s.len() {
267            let mut x = s[i];
268            if x == b'%' {
269                let (hi, lo) = (s[i + 1], s[i + 2]);
270                let mut octet = decode_octet(hi, lo);
271                if UNRESERVED.allows_ascii(octet) {
272                    if to_ascii_lowercase {
273                        octet = octet.to_ascii_lowercase();
274                    }
275                    buf.push(octet as char);
276                } else {
277                    buf.push('%');
278                    buf.push(hi.to_ascii_uppercase() as char);
279                    buf.push(lo.to_ascii_uppercase() as char);
280                }
281                i += 3;
282            } else {
283                if to_ascii_lowercase {
284                    x = x.to_ascii_lowercase();
285                }
286                buf.push(x as char);
287                i += 1;
288            }
289        }
290    } else {
291        let mut dec_buf = Vec::new();
292
293        while i < s.len() {
294            if s[i] == b'%' {
295                let (hi, lo) = (s[i + 1], s[i + 2]);
296                let mut octet = decode_octet(hi, lo);
297                if UNRESERVED.allows_ascii(octet) {
298                    consume_dec_buf(buf, &mut dec_buf, is_query);
299
300                    if to_ascii_lowercase {
301                        octet = octet.to_ascii_lowercase();
302                    }
303                    buf.push(octet as char);
304                } else {
305                    dec_buf.push(octet);
306                }
307                i += 3;
308            } else {
309                consume_dec_buf(buf, &mut dec_buf, is_query);
310
311                let (x, len) = utf8::next_code_point(s, i);
312                let mut x = char::from_u32(x).unwrap();
313                if to_ascii_lowercase {
314                    x = x.to_ascii_lowercase();
315                }
316                buf.push(x);
317                i += len;
318            }
319        }
320        consume_dec_buf(buf, &mut dec_buf, is_query);
321    }
322}
323
324fn consume_dec_buf(buf: &mut String, dec_buf: &mut Vec<u8>, is_query: bool) {
325    for chunk in Utf8Chunks::new(dec_buf) {
326        for ch in chunk.valid().chars() {
327            if is_ucschar(ch as u32) || (is_query && is_iprivate(ch as u32)) {
328                buf.push(ch);
329            } else {
330                for x in ch.encode_utf8(&mut [0; 4]).bytes() {
331                    encode_byte(x, buf);
332                }
333            }
334        }
335        for &x in chunk.invalid() {
336            encode_byte(x, buf);
337        }
338    }
339    dec_buf.clear();
340}
341
342// Taken from `impl Display for Ipv6Addr`.
343#[cfg(not(feature = "net"))]
344fn write_v6(buf: &mut String, segments: [u16; 8]) {
345    if let [0, 0, 0, 0, 0, 0xffff, ab, cd] = segments {
346        let [a, b] = ab.to_be_bytes();
347        let [c, d] = cd.to_be_bytes();
348        write!(buf, "::ffff:{a}.{b}.{c}.{d}").unwrap();
349    } else {
350        #[derive(Copy, Clone, Default)]
351        struct Span {
352            start: usize,
353            len: usize,
354        }
355
356        // Find the inner 0 span
357        let zeroes = {
358            let mut longest = Span::default();
359            let mut current = Span::default();
360
361            for (i, &segment) in segments.iter().enumerate() {
362                if segment == 0 {
363                    if current.len == 0 {
364                        current.start = i;
365                    }
366
367                    current.len += 1;
368
369                    if current.len > longest.len {
370                        longest = current;
371                    }
372                } else {
373                    current = Span::default();
374                }
375            }
376
377            longest
378        };
379
380        /// Write a colon-separated part of the address
381        #[inline]
382        fn write_subslice(buf: &mut String, chunk: &[u16]) {
383            if let Some((first, tail)) = chunk.split_first() {
384                write!(buf, "{first:x}").unwrap();
385                for segment in tail {
386                    write!(buf, ":{segment:x}").unwrap();
387                }
388            }
389        }
390
391        if zeroes.len > 1 {
392            write_subslice(buf, &segments[..zeroes.start]);
393            buf.push_str("::");
394            write_subslice(buf, &segments[zeroes.start + zeroes.len..]);
395        } else {
396            write_subslice(buf, &segments);
397        }
398    }
399}