Skip to main content

lwuri/
any_uri_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 core::fmt::Display;
18
19#[cfg(feature = "alloc")]
20use core::ops::Deref;
21#[cfg(feature = "alloc")]
22use alloc::string::{String, ToString};
23
24/// Trait for objects that represent logical URI-references. Useful for generic programming.
25///
26pub trait AnyUriRef {
27    /// Returns a `UriRawComponents` instance which contains all of the components for this
28    /// URI reference.
29    ///
30    /// This is the only method that is required to be implemented---all other methods have
31    /// defaults in place which use this method, but they may be inefficient.
32    #[must_use]
33    fn components(&self) -> UriRawComponents<'_>;
34
35    /// Returns true if the underlying URI-reference is actually the empty reference.
36    #[must_use]
37    fn is_empty(&self) -> bool {
38        self.components().is_empty()
39    }
40
41    /// Gets the [`UriType`] of the underlying URI-reference.
42    ///
43    /// [`UriType`]: enum.UriType.html
44    #[must_use]
45    fn uri_type(&self) -> UriType {
46        self.components().uri_type()
47    }
48
49    /// Creates a new [`UriRefBuf`] from this [`AnyUriRef`].
50    ///
51    /// The default implementation uses [`AnyUriRef::write_to_unsafe`] to render out
52    /// the content of the URI-reference.
53    #[cfg(feature = "alloc")]
54    #[must_use]
55    fn to_uri_ref_buf(&self) -> UriRefBuf {
56        unsafe { UriRefBuf::from_string_unchecked(self.display().to_string()) }
57    }
58
59    /// Hook for custom URI serialization implementation via [`AnyUriRefExt::write_to`].
60    /// **Override with care!**
61    ///
62    /// In general, you should never need to call this method directly: use
63    /// [`AnyUriRefExt::write_to`] instead. See the documentation for [`AnyUriRefExt::write_to`]
64    /// for more details on usage.
65    ///
66    /// This method is marked as `unsafe`. However, *only overriding the default implementation is
67    /// considered unsafe*: calling the method is actually safe. You can indirectly call this method
68    /// from safe code by calling [`AnyUriRefExt::write_to`], which is a non-overridable wrapper
69    /// around this method.
70    ///
71    /// # Override Safety
72    ///
73    /// Calling this method is not unsafe, *but overriding it is*! The underlying
74    /// guarantee is that the written URI reference SHALL be well-formed. Lots of
75    /// code depends on this guarantee in order to avoid undefined behavior.
76    ///
77    /// Bottom line: **If this method's implementation writes out something that is
78    /// not a valid, well-formed URI-reference, the resulting behavior is undefined.**
79    unsafe fn write_to_unsafe<T: core::fmt::Write + ?Sized>(
80        &self,
81        write: &mut T,
82    ) -> Result<(), core::fmt::Error> {
83        self.components().write_to(write)
84    }
85}
86
87/// Extension trait for [`AnyUriRef`] that provides methods that cannot be overridden from
88/// their default implementations.
89///
90/// This trait is automatically implemented for all types that implement [`AnyUriRef`].
91pub trait AnyUriRefExt: AnyUriRef {
92    /// Wraps this `AnyUriRef` instance in a [`UriDisplay`] object for use with formatting
93    /// macros like `write!` and `format!`.
94    ///
95    /// The resulting instance will ultimately use the [`AnyUriRef::write_to_unsafe`] method
96    /// to render the URI-reference.
97    ///
98    /// This method is similar to the [`display`][display-path] method on [`std::path::Path`].
99    ///
100    /// [display-path]: std::path::Path::display
101    ///
102    /// ## Example
103    ///
104    /// ```
105    /// use lwuri::prelude::*;
106    ///
107    /// let uri_ref = uri_ref!("http://example.com/");
108    ///
109    /// println!("uri_ref = {}", uri_ref.display());
110    /// ```
111    ///
112    /// [`UriDisplay`]: struct.UriDisplay.html
113    #[must_use]
114    fn display(&self) -> UriDisplay<'_, Self> {
115        UriDisplay(self)
116    }
117
118    /// Serializes this URI to anything implementing [`core::fmt::Write`].
119    ///
120    /// The purpose of this method is to provide a uniform way for a type that implements
121    /// [`AnyUriRef`] to write out a well-formed URI; without making any assumptions on what
122    /// that type might write out for [`core::fmt::Display`] (or even if it implements it at all).
123    ///
124    /// See the documentation for [`display`](AnyUriRefExt::display) and [`UriDisplay`] for
125    /// examples of usage.
126    ///
127    /// You can't change the implementation of this method directly, this method simply calls
128    /// [`AnyUriRef::write_to_unsafe`], which can be overridden (albeit unsafely). Be sure to
129    /// follow the warnings in the associated documentation.
130    fn write_to<T: core::fmt::Write + ?Sized>(
131        &self,
132        write: &mut T,
133    ) -> Result<(), core::fmt::Error> {
134        unsafe { self.write_to_unsafe(write) }
135    }
136
137    /// Writes out to a [`core::fmt::Write`] instance the result of performing URI resolution
138    /// against `target`, with `self` being the base URI.
139    fn write_resolved<T: core::fmt::Write + ?Sized, D: AnyUriRef + ?Sized>(
140        &self,
141        target: &D,
142        f: &mut T,
143    ) -> Result<(), ResolveError> {
144
145        if target.is_empty() {
146            self.write_to(f)?;
147            return Ok(());
148        }
149
150        let target_type = target.uri_type();
151
152        let target_components = target.components();
153
154        let base_type = self.uri_type();
155
156        // Handle some exceptions.
157        if base_type.cannot_be_a_base() {
158            match target_type {
159                UriType::Fragment => {
160                    self.components().trim_fragment().write_to(f)?;
161                    target.write_to(f)?;
162                    return Ok(());
163                }
164                UriType::Query => {
165                    self.components().trim_query().write_to(f)?;
166                    target.write_to(f)?;
167                    return Ok(());
168                }
169                x if x.is_ietf_rfc3986_relative_reference() => {
170                    return Err(ResolveError::CannotBeABase);
171                }
172                _ => (),
173            }
174        }
175
176        if target_components.scheme.is_some() {
177            target.write_to(f)?;
178            return Ok(());
179        }
180
181        let mut components = self.components();
182
183        if target_components.authority.is_some() {
184            components.authority = target_components.authority;
185        }
186
187        // Target fragment always gets used.
188        components.fragment = target_components.fragment;
189        if target_components.query.is_some() {
190            components.query = target_components.query;
191        } else if !target_components.path.is_empty() || target_components.authority.is_some() {
192            components.query = None;
193        }
194
195        if let Some(scheme) = components.scheme {
196            f.write_str(scheme)?;
197            f.write_char(':')?;
198        }
199
200        if let Some(authority) = components.authority {
201            f.write_str("//")?;
202            f.write_str(authority)?;
203        }
204
205        let mut base_path = components.path_as_rel_ref();
206        let target_path = target_components.path_as_rel_ref();
207
208        if !target_path.is_empty() || !target_type.has_absolute_path() {
209            let target_starts_with_slash = target_path.starts_with('/');
210            let base_starts_with_slash = base_path.starts_with('/');
211
212            if target_type.has_absolute_path() {
213                if base_starts_with_slash {
214                    base_path = irel_ref!("");
215                } else {
216                    base_path = irel_ref!("/");
217                }
218            } else if !target_path.is_empty() {
219                base_path = base_path.trim_resource();
220            }
221
222            let mut out_path_vec = heapless::Vec::<&str, 64>::new();
223
224            let seg_iter = base_path
225                .raw_path_segments()
226                .chain(target_path.raw_path_segments());
227
228            let path_will_be_absolute = target_starts_with_slash
229                || base_starts_with_slash
230                || (base_type.has_absolute_path() && !target_path.is_empty());
231
232            for seg in seg_iter {
233                match seg {
234                    "." => {
235                        let last = out_path_vec.last().copied();
236
237                        if last.map(str::is_empty) == Some(false) {
238                            out_path_vec.push("").map_err(|_| ResolveError::PathTooLong)?;
239                        }
240                        continue;
241                    }
242                    ".." => {
243                        let mut last = out_path_vec.pop();
244
245                        if last == Some("") {
246                            last = out_path_vec.pop();
247                        }
248
249                        match (last, path_will_be_absolute, out_path_vec.is_empty()) {
250                            (Some("."), false, _) => {
251                                out_path_vec.push("..").map_err(|_| ResolveError::PathTooLong)?;
252                            }
253                            (Some(".."), false, _) => {
254                                out_path_vec.push("..").map_err(|_| ResolveError::PathTooLong)?;
255                                out_path_vec.push("..").map_err(|_| ResolveError::PathTooLong)?;
256                            }
257                            (Some(_), true, _) => {
258                                out_path_vec.push("").map_err(|_| ResolveError::PathTooLong)?;
259                            }
260                            (Some(_), false, false) => {
261                                out_path_vec.push("").map_err(|_| ResolveError::PathTooLong)?;
262                            }
263                            (Some(_), false, true) => {
264                                out_path_vec.push(".").map_err(|_| ResolveError::PathTooLong)?;
265                            }
266                            (None, _, _) => (),
267                        };
268                    }
269                    seg => {
270                        match out_path_vec.last().copied() {
271                            Some(".") if seg.is_empty() => continue,
272                            Some(".") | Some("") => {
273                                out_path_vec.pop();
274                            }
275                            _ => (),
276                        };
277                        out_path_vec.push(seg).map_err(|_| ResolveError::PathTooLong)?;
278                    }
279                }
280            }
281
282            if path_will_be_absolute {
283                f.write_char('/')?;
284            }
285
286            for (n, seg) in out_path_vec.into_iter().enumerate() {
287                if n != 0 {
288                    f.write_char('/')?;
289                }
290                f.write_str(seg)?;
291            }
292        }
293
294        if let Some(query) = components.query {
295            f.write_char('?')?;
296            f.write_str(query)?;
297        }
298
299        if let Some(fragment) = components.fragment {
300            f.write_char('#')?;
301            f.write_str(fragment)?;
302        }
303
304        Ok(())
305    }
306
307    /// Creates a new [`UriRefBuf`] that contains the result of performing URI resolution with
308    /// `dest`.
309    #[cfg(feature = "alloc")]
310    fn resolved<T: AnyUriRef + ?Sized>(&self, dest: &T) -> Result<UriRefBuf, ResolveError> {
311        if dest.is_empty() {
312            return Ok(self.to_uri_ref_buf());
313        }
314
315        let mut ret = String::new();
316
317        self.write_resolved(dest, &mut ret)?;
318
319        // SAFETY: `write_resolved` is guaranteed to write well-formed UriRefs.
320        Ok(unsafe { UriRefBuf::from_string_unchecked(ret) })
321    }
322}
323
324/// Blanket implementation of `AnyUriRefExt` for all `AnyUriRef` instances.
325impl<T: AnyUriRef + ?Sized> AnyUriRefExt for T {}
326
327/// Blanket implementation for Copy-On-Write types.
328#[cfg(feature = "alloc")]
329impl<'a, T: AnyUriRef + Clone + ?Sized> AnyUriRef for Cow<'a, T> {
330    fn components(&self) -> UriRawComponents<'_> {
331        self.deref().components()
332    }
333
334    fn is_empty(&self) -> bool {
335        self.deref().is_empty()
336    }
337
338    fn uri_type(&self) -> UriType {
339        self.deref().uri_type()
340    }
341
342    #[cfg(feature = "alloc")]
343    fn to_uri_ref_buf(&self) -> UriRefBuf {
344        self.deref().to_uri_ref_buf()
345    }
346
347    unsafe fn write_to_unsafe<W: core::fmt::Write + ?Sized>(
348        &self,
349        write: &mut W,
350    ) -> Result<(), core::fmt::Error> {
351        self.deref().write_to_unsafe(write)
352    }
353}
354
355/// Helper class to assist with using [`AnyUriRef`] with formatters; instantiated by
356/// [`AnyUriRefExt::display`].
357///
358/// This type is similar to [`std::path::Display`].
359#[derive(Debug, Copy, Clone)]
360pub struct UriDisplay<'a, T: AnyUriRef + ?Sized>(&'a T);
361
362impl<'a, T: AnyUriRef + ?Sized> Display for UriDisplay<'a, T> {
363    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
364        self.0.write_to(f)
365    }
366}
367
368#[cfg(test)]
369mod test {
370    use super::*;
371    #[cfg(feature = "alloc")]
372    use alloc::{vec, vec::Vec, string::ToString, borrow::ToOwned};
373
374    #[test]
375    fn resolve_simple() {
376        let uri_test_table = vec![
377            (
378                "http://x/a/b/c",
379                "/abs-path",
380                Some(iuri_ref!("http://x/abs-path")),
381            ),
382            (
383                "http://x/a/b/c",
384                "f/s?c",
385                Some(iuri_ref!("http://x/a/b/f/s?c")),
386            ),
387            (
388                "http://x/a/b/c",
389                "/abs-path",
390                Some(iuri_ref!("http://x/abs-path")),
391            ),
392            (
393                "http://x/a/b/c",
394                "path",
395                Some(iuri_ref!("http://x/a/b/path")),
396            ),
397            (
398                "http://x/a/b/c/",
399                "path",
400                Some(iuri_ref!("http://x/a/b/c/path")),
401            ),
402            (
403                "http://x/a/b/c/",
404                "//y/d/e/f/",
405                Some(iuri_ref!("http://y/d/e/f/")),
406            ),
407            ("http://x/a/b/c", "?", Some(iuri_ref!("http://x/a/b/c?"))),
408            ("http://x", "a/b/c", Some(iuri_ref!("http://x/a/b/c"))),
409            ("http://x", "/a/b/c", Some(iuri_ref!("http://x/a/b/c"))),
410            ("http://x/", "a/b/c", Some(iuri_ref!("http://x/a/b/c"))),
411            ("http://x/a/b/c", "coap://x", Some(iuri_ref!("coap://x"))),
412        ];
413
414        for (a, b, c) in uri_test_table {
415            let uri_a = UriRef::from_str(a).expect(a);
416            let uri_b = UriRef::from_str(b).expect(b);
417            assert_eq!(
418                uri_a.resolved(uri_b).ok(),
419                c.map(|x| x.to_owned()),
420                "uri_a.resolved(): a:{} b:{} c:{:?}",
421                a,
422                b,
423                c
424            );
425        }
426    }
427
428    #[test]
429    fn resolve_relative_base() {
430        let uri_test_table = vec![
431            ("b/c/d;p?q", "g:h", Some(iuri_ref!("g:h"))),
432            ("b/c/d;p?q", "g", Some(iuri_ref!("b/c/g"))),
433            ("b/c/d;p?q", "./g", Some(iuri_ref!("b/c/g"))),
434            ("b/c/d;p?q", "g/", Some(iuri_ref!("b/c/g/"))),
435            ("b/c/d;p?q", "/g", Some(iuri_ref!("/g"))),
436            ("b/c/d;p?q", "//g", Some(iuri_ref!("//g"))),
437            ("b/c/d;p?q", "?y", Some(iuri_ref!("b/c/d;p?y"))),
438            ("b/c/d;p?q", "g?y", Some(iuri_ref!("b/c/g?y"))),
439            ("b/c/d;p?q", "#s", Some(iuri_ref!("b/c/d;p?q#s"))),
440            ("b/c/d;p?q", "g#s", Some(iuri_ref!("b/c/g#s"))),
441            ("b/c/d;p?q", "g?y#s", Some(iuri_ref!("b/c/g?y#s"))),
442            ("b/c/d;p?q", ";x", Some(iuri_ref!("b/c/;x"))),
443            ("b/c/d;p?q", "g;x", Some(iuri_ref!("b/c/g;x"))),
444            ("b/c/d;p?q", "g;x?y#s", Some(iuri_ref!("b/c/g;x?y#s"))),
445            ("b/c/d;p?q", "", Some(iuri_ref!("b/c/d;p?q"))),
446            ("b/c/d;p?q", ".", Some(iuri_ref!("b/c/"))),
447            ("b/c/d;p?q", "./", Some(iuri_ref!("b/c/"))),
448            ("b/c/d;p?q", "/./g", Some(iuri_ref!("/g"))),
449            ("b/c/d;p?q", "g.", Some(iuri_ref!("b/c/g."))),
450            ("b/c/d;p?q", ".g", Some(iuri_ref!("b/c/.g"))),
451            ("b/c/d;p?q", "g..", Some(iuri_ref!("b/c/g.."))),
452            ("b/c/d;p?q", "..g", Some(iuri_ref!("b/c/..g"))),
453            ("b/c/d;p?q", "g?y/./x", Some(iuri_ref!("b/c/g?y/./x"))),
454            ("b/c/d;p?q", "g?y/../x", Some(iuri_ref!("b/c/g?y/../x"))),
455            ("b/c/d;p?q", "g#s/./x", Some(iuri_ref!("b/c/g#s/./x"))),
456            ("b/c/d;p?q", "g#s/../x", Some(iuri_ref!("b/c/g#s/../x"))),
457            ("b/c/d;p?q", "..", Some(iuri_ref!("b/"))),
458            ("b/c/d;p?q", "../", Some(iuri_ref!("b/"))),
459            ("b/c/d;p?q", "../g", Some(iuri_ref!("b/g"))),
460            ("b/c/d;p?q", "../..", Some(iuri_ref!("."))),
461            ("b/c/d;p?q", "../../", Some(iuri_ref!("."))),
462            ("b/c/d;p?q", "../../g", Some(iuri_ref!("g"))),
463            ("b/c/d;p?q", "../../../g", Some(iuri_ref!("../g"))),
464            ("b/c/d;p?q", "../../../../g", Some(iuri_ref!("../../g"))),
465            ("b/c/d;p?q", "/../g", Some(iuri_ref!("/g"))),
466            ("b/c/d;p?q", "./../g", Some(iuri_ref!("b/g"))),
467            ("b/c/d;p?q", "./g/.", Some(iuri_ref!("b/c/g/"))),
468            ("b/c/d;p?q", "g/./h", Some(iuri_ref!("b/c/g/h"))),
469            ("b/c/d;p?q", "g/../h", Some(iuri_ref!("b/c/h"))),
470            ("b/c/d;p?q", "g;x=1/./y", Some(iuri_ref!("b/c/g;x=1/y"))),
471            ("b/c/d;p?q", "g;x=1/../y", Some(iuri_ref!("b/c/y"))),
472        ];
473
474        for (a, b, c) in uri_test_table {
475            let uri_a = UriRef::from_str(a).expect(a);
476            let uri_b = UriRef::from_str(b).expect(b);
477            assert_eq!(
478                uri_a.resolved(uri_b).ok(),
479                c.map(|x| x.to_owned()),
480                "uri_a.resolved(): a:{} b:{} c:{:?}",
481                a,
482                b,
483                c
484            );
485        }
486    }
487
488    #[test]
489    fn resolve_cannot_be_a_base() {
490        let uri_test_table = vec![
491            ("s:123", "/a/b/c", None),
492            ("s:123", "//a/b/c", None),
493            ("s:123", ".", None),
494            ("s:123", "", Some(iuri_ref!("s:123"))),
495            ("s:123", "?q=123", Some(iuri_ref!("s:123?q=123"))),
496            ("s:123", "#frag", Some(iuri_ref!("s:123#frag"))),
497            ("s:123", "#frag", Some(iuri_ref!("s:123#frag"))),
498            ("s:123", "file:/d/e/f", Some(iuri_ref!("file:/d/e/f"))),
499        ];
500
501        for (a, b, c) in uri_test_table {
502            let uri_a = UriRef::from_str(a).expect(a);
503            let uri_b = UriRef::from_str(b).expect(b);
504            assert_eq!(
505                uri_a.resolved(uri_b).ok(),
506                c.map(|x| x.to_owned()),
507                "uri_a.resolved(): a:{} b:{} c:{:?}",
508                a,
509                b,
510                c
511            );
512        }
513    }
514
515    #[test]
516    fn resolve_no_authority() {
517        let uri_test_table = vec![
518            ("file:/d/e/f", "//a/b/c", Some(iuri_ref!("file://a/b/c"))),
519            ("file:/d/e/f", "g", Some(iuri_ref!("file:/d/e/g"))),
520        ];
521
522        for (a, b, c) in uri_test_table {
523            let uri_a = UriRef::from_str(a).expect(a);
524            let uri_b = UriRef::from_str(b).expect(b);
525            assert_eq!(
526                uri_a.resolved(uri_b).ok(),
527                c.map(|x| x.to_owned()),
528                "uri_a.resolved(): a:{} b:{} c:{:?}",
529                a,
530                b,
531                c
532            );
533        }
534    }
535
536    #[test]
537    fn resolve_rfc3986_simple() {
538        let uri_test_table = vec![
539            // The following test vectors came directly from RFC3986
540            ("http://a/b/c/d;p?q", "g:h", Some(iuri_ref!("g:h"))),
541            ("http://a/b/c/d;p?q", "g", Some(iuri_ref!("http://a/b/c/g"))),
542            (
543                "http://a/b/c/d;p?q",
544                "./g",
545                Some(iuri_ref!("http://a/b/c/g")),
546            ),
547            (
548                "http://a/b/c/d;p?q",
549                "g/",
550                Some(iuri_ref!("http://a/b/c/g/")),
551            ),
552            ("http://a/b/c/d;p?q", "/g", Some(iuri_ref!("http://a/g"))),
553            ("http://a/b/c/d;p?q", "//g", Some(iuri_ref!("http://g"))),
554            (
555                "http://a/b/c/d;p?q",
556                "?y",
557                Some(iuri_ref!("http://a/b/c/d;p?y")),
558            ),
559            (
560                "http://a/b/c/d;p?q",
561                "g?y",
562                Some(iuri_ref!("http://a/b/c/g?y")),
563            ),
564            (
565                "http://a/b/c/d;p?q",
566                "#s",
567                Some(iuri_ref!("http://a/b/c/d;p?q#s")),
568            ),
569            (
570                "http://a/b/c/d;p?q",
571                "g#s",
572                Some(iuri_ref!("http://a/b/c/g#s")),
573            ),
574            (
575                "http://a/b/c/d;p?q",
576                "g?y#s",
577                Some(iuri_ref!("http://a/b/c/g?y#s")),
578            ),
579            (
580                "http://a/b/c/d;p?q",
581                ";x",
582                Some(iuri_ref!("http://a/b/c/;x")),
583            ),
584            (
585                "http://a/b/c/d;p?q",
586                "g;x",
587                Some(iuri_ref!("http://a/b/c/g;x")),
588            ),
589            (
590                "http://a/b/c/d;p?q",
591                "g;x?y#s",
592                Some(iuri_ref!("http://a/b/c/g;x?y#s")),
593            ),
594            (
595                "http://a/b/c/d;p?q",
596                "",
597                Some(iuri_ref!("http://a/b/c/d;p?q")),
598            ),
599            ("http://a/b/c/d;p?q", ".", Some(iuri_ref!("http://a/b/c/"))),
600            ("http://a/b/c/d;p?q", "./", Some(iuri_ref!("http://a/b/c/"))),
601            ("http://a/b/c/d;p?q", "/./g", Some(iuri_ref!("http://a/g"))),
602            (
603                "http://a/b/c/d;p?q",
604                "g.",
605                Some(iuri_ref!("http://a/b/c/g.")),
606            ),
607            (
608                "http://a/b/c/d;p?q",
609                ".g",
610                Some(iuri_ref!("http://a/b/c/.g")),
611            ),
612            (
613                "http://a/b/c/d;p?q",
614                "g..",
615                Some(iuri_ref!("http://a/b/c/g..")),
616            ),
617            (
618                "http://a/b/c/d;p?q",
619                "..g",
620                Some(iuri_ref!("http://a/b/c/..g")),
621            ),
622            (
623                "http://a/b/c/d;p?q",
624                "g?y/./x",
625                Some(iuri_ref!("http://a/b/c/g?y/./x")),
626            ),
627            (
628                "http://a/b/c/d;p?q",
629                "g?y/../x",
630                Some(iuri_ref!("http://a/b/c/g?y/../x")),
631            ),
632            (
633                "http://a/b/c/d;p?q",
634                "g#s/./x",
635                Some(iuri_ref!("http://a/b/c/g#s/./x")),
636            ),
637            (
638                "http://a/b/c/d;p?q",
639                "g#s/../x",
640                Some(iuri_ref!("http://a/b/c/g#s/../x")),
641            ),
642        ];
643
644        for (a, b, c) in uri_test_table {
645            let uri_a = UriRef::from_str(a).expect(a);
646            let uri_b = UriRef::from_str(b).expect(b);
647            assert_eq!(
648                uri_a.resolved(uri_b).ok(),
649                c.map(|x| x.to_owned()),
650                "uri_a.resolved(): a:{} b:{} c:{:?}",
651                a,
652                b,
653                c
654            );
655        }
656    }
657
658    #[test]
659    fn resolve_rfc3986_dot_dot() {
660        let uri_test_table = vec![
661            // The following test vectors came directly from RFC3986
662            ("http://a/b/c/d;p?q", "..", Some(iuri_ref!("http://a/b/"))),
663            ("http://a/b/c/d;p?q", "../", Some(iuri_ref!("http://a/b/"))),
664            (
665                "http://a/b/c/d;p?q",
666                "../g",
667                Some(iuri_ref!("http://a/b/g")),
668            ),
669            ("http://a/b/c/d;p?q", "../..", Some(iuri_ref!("http://a/"))),
670            ("http://a/b/c/d;p?q", "../../", Some(iuri_ref!("http://a/"))),
671            (
672                "http://a/b/c/d;p?q",
673                "../../g",
674                Some(iuri_ref!("http://a/g")),
675            ),
676            (
677                "http://a/b/c/d;p?q",
678                "../../../g",
679                Some(iuri_ref!("http://a/g")),
680            ),
681            (
682                "http://a/b/c/d;p?q",
683                "../../../../g",
684                Some(iuri_ref!("http://a/g")),
685            ),
686            ("http://a/b/c/d;p?q", "/../g", Some(iuri_ref!("http://a/g"))),
687            (
688                "http://a/b/c/d;p?q",
689                "./../g",
690                Some(iuri_ref!("http://a/b/g")),
691            ),
692            (
693                "http://a/b/c/d;p?q",
694                "./g/.",
695                Some(iuri_ref!("http://a/b/c/g/")),
696            ),
697            (
698                "http://a/b/c/d;p?q",
699                "g/./h",
700                Some(iuri_ref!("http://a/b/c/g/h")),
701            ),
702            (
703                "http://a/b/c/d;p?q",
704                "g/../h",
705                Some(iuri_ref!("http://a/b/c/h")),
706            ),
707            (
708                "http://a/b/c/d;p?q",
709                "g;x=1/./y",
710                Some(iuri_ref!("http://a/b/c/g;x=1/y")),
711            ),
712            (
713                "http://a/b/c/d;p?q",
714                "g;x=1/../y",
715                Some(iuri_ref!("http://a/b/c/y")),
716            ),
717        ];
718
719        for (a, b, c) in uri_test_table {
720            let uri_a = UriRef::from_str(a).expect(a);
721            let uri_b = UriRef::from_str(b).expect(b);
722            assert_eq!(
723                uri_a.resolved(uri_b).ok(),
724                c.map(|x| x.to_owned()),
725                "uri_a.resolved(): a:{} b:{} c:{:?}",
726                a,
727                b,
728                c
729            );
730        }
731    }
732}