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}