async_coap_uri/
uri_buf.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::fmt::Write;
18use std::ops::Deref;
19
20/// Sized, heap-allocated string type guaranteed to contain a well-formed [IETF-RFC3986] URI
21/// or [network path](enum.UriType.html#variant.NetworkPath).
22///
23/// The unsized counterpart is [`Uri`](crate::Uri).
24///
25/// This type implements [`std::ops::Deref<Uri>`], so you can also use all of the
26/// methods from [`Uri`] on this type.
27///
28/// [IETF-RFC3986]: https://tools.ietf.org/html/rfc3986
29#[derive(Clone, Eq, Hash)]
30pub struct UriBuf(pub(super) UriRefBuf);
31
32impl_uri_buf_traits!(UriBuf, Uri);
33
34impl Deref for UriBuf {
35    type Target = Uri;
36
37    fn deref(&self) -> &Self::Target {
38        self.as_uri()
39    }
40}
41
42impl AsRef<Uri> for UriBuf {
43    fn as_ref(&self) -> &Uri {
44        self.as_uri()
45    }
46}
47
48impl From<&Uri> for UriBuf {
49    fn from(x: &Uri) -> Self {
50        x.to_uri_buf()
51    }
52}
53
54impl std::fmt::Display for UriBuf {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
56        self.write_to(f)
57    }
58}
59
60/// # Unsafe Methods
61///
62/// `UriBuf` needs some unsafe methods in order to function properly. This section is where
63/// they are all located.
64impl UriBuf {
65    /// Unchecked version of [`UriBuf::from_string`].
66    ///
67    /// # Safety
68    ///
69    /// This method is marked as unsafe because it allows you to construct a `UriBuf` with
70    /// a value that is not a well-formed URI reference.
71    #[inline(always)]
72    pub unsafe fn from_string_unchecked(s: String) -> UriBuf {
73        UriBuf(UriRefBuf::from_string_unchecked(s))
74    }
75}
76
77impl UriBuf {
78    /// Creates a new `UriBuf` from *unescaped* component values.
79    pub fn new<Sch, Hos, Pat, Que, Frg>(
80        scheme: Sch,
81        host: Hos,
82        port: Option<u16>,
83        path: Pat,
84        query: Option<Que>,
85        fragment: Option<Frg>,
86    ) -> UriBuf
87    where
88        Sch: Into<String>,
89        Hos: AsRef<str>,
90        Pat: AsRef<str>,
91        Que: AsRef<str>,
92        Frg: AsRef<str>,
93    {
94        let mut ret: String = Self::from_scheme_host_port(scheme, host, port).into();
95
96        let mut path = path.as_ref();
97
98        if path.starts_with('/') {
99            path = &path[1..];
100        }
101
102        let path_segment_iter =
103            path.split('/')
104                .filter_map(|seg| if seg == "." { None } else { Some(seg) });
105
106        for seg in path_segment_iter {
107            ret.push('/');
108            ret.extend(seg.escape_uri());
109        }
110
111        if let Some(query) = query {
112            let mut first = true;
113            ret.push('?');
114            for seg in query.as_ref().split(|c| c == '&' || c == ';') {
115                if first {
116                    first = false;
117                } else {
118                    ret.push('&');
119                }
120                ret.extend(seg.escape_uri().for_query());
121            }
122        }
123
124        if let Some(fragment) = fragment {
125            ret.push('#');
126            ret.extend(fragment.as_ref().escape_uri().for_fragment());
127        }
128
129        unsafe { Self::from_string_unchecked(ret) }
130    }
131
132    /// Constructs a `UriBuf` from a scheme and authority.
133    ///
134    /// The authority should not be percent encoded. If the given scheme contains invalid
135    /// characters, this method will panic.
136    ///
137    /// # Example
138    ///
139    /// ```
140    /// use async_coap_uri::prelude::*;
141    /// let authority = "user@[2001:0db8:85a3::1%en2]:8080";
142    ///
143    /// let uri_buf = UriBuf::from_scheme_authority("http", authority);
144    ///
145    /// assert_eq!(uri_buf, uri!("http://user@[2001:0db8:85a3::1%25en2]:8080"));
146    ///
147    /// ```
148    pub fn from_scheme_authority<Sch, Aut>(scheme: Sch, authority: Aut) -> UriBuf
149    where
150        Sch: Into<String>,
151        Aut: AsRef<str>,
152    {
153        let mut ret = scheme.into();
154
155        // Make sure that the scheme is proper. This is likely a string constant,
156        // so we go ahead and panic if we detect that it is busted.
157        assert_eq!(
158            ret.find(|c: char| !(c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.')),
159            None,
160            "Scheme contains invalid characters: {:?}",
161            ret
162        );
163
164        ret.push_str("://");
165
166        ret.extend(authority.as_ref().escape_uri().for_authority());
167
168        unsafe { Self::from_string_unchecked(ret) }
169    }
170
171    /// Constructs a network path from a host and a relative reference.
172    ///
173    /// # Example
174    ///
175    /// ```
176    /// use async_coap_uri::prelude::*;
177    /// let host = "example.com";
178    /// let rel_ref = rel_ref!("./foobar?q");
179    ///
180    /// let uri_buf = UriBuf::from_host_rel_ref(host, rel_ref);
181    ///
182    /// assert_eq!(uri_buf, uri!("//example.com/foobar?q"));
183    ///
184    /// ```
185    pub fn from_host_rel_ref<Hos, RR>(host: Hos, rel_ref: RR) -> UriBuf
186    where
187        Hos: AsRef<str>,
188        RR: AsRef<RelRef>,
189    {
190        let host = host.as_ref();
191        let rel_ref = rel_ref
192            .as_ref()
193            .trim_leading_dot_slashes()
194            .trim_leading_slashes();
195
196        // UNWRAP-SAFETY: This is safe because we are fully
197        // escaping the host and we already know rel_ref to
198        // be well-formed.
199        uri_format!("//{}/{}", host.escape_uri().full(), rel_ref).unwrap()
200    }
201
202    /// Constructs a `UriBuf` from a scheme, host and an optional port number.
203    ///
204    /// The host should not be percent encoded. If the given scheme contains invalid
205    /// characters, this method will panic.
206    pub fn from_scheme_host_port<Sch, Hos>(scheme: Sch, host: Hos, port: Option<u16>) -> UriBuf
207    where
208        Sch: Into<String>,
209        Hos: AsRef<str>,
210    {
211        let mut ret = scheme.into();
212
213        // Make sure that the scheme is proper. This is likely a string constant,
214        // so we go ahead and panic if we detect that it is busted.
215        assert_eq!(
216            ret.find(|c: char| !(c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.')),
217            None,
218            "Scheme contains invalid characters: {:?}",
219            ret
220        );
221
222        ret.push_str("://");
223
224        let mut host = host.as_ref();
225
226        // Trim enclosing brackets.
227        if host.starts_with('[') && host.ends_with(']') {
228            host = &host[1..host.len()];
229        }
230
231        if host.find(':').is_some() {
232            ret.push('[');
233            ret.extend(host.escape_uri());
234            ret.push(']');
235        }
236
237        if let Some(port) = port {
238            write!(ret, ":{}", port).unwrap();
239        }
240
241        unsafe { Self::from_string_unchecked(ret) }
242    }
243
244    /// Attempts to create a new [`UriBuf`] from a string slice.
245    pub fn from_str<S: AsRef<str>>(s: S) -> Result<UriBuf, ParseError> {
246        let s = s.as_ref();
247        let components = UriRawComponents::from_str(s)?;
248
249        if components.uri_type().can_borrow_as_uri() {
250            Ok(unsafe { Self::from_string_unchecked(s.to_string()) })
251        } else {
252            Err(ParseError::new("Missing scheme or authority", None))
253        }
254    }
255
256    /// Attempts to create a new [`UriBuf`] from a [`String`].
257    pub fn from_string(s: String) -> Result<UriBuf, ParseError> {
258        let components = UriRawComponents::from_str(s.as_str())?;
259
260        if components.uri_type().can_borrow_as_uri() {
261            Ok(unsafe { Self::from_string_unchecked(s) })
262        } else {
263            Err(ParseError::new("Missing scheme or authority", None))
264        }
265    }
266
267    /// Attempts to create a new [`UriBuf`] from a `UriRef` slice.
268    pub fn from_uri<S: AsRef<UriRef>>(s: S) -> Option<UriBuf> {
269        if s.as_ref().uri_type().can_borrow_as_uri() {
270            Some(UriBuf(s.as_ref().to_uri_ref_buf()))
271        } else {
272            None
273        }
274    }
275}
276
277impl UriBuf {
278    /// Borrows a [`Uri`] slice containing this URI.
279    #[inline(always)]
280    pub fn as_uri(&self) -> &Uri {
281        unsafe { Uri::from_str_unchecked(self.as_str()) }
282    }
283}
284
285/// # Manipulation
286impl UriBuf {
287    /// Using this URI as the base, performs "relative resolution" to the given instance
288    /// implementing [`AnyUriRef`], updating the content of this `UriBuf` with the result.
289    pub fn resolve<T: AnyUriRef + ?Sized>(&mut self, dest: &T) -> Result<(), ResolveError> {
290        self.0.resolve(dest)
291    }
292
293    /// Replaces the path, query, and fragment with that from `rel`.
294    pub fn replace_path(&mut self, rel: &RelRef) {
295        self.0.replace_path(rel)
296    }
297}
298
299inherits_uri_ref_buf!(UriBuf);