async_coap_uri/
rel_ref.rs

1// Copyright 2019 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15
16use super::*;
17use std::ops::Deref;
18
19/// Unsized string-slice type guaranteed to contain a well-formed [IETF-RFC3986] [relative reference].
20///
21/// [relative reference]: https://tools.ietf.org/html/rfc3986#section-4.2
22///
23/// The sized counterpart is [`RelRefBuf`].
24///
25/// *This type cannot hold a network path*. If this type contains a path that looks like a network
26/// path, it will be considered [degenerate](crate::RelRef::is_degenerate) and you will not be able
27/// to losslessly convert it to a [`UriRef`](crate::UriRef) or [`UriRefBuf`](crate::UriRefBuf).
28/// See ["Network Path Support"](index.html#network-path-support) for more details.
29///
30/// You can create static constants with this class by using the [`rel_ref!`] macro:
31///
32/// [`rel_ref!`]: macro.rel_ref.html
33///
34/// ```
35/// # use async_coap_uri::*;
36/// # fn main() {
37///     let uri = rel_ref!("/test?query");
38///     let components = uri.components();
39///     assert_eq!(None,          components.scheme());
40///     assert_eq!(None,          components.raw_host());
41///     assert_eq!(None,          components.port());
42///     assert_eq!("/test",       components.raw_path());
43///     assert_eq!(Some("query"), components.raw_query());
44/// # }
45/// ```
46///
47/// ## RelRef and Deref
48///
49/// You might think that since both relative and absolute URIs are just special
50/// cases of URIs that they could both safely implement [`Deref<Target=UriRef>`](core::ops::Deref).
51/// This is true for [`Uri`], but not [`RelRef`]. This section is dedicated to explaining why.
52///
53/// There is this pesky [section 4.2 of RFC3986](https://tools.ietf.org/html/rfc3986#section-4.2)
54/// that throws a wrench into that noble endeavour:
55///
56/// >  A path segment that contains a colon character (e.g., "this:that")
57/// >  cannot be used as the first segment of a relative-path reference, as
58/// >  it would be mistaken for a scheme name.  Such a segment must be
59/// >  preceded by a dot-segment (e.g., "./this:that") to make a relative-
60/// >  path reference.
61///
62/// This causes big problems for type-safety when derefing a [`RelRef`] into a [`UriRef`]: there is
63/// no way for [`UriRef`] to know that it came from a [`RelRef`] and thus recognize that something
64/// like `rel_ref!("this:that")` does *NOT* have a scheme of `this`.
65///
66/// These are tricky edge cases that have serious security implications---it's important
67/// that this case be considered and handled appropriately.
68///
69/// The solution used in this library is to make the transition from [`RelRef`] to [`UriRef`] not
70/// guaranteed. However, a transition from a [`RelRef`] to a [`RelRefBuf`] is guaranteed, since the
71/// offending colon can be escaped in that case. This is preferred instead of prepending a `"./"`,
72/// due to the additional complications that could occur when manipulating paths.
73///
74/// You can check any [`RelRef`] for this degenerate condition via the method
75/// [`is_degenerate()`](#method.is_degenerate).
76///
77/// [IETF-RFC3986]: https://tools.ietf.org/html/rfc3986
78#[derive(Eq, Hash)]
79pub struct RelRef(pub(super) UriRef);
80
81_impl_uri_traits_base!(RelRef);
82
83impl Deref for RelRef {
84    type Target = str;
85
86    fn deref(&self) -> &Self::Target {
87        self.as_str()
88    }
89}
90
91impl Default for &RelRef {
92    /// Returns an *empty relative reference*.
93    ///
94    /// Empty relative references do nothing but clear the base fragment when resolved
95    /// against a base.
96    fn default() -> Self {
97        rel_ref!("")
98    }
99}
100
101impl Default for &mut RelRef {
102    /// Mutable version of `(&RelRef)::default`.
103    ///
104    /// Despite being marked mutable, since the length is zero the value is effectively immutable.
105    fn default() -> Self {
106        use std::slice::from_raw_parts_mut;
107        use std::str::from_utf8_unchecked_mut;
108        unsafe {
109            // SAFETY: An empty slice is pretty harmless, mutable or not.
110            let empty_slice = from_raw_parts_mut(0usize as *mut u8, 0);
111            let empty_string = from_utf8_unchecked_mut(empty_slice);
112            RelRef::from_str_unchecked_mut(empty_string)
113        }
114    }
115}
116
117impl AnyUriRef for RelRef {
118    fn write_to<T: core::fmt::Write + ?Sized>(
119        &self,
120        write: &mut T,
121    ) -> Result<(), core::fmt::Error> {
122        if let Some(i) = self.colon_in_first_path_segment() {
123            write!(write, "{}%3A{}", &self[..i], &self[i + 1..])
124        } else {
125            if self.starts_with("//") {
126                write.write_str("/.")?;
127            }
128            write.write_str(self.as_str())
129        }
130    }
131
132    fn is_empty(&self) -> bool {
133        self.0.is_empty()
134    }
135
136    /// Determines what kind of relative reference this is:
137    ///
138    /// This function may return any one of the following values:
139    ///
140    /// * [`UriType::Fragment`](enum.UriType.html#variant.Fragment)
141    /// * [`UriType::Query`](enum.UriType.html#variant.Query)
142    /// * [`UriType::AbsolutePath`](enum.UriType.html#variant.AbsolutePath)
143    /// * [`UriType::RelativePath`](enum.UriType.html#variant.RelativePath)
144    fn uri_type(&self) -> UriType {
145        if self.starts_with('#') {
146            return UriType::Fragment;
147        } else if self.starts_with('?') {
148            return UriType::Query;
149        } else if self.starts_with('/') {
150            return UriType::AbsolutePath;
151        } else {
152            return UriType::RelativePath;
153        }
154    }
155
156    /// Breaks down this relative reference into its [raw components][UriRawComponents].
157    fn components(&self) -> UriRawComponents<'_> {
158        UriRawComponents {
159            scheme: None,
160            authority: None,
161            userinfo: None,
162            host: None,
163            port: None,
164            path: self.path_as_rel_ref(),
165            query: self.raw_query(),
166            fragment: self.raw_fragment(),
167        }
168    }
169}
170
171/// RelRef will always format the relative reference for display in an unambiguous fashion.
172impl std::fmt::Display for RelRef {
173    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
174        self.write_to(f)
175    }
176}
177
178impl RelRef {
179    /// Attempts to convert a string slice into a [`&RelRef`](RelRef), returning `Err(ParseError)`
180    /// if the string slice contains data that is not a valid relative-reference.
181    pub fn from_str(s: &str) -> Result<&RelRef, ParseError> {
182        if let Some(first_error) = s.unescape_uri().first_error() {
183            return Err(ParseError::new(
184                "Bad percent encoding or illegal characters",
185                Some(first_error..first_error + 1),
186            ));
187        } else {
188            Ok(unsafe { Self::from_str_unchecked(s.as_ref()) })
189        }
190    }
191
192    /// Determines if the given string can be considered a well-formed [relative-reference].
193    /// [relative-reference]: https://tools.ietf.org/html/rfc3986#section-4.2
194    pub fn is_str_valid<S: AsRef<str>>(s: S) -> bool {
195        s.as_ref().unescape_uri().first_error().is_none()
196    }
197
198    /// Constructs a new `RelRefBuf` from a `&RelRef`, disambiguating if degenerate.
199    #[inline(always)]
200    pub fn to_rel_ref_buf(&self) -> RelRefBuf {
201        RelRefBuf::from_rel_ref(self)
202    }
203
204    /// Constructs a new `UriRefBuf` from a `&RelRef`, disambiguating if degenerate.
205    pub fn to_uri_ref_buf(&self) -> UriRefBuf {
206        self.to_rel_ref_buf().into()
207    }
208
209    /// Casts this relative reference to a string slice.
210    #[inline(always)]
211    pub const fn as_str(&self) -> &str {
212        self.0.as_str()
213    }
214
215    /// Casts a non-degenerate relative reference to a `&UriRef`.
216    /// Returns `None` if the relative reference [is degenerate][RelRef::is_degenerate].
217    pub fn try_as_uri_ref(&self) -> Option<&UriRef> {
218        if self.is_degenerate() {
219            None
220        } else {
221            Some(&self.0)
222        }
223    }
224
225    /// Returns a [`Cow<UriRef>`] that usually just contains a reference to
226    /// this slice, but will contain an owned instance if this relative reference
227    /// [is degenerate][RelRef::is_degenerate].
228    #[cfg(feature = "std")]
229    pub fn as_uri_ref(&self) -> Cow<'_, UriRef> {
230        if let Some(uri_ref) = self.try_as_uri_ref() {
231            Cow::Borrowed(uri_ref)
232        } else {
233            Cow::Owned(self.to_uri_ref_buf())
234        }
235    }
236}
237
238/// # URI Component Accessors
239impl RelRef {
240    /// Trims the query and fragment from this relative reference, leaving only the path.
241    ///
242    /// See also [`RelRef::trim_query`].
243    #[must_use = "this returns a new slice, without modifying the original"]
244    pub fn path_as_rel_ref(&self) -> &RelRef {
245        self.trim_query()
246    }
247
248    /// See [`UriRef::query_as_rel_ref`] for more information.
249    #[must_use = "this returns a new slice, without modifying the original"]
250    #[inline(always)]
251    pub fn query_as_rel_ref(&self) -> Option<&RelRef> {
252        self.0.query_as_rel_ref()
253    }
254
255    /// See [`UriRef::raw_path`] for more information.
256    #[must_use]
257    #[inline(always)]
258    pub fn raw_path(&self) -> &str {
259        self.path_as_rel_ref().as_str()
260    }
261
262    /// See [`UriRef::raw_query`] for more information.
263    #[must_use = "this returns a new slice, without modifying the original"]
264    #[inline(always)]
265    pub fn raw_query(&self) -> Option<&str> {
266        self.0.raw_query()
267    }
268
269    /// See [`UriRef::raw_fragment`] for more information.
270    #[must_use = "this returns a new slice, without modifying the original"]
271    #[inline(always)]
272    pub fn raw_fragment(&self) -> Option<&str> {
273        self.0.raw_fragment()
274    }
275
276    /// See [`UriRef::raw_path_segments`] for more information.
277    #[must_use]
278    pub fn raw_path_segments(&self) -> impl Iterator<Item = &str> {
279        let path = self.path_as_rel_ref();
280
281        let mut ret = path.as_str().split('/');
282
283        if path.is_empty() {
284            // Skip non-existant segments
285            let _ = ret.next();
286        } else if path.starts_with("/") {
287            // Skip leading slash.
288            let _ = ret.next();
289        }
290
291        ret
292    }
293
294    /// See [`UriRef::raw_query_items`] for more information.
295    #[must_use]
296    #[inline(always)]
297    pub fn raw_query_items(&self) -> impl Iterator<Item = &str> {
298        self.0.raw_query_items()
299    }
300
301    /// See [`UriRef::raw_query_key_values`] for more information.
302    #[must_use]
303    #[inline(always)]
304    pub fn raw_query_key_values(&self) -> impl Iterator<Item = (&str, &str)> {
305        self.0.raw_query_key_values()
306    }
307
308    /// See [`UriRef::fragment`] for more information.
309    #[must_use]
310    #[cfg(feature = "std")]
311    #[inline(always)]
312    pub fn fragment(&self) -> Option<Cow<'_, str>> {
313        self.0.fragment()
314    }
315
316    /// See [`UriRef::path_segments`] for more information.
317    #[must_use]
318    #[cfg(feature = "std")]
319    #[inline(always)]
320    pub fn path_segments(&self) -> impl Iterator<Item = Cow<'_, str>> {
321        self.0.path_segments()
322    }
323
324    /// See [`UriRef::query_items`] for more information.
325    #[must_use]
326    #[cfg(feature = "std")]
327    #[inline(always)]
328    pub fn query_items(&self) -> impl Iterator<Item = Cow<'_, str>> {
329        self.0.query_items()
330    }
331
332    /// See [`UriRef::query_key_values`] for more information.
333    #[must_use]
334    #[cfg(feature = "std")]
335    #[inline(always)]
336    pub fn query_key_values(&self) -> impl Iterator<Item = (Cow<'_, str>, Cow<'_, str>)> {
337        self.0.query_key_values()
338    }
339
340    /// See [`UriRef::has_trailing_slash`] for more information.
341    #[must_use]
342    #[inline(always)]
343    pub fn has_trailing_slash(&self) -> bool {
344        self.0.has_trailing_slash()
345    }
346
347    /// Determines if this [`RelRef`] is degenerate specifically because it is a relative path
348    /// with a colon in the first path segment and no special characters appearing
349    /// before it.
350    ///
351    /// See the section ["RelRef"](#relref-and-deref) for more details.
352    #[must_use]
353    pub fn colon_in_first_path_segment(&self) -> Option<usize> {
354        for (i, b) in self.bytes().enumerate() {
355            match b {
356                b if i == 0 && (b as char).is_numeric() => return None,
357                b if (b as char).is_ascii_alphanumeric() => continue,
358                b'+' | b'-' | b'.' => continue,
359                b':' => return Some(i),
360                _ => return None,
361            }
362        }
363        None
364    }
365
366    /// Determines if this [`RelRef`] is degenerate.
367    ///
368    /// See the section ["RelRef"](#relref-and-deref) for more details.
369    pub fn is_degenerate(&self) -> bool {
370        self.starts_with("//") || self.colon_in_first_path_segment().is_some()
371    }
372}
373
374/// # URI Resolution
375impl RelRef {
376    /// Resolves a relative URI against this relative URI, yielding a
377    /// new relative URI as a `RelRefBuf`.
378    #[cfg(feature = "std")]
379    #[must_use]
380    pub fn resolved_rel_ref<UF: AsRef<RelRef>>(&self, dest: UF) -> RelRefBuf {
381        let mut ret = String::with_capacity(self.len() + dest.as_ref().len());
382
383        self.write_resolved(dest.as_ref(), &mut ret)
384            .expect("URI resolution failed");
385        ret.shrink_to_fit();
386
387        // SAFETY: `write_resolved` is guaranteed to write well-formed RelRefs when
388        //         both the base and target are RelRefs.
389        let mut ret = unsafe { RelRefBuf::from_string_unchecked(ret) };
390
391        ret.disambiguate();
392
393        return ret;
394    }
395}
396
397/// # Trimming
398impl RelRef {
399    /// Returns this relative reference slice without the fragment component.
400    #[must_use = "this returns the trimmed uri as a new slice, \
401                  without modifying the original"]
402    pub fn trim_fragment(&self) -> &RelRef {
403        // SAFETY: Trimming on a boundary guaranteed not to be inside of an escaped byte.
404        unsafe { RelRef::from_str_unchecked(self.0.trim_fragment().as_str()) }
405    }
406
407    /// Returns this relative reference slice without the query or fragment components.
408    #[must_use = "this returns the trimmed uri as a new slice, \
409                  without modifying the original"]
410    pub fn trim_query(&self) -> &RelRef {
411        // SAFETY: Trimming on a boundary guaranteed not to be inside of an escaped byte.
412        unsafe { RelRef::from_str_unchecked(self.0.trim_query().as_str()) }
413    }
414
415    /// See [`UriRef::trim_resource`] for more information.
416    #[must_use = "this returns the trimmed uri as a new slice, \
417                  without modifying the original"]
418    pub fn trim_resource(&self) -> &RelRef {
419        // SAFETY: Trimming on a boundary guaranteed not to be inside of an escaped byte.
420        unsafe { RelRef::from_str_unchecked(self.0.trim_resource().as_str()) }
421    }
422
423    /// Removes any trailing slash that might be at the end of the path, along with
424    /// the query and fragment.
425    ///
426    /// If the path consists of a single slash ("`/`"), then it is not removed.
427    ///
428    /// ## Examples
429    ///
430    /// ```
431    /// use async_coap_uri::prelude::*;
432    /// assert_eq!(rel_ref!("a").trim_trailing_slash(),               rel_ref!("a"));
433    /// assert_eq!(rel_ref!("a/b/c/?blah#frag").trim_trailing_slash(),rel_ref!("a/b/c"));
434    /// assert_eq!(rel_ref!("/").trim_trailing_slash(),               rel_ref!("/"));
435    /// assert_eq!(rel_ref!(unsafe "//").trim_trailing_slash(),       rel_ref!("/"));
436    /// assert_eq!(rel_ref!(unsafe "//foo/?bar").trim_trailing_slash(),rel_ref!(unsafe "//foo"));
437    /// ```
438    ///
439    /// Note that the behavior of this method is different than the behavior for
440    /// [`UriRef::trim_trailing_slash`]\: "`//`" is considered to be a path starting with two
441    /// slashes rather than a network path with an empty authority and an empty path:
442    ///
443    /// ```
444    /// # use async_coap_uri::prelude::*;
445    /// assert_eq!(rel_ref!(unsafe "//").trim_trailing_slash(),    rel_ref!("/"));
446    /// assert_eq!(rel_ref!(unsafe "///").trim_trailing_slash(),   rel_ref!(unsafe "//"));
447    /// assert_eq!(rel_ref!(unsafe "////").trim_trailing_slash(),  rel_ref!(unsafe "///"));
448    /// ```
449    ///
450    #[must_use = "this returns the trimmed uri as a new slice, \
451                  without modifying the original"]
452    pub fn trim_trailing_slash(&self) -> &RelRef {
453        let path_end = self.0.path_end();
454        if path_end > 1 && &self[path_end - 1..path_end] == "/" {
455            unsafe { Self::from_str_unchecked(&self[..path_end - 1]) }
456        } else {
457            self.trim_query()
458        }
459    }
460
461    /// Returns this relative reference slice without any leading slashes.
462    #[must_use = "this returns the trimmed uri as a new slice, \
463                  without modifying the original"]
464    pub fn trim_leading_slashes(&self) -> &RelRef {
465        // SAFETY: Trimming on a boundary guaranteed not to be inside of an escaped byte.
466        unsafe { RelRef::from_str_unchecked(self.trim_start_matches('/')) }
467    }
468
469    /// Returns this relative reference slice without any leading instances of `"./"` or `"/."`.
470    #[must_use = "this returns the trimmed uri as a new slice, \
471                  without modifying the original"]
472    pub fn trim_leading_dot_slashes(&self) -> &RelRef {
473        // SAFETY: Trimming on a boundary guaranteed not to be inside of an escaped byte.
474        unsafe {
475            let mut str_ref = self.as_str();
476
477            while str_ref.starts_with("/./") {
478                str_ref = &str_ref[2..];
479            }
480
481            str_ref = str_ref.trim_start_matches("./");
482            if str_ref == "." {
483                str_ref = &str_ref[..0];
484            }
485            RelRef::from_str_unchecked(str_ref)
486        }
487    }
488
489    /// Returns this relative reference slice without its first path segment.
490    #[must_use = "this returns the leading path item trimmed uri as a new slice, \
491                  without modifying the original"]
492    pub fn trim_leading_path_segment(&self) -> (&str, &RelRef) {
493        let trimmed = self.trim_leading_slashes();
494        if let Some(i) = trimmed.find(|c| c == '/' || c == '?' || c == '#') {
495            match trimmed.as_bytes()[i] {
496                b'/' => (&trimmed[..i], unsafe {
497                    // SAFETY: Trimming on a boundary guaranteed not to
498                    //         be inside of an escaped byte.
499                    RelRef::from_str_unchecked(&trimmed[i + 1..])
500                }),
501                _ => (&trimmed[..i], unsafe {
502                    // SAFETY: Trimming on a boundary guaranteed not to
503                    //         be inside of an escaped byte.
504                    RelRef::from_str_unchecked(&trimmed[i..])
505                }),
506            }
507        } else {
508            (trimmed.as_str(), unsafe {
509                // SAFETY: Trimming on a boundary guaranteed not to
510                //         be inside of an escaped byte.
511                RelRef::from_str_unchecked(&trimmed[trimmed.len()..])
512            })
513        }
514    }
515
516    #[must_use]
517    fn _trim_leading_n_path_segments(&self, n: usize) -> (&str, &RelRef) {
518        let mut next = self;
519
520        for _ in 0..n {
521            next = next.trim_leading_path_segment().1;
522        }
523
524        let i = next.as_ptr() as usize - self.as_ptr() as usize;
525
526        ((&self[..i]).trim_end_matches('/'), next)
527    }
528
529    /// Returns a tuple with a string slice contianing the first `n` path segments and
530    /// a `&RelRef` containing the rest of the relative reference.
531    #[must_use = "this returns the trimmed uri as a new slice, without modifying the original"]
532    pub fn trim_leading_n_path_segments(&self, n: usize) -> (&str, &RelRef) {
533        self.trim_leading_slashes()._trim_leading_n_path_segments(n)
534    }
535
536    /// Attempts to return a shortened version of this relative reference that is
537    /// relative to `base`.
538    #[must_use = "this returns the trimmed uri as a new slice, without modifying the original"]
539    pub fn trim_to_shorten(&self, base: &RelRef) -> Option<&RelRef> {
540        self.0.trim_to_shorten(base.try_as_uri_ref()?)
541    }
542}
543
544/// # Unsafe Methods
545///
546/// `RelRef` needs some unsafe methods in order to function properly. This section is where
547/// they are all located.
548impl RelRef {
549    /// Converts a string slice to a `RelRef` slice without checking
550    /// that the string contains valid URI-Reference.
551    ///
552    /// See the safe version, [`from_str`](#method.from_str), for more information.
553    ///
554    /// ## Safety
555    ///
556    /// This function is unsafe because it does not check that the string passed to
557    /// it is a valid URI-reference. If this constraint is violated, undefined behavior
558    /// results.
559    #[inline(always)]
560    pub unsafe fn from_str_unchecked(s: &str) -> &RelRef {
561        &*(s as *const str as *const RelRef)
562    }
563
564    /// Converts a string slice to a `RelRef` slice without checking
565    /// that the string contains valid URI-Reference; mutable version.
566    ///
567    /// See the immutable version, [`from_str_unchecked`](#method.from_str), for more information.
568    #[inline(always)]
569    pub unsafe fn from_str_unchecked_mut(s: &mut str) -> &mut RelRef {
570        &mut *(s as *mut str as *mut RelRef)
571    }
572
573    /// Returns this slice as a mutable `str` slice.
574    ///
575    /// ## Safety
576    ///
577    /// This is unsafe because it allows you to change the contents of the slice in
578    /// such a way that would make it no longer consistent with the `UriRef`'s promise
579    /// that it can only ever contain a valid URI-reference.
580    #[inline(always)]
581    pub unsafe fn as_mut_str(&mut self) -> &mut str {
582        self.0.as_mut_str()
583    }
584
585    /// Directly converts this `&RelRef` to a `&UriRef`, without performing the
586    /// checks that [`as_uri_ref()`](#method.as_uri_ref) does.
587    ///
588    /// This is unsafe for the reasons described [here](#relref-and-deref).
589    #[inline(always)]
590    pub const unsafe fn as_uri_ref_unchecked(&self) -> &UriRef {
591        &self.0
592    }
593
594    /// Mutable version of [`RelRef::path_as_rel_ref`]. Trims the query and fragment from this
595    /// relative reference, leaving only the path.
596    #[doc(hidden)]
597    #[must_use = "this returns a new slice, without modifying the original"]
598    pub unsafe fn path_as_rel_ref_mut(&mut self) -> &mut RelRef {
599        let i = self.trim_query().len();
600        let str_mut: &mut str = core::mem::transmute(self.as_mut_str());
601
602        RelRef::from_str_unchecked_mut(&mut str_mut[..i])
603    }
604
605    /// See [`UriRef::query_as_rel_ref_mut`] for more information.
606    #[doc(hidden)]
607    #[must_use = "this returns a new slice, without modifying the original"]
608    pub unsafe fn query_as_rel_ref_mut(&mut self) -> Option<&mut RelRef> {
609        self.0.query_as_rel_ref_mut()
610    }
611
612    /// **Experimental**: Similar to [`raw_path_segment_iter()`], but uses the space of the mutable `UriRef`
613    /// to individually unescape the items.
614    ///
615    /// ## Safety
616    ///
617    /// This method is marked as unsafe because the contents of `self` is undefined
618    /// after it terminates. The method can be used safely as long the buffer which
619    /// held `self` is no longer accessed directly. See [`UriUnescapeBuf`] for an example.
620    ///
621    /// [`raw_path_segment_iter()`]: #method.raw_path_segment_iter
622    #[must_use]
623    pub unsafe fn unsafe_path_segment_iter(&mut self) -> impl Iterator<Item = &str> {
624        let path = self.path_as_rel_ref_mut();
625        let is_empty = path.is_empty();
626        let starts_with_slash = path.starts_with("/");
627
628        let mut_bytes = path.as_mut_str().as_bytes_mut();
629
630        let mut ret = mut_bytes.split_mut(|b| *b == b'/').filter_map(|seg| {
631            let seg = std::str::from_utf8_unchecked_mut(seg);
632            if seg == "." {
633                None
634            } else {
635                Some(&*seg.unescape_uri_in_place())
636            }
637        });
638
639        if is_empty || starts_with_slash {
640            // Skip non-existant segments or leading slash
641            let _ = ret.next();
642        }
643
644        ret
645    }
646
647    /// **Experimental**: Similar to [`raw_query_item_iter()`], but uses the space of the mutable `UriRef`
648    /// to individually unescape the query items.
649    ///
650    /// ## Safety
651    ///
652    /// This method is marked as unsafe because the contents of `self` is undefined
653    /// after it terminates. The method can be used safely as long the `&mut UriRef` (and its
654    /// owner) is never directly used again. See [`UriUnescapeBuf`] for an example.
655    ///
656    /// [`raw_query_item_iter()`]: #method.raw_query_item_iter
657    #[must_use]
658    pub unsafe fn unsafe_query_item_iter(&mut self) -> impl Iterator<Item = &str> {
659        let query = self.query_as_rel_ref_mut().unwrap_or(Default::default());
660        let is_empty = query.is_empty();
661        let starts_with_delim = query.starts_with(|c| c == '&' || c == ';');
662
663        let mut mut_bytes = query.as_mut_str().as_bytes_mut();
664
665        if is_empty == false && mut_bytes[0] == b'?' {
666            mut_bytes = &mut mut_bytes[1..];
667        }
668
669        let mut ret = mut_bytes
670            .split_mut(|b| *b == b'&' || *b == b';')
671            .map(|seg| {
672                let seg = std::str::from_utf8_unchecked_mut(seg);
673                &*seg.unescape_uri_in_place()
674            });
675
676        if is_empty || starts_with_delim {
677            // Skip non-existant segments or leading slash
678            let _ = ret.next();
679        }
680
681        ret
682    }
683}
684
685#[cfg(test)]
686mod tests {
687    use super::*;
688
689    #[test]
690    fn path() {
691        assert_eq!(rel_ref!("example/"), rel_ref!("example/").path_as_rel_ref());
692        assert_eq!(
693            rel_ref!(unsafe "http:example.com/blah/"),
694            rel_ref!(unsafe "http:example.com/blah/?q").path_as_rel_ref()
695        );
696    }
697
698    #[test]
699    fn path_segment_iter() {
700        assert_eq!(
701            vec!["example", ""],
702            rel_ref!("example/")
703                .raw_path_segments()
704                .collect::<Vec::<_>>()
705        );
706        assert_eq!(
707            vec!["http:example.com", "blah", ""],
708            rel_ref!(unsafe "http:example.com/blah/?q")
709                .raw_path_segments()
710                .collect::<Vec::<_>>()
711        );
712    }
713
714    #[test]
715    fn avoid_scheme_confusion() {
716        assert_eq!(None, rel_ref!("this/that").colon_in_first_path_segment());
717        assert_eq!(None, rel_ref!("1this:that").colon_in_first_path_segment());
718        assert_eq!(None, rel_ref!("/this:that").colon_in_first_path_segment());
719        assert_eq!(None, rel_ref!("%20this:that").colon_in_first_path_segment());
720        assert_eq!(
721            Some(4),
722            rel_ref!(unsafe "this:that").colon_in_first_path_segment()
723        );
724        assert_eq!(
725            Some(4),
726            rel_ref!(unsafe "th1s:that").colon_in_first_path_segment()
727        );
728        assert_eq!(None, rel_ref!(unsafe "this:that").to_uri_ref_buf().scheme());
729        assert_eq!(None, rel_ref!(unsafe "this:that").try_as_uri_ref());
730        assert_eq!(
731            &rel_ref!(unsafe "this:that").to_uri_ref_buf(),
732            rel_ref!("this%3Athat"),
733        );
734    }
735
736    #[test]
737    fn trim_leading_path_segment() {
738        assert_eq!(
739            ("example", rel_ref!("")),
740            rel_ref!("example/").trim_leading_path_segment()
741        );
742        assert_eq!(
743            ("example", rel_ref!("")),
744            rel_ref!("/example/").trim_leading_path_segment()
745        );
746        assert_eq!(
747            ("a", rel_ref!("b/c/d/")),
748            rel_ref!("a/b/c/d/").trim_leading_path_segment()
749        );
750        assert_eq!(
751            ("a", rel_ref!("?query")),
752            rel_ref!("a?query").trim_leading_path_segment()
753        );
754        assert_eq!(
755            ("a", rel_ref!("#frag")),
756            rel_ref!("a#frag").trim_leading_path_segment()
757        );
758        assert_eq!(
759            ("fool:ish", rel_ref!("/thoughts?")),
760            rel_ref!(unsafe "fool:ish//thoughts?").trim_leading_path_segment()
761        );
762        assert_eq!(("", rel_ref!("")), rel_ref!("").trim_leading_path_segment());
763    }
764
765    #[test]
766    fn trim_leading_n_path_segments() {
767        assert_eq!(
768            ("", rel_ref!("a/b/c/d")),
769            rel_ref!("a/b/c/d").trim_leading_n_path_segments(0)
770        );
771        assert_eq!(
772            ("a", rel_ref!("b/c/d")),
773            rel_ref!("a/b/c/d").trim_leading_n_path_segments(1)
774        );
775        assert_eq!(
776            ("a/b", rel_ref!("c/d")),
777            rel_ref!("a/b/c/d").trim_leading_n_path_segments(2)
778        );
779        assert_eq!(
780            ("a/b/c", rel_ref!("d")),
781            rel_ref!("a/b/c/d").trim_leading_n_path_segments(3)
782        );
783        assert_eq!(
784            ("a/b/c/d", rel_ref!("")),
785            rel_ref!("a/b/c/d").trim_leading_n_path_segments(4)
786        );
787        assert_eq!(
788            ("a/b/c/d", rel_ref!("")),
789            rel_ref!("a/b/c/d").trim_leading_n_path_segments(5)
790        );
791
792        assert_eq!(
793            ("a/b/c", rel_ref!("d?blah")),
794            rel_ref!("a/b/c/d?blah").trim_leading_n_path_segments(3)
795        );
796        assert_eq!(
797            ("a/b/c/d", rel_ref!("?blah")),
798            rel_ref!("a/b/c/d?blah").trim_leading_n_path_segments(4)
799        );
800        assert_eq!(
801            ("a/b/c/d", rel_ref!("?blah")),
802            rel_ref!("a/b/c/d?blah").trim_leading_n_path_segments(5)
803        );
804
805        assert_eq!(
806            ("a/b/c", rel_ref!("d?blah")),
807            rel_ref!("/a/b/c/d?blah").trim_leading_n_path_segments(3)
808        );
809    }
810}