fluent_uri/pct_enc/
mod.rs

1//! Percent-encoding utilities.
2
3pub mod encoder;
4#[cfg(feature = "alloc")]
5mod estring;
6pub(crate) mod table;
7
8#[cfg(feature = "alloc")]
9pub use estring::EString;
10pub use table::Table;
11
12use crate::imp::PathEncoder;
13use core::{cmp::Ordering, hash, iter::FusedIterator, marker::PhantomData, str};
14use ref_cast::{ref_cast_custom, RefCastCustom};
15
16#[cfg(feature = "alloc")]
17use alloc::{
18    borrow::{Cow, ToOwned},
19    string::String,
20    vec::Vec,
21};
22
23/// A trait used by [`EStr`] and [`EString`] to specify the table used for encoding.
24///
25/// # Sub-encoders
26///
27/// A sub-encoder `SubE` of `E` is an encoder such that `SubE::TABLE` is a [subset] of `E::TABLE`.
28///
29/// [subset]: Table::is_subset
30pub trait Encoder: 'static {
31    /// The table used for encoding.
32    const TABLE: &'static Table;
33}
34
35/// Percent-encoded string slices.
36///
37/// The owned counterpart of `EStr` is [`EString`]. See its documentation
38/// if you want to build a percent-encoded string from scratch.
39///
40/// # Type parameter
41///
42/// The `EStr<E>` type is parameterized over a type `E` that implements [`Encoder`].
43/// The associated constant `E::TABLE` of type [`Table`] specifies the byte patterns
44/// allowed in a string. In short, the underlying byte sequence of an `EStr<E>` slice
45/// can be formed by joining any number of the following byte sequences:
46///
47/// - `ch.encode_utf8(&mut [0; 4])` where `E::TABLE.allows(ch)`.
48/// - `[b'%', hi, lo]` where `E::TABLE.allows_pct_encoded() && hi.is_ascii_hexdigit() && lo.is_ascii_hexdigit()`.
49///
50/// # Comparison
51///
52/// `EStr` slices are compared [lexicographically](Ord#lexicographical-comparison)
53/// by their byte values. Normalization is **not** performed prior to comparison.
54///
55/// # Examples
56///
57/// Parse key-value pairs from a query string into a hash map:
58///
59/// ```
60/// use fluent_uri::{pct_enc::EStr, UriRef};
61/// use std::collections::HashMap;
62///
63/// let s = "?name=%E5%BC%A0%E4%B8%89&speech=%C2%A1Ol%C3%A9%21";
64/// let query = UriRef::parse(s)?.query().unwrap();
65/// let map: HashMap<_, _> = query
66///     .split('&')
67///     .map(|s| s.split_once('=').unwrap_or((s, EStr::EMPTY)))
68///     .map(|(k, v)| (k.decode().to_string_lossy(), v.decode().to_string_lossy()))
69///     .collect();
70/// assert_eq!(map["name"], "张三");
71/// assert_eq!(map["speech"], "¡Olé!");
72/// # Ok::<_, fluent_uri::ParseError>(())
73/// ```
74#[derive(RefCastCustom)]
75#[repr(transparent)]
76pub struct EStr<E: Encoder> {
77    encoder: PhantomData<E>,
78    inner: str,
79}
80
81#[cfg(feature = "alloc")]
82struct Assert<L: Encoder, R: Encoder> {
83    _marker: PhantomData<(L, R)>,
84}
85
86#[cfg(feature = "alloc")]
87impl<L: Encoder, R: Encoder> Assert<L, R> {
88    const L_IS_SUB_ENCODER_OF_R: () = assert!(L::TABLE.is_subset(R::TABLE), "not a sub-encoder");
89}
90
91impl<E: Encoder> EStr<E> {
92    const ASSERT_ALLOWS_PCT_ENCODED: () = assert!(
93        E::TABLE.allows_pct_encoded(),
94        "table does not allow percent-encoded octets"
95    );
96
97    /// Converts a string slice to an `EStr` slice assuming validity.
98    #[ref_cast_custom]
99    pub(crate) const fn new_validated(s: &str) -> &Self;
100
101    /// An empty `EStr` slice.
102    pub const EMPTY: &'static Self = Self::new_validated("");
103
104    pub(crate) fn cast<F: Encoder>(&self) -> &EStr<F> {
105        EStr::new_validated(&self.inner)
106    }
107
108    /// Converts a string slice to an `EStr` slice.
109    ///
110    /// # Panics
111    ///
112    /// Panics if the string is not properly encoded with `E`.
113    /// For a non-panicking variant, use [`new`](Self::new).
114    #[must_use]
115    pub const fn new_or_panic(s: &str) -> &Self {
116        match Self::new(s) {
117            Some(s) => s,
118            None => panic!("improperly encoded string"),
119        }
120    }
121
122    /// Converts a string slice to an `EStr` slice, returning `None` if the conversion fails.
123    #[must_use]
124    pub const fn new(s: &str) -> Option<&Self> {
125        if E::TABLE.validate(s.as_bytes()) {
126            Some(Self::new_validated(s))
127        } else {
128            None
129        }
130    }
131
132    /// Yields the underlying string slice.
133    #[must_use]
134    pub fn as_str(&self) -> &str {
135        &self.inner
136    }
137
138    /// Returns the length of the `EStr` slice in bytes.
139    #[must_use]
140    pub fn len(&self) -> usize {
141        self.inner.len()
142    }
143
144    /// Checks whether the `EStr` slice is empty.
145    #[must_use]
146    pub fn is_empty(&self) -> bool {
147        self.inner.is_empty()
148    }
149
150    /// Upcasts the `EStr` slice to associate it with the given super-encoder.
151    ///
152    /// # Panics
153    ///
154    /// Panics at compile time if `E` is not a [sub-encoder](Encoder#sub-encoders) of `SuperE`.
155    ///
156    /// # Example
157    ///
158    /// ```
159    /// use fluent_uri::pct_enc::{encoder::{IPath, Path}, EStr};
160    ///
161    /// let path = EStr::<Path>::new_or_panic("foo");
162    /// let path: &EStr<IPath> = path.upcast();
163    /// ```
164    #[cfg(fluent_uri_unstable)]
165    #[must_use]
166    pub fn upcast<SuperE: Encoder>(&self) -> &EStr<SuperE> {
167        () = Assert::<E, SuperE>::L_IS_SUB_ENCODER_OF_R;
168        EStr::new_validated(self.as_str())
169    }
170
171    /// Checks whether the `EStr` slice is unencoded, i.e., does not contain `'%'`.
172    ///
173    /// # Examples
174    ///
175    /// ```
176    /// use fluent_uri::pct_enc::{encoder::Path, EStr};
177    ///
178    /// assert!(EStr::<Path>::new_or_panic("Hello!").is_unencoded());
179    /// assert!(!EStr::<Path>::new_or_panic("%C2%A1Hola%21").is_unencoded());
180    /// ```
181    #[cfg(fluent_uri_unstable)]
182    #[must_use]
183    pub fn is_unencoded(&self) -> bool {
184        !(E::TABLE.allows_pct_encoded() && self.inner.contains('%'))
185    }
186
187    /// Returns an iterator used to decode the `EStr` slice.
188    ///
189    /// Always **split before decoding**, as otherwise the data may be
190    /// mistaken for component delimiters.
191    ///
192    /// Note that the iterator will **not** decode `U+002B` (+) as `0x20` (space).
193    ///
194    /// # Panics
195    ///
196    /// Panics at compile time if `E::TABLE` does not [allow percent-encoded octets].
197    ///
198    /// [allow percent-encoded octets]: Table::allows_pct_encoded
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// use fluent_uri::pct_enc::{encoder::Path, EStr};
204    ///
205    /// let dec = EStr::<Path>::new_or_panic("%C2%A1Hola%21").decode();
206    /// assert_eq!(*dec.clone().to_bytes(), [0xc2, 0xa1, 0x48, 0x6f, 0x6c, 0x61, 0x21]);
207    /// assert_eq!(dec.to_string().unwrap(), "¡Hola!");
208    /// ```
209    pub fn decode(&self) -> Decode<'_> {
210        () = Self::ASSERT_ALLOWS_PCT_ENCODED;
211        Decode {
212            source: &self.inner,
213        }
214    }
215
216    /// Returns an iterator over subslices of the `EStr` slice separated by the given delimiter.
217    ///
218    /// # Panics
219    ///
220    /// Panics if the delimiter is not a [reserved] character.
221    ///
222    /// [reserved]: https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
223    ///
224    /// # Examples
225    ///
226    /// ```
227    /// use fluent_uri::pct_enc::{encoder::Path, EStr};
228    ///
229    /// assert!(EStr::<Path>::new_or_panic("a,b,c").split(',').eq(["a", "b", "c"]));
230    /// assert!(EStr::<Path>::new_or_panic(",").split(',').eq(["", ""]));
231    /// assert!(EStr::<Path>::EMPTY.split(',').eq([""]));
232    /// ```
233    pub fn split(&self, delim: char) -> Split<'_, E> {
234        assert!(
235            delim.is_ascii() && table::RESERVED.allows(delim),
236            "splitting with non-reserved character"
237        );
238        Split {
239            inner: self.inner.split(delim),
240            encoder: PhantomData,
241        }
242    }
243
244    /// Splits the `EStr` slice on the first occurrence of the given delimiter and
245    /// returns prefix before delimiter and suffix after delimiter.
246    ///
247    /// Returns `None` if the delimiter is not found.
248    ///
249    /// # Panics
250    ///
251    /// Panics if the delimiter is not a [reserved] character.
252    ///
253    /// [reserved]: https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
254    ///
255    /// # Examples
256    ///
257    /// ```
258    /// use fluent_uri::pct_enc::{encoder::Path, EStr};
259    ///
260    /// assert_eq!(
261    ///     EStr::<Path>::new_or_panic("foo;bar;baz").split_once(';'),
262    ///     Some((EStr::new_or_panic("foo"), EStr::new_or_panic("bar;baz")))
263    /// );
264    ///
265    /// assert_eq!(EStr::<Path>::new_or_panic("foo").split_once(';'), None);
266    /// ```
267    #[must_use]
268    pub fn split_once(&self, delim: char) -> Option<(&Self, &Self)> {
269        assert!(
270            delim.is_ascii() && table::RESERVED.allows(delim),
271            "splitting with non-reserved character"
272        );
273        self.inner
274            .split_once(delim)
275            .map(|(a, b)| (Self::new_validated(a), Self::new_validated(b)))
276    }
277
278    /// Splits the `EStr` slice on the last occurrence of the given delimiter and
279    /// returns prefix before delimiter and suffix after delimiter.
280    ///
281    /// Returns `None` if the delimiter is not found.
282    ///
283    /// # Panics
284    ///
285    /// Panics if the delimiter is not a [reserved] character.
286    ///
287    /// [reserved]: https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
288    ///
289    /// # Examples
290    ///
291    /// ```
292    /// use fluent_uri::pct_enc::{encoder::Path, EStr};
293    ///
294    /// assert_eq!(
295    ///     EStr::<Path>::new_or_panic("foo;bar;baz").rsplit_once(';'),
296    ///     Some((EStr::new_or_panic("foo;bar"), EStr::new_or_panic("baz")))
297    /// );
298    ///
299    /// assert_eq!(EStr::<Path>::new_or_panic("foo").rsplit_once(';'), None);
300    /// ```
301    #[must_use]
302    pub fn rsplit_once(&self, delim: char) -> Option<(&Self, &Self)> {
303        assert!(
304            delim.is_ascii() && table::RESERVED.allows(delim),
305            "splitting with non-reserved character"
306        );
307        self.inner
308            .rsplit_once(delim)
309            .map(|(a, b)| (Self::new_validated(a), Self::new_validated(b)))
310    }
311}
312
313impl<E: Encoder> AsRef<Self> for EStr<E> {
314    fn as_ref(&self) -> &Self {
315        self
316    }
317}
318
319impl<E: Encoder> AsRef<str> for EStr<E> {
320    fn as_ref(&self) -> &str {
321        &self.inner
322    }
323}
324
325impl<E: Encoder> PartialEq for EStr<E> {
326    fn eq(&self, other: &Self) -> bool {
327        self.inner == other.inner
328    }
329}
330
331impl<E: Encoder> PartialEq<str> for EStr<E> {
332    fn eq(&self, other: &str) -> bool {
333        &self.inner == other
334    }
335}
336
337impl<E: Encoder> PartialEq<EStr<E>> for str {
338    fn eq(&self, other: &EStr<E>) -> bool {
339        self == &other.inner
340    }
341}
342
343impl<E: Encoder> Eq for EStr<E> {}
344
345impl<E: Encoder> hash::Hash for EStr<E> {
346    fn hash<H: hash::Hasher>(&self, state: &mut H) {
347        self.inner.hash(state);
348    }
349}
350
351impl<E: Encoder> PartialOrd for EStr<E> {
352    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
353        Some(self.cmp(other))
354    }
355}
356
357impl<E: Encoder> Ord for EStr<E> {
358    fn cmp(&self, other: &Self) -> Ordering {
359        self.inner.cmp(&other.inner)
360    }
361}
362
363impl<E: Encoder> Default for &EStr<E> {
364    /// Creates an empty `EStr` slice.
365    fn default() -> Self {
366        EStr::EMPTY
367    }
368}
369
370#[cfg(feature = "alloc")]
371impl<E: Encoder> ToOwned for EStr<E> {
372    type Owned = EString<E>;
373
374    fn to_owned(&self) -> EString<E> {
375        EString::new_validated(self.inner.to_owned())
376    }
377
378    fn clone_into(&self, target: &mut EString<E>) {
379        self.inner.clone_into(&mut target.buf);
380    }
381}
382
383/// Extension methods for the [path] component.
384///
385/// [path]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
386impl<E: PathEncoder> EStr<E> {
387    /// Checks whether the path is absolute, i.e., starting with `'/'`.
388    #[inline]
389    #[must_use]
390    pub fn is_absolute(&self) -> bool {
391        self.inner.starts_with('/')
392    }
393
394    /// Checks whether the path is rootless, i.e., not starting with `'/'`.
395    #[inline]
396    #[must_use]
397    pub fn is_rootless(&self) -> bool {
398        !self.inner.starts_with('/')
399    }
400
401    /// Returns an iterator over the path segments, separated by `'/'`.
402    ///
403    /// Returns `None` if the path is [rootless]. Use [`split`]
404    /// instead if you need to split a rootless path on occurrences of `'/'`.
405    ///
406    /// Note that the path can be [empty] when authority is present,
407    /// in which case this method will return `None`.
408    ///
409    /// [rootless]: Self::is_rootless
410    /// [`split`]: Self::split
411    /// [empty]: Self::is_empty
412    ///
413    /// # Examples
414    ///
415    /// ```
416    /// use fluent_uri::Uri;
417    ///
418    /// // Segments are separated by '/'.
419    /// // The empty string before a leading '/' is not a segment.
420    /// // However, segments can be empty in the other cases.
421    /// let path = Uri::parse("file:///path/to//dir/")?.path();
422    /// assert_eq!(path, "/path/to//dir/");
423    /// assert!(path.segments_if_absolute().unwrap().eq(["path", "to", "", "dir", ""]));
424    ///
425    /// let path = Uri::parse("foo:bar/baz")?.path();
426    /// assert_eq!(path, "bar/baz");
427    /// assert!(path.segments_if_absolute().is_none());
428    ///
429    /// let path = Uri::parse("http://example.com")?.path();
430    /// assert!(path.is_empty());
431    /// assert!(path.segments_if_absolute().is_none());
432    /// # Ok::<_, fluent_uri::ParseError>(())
433    /// ```
434    #[inline]
435    #[must_use]
436    pub fn segments_if_absolute(&self) -> Option<Split<'_, E>> {
437        self.inner
438            .strip_prefix('/')
439            .map(|s| Self::new_validated(s).split('/'))
440    }
441}
442
443const fn gen_octet_table(hi: bool) -> [u8; 256] {
444    let mut out = [0xff; 256];
445    let shift = if hi { 4 } else { 0 };
446
447    let mut i = 0;
448    while i < 10 {
449        out[(i + b'0') as usize] = i << shift;
450        i += 1;
451    }
452    while i < 16 {
453        out[(i - 10 + b'A') as usize] = i << shift;
454        out[(i - 10 + b'a') as usize] = i << shift;
455        i += 1;
456    }
457    out
458}
459
460const OCTET_TABLE_HI: &[u8; 256] = &gen_octet_table(true);
461pub(crate) const OCTET_TABLE_LO: &[u8; 256] = &gen_octet_table(false);
462
463/// Decodes a percent-encoded octet, assuming that the bytes are hexadecimal.
464pub(crate) fn decode_octet(hi: u8, lo: u8) -> u8 {
465    debug_assert!(hi.is_ascii_hexdigit() && lo.is_ascii_hexdigit());
466    OCTET_TABLE_HI[hi as usize] | OCTET_TABLE_LO[lo as usize]
467}
468
469#[cfg(feature = "alloc")]
470pub(crate) fn encode_byte(x: u8, buf: &mut alloc::string::String) {
471    const HEX_TABLE: [u8; 512] = {
472        const HEX_DIGITS: &[u8; 16] = b"0123456789ABCDEF";
473
474        let mut i = 0;
475        let mut table = [0; 512];
476        while i < 256 {
477            table[i * 2] = HEX_DIGITS[i >> 4];
478            table[i * 2 + 1] = HEX_DIGITS[i & 0b1111];
479            i += 1;
480        }
481        table
482    };
483
484    buf.push('%');
485    buf.push(HEX_TABLE[x as usize * 2] as char);
486    buf.push(HEX_TABLE[x as usize * 2 + 1] as char);
487}
488
489/// An iterator used to decode an [`EStr`] slice.
490///
491/// This struct is created by [`EStr::decode`]. Normally you'll use the methods below
492/// instead of iterating over a `Decode` manually, unless you need precise control
493/// over allocation.
494///
495/// See the [`DecodedChunk`] type for documentation of the items yielded by this iterator.
496#[derive(Clone, Debug)]
497#[must_use = "iterators are lazy and do nothing unless consumed"]
498pub struct Decode<'a> {
499    source: &'a str,
500}
501
502/// An item returned by the [`Decode`] iterator.
503#[derive(Clone, Copy, Debug)]
504pub enum DecodedChunk<'a> {
505    /// An unencoded subslice.
506    Unencoded(&'a str),
507    /// A percent-encoded octet, decoded (for example, `"%20"` decoded as `0x20`).
508    PctDecoded(u8),
509}
510
511impl<'a> Iterator for Decode<'a> {
512    type Item = DecodedChunk<'a>;
513
514    fn next(&mut self) -> Option<Self::Item> {
515        if self.source.is_empty() {
516            return None;
517        }
518
519        let i = self
520            .source
521            .bytes()
522            .position(|x| x == b'%')
523            .unwrap_or(self.source.len());
524
525        if i == 0 {
526            let (s, rest) = self.source.split_at(3);
527            let x = decode_octet(s.as_bytes()[1], s.as_bytes()[2]);
528            self.source = rest;
529            Some(DecodedChunk::PctDecoded(x))
530        } else {
531            let (s, rest) = self.source.split_at(i);
532            self.source = rest;
533            Some(DecodedChunk::Unencoded(s))
534        }
535    }
536}
537
538impl FusedIterator for Decode<'_> {}
539
540#[cfg(feature = "alloc")]
541enum DecodedUtf8Chunk<'a, 'b> {
542    Unencoded(&'a str),
543    Decoded { valid: &'b str, invalid: &'b [u8] },
544}
545
546#[cfg(feature = "alloc")]
547fn decode_utf8<'a>(
548    iter: impl Iterator<Item = DecodedChunk<'a>>,
549    mut handle_chunk: impl FnMut(DecodedUtf8Chunk<'a, '_>),
550) {
551    use crate::utf8::Utf8Chunks;
552
553    let mut buf = [0; 32];
554    let mut cnt = 0;
555
556    'decode: for chunk in iter {
557        match chunk {
558            DecodedChunk::Unencoded(s) => {
559                if cnt > 0 {
560                    for chunk in Utf8Chunks::new(&buf[..cnt]) {
561                        handle_chunk(DecodedUtf8Chunk::Decoded {
562                            valid: chunk.valid(),
563                            invalid: chunk.invalid(),
564                        });
565                    }
566                    cnt = 0;
567                }
568                handle_chunk(DecodedUtf8Chunk::Unencoded(s));
569            }
570            DecodedChunk::PctDecoded(x) => {
571                buf[cnt] = x;
572                cnt += 1;
573
574                if cnt >= buf.len() {
575                    for chunk in Utf8Chunks::new(&buf[..cnt]) {
576                        if chunk.incomplete() {
577                            handle_chunk(DecodedUtf8Chunk::Decoded {
578                                valid: chunk.valid(),
579                                invalid: &[],
580                            });
581
582                            let invalid_len = chunk.invalid().len();
583                            buf.copy_within(cnt - invalid_len..cnt, 0);
584
585                            cnt = invalid_len;
586                            continue 'decode;
587                        }
588                        handle_chunk(DecodedUtf8Chunk::Decoded {
589                            valid: chunk.valid(),
590                            invalid: chunk.invalid(),
591                        });
592                    }
593                    cnt = 0;
594                }
595            }
596        }
597    }
598
599    for chunk in Utf8Chunks::new(&buf[..cnt]) {
600        handle_chunk(DecodedUtf8Chunk::Decoded {
601            valid: chunk.valid(),
602            invalid: chunk.invalid(),
603        });
604    }
605}
606
607#[cfg(feature = "alloc")]
608impl<'a> Decode<'a> {
609    /// Decodes the slice to bytes.
610    ///
611    /// This method allocates only when the slice contains any percent-encoded octet.
612    #[must_use]
613    pub fn to_bytes(self) -> Cow<'a, [u8]> {
614        let len = self.source.len();
615        let mut iter = self.peekable();
616
617        let mut buf;
618        match iter.peek() {
619            Some(&DecodedChunk::Unencoded(s)) => {
620                iter.next();
621                if iter.peek().is_none() {
622                    return Cow::Borrowed(s.as_bytes());
623                }
624                buf = Vec::with_capacity(len);
625                buf.extend_from_slice(s.as_bytes());
626            }
627            None => return Cow::Borrowed(&[]),
628            _ => buf = Vec::with_capacity(len),
629        }
630
631        for chunk in iter {
632            match chunk {
633                DecodedChunk::Unencoded(s) => buf.extend_from_slice(s.as_bytes()),
634                DecodedChunk::PctDecoded(s) => buf.push(s),
635            }
636        }
637        Cow::Owned(buf)
638    }
639
640    /// Attempts to decode the slice to a string.
641    ///
642    /// This method allocates only when the slice contains any percent-encoded octet.
643    ///
644    /// # Errors
645    ///
646    /// Returns `Err` containing the decoded bytes if they are not valid UTF-8.
647    pub fn to_string(self) -> Result<Cow<'a, str>, Vec<u8>> {
648        let len = self.source.len();
649        let mut iter = self.peekable();
650
651        let mut buf;
652        match iter.peek() {
653            Some(&DecodedChunk::Unencoded(s)) => {
654                iter.next();
655                if iter.peek().is_none() {
656                    return Ok(Cow::Borrowed(s));
657                }
658                buf = String::with_capacity(len);
659                buf.push_str(s);
660            }
661            None => return Ok(Cow::Borrowed("")),
662            _ => buf = String::with_capacity(len),
663        }
664
665        let mut buf = Ok::<_, Vec<u8>>(buf);
666
667        decode_utf8(iter, |chunk| match chunk {
668            DecodedUtf8Chunk::Unencoded(s) => match &mut buf {
669                Ok(string) => string.push_str(s),
670                Err(vec) => vec.extend_from_slice(s.as_bytes()),
671            },
672            DecodedUtf8Chunk::Decoded { valid, invalid } => match &mut buf {
673                Ok(string) => {
674                    string.push_str(valid);
675                    if !invalid.is_empty() {
676                        let mut vec = core::mem::take(string).into_bytes();
677                        vec.extend_from_slice(invalid);
678                        buf = Err(vec);
679                    }
680                }
681                Err(vec) => {
682                    vec.extend_from_slice(valid.as_bytes());
683                    vec.extend_from_slice(invalid);
684                }
685            },
686        });
687
688        match buf {
689            Ok(buf) => Ok(Cow::Owned(buf)),
690            Err(buf) => Err(buf),
691        }
692    }
693
694    /// Decodes the slice to a string, replacing any invalid UTF-8 sequences with
695    /// [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD].
696    ///
697    /// [U+FFFD]: core::char::REPLACEMENT_CHARACTER
698    ///
699    /// This method allocates only when the slice contains any percent-encoded octet.
700    #[must_use]
701    pub fn to_string_lossy(self) -> Cow<'a, str> {
702        let len = self.source.len();
703        let mut iter = self.peekable();
704
705        let mut buf;
706        match iter.peek() {
707            Some(&DecodedChunk::Unencoded(s)) => {
708                iter.next();
709                if iter.peek().is_none() {
710                    return Cow::Borrowed(s);
711                }
712                buf = String::with_capacity(len);
713                buf.push_str(s);
714            }
715            None => return Cow::Borrowed(""),
716            _ => buf = String::with_capacity(len),
717        }
718
719        decode_utf8(iter, |chunk| match chunk {
720            DecodedUtf8Chunk::Unencoded(s) => buf.push_str(s),
721            DecodedUtf8Chunk::Decoded { valid, invalid } => {
722                buf.push_str(valid);
723                if !invalid.is_empty() {
724                    buf.push(char::REPLACEMENT_CHARACTER);
725                }
726            }
727        });
728        Cow::Owned(buf)
729    }
730}
731
732/// An iterator over subslices of an [`EStr`] slice separated by a delimiter.
733///
734/// This struct is created by [`EStr::split`].
735#[derive(Clone, Debug)]
736#[must_use = "iterators are lazy and do nothing unless consumed"]
737pub struct Split<'a, E: Encoder> {
738    inner: str::Split<'a, char>,
739    encoder: PhantomData<E>,
740}
741
742impl<'a, E: Encoder> Iterator for Split<'a, E> {
743    type Item = &'a EStr<E>;
744
745    fn next(&mut self) -> Option<&'a EStr<E>> {
746        self.inner.next().map(EStr::new_validated)
747    }
748}
749
750impl<'a, E: Encoder> DoubleEndedIterator for Split<'a, E> {
751    fn next_back(&mut self) -> Option<&'a EStr<E>> {
752        self.inner.next_back().map(EStr::new_validated)
753    }
754}
755
756impl<E: Encoder> FusedIterator for Split<'_, E> {}