uriparse-0.6.4/.cargo_vcs_info.json0000644000000001120000000000100126670ustar { "git": { "sha1": "feffe750d6ee5bfccc7062a3f5a362871e81caf0" } } uriparse-0.6.4/.gitignore000064400000000000000000000000410072674642500135000ustar 00000000000000/target **/*.rs.bk Cargo.lock uriparse-0.6.4/.travis.yml000064400000000000000000000000620072674642500136240ustar 00000000000000language: rust cache: cargo rust: - nightlyuriparse-0.6.4/Cargo.toml0000644000000021620000000000100106740ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "uriparse" version = "0.6.4" authors = ["Scott Godwin "] description = "A URI parser including relative references" homepage = "https://github.com/sgodwincs/uriparse-rs" readme = "README.md" categories = ["parsing"] license = "MIT" repository = "https://github.com/sgodwincs/uriparse-rs" [[bench]] name = "parse" harness = false [dependencies.fnv] version = "1.0.7" [dependencies.lazy_static] version = "1.4.0" [dependencies.serde] version = "1.0.115" features = ["derive"] optional = true [dev-dependencies.criterion] version = "0.2.10" [dev-dependencies.serde_json] version = "1.0.57" [features] default = [] uriparse-0.6.4/Cargo.toml.orig000064400000000000000000000011600072674642500144020ustar 00000000000000[package] authors = ["Scott Godwin "] categories = ["parsing"] description = "A URI parser including relative references" edition = "2018" homepage = "https://github.com/sgodwincs/uriparse-rs" license = "MIT" name = "uriparse" readme = "README.md" repository = "https://github.com/sgodwincs/uriparse-rs" version = "0.6.4" [[bench]] harness = false name = "parse" [features] default = [] [dependencies] fnv = "1.0.7" lazy_static = "1.4.0" serde = { version = "1.0.115", features = ["derive"], optional = true } [dev-dependencies] criterion = "0.2.10" serde_json = "1.0.57" uriparse-0.6.4/LICENSE000064400000000000000000000020770072674642500125300ustar 00000000000000MIT License Copyright (c) 2017 sgodwincs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. uriparse-0.6.4/README.md000064400000000000000000000130060072674642500127740ustar 00000000000000# uriparse-rs [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Build Status](https://travis-ci.org/sgodwincs/uriparse-rs.svg?branch=master)](https://travis-ci.org/sgodwincs/uriparse-rs) [![Crates.io Version](https://img.shields.io/crates/v/uriparse.svg)](https://crates.io/crates/uriparse) Implementation of [RFC3986](https://tools.ietf.org/html/rfc3986) including URIs and URI references. [Documentation](https://docs.rs/uriparse/) ## Goals The goal of this crate is to provide types for efficiently creating, modifying, and parsing URIs, relative references, and URI references. Each of the three types are described below. ### What is a `URI`? URI stands for Uniform Resource Indentifier and serves to identify some resource. OK, that probably doesn't explain much, so let's look at some examples: - https://www.google.com/ - ftps://example.com/help?q=example - urn:resource - http://mywebsite:8080#tag As you can see, a URI is composed of individual parts, specifically: scheme, authority, path, query, and fragment. #### Scheme The scheme is the portion before the first `':'`, such as `"https"` or `"urn"` and determines the specification that is used for the rest of the URI. Each scheme may have varying restrictions on the form of the URI. For example, the URN (Uniform Resource Name) scheme does not use `'/'` at all, whereas HTTP obviously does. This crate supports the most generic syntax that is valid, that is, a URI such as `"urn:/test/"` may be a valid URI, but it is not a valid URN URI. There are a lot of registered schemes that can be seen at [here](https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml), and this crate provides an easy way of accessing them with the `Scheme` type (e.g. `Scheme::HTTPS`). You can also use schemes that are not registered. #### Authority The authority is always preceded by `"//"` and is everything up to a `'/'`, `'?'`, `'#'`, or until the end of the URI. It's composed of three parts: user information, host, and port. The user information allows for providing a username and password (e.g. `"http://username:password@127.0.0.1/"`) for the resource. However, using a password is deprecated by the specification, so you should avoid it if you can as lots of servers log URIs. The host provides the name of the machine that hosts the resource. It is either an IPv4 address, an IPv6 address, or a registered name (e.g. a domain name). Lastly is the port for which to one is to establish a connection to the machine, presumably using TCP or UDP. It is not required to be specified and many schemes specify default ports (e.g. HTTP uses port 80 by default). #### Path The path is the main part of a URI and is always there even if it does not appear to be so. For example, the path for the URI `"http://example.com"` is `"/"` and the path for the URI `"urn:"` is `""`. A path following an authority *always* starts with a preceding `'/'`. Looking at a URI, the path starts from the authority (if there is one, otherwise the scheme) and continues until a `'?'`, `'#'`, or the end of the URI. The meaning of the contents of the query are highly dependent on the scheme being used. There are quite a few different classifications that paths can have, and I recommend reading over the corresponding section in the specification for more information. #### Query The query is everything after the first `'?'` until a `'#'` or the end of the URI. Like the path, the meaning of the contents of the query are highly dependent on the scheme being used. #### Fragment The fragment is everything after the first `'#'`. It serves to identify a secondary resource and is dependent on the media type of the primary resource being described. Due to this, it's description is often orthogonal to the scheme's specification. ### What is a `RelativeReference`? A relative reference is very similar to a URI with one key distinction: it does not have a scheme. Everything else, however, is identical. Relative references are often used where the base URI for a resource is implicitly defined. ### What is a `URIReference`? A URI reference is simply the union of all valid URIs and relative references. It can represent references that may or may not have schemes specified. ## Performance As stated in the goals section, this crate aims to be able to efficiently parse URIs. This primarily means minimal allocations. Currently, the only allocation performed is when parsing the path into a `Vec>`, although the segments themselves are still references to the original source. ## Normalization This crate, by default, does not perform any normalization. However, the user can normalize components after parsing either individually or all at once using the provided `normalize` functions on the corresponding components. The normalizations provided are case normalization, percent-encoding normalization, and path segment normalization. However, when comparing components, percent-encoding normalization is taken into consideration. For example, if you were to compare the two paths `/my/path` and `/my/p%61th`, they would be considered equal by this crate. It is done similarly with respect to hashing as well. ## Query String This crate does not do query string parsing, it will simply make sure that it is a valid query string as defined by [[RFC3986, Section 3.4]](https://tools.ietf.org/html/rfc3986#section-3.4). You will need to use another crate (e.g. [queryst](https://github.com/rustless/queryst)) if you want it parsed. uriparse-0.6.4/RELEASES.md000064400000000000000000000101150072674642500132400ustar 00000000000000# 0.6.4 - Added `URI::to_borrowed`, `URIReference::to_borrowed`, and `RelativeReference::to_borrowed`. - Updated dependencies. - Bug fixes. # 0.6.3 - Add serde support behind feature - @chipsenkbeil. # 0.6.2 - Remove `non_exhaustive` nightly feature as it's now stable. Crate now works on stable. # 0.6.1 - Add new schemes: * amss * android * cast * casts * dab * drm * drop * fm * fuchsia-pkg * lorawan - Add `Host::into_owned` to avoid having to match against `RegisteredName` to convert it into an owned copy. - Expose `parse_port` to parse port from byte slice. This is useful since `parse::()` can only be used for strings, not byte slices. # 0.6.0 - Rename errors and no longer directly implement `Error::description` (use `Display` instead). - Refactor builders to use a `method`, `try_method`, and `with_method` approach. # 0.5.0 - Add new schemes: * mss - Export `InvalidUnregisteredName` from the authority. - Update dependency on `lazy_static` from 1.2.0 to 1.3.0. - Update dev dependecy on `criterion` from 0.2.5 to 0.2.10. - Switch from using `!` to `Infallible` as the latter is being stabilized in 1.34 as a temporary replacement. - Remove required `#![feature(try_from)]` as it is being stabilized in 1.34. With the above changes, we're very close to having this crate be on stable! The only feature left is `non_exhaustive`. # 0.4.0 - Add new schemes: * calculator * ms-calculator - Fix typo in `ms-drive-to` scheme variant name. - Fix two duplicates in schemes: `aaas` and `tag`. - Fix percent encoding equality and hash implementations. Percent encoding comparison is now only done for characters in the unreserved set. An example of what would have passed before, but does not now is comparing the following two URIs: `http://example.com/#/` `http://example.com/#%2F` This is because while `/` is an allowed character in the fragment component, it is not in the unreserved character set and so percent decoding is not guaranteed to be a "safe" operation. It could be fine in a lot of protocols, but it may fail in another protocol that assigns a special meaning to `/` in the fragment component. - Fixed bug where parsing a username directly from a byte source would allow the username to contain colons. - Fixed bug where parsing a query directly from source containing percent-encoded characters would return the wrong query. - Added normalization for all components. - References can now be resolved against URIs. - Add missing `has_port` function to authority, URI, RelativeReference, and URIReference. # 0.3.3 - Add new schemes: * ms-eyecontrolspeech * ms-screenclip * ms-screensketch * ms-search - Small amount of refactoring. # 0.3.2 - Update number of schemes to include the newest from v0.3.1. # 0.3.1 - Add new schemes: * bitcoincash # 0.3.0 - Fix serialization of IPv6 addresses. - Changed behavior of `Path::push` when the current path is just one empty segment. For example: ```rust let mut path = Path::try_from("/").unwrap(); path.push("test"); assert_eq!(path, "/test"); // Before, the path would have been `"//test"`. ``` But the ability to make paths with a `//` prefix is still possible: ```rust let mut path = Path::try_from("/").unwrap(); path.push(""); assert_eq!(path, "//"); // This conforms to the previous functionality. ``` - Added authority mutability functions. - Added URI mutability functions. # 0.2.1 - Added more conversions between types. - Fixed lifetime issue with username. # 0.2.0 - Performance fixes. - Internal cleanup. - Fixed one parsing bug. - URI reference parsing has been fuzzed for an entire week! - Significantly increased testing coverage (mainly via doc tests). - Added a lot of documentation. - Added a `RelativeReference` struct that can only represent schemeless URI references. - Added builder types for `URI`, `RelativeReference` and `URIReference` structs. # 0.1.0 Initial release. uriparse-0.6.4/benches/parse.rs000064400000000000000000000007570072674642500146150ustar 00000000000000#[macro_use] extern crate criterion; extern crate uriparse; use criterion::Criterion; use std::convert::TryFrom; use uriparse::URIReference; fn parse_benchmark(c: &mut Criterion) { let uri = "http://user:pass@192.168.1.1:8080/this/is/a/test/path?thisis=aquery#thisisafragment"; c.bench_function("parse URI reference", move |b| { b.iter(|| URIReference::try_from(uri).unwrap()) }); } criterion_group!(benches, parse_benchmark); criterion_main!(benches); uriparse-0.6.4/src/authority.rs000064400000000000000000002327640072674642500147200ustar 00000000000000//! Authority Component //! //! See [[RFC3986, Section 3.2](https://tools.ietf.org/html/rfc3986#section-3.2)]. //! //! # Examples //! //! ``` //! use std::convert::TryFrom; //! //! use uriparse::Authority; //! //! let authority = Authority::try_from("example.com").unwrap(); //! let host = authority.into_parts().2; //! let authority = //! Authority::from_parts(Some("username"), Some("password"), host, Some(80)).unwrap(); //! assert_eq!(authority.to_string(), "username:password@example.com:80"); //! ``` //! //! # Equality //! //! While many components in this library support string comparison, [`Authority`] does not. This //! comes down to it just being too expensive to do a proper host comparison. To do so would require //! conversion to [`IpAddr`], which in the case of [`Ipv6Addr`] can be expensive. use std::borrow::Cow; use std::convert::{Infallible, TryFrom}; use std::error::Error; use std::fmt::{self, Display, Formatter, Write}; use std::hash::{Hash, Hasher}; use std::mem; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::ops::Deref; use std::str; use crate::utility::{ get_percent_encoded_value, normalize_string, percent_encoded_equality, percent_encoded_hash, UNRESERVED_CHAR_MAP, }; /// A map of byte characters that determines if a character is a valid IPv4 or registered name /// character. #[rustfmt::skip] const IPV4_AND_REGISTERED_NAME_CHAR_MAP: [u8; 256] = [ // 0 1 2 3 4 5 6 7 8 9 A B C D E F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 0, b'!', 0, 0, b'$', b'%', b'&',b'\'', b'(', b')', b'*', b'+', b',', b'-', b'.', 0, // 2 b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', 0, b';', 0, b'=', 0, 0, // 3 0, b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 4 b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', 0, 0, 0, 0, b'_', // 5 0, b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', // 6 b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', 0, 0, 0, b'~', 0, // 7 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F ]; /// A map of byte characters that determines if a character is a valid future IP literal character. #[rustfmt::skip] const IPV_FUTURE_CHAR_MAP: [u8; 256] = [ // 0 1 2 3 4 5 6 7 8 9 A B C D E F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 0, b'!', 0, 0, b'$', 0, b'&',b'\'', b'(', b')', b'*', b'+', b',', b'-', b'.', 0, // 2 b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', b';', 0, b'=', 0, 0, // 3 0, b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 4 b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', 0, 0, 0, 0, b'_', // 5 0, b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', // 6 b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', 0, 0, 0, b'~', 0, // 7 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F ]; /// A map of byte characters that determines if a character is a valid user information character. #[rustfmt::skip] const USER_INFO_CHAR_MAP: [u8; 256] = [ // 0 1 2 3 4 5 6 7 8 9 A B C D E F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 0, b'!', 0, 0, b'$', b'%', b'&',b'\'', b'(', b')', b'*', b'+', b',', b'-', b'.', 0, // 2 b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', b';', 0, b'=', 0, 0, // 3 0, b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 4 b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', 0, 0, 0, 0, b'_', // 5 0, b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', // 6 b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', 0, 0, 0, b'~', 0, // 7 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F ]; /// The authority component as defined in /// [[RFC3986, Section 3.2](https://tools.ietf.org/html/rfc3986#section-3.2)]. /// /// Any conversions to a string will **not** hide the password component of the authority. Be /// careful if you decide to perform logging. #[derive(Clone, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Authority<'authority> { /// The host component of the authority as defined in /// [[RFC3986, Section 3.2.2](https://tools.ietf.org/html/rfc3986#section-3.2.2)]. host: Host<'authority>, /// The password component of the authority as defined in /// [[RFC3986, Section 3.2.1](https://tools.ietf.org/html/rfc3986#section-3.2.1)]. password: Option>, /// The port component of the authority as defined in /// [[RFC3986, Section 3.2.3](https://tools.ietf.org/html/rfc3986#section-3.2.3)]. port: Option, /// The username component of the authority as defined in /// [[RFC3986, Section 3.2.1](https://tools.ietf.org/html/rfc3986#section-3.2.1)]. username: Option>, } impl<'authority> Authority<'authority> { pub fn as_borrowed(&self) -> Authority { let host = match &self.host { Host::RegisteredName(name) => Host::RegisteredName(name.as_borrowed()), Host::IPv4Address(ipv4) => Host::IPv4Address(*ipv4), Host::IPv6Address(ipv6) => Host::IPv6Address(*ipv6), }; let password = self.password.as_ref().map(Password::as_borrowed); let username = self.username.as_ref().map(Username::as_borrowed); Authority { host, password, port: self.port, username, } } /// Constructs a new [`Authority`] from the individual parts: username, password, host, and /// port. /// /// The lifetime used by the resulting value will be the lifetime of the part that is most /// restricted in scope. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Authority; /// /// let authority = Authority::from_parts( /// Some("username"), /// Some("password"), /// "example.com", /// Some(80) /// ).unwrap(); /// assert_eq!(authority.to_string(), "username:password@example.com:80"); /// ``` pub fn from_parts< 'new_authority, TUsername, TPassword, THost, TUsernameError, TPasswordError, THostError, >( username: Option, password: Option, host: THost, port: Option, ) -> Result, AuthorityError> where Username<'new_authority>: TryFrom, Password<'new_authority>: TryFrom, Host<'new_authority>: TryFrom, AuthorityError: From + From + From, { let username = match username { Some(username) => Some(Username::try_from(username)?), None => None, }; let password = match password { Some(password) => Some(Password::try_from(password)?), None => None, }; let host = Host::try_from(host)?; Ok(Authority { host, password, port, username, }) } /// Returns whether there is a password in the authority as defined in /// [[RFC3986, Section 3.2.1](https://tools.ietf.org/html/rfc3986#section-3.2.1)]. /// /// There will only be a password if the URI has a user information component *and* the /// component contains the `':'` delimiter. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Authority; /// /// let authority = Authority::try_from("username:password@example.com").unwrap(); /// assert!(authority.has_password()); /// ``` pub fn has_password(&self) -> bool { self.password.is_some() } /// Returns whether there is a password in the authority as defined in /// [[RFC3986, Section 3.2.1](https://tools.ietf.org/html/rfc3986#section-3.2.1)]. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Authority; /// /// let authority = Authority::try_from("example.com:8080").unwrap(); /// assert!(authority.has_port()); /// ``` pub fn has_port(&self) -> bool { self.port.is_some() } /// Returns whether there is a username in the authority as defined in /// [[RFC3986, Section 3.2.1](https://tools.ietf.org/html/rfc3986#section-3.2.1)]. /// /// There will *always* be a username as long as there is a `'@'` delimiter present in the /// authority. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Authority; /// /// let authority = Authority::try_from("username@example.com").unwrap(); /// assert!(authority.has_username()); /// ``` pub fn has_username(&self) -> bool { self.username.is_some() } /// The host component of the authority as defined in /// [[RFC3986, Section 3.2.2](https://tools.ietf.org/html/rfc3986#section-3.2.2)]. /// /// An authority component always has a host, though it may be an empty registered name. /// /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Authority; /// /// let authority = Authority::try_from("username:password@example.com").unwrap(); /// assert_eq!(authority.host().to_string().as_str(), "example.com"); /// ``` pub fn host(&self) -> &Host<'authority> { &self.host } /// Converts the [`Authority`] into an owned copy. /// /// If you construct the authority from a source with a non-static lifetime, you may run into /// lifetime problems due to the way the struct is designed. Calling this function will ensure /// that the returned value has a static lifetime. /// /// This is different from just cloning. Cloning the authority will just copy the eferences, and /// thus the lifetime will remain the same. pub fn into_owned(self) -> Authority<'static> { let password = self.password.map(Password::into_owned); let username = self.username.map(Username::into_owned); let host = match self.host { Host::RegisteredName(name) => Host::RegisteredName(name.into_owned()), Host::IPv4Address(ipv4) => Host::IPv4Address(ipv4), Host::IPv6Address(ipv6) => Host::IPv6Address(ipv6), }; Authority { host, port: self.port, password, username, } } /// Consumes the [`Authority`] and returns its parts: username, password, host, and port. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Authority; /// /// let authority = Authority::try_from("username:password@example.com:80").unwrap(); /// let (username, password, host, port) = authority.into_parts(); /// /// assert_eq!(username.unwrap(), "username"); /// assert_eq!(password.unwrap(), "password"); /// assert_eq!(host.to_string(), "example.com"); /// assert_eq!(port.unwrap(), 80); /// ``` pub fn into_parts( self, ) -> ( Option>, Option>, Host<'authority>, Option, ) { (self.username, self.password, self.host, self.port) } /// Returns whether the authority is normalized. /// /// A normalized authority will have all of its sub-components normalized. /// /// This function runs in constant-time. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Authority; /// /// let authority = Authority::try_from("username:password@example.com").unwrap(); /// assert!(authority.is_normalized()); /// /// let mut authority = Authority::try_from("username:p%61ssword@EXAMPLE.COM").unwrap(); /// assert!(!authority.is_normalized()); /// authority.normalize(); /// assert!(authority.is_normalized()); /// ``` pub fn is_normalized(&self) -> bool { if let Some(username) = self.username.as_ref() { if !username.is_normalized() { return false; } } if let Some(password) = self.password.as_ref() { if !password.is_normalized() { return false; } } self.host.is_normalized() } /// Maps the host using the given map function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Host}; /// /// let mut authority = Authority::try_from("example.com").unwrap(); /// authority.map_host(|_| Host::try_from("127.0.0.1").unwrap()); /// assert_eq!(authority.to_string(), "127.0.0.1"); /// ``` pub fn map_host(&mut self, mapper: TMapper) -> &Host<'authority> where TMapper: FnOnce(Host<'authority>) -> Host<'authority>, { let temp_host = Host::RegisteredName(RegisteredName { normalized: true, registered_name: Cow::from(""), }); let host = mapper(mem::replace(&mut self.host, temp_host)); self.set_host(host) .expect("mapped host resulted in invalid state") } /// Maps the password using the given map function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Password}; /// /// let mut authority = Authority::try_from("example.com").unwrap(); /// authority.map_password(|_| Some(Password::try_from("password").unwrap())); /// assert_eq!(authority.to_string(), ":password@example.com"); /// ``` pub fn map_password(&mut self, mapper: TMapper) -> Option<&Password<'authority>> where TMapper: FnOnce(Option>) -> Option>, { let password = mapper(self.password.take()); self.set_password(password) .expect("mapped password resulted in invalid state") } /// Maps the port using the given map function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Authority; /// /// let mut authority = Authority::try_from("example.com").unwrap(); /// authority.map_port(|_| Some(8080)); /// assert_eq!(authority.to_string(), "example.com:8080"); /// ``` pub fn map_port(&mut self, mapper: TMapper) -> Option where TMapper: FnOnce(Option) -> Option, { let port = mapper(self.port); self.set_port(port) } /// Maps the username using the given map function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Username}; /// /// let mut authority = Authority::try_from("example.com").unwrap(); /// authority.map_username(|_| Some(Username::try_from("username").unwrap())); /// assert_eq!(authority.to_string(), "username@example.com"); /// ``` pub fn map_username(&mut self, mapper: TMapper) -> Option<&Username<'authority>> where TMapper: FnOnce(Option>) -> Option>, { let username = mapper(self.username.take()); self.set_username(username) .expect("mapped username resulted in invalid state") } /// Normalizes the authority. /// /// A normalized authority will have all of its sub-components normalized. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Authority; /// /// let mut authority = Authority::try_from("username:password@example.com").unwrap(); /// authority.normalize(); /// assert_eq!(authority.to_string(), "username:password@example.com"); /// /// let mut authority = Authority::try_from("username:p%61ssword@EXAMPLE.COM").unwrap(); /// assert_eq!(authority.to_string(), "username:p%61ssword@EXAMPLE.COM"); /// authority.normalize(); /// assert_eq!(authority.to_string(), "username:password@example.com"); /// ``` pub fn normalize(&mut self) { if let Some(username) = self.username.as_mut() { username.normalize(); } if let Some(password) = self.password.as_mut() { password.normalize(); } self.host.normalize(); } /// The password component of the authority as defined in /// [[RFC3986, Section 3.2.1](https://tools.ietf.org/html/rfc3986#section-3.2.1)]. /// /// The password will be `None` if the user information component of the authority did not /// contain a `':'`. Otherwise, it will be whatever is after the `':'` until the `'@'` /// character. It may be empty as well. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Authority; /// /// let authority = Authority::try_from("username:password@example.com").unwrap(); /// assert_eq!(authority.password().unwrap(), "password"); /// ``` pub fn password(&self) -> Option<&Password<'authority>> { self.password.as_ref() } /// The port component of the authority as defined in /// [[RFC3986, Section 3.2.3]](https://tools.ietf.org/html/rfc3986#section-3.2.3). /// /// The port will be `None` if a port was not specified. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Authority; /// /// let authority = Authority::try_from("example.com:80").unwrap(); /// assert_eq!(authority.port().unwrap(), 80); /// ``` pub fn port(&self) -> Option { self.port } /// Sets the host of the authority. /// /// An error will be returned if the conversion to a [`Host`] fails. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// use std::net::Ipv6Addr; /// /// use uriparse::{Authority, Host}; /// /// let mut authority = Authority::try_from("example.com:8080").unwrap(); /// authority.set_host("127.0.0.1"); /// assert_eq!(authority.to_string(), "127.0.0.1:8080"); /// authority.set_host(Host::IPv6Address("::1".parse().unwrap())); /// assert_eq!(authority.to_string(), "[::1]:8080"); /// ``` pub fn set_host( &mut self, host: THost, ) -> Result<&Host<'authority>, AuthorityError> where Host<'authority>: TryFrom, AuthorityError: From, { self.host = Host::try_from(host)?; Ok(self.host()) } /// Sets the password of the authority. /// /// An error will be returned if the conversion to a [`Password`] fails. /// /// If the given password is not `None`, then the username will be set to `""` if it is /// currently not set. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Authority; /// /// let mut authority = Authority::try_from("example.com").unwrap(); /// authority.set_password(Some("secret")); /// assert_eq!(authority.to_string(), ":secret@example.com"); /// ``` pub fn set_password( &mut self, password: Option, ) -> Result>, AuthorityError> where Password<'authority>: TryFrom, AuthorityError: From, { self.password = match password { Some(password) => { let password = Password::try_from(password)?; if self.username.is_none() { self.username = Some(Username { normalized: true, username: Cow::from(""), }); } Some(password) } None => None, }; Ok(self.password()) } /// Sets the port of the authority. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Authority; /// /// let mut authority = Authority::try_from("example.com").unwrap(); /// authority.set_port(Some(8080)); /// assert_eq!(authority.to_string(), "example.com:8080"); /// ``` pub fn set_port(&mut self, port: Option) -> Option { self.port = port; self.port } /// Sets the username of the authority. /// /// An error will be returned if the conversion to a [`Username`] fails. /// /// If the given username is `None`, this will also remove any set password. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Username}; /// /// let mut authority = Authority::try_from("example.com").unwrap(); /// authority.set_username(Some("myname")); /// assert_eq!(authority.to_string(), "myname@example.com"); /// /// let mut authority = Authority::try_from("user:pass@example.com").unwrap(); /// authority.set_username(None::); /// assert_eq!(authority.to_string(), "example.com"); /// ``` pub fn set_username( &mut self, username: Option, ) -> Result>, AuthorityError> where Username<'authority>: TryFrom, AuthorityError: From, { self.username = match username { Some(username) => Some(Username::try_from(username)?), None => { self.password = None; None } }; Ok(self.username()) } /// The username component of the authority as defined in /// [[RFC3986, Section 3.2.1](https://tools.ietf.org/html/rfc3986#section-3.2.1)]. /// /// The username will be `None` if the user information component of the authority did not /// contain a `':'`. Otherwise, it will be whatever is after the `':'` until the `'@'` /// character. It may be empty as well. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Authority; /// /// let authority = Authority::try_from("username:password@example.com").unwrap(); /// assert_eq!(authority.password().unwrap(), "password"); /// ``` pub fn username(&self) -> Option<&Username<'authority>> { self.username.as_ref() } } impl Display for Authority<'_> { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { if let Some(ref username) = self.username { username.fmt(formatter)?; if let Some(ref password) = self.password { formatter.write_char(':')?; password.fmt(formatter)?; } formatter.write_char('@')?; } self.host.fmt(formatter)?; if let Some(port) = self.port { formatter.write_char(':')?; port.fmt(formatter)?; } Ok(()) } } impl<'authority> From> for String { fn from(value: Authority<'authority>) -> String { value.to_string() } } impl<'authority> TryFrom<&'authority [u8]> for Authority<'authority> { type Error = AuthorityError; fn try_from(value: &'authority [u8]) -> Result { let (authority, rest) = parse_authority(value)?; if rest.is_empty() { Ok(authority) } else if authority.has_port() { Err(AuthorityError::Port(PortError::InvalidCharacter)) } else if authority.host().is_ipv6_address() { Err(AuthorityError::Host(HostError::InvalidIPv6Character)) } else { Err(AuthorityError::Host( HostError::InvalidIPv4OrRegisteredNameCharacter, )) } } } impl<'authority> TryFrom<&'authority str> for Authority<'authority> { type Error = AuthorityError; fn try_from(value: &'authority str) -> Result { Authority::try_from(value.as_bytes()) } } /// The host component of the authority as defined in /// [[RFC3986, Section 3.2.2](https://tools.ietf.org/html/rfc3986#section-3.2.2)]. /// /// The RFC mentions support for future IP address literals. Of course, as of this moment there /// exist none, so hosts of the form `"[v*...]"` where `'*'` is a hexadecimal digit and `'...'` is /// the actual IP literal are not considered valid. /// /// Also, the host is case-insensitive meaning that `"example.com"` and `"ExAmPlE.CoM"` refer to the /// same host. Furthermore, percent-encoding plays no role in equality checking for characters in /// the unreserved character set meaning that `"example.com"` and `"ex%61mple.com"` are identical. /// Both of these attributes are reflected in the equality and hash functions. /// /// However, be aware that just because percent-encoding plays no role in equality checking does not /// mean that the host is normalized. If the host needs to be normalized, use the /// [`Host::normalize`] function. #[derive(Clone, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Host<'host> { /// An IPv4 address. Based on the `std`'s implementation, leading zeros for octets are allowed /// for up to three digits. So for example, `"000.000.000.000"` is still considered a valid IPv4 /// address, but `"000.000.000.0000"` is not. Thus, it would be considered a registered name. IPv4Address(Ipv4Addr), /// An IPv6 address. This will always be encased in brackets (`'['` and `']'`). IPv6Address(Ipv6Addr), /// Any other host that does not follow the syntax of an IP address. This includes even hosts of /// the form `"999.999.999.999"`. One might expect this to produce an invalid IPv4 error, but /// the RFC states that it is a "first-match-wins" algorithm, and that host does not match the /// IPv4 literal syntax. /// /// This may be changed in the future, since arguments can be made from either side. RegisteredName(RegisteredName<'host>), } impl Host<'_> { /// Returns a new host which is identical but has a lifetime tied to this host. pub fn as_borrowed(&self) -> Host { use self::Host::*; match self { IPv4Address(ipv4) => IPv4Address(*ipv4), IPv6Address(ipv6) => IPv6Address(*ipv6), RegisteredName(name) => RegisteredName(name.as_borrowed()), } } /// Converts the [`Host`] into an owned copy. /// /// If you construct the host from a source with a non-static lifetime, you may run into /// lifetime problems due to the way the struct is designed. Calling this function will ensure /// that the returned value has a static lifetime. /// /// This is different from just cloning. Cloning the host will just copy the references, and /// thus the lifetime will remain the same. pub fn into_owned(self) -> Host<'static> { use self::Host::*; match self { IPv4Address(ipv4) => IPv4Address(ipv4), IPv6Address(ipv6) => IPv6Address(ipv6), RegisteredName(name) => RegisteredName(name.into_owned()), } } /// Returns whether the host is an IPv4 address. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Host; /// /// let host = Host::try_from("192.168.1.1").unwrap(); /// assert!(host.is_ipv4_address()); /// ``` pub fn is_ipv4_address(&self) -> bool { match self { Host::IPv4Address(_) => true, _ => false, } } /// Returns whether the host is an IPv6 address. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Host; /// /// let host = Host::try_from("[::1]").unwrap(); /// assert!(host.is_ipv6_address()); /// ``` pub fn is_ipv6_address(&self) -> bool { match self { Host::IPv6Address(_) => true, _ => false, } } /// Returns whether the host is normalized. /// /// IPv4 and IPv6 hosts will always be normalized. Registered names are considered normalized /// if all characters are lowercase, no bytes that are in the unreserved character set are /// percent-encoded, and all alphabetical characters in percent-encodings are uppercase. /// /// This function runs in constant-time. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Host; /// /// let host = Host::try_from("192.168.1.1").unwrap(); /// assert!(host.is_normalized()); /// /// let mut host = Host::try_from("EXAMPLE.COM").unwrap(); /// assert!(!host.is_normalized()); /// host.normalize(); /// assert!(host.is_normalized()); /// ``` pub fn is_normalized(&self) -> bool { match self { Host::RegisteredName(name) => name.is_normalized(), _ => true, } } /// Returns whether the host is a registered name. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Host; /// /// let host = Host::try_from("example.com").unwrap(); /// assert!(host.is_registered_name()); /// ``` pub fn is_registered_name(&self) -> bool { match self { Host::RegisteredName(_) => true, _ => false, } } /// Normalizes the host such that all characters are lowercase, no bytes that are in the /// unreserved character set are percent-encoded, and all alphabetical characters in /// percent-encodings are uppercase. /// /// If the host is already normalized, the function will return immediately. Otherwise, if /// the host is not owned, this function will perform an allocation to clone it. The /// normalization itself though, is done in-place with no extra memory allocations required. /// /// IPv4 and IPv6 hosts are always considered normalized. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Host; /// /// let mut host = Host::try_from("192.168.1.1").unwrap(); /// host.normalize(); /// assert_eq!(host.to_string(), "192.168.1.1"); /// /// let mut host = Host::try_from("%ff%41").unwrap(); /// assert_eq!(host.to_string(), "%ff%41"); /// host.normalize(); /// assert_eq!(host.to_string(), "%FFA"); /// ``` pub fn normalize(&mut self) { if let Host::RegisteredName(name) = self { name.normalize() } } } impl Display for Host<'_> { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { use self::Host::*; match self { IPv4Address(address) => address.fmt(formatter), IPv6Address(address) => { formatter.write_char('[')?; address.fmt(formatter)?; formatter.write_char(']') } RegisteredName(name) => formatter.write_str(name.as_str()), } } } impl<'host> From> for String { fn from(value: Host<'host>) -> String { value.to_string() } } impl From for Host<'static> { fn from(value: IpAddr) -> Self { match value { IpAddr::V4(address) => Host::IPv4Address(address), IpAddr::V6(address) => Host::IPv6Address(address), } } } impl From for Host<'static> { fn from(value: Ipv4Addr) -> Self { Host::IPv4Address(value) } } impl From for Host<'static> { fn from(value: Ipv6Addr) -> Self { Host::IPv6Address(value) } } impl<'host> TryFrom<&'host [u8]> for Host<'host> { type Error = HostError; fn try_from(value: &'host [u8]) -> Result { if value.is_empty() { let registered_name = RegisteredName { normalized: true, registered_name: Cow::from(""), }; return Ok(Host::RegisteredName(registered_name)); } match (value.get(0), value.get(value.len() - 1)) { (Some(b'['), Some(b']')) => { match value.get(1..3) { Some(&[prefix, version]) if prefix.to_ascii_lowercase() == b'v' && version.is_ascii_hexdigit() => { // IPvFuture let ipvfuture = &value[3..value.len() - 1]; if check_ipvfuture(ipvfuture) { return Err(HostError::AddressMechanismNotSupported); } else { return Err(HostError::InvalidIPvFutureCharacter); } } _ => (), } // IPv6 let ipv6 = &value[1..value.len() - 1]; if !check_ipv6(ipv6) { return Err(HostError::InvalidIPv6Character); } // Unsafe: The function above [`check_ipv6`] ensures this is valid ASCII-US. let ipv6: Ipv6Addr = unsafe { str::from_utf8_unchecked(ipv6) } .parse() .map_err(|_| HostError::InvalidIPv6Format)?; Ok(Host::IPv6Address(ipv6)) } _ => { let (valid, normalized) = check_ipv4_or_registered_name(value); if valid { // Unsafe: The function above [`check_ipv4_or_registered_name`] ensures // this is valid ASCII-US. let value_string = unsafe { str::from_utf8_unchecked(value) }; match value_string.parse() { Ok(ipv4) => Ok(Host::IPv4Address(ipv4)), Err(_) => Ok(Host::RegisteredName(RegisteredName { normalized, registered_name: Cow::from(value_string), })), } } else { Err(HostError::InvalidIPv4OrRegisteredNameCharacter) } } } } } impl<'host> TryFrom<&'host str> for Host<'host> { type Error = HostError; fn try_from(value: &'host str) -> Result { Host::try_from(value.as_bytes()) } } /// The password component of the authority as defined in /// [[RFC3986, Section 3.2.1](https://tools.ietf.org/html/rfc3986#section-3.2.1)]. /// /// Even though this library supports parsing the password from the user information, it should be /// noted that the format "username:password" is deprecated. Also, be careful logging this! /// /// The password is case-sensitive. Furthermore, percent-encoding plays no role in equality checking /// for characters in the unreserved character set meaning that `"password"` and `"p%61ssword"` are /// identical. Both of these attributes are reflected in the equality and hash functions. /// /// Be aware that just because percent-encoding plays no role in equality checking does not /// mean that the password is normalized. If the password needs to be normalized, use the /// [`Password::normalize`] function. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Password<'password> { /// Whether the password is normalized. normalized: bool, /// The internal password source that is either owned or borrowed. password: Cow<'password, str>, } impl Password<'_> { /// Returns a new password which is identical but has a lifetime tied to this password. pub fn as_borrowed(&self) -> Password { use self::Cow::*; let password = match &self.password { Borrowed(borrowed) => *borrowed, Owned(owned) => owned.as_str(), }; Password { normalized: self.normalized, password: Cow::Borrowed(password), } } /// Returns a `str` representation of the password. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Password; /// /// let password = Password::try_from("password").unwrap(); /// assert_eq!(password, "password"); /// ``` pub fn as_str(&self) -> &str { &self.password } /// Converts the [`Password`] into an owned copy. /// /// If you construct the authority from a source with a non-static lifetime, you may run into /// lifetime problems due to the way the struct is designed. Calling this function will ensure /// that the returned value has a static lifetime. /// /// This is different from just cloning. Cloning the password will just copy the references, and /// thus the lifetime will remain the same. pub fn into_owned(self) -> Password<'static> { Password { normalized: self.normalized, password: Cow::from(self.password.into_owned()), } } /// Returns whether the password is normalized. /// /// A normalized password will have no bytes that are in the unreserved character set /// percent-encoded and all alphabetical characters in percent-encodings will be uppercase. /// /// This function runs in constant-time. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Password; /// /// let password = Password::try_from("password").unwrap(); /// assert!(password.is_normalized()); /// /// let mut password = Password::try_from("%ff%ff").unwrap(); /// assert!(!password.is_normalized()); /// password.normalize(); /// assert!(password.is_normalized()); /// ``` pub fn is_normalized(&self) -> bool { self.normalized } /// Normalizes the password such that it will have no bytes that are in the unreserved character /// set percent-encoded and all alphabetical characters in percent-encodings will be uppercase. /// /// If the password is already normalized, the function will return immediately. Otherwise, if /// the password is not owned, this function will perform an allocation to clone it. The /// normalization itself though, is done in-place with no extra memory allocations required. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Password; /// /// let mut password = Password::try_from("password").unwrap(); /// password.normalize(); /// assert_eq!(password, "password"); /// /// let mut password = Password::try_from("%ff%41").unwrap(); /// assert_eq!(password, "%ff%41"); /// password.normalize(); /// assert_eq!(password, "%FFA"); /// ``` pub fn normalize(&mut self) { if !self.normalized { // Unsafe: Passwords must be valid ASCII-US, so this is safe. unsafe { normalize_string(&mut self.password.to_mut(), true) }; self.normalized = true; } } } impl AsRef<[u8]> for Password<'_> { fn as_ref(&self) -> &[u8] { self.password.as_bytes() } } impl AsRef for Password<'_> { fn as_ref(&self) -> &str { &self.password } } impl Deref for Password<'_> { type Target = str; fn deref(&self) -> &Self::Target { &self.password } } impl Display for Password<'_> { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { formatter.write_str(&self.password) } } impl Eq for Password<'_> {} impl<'password> From> for String { fn from(value: Password<'password>) -> String { value.to_string() } } impl Hash for Password<'_> { fn hash(&self, state: &mut H) where H: Hasher, { percent_encoded_hash(self.password.as_bytes(), state, true); } } impl PartialEq for Password<'_> { fn eq(&self, other: &Password) -> bool { *self == *other.as_bytes() } } impl PartialEq<[u8]> for Password<'_> { fn eq(&self, other: &[u8]) -> bool { percent_encoded_equality(self.password.as_bytes(), other, true) } } impl<'password> PartialEq> for [u8] { fn eq(&self, other: &Password<'password>) -> bool { other == self } } impl<'a> PartialEq<&'a [u8]> for Password<'_> { fn eq(&self, other: &&'a [u8]) -> bool { self == *other } } impl<'a, 'password> PartialEq> for &'a [u8] { fn eq(&self, other: &Password<'password>) -> bool { other == *self } } impl PartialEq for Password<'_> { fn eq(&self, other: &str) -> bool { self == other.as_bytes() } } impl<'password> PartialEq> for str { fn eq(&self, other: &Password<'password>) -> bool { other == self.as_bytes() } } impl<'a> PartialEq<&'a str> for Password<'_> { fn eq(&self, other: &&'a str) -> bool { self == other.as_bytes() } } impl<'a, 'password> PartialEq> for &'a str { fn eq(&self, other: &Password<'password>) -> bool { other == self.as_bytes() } } impl<'password> TryFrom<&'password [u8]> for Password<'password> { type Error = PasswordError; fn try_from(value: &'password [u8]) -> Result { let normalized = check_user_info(value, false)?; // Unsafe: The function above [`check_user_info`] ensures this is valid ASCII-US. Ok(Password { normalized, password: Cow::from(unsafe { str::from_utf8_unchecked(value) }), }) } } impl<'password> TryFrom<&'password str> for Password<'password> { type Error = PasswordError; fn try_from(value: &'password str) -> Result { Password::try_from(value.as_bytes()) } } /// A host that is a registered name (i.e. not an IP literal). /// /// The registered name is case-insensitive meaning that `"example.com"` and `"ExAmPlE.CoM"` refer /// to the same registered name. Furthermore, percent-encoding plays no role in equality checking /// for characters in the unreserved character set meaning that `"example.com"` and /// `"ex%61mple.com"` are identical. Both of these attributes are reflected in the equality and hash /// functions. /// /// However, be aware that just because percent-encoding plays no role in equality checking does not /// mean that the host is normalized. If the registered name needs to be normalized, use the /// [`RegisteredName::normalize`] function. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct RegisteredName<'name> { /// Whether the registered name is normalized. normalized: bool, /// The internal registered name source that is either owned or borrowed. registered_name: Cow<'name, str>, } impl RegisteredName<'_> { /// Returns a new registered name which is identical but has a lifetime tied to this registered /// name. pub fn as_borrowed(&self) -> RegisteredName { use self::Cow::*; let name = match &self.registered_name { Borrowed(borrowed) => *borrowed, Owned(owned) => owned.as_str(), }; RegisteredName { normalized: self.normalized, registered_name: Cow::Borrowed(name), } } /// Returns a `str` representation of the registered name. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RegisteredName; /// /// let name = RegisteredName::try_from("example.com").unwrap(); /// assert_eq!(name.as_str(), "example.com"); /// ``` pub fn as_str(&self) -> &str { &self.registered_name } /// Converts the [`RegisteredName`] into an owned copy. /// /// If you construct the registered name from a source with a non-static lifetime, you may run /// into lifetime problems due to the way the struct is designed. Calling this function will /// ensure that the returned value has a static lifetime. /// /// This is different from just cloning. Cloning the registered name will just copy the /// references, and thus the lifetime will remain the same. pub fn into_owned(self) -> RegisteredName<'static> { RegisteredName { normalized: self.normalized, registered_name: Cow::from(self.registered_name.into_owned()), } } /// Returns whether the registered name is normalized. /// /// Registered names are considered normalized if all characters are lowercase, no bytes that /// are in the unreserved character set are percent-encoded, and all alphabetical characters in /// percent-encodings are uppercase. /// /// This function runs in constant-time. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RegisteredName; /// /// let name = RegisteredName::try_from("example.com").unwrap(); /// assert!(name.is_normalized()); /// /// let mut name = RegisteredName::try_from("EXAMPLE.COM").unwrap(); /// assert!(!name.is_normalized()); /// name.normalize(); /// assert!(name.is_normalized()); /// ``` pub fn is_normalized(&self) -> bool { self.normalized } /// Normalizes the registered name such that all characters are lowercase, no bytes that are in /// the unreserved character set are percent-encoded, and all alphabetical characters in /// percent-encodings are uppercase. /// /// If the registered name is already normalized, the function will return immediately. /// Otherwise, if the registered name is not owned, this function will perform an allocation to /// clone it. The normalization itself though, is done in-place with no extra memory allocations /// required. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RegisteredName; /// /// let mut name = RegisteredName::try_from("example.com").unwrap(); /// name.normalize(); /// assert_eq!(name.to_string(), "example.com"); /// /// let mut name = RegisteredName::try_from("%ff%41").unwrap(); /// assert_eq!(name.to_string(), "%ff%41"); /// name.normalize(); /// assert_eq!(name.to_string(), "%FFA"); /// ``` pub fn normalize(&mut self) { if !self.normalized { // Unsafe: Registered names must be valid ASCII-US, so this is safe. unsafe { normalize_string(&mut self.registered_name.to_mut(), false) }; self.normalized = true; } } } impl AsRef<[u8]> for RegisteredName<'_> { fn as_ref(&self) -> &[u8] { self.registered_name.as_bytes() } } impl AsRef for RegisteredName<'_> { fn as_ref(&self) -> &str { &self.registered_name } } impl Deref for RegisteredName<'_> { type Target = str; fn deref(&self) -> &Self::Target { &self.registered_name } } impl Display for RegisteredName<'_> { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { formatter.write_str(&self.registered_name) } } impl Eq for RegisteredName<'_> {} impl<'name> From> for String { fn from(value: RegisteredName<'name>) -> String { value.to_string() } } impl Hash for RegisteredName<'_> { fn hash(&self, state: &mut H) where H: Hasher, { percent_encoded_hash(self.registered_name.as_bytes(), state, false); } } impl PartialEq for RegisteredName<'_> { fn eq(&self, other: &RegisteredName) -> bool { *self == *other.as_bytes() } } impl PartialEq<[u8]> for RegisteredName<'_> { fn eq(&self, other: &[u8]) -> bool { percent_encoded_equality(self.registered_name.as_bytes(), other, false) } } impl<'name> PartialEq> for [u8] { fn eq(&self, other: &RegisteredName<'name>) -> bool { other == self } } impl<'a> PartialEq<&'a [u8]> for RegisteredName<'_> { fn eq(&self, other: &&'a [u8]) -> bool { self == *other } } impl<'a, 'name> PartialEq> for &'a [u8] { fn eq(&self, other: &RegisteredName<'name>) -> bool { other == *self } } impl PartialEq for RegisteredName<'_> { fn eq(&self, other: &str) -> bool { self == other.as_bytes() } } impl<'name> PartialEq> for str { fn eq(&self, other: &RegisteredName<'name>) -> bool { other == self.as_bytes() } } impl<'a> PartialEq<&'a str> for RegisteredName<'_> { fn eq(&self, other: &&'a str) -> bool { self == other.as_bytes() } } impl<'a, 'name> PartialEq> for &'a str { fn eq(&self, other: &RegisteredName<'name>) -> bool { other == self.as_bytes() } } impl<'name> TryFrom<&'name [u8]> for RegisteredName<'name> { type Error = RegisteredNameError; fn try_from(value: &'name [u8]) -> Result { match Host::try_from(value) { Ok(Host::RegisteredName(name)) => Ok(name), _ => Err(RegisteredNameError), } } } impl<'name> TryFrom<&'name str> for RegisteredName<'name> { type Error = RegisteredNameError; fn try_from(value: &'name str) -> Result { RegisteredName::try_from(value.as_bytes()) } } /// The username component of the authority as defined in /// [[RFC3986, Section 3.2.1](https://tools.ietf.org/html/rfc3986#section-3.2.1)]. /// /// The username is case-sensitive. Furthermore, percent-encoding plays no role in equality checking /// for characters in the unreserved character set meaning that `"username"` and `"usern%61me"` are /// identical. Both of these attributes are reflected in the equality and hash functions. /// /// Be aware that just because percent-encoding plays no role in equality checking does not /// mean that the username is normalized. If the username needs to be normalized, use the /// [`Username::normalize`] function. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Username<'username> { /// Whether the username is normalized. normalized: bool, /// The internal username source that is either owned or borrowed. username: Cow<'username, str>, } impl Username<'_> { /// Returns a new username which is identical but has a lifetime tied to this username. pub fn as_borrowed(&self) -> Username { use self::Cow::*; let username = match &self.username { Borrowed(borrowed) => *borrowed, Owned(owned) => owned.as_str(), }; Username { normalized: self.normalized, username: Cow::Borrowed(username), } } /// Returns a `str` representation of the username. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Username; /// /// let username = Username::try_from("username").unwrap(); /// assert_eq!(username.as_str(), "username"); /// ``` pub fn as_str(&self) -> &str { &self.username } /// Converts the [`Username`] into an owned copy. /// /// If you construct the username from a source with a non-static lifetime, you may run into /// lifetime problems due to the way the struct is designed. Calling this function will ensure /// that the returned value has a static lifetime. /// /// This is different from just cloning. Cloning the username will just copy the references, and /// thus the lifetime will remain the same. pub fn into_owned(self) -> Username<'static> { Username { normalized: self.normalized, username: Cow::from(self.username.into_owned()), } } /// Returns whether the username is normalized. /// /// A normalized username will have no bytes that are in the unreserved character set /// percent-encoded and all alphabetical characters in percent-encodings will be uppercase. /// /// This function runs in constant-time. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Username; /// /// let username = Username::try_from("username").unwrap(); /// assert!(username.is_normalized()); /// /// let mut username = Username::try_from("%ff%ff").unwrap(); /// assert!(!username.is_normalized()); /// username.normalize(); /// assert!(username.is_normalized()); /// ``` pub fn is_normalized(&self) -> bool { self.normalized } /// Normalizes the username such that it will have no bytes that are in the unreserved character /// set percent-encoded and all alphabetical characters in percent-encodings will be uppercase. /// /// If the username is already normalized, the function will return immediately. Otherwise, if /// the username is not owned, this function will perform an allocation to clone it. The /// normalization itself though, is done in-place with no extra memory allocations required. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Username; /// /// let mut username = Username::try_from("username").unwrap(); /// username.normalize(); /// assert_eq!(username, "username"); /// /// let mut username = Username::try_from("%ff%41").unwrap(); /// assert_eq!(username, "%ff%41"); /// username.normalize(); /// assert_eq!(username, "%FFA"); /// ``` pub fn normalize(&mut self) { if !self.normalized { // Unsafe: Usernames must be valid ASCII-US, so this is safe. unsafe { normalize_string(&mut self.username.to_mut(), true) }; self.normalized = true; } } } impl AsRef<[u8]> for Username<'_> { fn as_ref(&self) -> &[u8] { self.username.as_bytes() } } impl AsRef for Username<'_> { fn as_ref(&self) -> &str { &self.username } } impl Deref for Username<'_> { type Target = str; fn deref(&self) -> &Self::Target { &self.username } } impl Display for Username<'_> { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { formatter.write_str(&self.username) } } impl<'username> Eq for Username<'username> {} impl<'username> From> for String { fn from(value: Username<'username>) -> String { value.to_string() } } impl Hash for Username<'_> { fn hash(&self, state: &mut H) where H: Hasher, { percent_encoded_hash(self.username.as_bytes(), state, true); } } impl PartialEq for Username<'_> { fn eq(&self, other: &Username) -> bool { *self == *other.as_bytes() } } impl PartialEq<[u8]> for Username<'_> { fn eq(&self, other: &[u8]) -> bool { percent_encoded_equality(self.username.as_bytes(), other, true) } } impl<'username> PartialEq> for [u8] { fn eq(&self, other: &Username<'username>) -> bool { other == self } } impl<'a> PartialEq<&'a [u8]> for Username<'_> { fn eq(&self, other: &&'a [u8]) -> bool { self == *other } } impl<'a, 'username> PartialEq> for &'a [u8] { fn eq(&self, other: &Username<'username>) -> bool { other == *self } } impl PartialEq for Username<'_> { fn eq(&self, other: &str) -> bool { self == other.as_bytes() } } impl<'username> PartialEq> for str { fn eq(&self, other: &Username<'username>) -> bool { other == self.as_bytes() } } impl<'a> PartialEq<&'a str> for Username<'_> { fn eq(&self, other: &&'a str) -> bool { self == other.as_bytes() } } impl<'a, 'username> PartialEq> for &'a str { fn eq(&self, other: &Username<'username>) -> bool { other == self.as_bytes() } } impl<'username> TryFrom<&'username [u8]> for Username<'username> { type Error = UsernameError; fn try_from(value: &'username [u8]) -> Result { let normalized = check_user_info(value, true)?; // Unsafe: The function above [`check_user_info`] ensure this is valid ASCII-US. Ok(Username { normalized, username: Cow::from(unsafe { str::from_utf8_unchecked(value) }), }) } } impl<'username> TryFrom<&'username str> for Username<'username> { type Error = UsernameError; fn try_from(value: &'username str) -> Result { Username::try_from(value.as_bytes()) } } /// An error representing an invalid authority. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum AuthorityError { /// The host component of the authority was invalid. Host(HostError), /// The password component of the authority was invalid. Password(PasswordError), /// The port component of the authority was invalid. Port(PortError), /// The username component of the authority was invalid. Username(UsernameError), } impl Display for AuthorityError { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { use self::AuthorityError::*; match self { Host(error) => error.fmt(formatter), Password(error) => error.fmt(formatter), Port(error) => error.fmt(formatter), Username(error) => error.fmt(formatter), } } } impl Error for AuthorityError {} impl From for AuthorityError { fn from(_: Infallible) -> Self { AuthorityError::Host(HostError::InvalidIPv4OrRegisteredNameCharacter) } } impl From for AuthorityError { fn from(value: HostError) -> Self { AuthorityError::Host(value) } } impl From for AuthorityError { fn from(value: PasswordError) -> Self { AuthorityError::Password(value) } } impl From for AuthorityError { fn from(value: PortError) -> Self { AuthorityError::Port(value) } } impl From for AuthorityError { fn from(value: UserInfoError) -> Self { use self::AuthorityError::*; match value { UserInfoError::Password(error) => Password(error), UserInfoError::Username(error) => Username(error), } } } impl From for AuthorityError { fn from(value: UsernameError) -> Self { AuthorityError::Username(value) } } /// An error representing an invalid host. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum HostError { /// The syntax for a future IP literal was used and is not currently supported. AddressMechanismNotSupported, /// An invalid character for an IPv4 address or registered name was used. Due to the ambiguity /// of the grammar, it is not possible to say which. It is also possible that all the characters /// were valid, but there was an invalid percent encoding (e.g. `"%ZZ"`). InvalidIPv4OrRegisteredNameCharacter, /// The syntax for an IPv6 literal was used (i.e. `"[...]"`), but it contained an invalid IPv6 /// character. InvalidIPv6Character, /// The syntax for an IPv6 literal was used (i.e. `"[...]"`) and all of the characters were /// valid IPv6 characters. However, the format of the literal was invalid. InvalidIPv6Format, /// The syntax for a future IP literal was used (i.e. `"[v*...]"` where `"*"` is a hexadecimal /// digit), but it contained an invalid character. InvalidIPvFutureCharacter, } impl Display for HostError { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { use self::HostError::*; match self { AddressMechanismNotSupported => { write!(formatter, "host address mechanism not supported") } InvalidIPv4OrRegisteredNameCharacter => { write!(formatter, "invalid host IPv4 or registered name character") } InvalidIPv6Character => write!(formatter, "invalid host IPv6 character"), InvalidIPv6Format => write!(formatter, "invalid host IPv6 format"), InvalidIPvFutureCharacter => write!(formatter, "invalid host IPvFuture character"), } } } impl Error for HostError {} impl From for HostError { fn from(_: Infallible) -> Self { HostError::InvalidIPv4OrRegisteredNameCharacter } } /// An error representing an invalid password component. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum PasswordError { /// The password contained an invalid character. InvalidCharacter, /// The password contained an invalid percent encoding (e.g. `"%ZZ"`). InvalidPercentEncoding, } impl Display for PasswordError { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { use self::PasswordError::*; match self { InvalidCharacter => write!(formatter, "invalid password character"), InvalidPercentEncoding => write!(formatter, "invalid password percent encoding"), } } } impl Error for PasswordError {} impl From for PasswordError { fn from(_: Infallible) -> Self { PasswordError::InvalidCharacter } } impl From for PasswordError { fn from(value: UserInfoError) -> Self { match value { UserInfoError::Password(error) => error, _ => panic!("unexpected user info error"), } } } /// An error representing an invalid port. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum PortError { /// An invalid character was used in the port. Only decimal digits are allowed. InvalidCharacter, /// The port was a valid number, but it was too large to fit in a `u16`. Overflow, } impl Display for PortError { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { use self::PortError::*; match self { InvalidCharacter => write!(formatter, "invalid port character"), Overflow => write!(formatter, "port overflow"), } } } impl Error for PortError {} impl From for PortError { fn from(_: Infallible) -> Self { PortError::InvalidCharacter } } /// An error representing an invalid registered name. /// /// This implies that the registered name contained an invalid host character or had an invalid /// percent encoding. This error is not possible from parsing an authority. It can only be returned /// from directly parsing a registered name. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct RegisteredNameError; impl Display for RegisteredNameError { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { write!(formatter, "invalid registered name") } } impl Error for RegisteredNameError {} /// An error representing an invalid user information component. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] enum UserInfoError { /// The password component of the user information was invalid. Password(PasswordError), /// The username component of the user information was invalid. Username(UsernameError), } impl Display for UserInfoError { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { use self::UserInfoError::*; match self { Password(error) => error.fmt(formatter), Username(error) => error.fmt(formatter), } } } impl Error for UserInfoError {} impl From for UserInfoError { fn from(_: Infallible) -> Self { UserInfoError::Username(UsernameError::InvalidCharacter) } } impl From for UserInfoError { fn from(value: PasswordError) -> Self { UserInfoError::Password(value) } } impl From for UserInfoError { fn from(value: UsernameError) -> Self { UserInfoError::Username(value) } } /// An error representing an invalid username component. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum UsernameError { /// The username contained a `':'` which is only to be used as a delimiter between the username /// and password. This variant can only happen when trying to directly parse a username from a /// byte source. ContainsColon, /// The username contained an invalid character. InvalidCharacter, /// The username contained an invalid percent encoding (e.g. `"%ZZ"`). InvalidPercentEncoding, } impl Display for UsernameError { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { use self::UsernameError::*; match self { ContainsColon => write!(formatter, "username contains a colon character"), InvalidCharacter => write!(formatter, "invalid username character"), InvalidPercentEncoding => write!(formatter, "invalid username percent encoding"), } } } impl Error for UsernameError {} impl From for UsernameError { fn from(_: Infallible) -> Self { UsernameError::InvalidCharacter } } impl From for UsernameError { fn from(value: UserInfoError) -> Self { match value { UserInfoError::Username(error) => error, _ => panic!("unexpected user info error"), } } } /// Returns true if the byte string contains only valid IPv4 or registered name characters. This /// also ensures that percent encodings are valid. fn check_ipv4_or_registered_name(value: &[u8]) -> (bool, bool) { let mut bytes = value.iter(); let mut normalized = true; while let Some(&byte) = bytes.next() { match IPV4_AND_REGISTERED_NAME_CHAR_MAP[byte as usize] { 0 => return (false, false), b'%' => match get_percent_encoded_value(bytes.next().cloned(), bytes.next().cloned()) { Ok((hex_value, uppercase)) => { if !uppercase || UNRESERVED_CHAR_MAP[hex_value as usize] != 0 { normalized = false; } } _ => return (false, false), }, b'A'..=b'Z' => normalized = false, _ => (), } } (true, normalized) } /// Returns true if the byte string contains only valid IPv6 characters. fn check_ipv6(value: &[u8]) -> bool { for &byte in value { if !byte.is_ascii_hexdigit() && byte != b':' && byte != b'.' { return false; } } true } /// Returns true if the byte string contains only valid future IP literal characters. This also /// ensures that percent encodings are valid. fn check_ipvfuture(value: &[u8]) -> bool { for &byte in value { if let 0 = IPV_FUTURE_CHAR_MAP[byte as usize] { return false; } } true } /// Checks if the user information component contains valid characters and percent encodings. If so, /// it will return an `Option` indicating the separator index for the username and password. fn check_user_info(value: &[u8], is_username: bool) -> Result { let mut bytes = value.iter(); let mut normalized = true; while let Some(&byte) = bytes.next() { match USER_INFO_CHAR_MAP[byte as usize] { 0 => { return if is_username { Err(UsernameError::InvalidCharacter.into()) } else { Err(PasswordError::InvalidCharacter.into()) }; } b'%' => match get_percent_encoded_value(bytes.next().cloned(), bytes.next().cloned()) { Ok((hex_value, uppercase)) => { if !uppercase || UNRESERVED_CHAR_MAP[hex_value as usize] != 0 { normalized = false; } } Err(_) => { return if is_username { Err(UsernameError::InvalidPercentEncoding.into()) } else { Err(PasswordError::InvalidPercentEncoding.into()) }; } }, b':' if is_username => return Err(UsernameError::ContainsColon.into()), _ => (), } } Ok(normalized) } /// Parses the authority from the given byte string. pub(crate) fn parse_authority(value: &[u8]) -> Result<(Authority, &[u8]), AuthorityError> { let mut at_index = None; let mut last_colon_index = None; let mut end_index = value.len(); for (index, &byte) in value.iter().enumerate() { match byte { b'@' => { if at_index.is_none() { at_index = Some(index); last_colon_index = None; } } b':' => last_colon_index = Some(index), b']' => last_colon_index = None, b'/' | b'?' | b'#' => { end_index = index; break; } _ => (), } } let (value, rest) = value.split_at(end_index); let (username, password, host_start_index) = match at_index { Some(index) => { let (username, password) = parse_user_info(&value[..index])?; (Some(username), password, index + 1) } None => (None, None, 0), }; let (host, port) = match last_colon_index { Some(index) => ( Host::try_from(&value[host_start_index..index])?, parse_port(&value[index + 1..])?, ), None => (Host::try_from(&value[host_start_index..])?, None), }; let authority = Authority { host, port, password, username, }; Ok((authority, rest)) } /// Parses the port from the given byte string. pub fn parse_port(value: &[u8]) -> Result, PortError> { if value.is_empty() { Ok(None) } else { let mut port = 0u16; for &byte in value { if !byte.is_ascii_digit() { return Err(PortError::InvalidCharacter); } port = port.checked_mul(10).ok_or(PortError::Overflow)?; port = port .checked_add((byte - b'0').into()) .ok_or(PortError::Overflow)?; } Ok(Some(port)) } } /// Parses the user information from the given byte string. fn parse_user_info(value: &[u8]) -> Result<(Username, Option), UserInfoError> { let mut bytes = value.iter().enumerate(); let mut first_colon_index = None; let mut password_normalized = true; let mut username_normalized = true; while let Some((index, &byte)) = bytes.next() { match USER_INFO_CHAR_MAP[byte as usize] { 0 => { return if first_colon_index.is_some() { Err(PasswordError::InvalidCharacter.into()) } else { Err(UsernameError::InvalidCharacter.into()) } } b'%' => match get_percent_encoded_value( bytes.next().map(|(_, &byte)| byte), bytes.next().map(|(_, &byte)| byte), ) { Ok((hex_value, uppercase)) => { if !uppercase || UNRESERVED_CHAR_MAP[hex_value as usize] != 0 { if first_colon_index.is_some() { password_normalized = false; } else { username_normalized = false; } } } Err(_) => { return if first_colon_index.is_some() { Err(PasswordError::InvalidPercentEncoding.into()) } else { Err(UsernameError::InvalidPercentEncoding.into()) } } }, b':' => { if first_colon_index.is_none() { first_colon_index = Some(index); } } _ => (), } } // Unsafe: All characters are ASCII-US, as checked above. Ok(match first_colon_index { Some(index) => { let username = Username { normalized: username_normalized, username: Cow::from(unsafe { str::from_utf8_unchecked(&value[..index]) }), }; let password = Password { normalized: password_normalized, password: Cow::from(unsafe { str::from_utf8_unchecked(&value[index + 1..]) }), }; (username, Some(password)) } _ => { let username = Username { normalized: username_normalized, username: Cow::from(unsafe { str::from_utf8_unchecked(value) }), }; (username, None) } }) } uriparse-0.6.4/src/fragment.rs000064400000000000000000000304740072674642500144650ustar 00000000000000//! Fragment Component //! //! See [[RFC3986, Section 3.5](https://tools.ietf.org/html/rfc3986#section-3.5)]. use std::borrow::Cow; use std::convert::{Infallible, TryFrom}; use std::error::Error; use std::fmt::{self, Display, Formatter}; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::str; use crate::utility::{ get_percent_encoded_value, normalize_string, percent_encoded_equality, percent_encoded_hash, UNRESERVED_CHAR_MAP, }; /// A map of byte characters that determines if a character is a valid fragment character. #[rustfmt::skip] const FRAGMENT_CHAR_MAP: [u8; 256] = [ // 0 1 2 3 4 5 6 7 8 9 A B C D E F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 0, b'!', 0, 0, b'$', b'%', b'&',b'\'', b'(', b')', b'*', b'+', b',', b'-', b'.', b'/', // 2 b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', b';', 0, b'=', 0, b'?', // 3 b'@', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 4 b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', 0, 0, 0, 0, b'_', // 5 0, b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', // 6 b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', 0, 0, 0, b'~', 0, // 7 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F ]; /// The fragment component as defined in /// [[RFC3986, Section 3.5](https://tools.ietf.org/html/rfc3986#section-3.5)]. /// /// The fragment is case-sensitive. Furthermore, percent-encoding plays no role in equality checking /// for characters in the unreserved character set meaning that `"fragment"` and `"fr%61gment"` are /// identical. Both of these attributes are reflected in the equality and hash functions. /// /// However, be aware that just because percent-encoding plays no role in equality checking does not /// mean that the fragment is normalized. If the fragment needs to be normalized, use the /// [`Fragment::normalize`] function. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Fragment<'fragment> { /// The internal fragment source that is either owned or borrowed. fragment: Cow<'fragment, str>, /// Whether the fragment is normalized. normalized: bool, } impl Fragment<'_> { /// Returns a new fragment which is identical but has a lifetime tied to this fragment. pub fn as_borrowed(&self) -> Fragment { use self::Cow::*; let fragment = match &self.fragment { Borrowed(borrowed) => *borrowed, Owned(owned) => owned.as_str(), }; Fragment { fragment: Cow::Borrowed(fragment), normalized: self.normalized, } } /// Returns a `str` representation of the fragment. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Fragment; /// /// let fragment = Fragment::try_from("fragment").unwrap(); /// assert_eq!(fragment.as_str(), "fragment"); /// ``` pub fn as_str(&self) -> &str { &self.fragment } /// Converts the [`Fragment`] into an owned copy. /// /// If you construct the fragment from a source with a non-static lifetime, you may run into /// lifetime problems due to the way the struct is designed. Calling this function will ensure /// that the returned value has a static lifetime. /// /// This is different from just cloning. Cloning the fragment will just copy the references, and /// thus the lifetime will remain the same. pub fn into_owned(self) -> Fragment<'static> { Fragment { fragment: Cow::from(self.fragment.into_owned()), normalized: self.normalized, } } /// Returns whether the fragment is normalized. /// /// A normalized fragment will have no bytes that are in the unreserved character set /// percent-encoded and all alphabetical characters in percent-encodings will be uppercase. /// /// This function runs in constant-time. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Fragment; /// /// let fragment = Fragment::try_from("fragment").unwrap(); /// assert!(fragment.is_normalized()); /// /// let mut fragment = Fragment::try_from("%ff%ff").unwrap(); /// assert!(!fragment.is_normalized()); /// fragment.normalize(); /// assert!(fragment.is_normalized()); /// ``` pub fn is_normalized(&self) -> bool { self.normalized } /// Normalizes the fragment such that it will have no bytes that are in the unreserved character /// set percent-encoded and all alphabetical characters in percent-encodings will be uppercase. /// /// If the fragment is already normalized, the function will return immediately. Otherwise, if /// the fragment is not owned, this function will perform an allocation to clone it. The /// normalization itself though, is done in-place with no extra memory allocations required. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Fragment; /// /// let mut fragment = Fragment::try_from("fragment").unwrap(); /// fragment.normalize(); /// assert_eq!(fragment, "fragment"); /// /// let mut fragment = Fragment::try_from("%ff%41").unwrap(); /// assert_eq!(fragment, "%ff%41"); /// fragment.normalize(); /// assert_eq!(fragment, "%FFA"); /// ``` pub fn normalize(&mut self) { if !self.normalized { // Unsafe: Fragments must be valid ASCII-US, so this is safe. unsafe { normalize_string(&mut self.fragment.to_mut(), true) }; self.normalized = true; } } } impl AsRef<[u8]> for Fragment<'_> { fn as_ref(&self) -> &[u8] { self.fragment.as_bytes() } } impl AsRef for Fragment<'_> { fn as_ref(&self) -> &str { &self.fragment } } impl Deref for Fragment<'_> { type Target = str; fn deref(&self) -> &Self::Target { &self.fragment } } impl Display for Fragment<'_> { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { formatter.write_str(&self.fragment) } } impl Eq for Fragment<'_> {} impl<'fragment> From> for String { fn from(value: Fragment<'fragment>) -> Self { value.to_string() } } impl Hash for Fragment<'_> { fn hash(&self, state: &mut H) where H: Hasher, { percent_encoded_hash(self.fragment.as_bytes(), state, true); } } impl PartialEq for Fragment<'_> { fn eq(&self, other: &Fragment) -> bool { *self == *other.as_bytes() } } impl PartialEq<[u8]> for Fragment<'_> { fn eq(&self, other: &[u8]) -> bool { percent_encoded_equality(self.fragment.as_bytes(), other, true) } } impl<'fragment> PartialEq> for [u8] { fn eq(&self, other: &Fragment<'fragment>) -> bool { other == self } } impl<'a> PartialEq<&'a [u8]> for Fragment<'_> { fn eq(&self, other: &&'a [u8]) -> bool { self == *other } } impl<'a, 'fragment> PartialEq> for &'a [u8] { fn eq(&self, other: &Fragment<'fragment>) -> bool { other == *self } } impl PartialEq for Fragment<'_> { fn eq(&self, other: &str) -> bool { self == other.as_bytes() } } impl<'fragment> PartialEq> for str { fn eq(&self, other: &Fragment<'fragment>) -> bool { other == self.as_bytes() } } impl<'a> PartialEq<&'a str> for Fragment<'_> { fn eq(&self, other: &&'a str) -> bool { self == other.as_bytes() } } impl<'a, 'fragment> PartialEq> for &'a str { fn eq(&self, other: &Fragment<'fragment>) -> bool { other == self.as_bytes() } } impl<'fragment> TryFrom<&'fragment [u8]> for Fragment<'fragment> { type Error = FragmentError; fn try_from(value: &'fragment [u8]) -> Result { let mut bytes = value.iter(); let mut normalized = true; while let Some(&byte) = bytes.next() { match FRAGMENT_CHAR_MAP[byte as usize] { 0 => return Err(FragmentError::InvalidCharacter), b'%' => { match get_percent_encoded_value(bytes.next().cloned(), bytes.next().cloned()) { Ok((hex_value, uppercase)) => { if !uppercase || UNRESERVED_CHAR_MAP[hex_value as usize] != 0 { normalized = false; } } Err(_) => return Err(FragmentError::InvalidPercentEncoding), } } _ => (), } } // Unsafe: The loop above makes sure the byte string is valid ASCII-US. Ok(Fragment { fragment: Cow::from(unsafe { str::from_utf8_unchecked(value) }), normalized, }) } } impl<'fragment> TryFrom<&'fragment str> for Fragment<'fragment> { type Error = FragmentError; fn try_from(value: &'fragment str) -> Result { Fragment::try_from(value.as_bytes()) } } /// An error representing an invalid fragment. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum FragmentError { /// The fragment contained an invalid character. InvalidCharacter, /// The fragment contained an invalid percent encoding (e.g. `"%ZZ"`). InvalidPercentEncoding, } impl Display for FragmentError { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { use self::FragmentError::*; match self { InvalidCharacter => write!(formatter, "invalid fragment character"), InvalidPercentEncoding => write!(formatter, "invalid fragment percent encoding"), } } } impl Error for FragmentError {} impl From for FragmentError { fn from(_: Infallible) -> Self { FragmentError::InvalidCharacter } } #[cfg(test)] mod test { use super::*; #[test] fn test_fragment_normalize() { fn test_case(value: &str, expected: &str) { let mut fragment = Fragment::try_from(value).unwrap(); fragment.normalize(); assert_eq!(fragment, expected); } test_case("", ""); test_case("%ff", "%FF"); test_case("%41", "A"); } #[test] fn test_fragment_parse() { use self::FragmentError::*; assert_eq!(Fragment::try_from("").unwrap(), ""); assert_eq!(Fragment::try_from("fragment").unwrap(), "fragment"); assert_eq!(Fragment::try_from("fRaGmEnT").unwrap(), "fRaGmEnT"); assert_eq!(Fragment::try_from("%ff%ff%ff%41").unwrap(), "%ff%ff%ff%41"); assert_eq!(Fragment::try_from(" "), Err(InvalidCharacter)); assert_eq!(Fragment::try_from("%"), Err(InvalidPercentEncoding)); assert_eq!(Fragment::try_from("%f"), Err(InvalidPercentEncoding)); assert_eq!(Fragment::try_from("%zz"), Err(InvalidPercentEncoding)); } } uriparse-0.6.4/src/lib.rs000064400000000000000000000014760072674642500134300ustar 00000000000000mod utility; pub mod authority; pub mod fragment; pub mod path; pub mod query; pub mod relative_reference; pub mod scheme; pub mod uri; pub mod uri_reference; pub use self::authority::{ Authority, AuthorityError, Host, HostError, Password, PasswordError, PortError, RegisteredName, RegisteredNameError, Username, UsernameError, }; pub use self::fragment::{Fragment, FragmentError}; pub use self::path::{Path, PathError, Segment}; pub use self::query::{Query, QueryError}; pub use self::relative_reference::{ RelativeReference, RelativeReferenceBuilder, RelativeReferenceError, }; pub use self::scheme::{Scheme, SchemeError, SchemeStatus, UnregisteredScheme}; pub use self::uri::{URIBuilder, URIError, URI}; pub use self::uri_reference::{URIReference, URIReferenceBuilder, URIReferenceError}; uriparse-0.6.4/src/path.rs000064400000000000000000001325020072674642500136110ustar 00000000000000//! Path Component //! //! See [[RFC3986, Section 3.3](https://tools.ietf.org/html/rfc3986#section-3.3)]. use std::borrow::Cow; use std::convert::{Infallible, TryFrom}; use std::error::Error; use std::fmt::{self, Display, Formatter, Write}; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::str; use crate::utility::{ get_percent_encoded_value, normalize_string, percent_encoded_equality, percent_encoded_hash, UNRESERVED_CHAR_MAP, }; /// A map of byte characters that determines if a character is a valid path character. #[rustfmt::skip] const PATH_CHAR_MAP: [u8; 256] = [ // 0 1 2 3 4 5 6 7 8 9 A B C D E F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 0, b'!', 0, 0, b'$', b'%', b'&',b'\'', b'(', b')', b'*', b'+', b',', b'-', b'.', 0, // 2 b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', b';', 0, b'=', 0, 0, // 3 b'@', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 4 b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', 0, 0, 0, 0, b'_', // 5 0, b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', // 6 b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', 0, 0, 0, b'~', 0, // 7 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F ]; /// The path component as defined in /// [[RFC3986, Section 3.3](https://tools.ietf.org/html/rfc3986#section-3.3)]. /// /// A path is composed of a sequence of segments. It is also either absolute or relative, where an /// absolute path starts with a `'/'`. A URI with an authority *always* has an absolute path /// regardless of whether the path was empty (i.e. "http://example.com" has a single empty /// path segment and is absolute). /// /// Each segment in the path is case-sensitive. Furthermore, percent-encoding plays no role in /// equality checking for characters in the unreserved character set meaning that `"segment"` and /// `"s%65gment"` are identical. Both of these attributes are reflected in the equality and hash /// functions. /// /// However, be aware that just because percent-encoding plays no role in equality checking does not /// mean that either the path or a given segment is normalized. If the path or a segment needs to be /// normalized, use either the [`Path::normalize`] or [`Segment::normalize`] functions, /// respectively. #[derive(Clone, Debug, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Path<'path> { /// whether the path is absolute. Specifically, a path is absolute if it starts with a /// `'/'`. absolute: bool, /// The total number of double dot segments in the path. double_dot_segment_count: u16, /// The number of double dot segments consecutive from the beginning of the path. leading_double_dot_segment_count: u16, /// The sequence of segments that compose the path. segments: Vec>, /// The total number of single dot segments in the path. single_dot_segment_count: u16, /// The total number of unnormalized segments in the path. unnormalized_count: u16, } impl<'path> Path<'path> { /// Clears all segments from the path leaving a single empty segment. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Path; /// /// let mut path = Path::try_from("/my/path").unwrap(); /// assert_eq!(path, "/my/path"); /// path.clear(); /// assert_eq!(path, "/"); /// ``` pub fn clear(&mut self) { self.segments.clear(); self.segments.push(Segment::empty()); } /// Converts the [`Path`] into an owned copy. /// /// If you construct the path from a source with a non-static lifetime, you may run into /// lifetime problems due to the way the struct is designed. Calling this function will ensure /// that the returned value has a static lifetime. /// /// This is different from just cloning. Cloning the path will just copy the references, and /// thus the lifetime will remain the same. pub fn into_owned(self) -> Path<'static> { let segments = self .segments .into_iter() .map(Segment::into_owned) .collect::>>(); Path { absolute: self.absolute, double_dot_segment_count: self.double_dot_segment_count, leading_double_dot_segment_count: self.leading_double_dot_segment_count, segments, single_dot_segment_count: self.single_dot_segment_count, unnormalized_count: self.unnormalized_count, } } /// Returns whether the path is absolute (i.e. it starts with a `'/'`). /// /// Any path following an [`Authority`] will *always* be parsed to be absolute. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Path; /// /// let path = Path::try_from("/my/path").unwrap(); /// assert_eq!(path.is_absolute(), true); /// ``` pub fn is_absolute(&self) -> bool { self.absolute } /// Returns whether the path is normalized either as or as not a reference. /// /// See [`Path::normalize`] for a full description of what path normalization entails. /// /// Although this function does not operate in constant-time in general, it will be /// constant-time in the vast majority of cases. /// /// /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Path; /// /// let path = Path::try_from("/my/path").unwrap(); /// assert!(path.is_normalized(false)); /// /// let path = Path::try_from("/my/p%61th").unwrap(); /// assert!(!path.is_normalized(false)); /// /// let path = Path::try_from("..").unwrap(); /// assert!(path.is_normalized(true)); /// /// let path = Path::try_from("../.././.").unwrap(); /// assert!(!path.is_normalized(true)); /// ``` pub fn is_normalized(&self, as_reference: bool) -> bool { if self.unnormalized_count != 0 { return false; } if self.absolute || !as_reference { self.single_dot_segment_count == 0 && self.double_dot_segment_count == 0 } else { (self.single_dot_segment_count == 0 || (self.single_dot_segment_count == 1 && self.segments[0].is_single_dot_segment() && self.segments.len() > 1 && self.segments[1].contains(':'))) && self.double_dot_segment_count == self.leading_double_dot_segment_count } } /// Returns whether the path is relative (i.e. it does not start with a `'/'`). /// /// Any path following an [`Authority`] will *always* be parsed to be absolute. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Path; /// /// let path = Path::try_from("my/path").unwrap(); /// assert_eq!(path.is_relative(), true); /// ``` pub fn is_relative(&self) -> bool { !self.absolute } /// Creates a path with no segments on it. /// /// This is only used to avoid allocations for temporary paths. Any path created using this /// function is **not** valid! pub(crate) unsafe fn new_with_no_segments(absolute: bool) -> Path<'static> { Path { absolute, double_dot_segment_count: 0, leading_double_dot_segment_count: 0, segments: Vec::new(), single_dot_segment_count: 0, unnormalized_count: 0, } } /// Normalizes the path and all of its segments. /// /// There are two components to path normalization, the normalization of each segment /// individually and the removal of unnecessary dot segments. It is also guaranteed that whether /// the path is absolute will not change as a result of normalization. /// /// The normalization of each segment will proceed according to [`Segment::normalize`]. /// /// If the path is absolute (i.e., it starts with a `'/'`), then `as_reference` will be set to /// `false` regardless of its set value. /// /// If `as_reference` is `false`, then all dot segments will be removed as they would be if you /// had called [`Path::remove_dot_segments`]. Otherwise, when a dot segment is removed is /// dependent on whether it's `"."` or `".."` and its location in the path. /// /// In general, `"."` dot segments are always removed except for when it is at the beginning of /// the path and is followed by a segment containing a `':'`, e.g. `"./a:b"` stays the same. /// /// For `".."` dot segments, they are kept whenever they are at the beginning of the path and /// removed whenever they are not, e.g. `"a/../.."` normalizes to `".."`. pub fn normalize(&mut self, as_reference: bool) { if self.is_normalized(as_reference) { return; } self.unnormalized_count = 0; if self.absolute || !as_reference { self.remove_dot_segments_helper(true); return; } let mut double_dot_segment_count = 0; let mut last_dot_segment = None; let mut new_length = 0; for i in 0..self.segments.len() { let segment = &self.segments[i]; if segment.is_single_dot_segment() && (new_length > 0 || i == self.segments.len() - 1 || !self.segments[i + 1].as_str().contains(':')) { continue; } if segment.is_double_dot_segment() { match last_dot_segment { None if new_length == 0 => (), Some(index) if index == new_length - 1 => (), _ => { if new_length == 2 && self.segments[0].is_single_dot_segment() && (i == self.segments.len() - 1 || !self.segments[i + 1].as_str().contains(':')) { new_length -= 1 } new_length -= 1; continue; } } double_dot_segment_count += 1; last_dot_segment = Some(new_length); } self.segments.swap(i, new_length); self.segments[new_length].normalize(); new_length += 1; } if new_length == 0 { self.segments[0] = Segment::empty(); new_length = 1; } self.double_dot_segment_count = double_dot_segment_count; self.leading_double_dot_segment_count = double_dot_segment_count; self.single_dot_segment_count = if self.segments[0].is_single_dot_segment() { 1 } else { 0 }; self.segments.truncate(new_length); } /// Pops the last segment off of the path. /// /// If the path only contains one segment, then that segment will become empty. /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Path; /// /// let mut path = Path::try_from("/my/path").unwrap(); /// path.pop(); /// assert_eq!(path, "/my"); /// path.pop(); /// assert_eq!(path, "/"); /// ``` pub fn pop(&mut self) { let segment = self.segments.pop().unwrap(); if segment.is_single_dot_segment() { self.single_dot_segment_count = self.single_dot_segment_count.checked_sub(1).unwrap_or(0); } if segment.is_double_dot_segment() { self.double_dot_segment_count = self.double_dot_segment_count.checked_sub(1).unwrap_or(0); if self.double_dot_segment_count < self.leading_double_dot_segment_count { self.leading_double_dot_segment_count -= 1; } } if !segment.is_normalized() { self.unnormalized_count = self.unnormalized_count.checked_sub(1).unwrap_or(0); } if self.segments.is_empty() { self.segments.push(Segment::empty()); } } /// Pushes a segment onto the path. /// /// If the conversion to a [`Segment`] fails, an [`InvalidPath`] will be returned. /// /// The behavior of this function is different if the current path is just one empty segment. In /// this case, the pushed segment will replace that empty segment unless the pushed segment is /// itself empty. /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Path; /// /// let mut path = Path::try_from("/my/path").unwrap(); /// path.push("test"); /// assert_eq!(path, "/my/path/test"); /// /// let mut path = Path::try_from("/").unwrap(); /// path.push("test"); /// assert_eq!(path, "/test"); /// /// let mut path = Path::try_from("/").unwrap(); /// path.push(""); /// assert_eq!(path, "//"); /// ``` pub fn push(&mut self, segment: TSegment) -> Result<(), PathError> where Segment<'path>: TryFrom, PathError: From, { if self.segments.len() as u16 == u16::max_value() { return Err(PathError::ExceededMaximumLength); } let segment = Segment::try_from(segment)?; if segment.is_single_dot_segment() { self.single_dot_segment_count += 1; } if segment.is_double_dot_segment() { if self.segments.len() as u16 == self.double_dot_segment_count { self.leading_double_dot_segment_count += 1; } self.double_dot_segment_count += 1; } if !segment.is_normalized() { self.unnormalized_count += 1; } if segment != "" && self.segments.len() == 1 && self.segments[0].as_str().is_empty() { self.segments[0] = segment; } else { self.segments.push(segment); } Ok(()) } /// Removes all dot segments from the path according to the algorithm described in /// [[RFC3986, Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4)]. /// /// This function will perform no memory allocations during removal of dot segments. /// /// If the path currently has no dot segments, then this function is a no-op. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Path; /// /// let mut path = Path::try_from("/a/b/c/./../../g").unwrap(); /// path.remove_dot_segments(); /// assert_eq!(path, "/a/g"); /// ``` pub fn remove_dot_segments(&mut self) { if self.single_dot_segment_count == 0 && self.double_dot_segment_count == 0 { return; } self.remove_dot_segments_helper(false); } /// Helper function that removes all dot segments with optional segment normalization. fn remove_dot_segments_helper(&mut self, normalize_segments: bool) { let mut input_absolute = self.absolute; let mut new_length = 0; for i in 0..self.segments.len() { let segment = &self.segments[i]; if input_absolute { if segment.is_single_dot_segment() { continue; } else if segment.is_double_dot_segment() { if new_length > 0 { new_length -= 1; } else { self.absolute = false; } continue; } if new_length == 0 { self.absolute = true; } } else if segment.is_single_dot_segment() || segment.is_double_dot_segment() { continue; } self.segments.swap(i, new_length); if normalize_segments { self.segments[new_length].normalize(); } new_length += 1; if i < self.segments.len() - 1 { input_absolute = true; } else { input_absolute = false; } } if input_absolute { if new_length == 0 { self.absolute = true; } else { self.segments[new_length] = Segment::empty(); new_length += 1; } } if new_length == 0 { self.segments[0] = Segment::empty(); new_length = 1; } self.double_dot_segment_count = 0; self.leading_double_dot_segment_count = 0; self.single_dot_segment_count = 0; self.segments.truncate(new_length); } /// Returns the segments of the path. /// /// If you require mutability, use [`Path::segments_mut`]. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Path; /// /// let mut path = Path::try_from("/my/path").unwrap(); /// assert_eq!(path.segments()[1], "path"); /// ``` pub fn segments(&self) -> &[Segment<'path>] { &self.segments } /// Returns the segments of the path mutably. /// /// Due to the required restriction that there must be at least one segment in a path, this /// mutability only applies to the segments themselves, not the container. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Path, Segment}; /// /// let mut path = Path::try_from("/my/path").unwrap(); /// let mut segments = path.segments_mut(); /// segments[1] = Segment::try_from("test").unwrap(); /// /// assert_eq!(path, "/my/test"); /// ``` pub fn segments_mut(&mut self) -> &mut [Segment<'path>] { &mut self.segments } /// Sets whether the path is absolute (i.e. it starts with a `'/'`). /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Path; /// /// let mut path = Path::try_from("/my/path").unwrap(); /// path.set_absolute(false); /// assert_eq!(path, "my/path"); /// ``` pub fn set_absolute(&mut self, absolute: bool) { self.absolute = absolute; } /// Returns a new path which is identical but has a lifetime tied to this path. /// /// This function will perform a memory allocation. pub fn to_borrowed(&self) -> Path { let segments = self.segments.iter().map(Segment::as_borrowed).collect(); Path { absolute: self.absolute, double_dot_segment_count: self.double_dot_segment_count, leading_double_dot_segment_count: self.leading_double_dot_segment_count, segments, single_dot_segment_count: self.single_dot_segment_count, unnormalized_count: self.unnormalized_count, } } } impl Display for Path<'_> { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { if self.absolute { formatter.write_char('/')?; } for (index, segment) in self.segments.iter().enumerate() { formatter.write_str(segment.as_str())?; if index < self.segments.len() - 1 { formatter.write_char('/')?; } } Ok(()) } } impl Hash for Path<'_> { fn hash(&self, state: &mut H) where H: Hasher, { self.segments.hash(state) } } impl<'path> From> for String { fn from(value: Path<'path>) -> Self { value.to_string() } } impl PartialEq for Path<'_> { fn eq(&self, other: &Path) -> bool { self.segments == other.segments } } impl PartialEq<[u8]> for Path<'_> { fn eq(&self, mut other: &[u8]) -> bool { if self.absolute { match other.get(0) { Some(&byte) => { if byte != b'/' { return false; } } None => return false, } other = &other[1..]; } for (index, segment) in self.segments.iter().enumerate() { let len = segment.as_str().len(); if other.len() < len || &other[..len] != segment { return false; } other = &other[len..]; if index < self.segments.len() - 1 { match other.get(0) { Some(&byte) => { if byte != b'/' { return false; } } None => return false, } other = &other[1..]; } } true } } impl<'path> PartialEq> for [u8] { fn eq(&self, other: &Path<'path>) -> bool { other == self } } impl<'a> PartialEq<&'a [u8]> for Path<'_> { fn eq(&self, other: &&'a [u8]) -> bool { self == *other } } impl<'a, 'path> PartialEq> for &'a [u8] { fn eq(&self, other: &Path<'path>) -> bool { other == *self } } impl PartialEq for Path<'_> { fn eq(&self, other: &str) -> bool { self == other.as_bytes() } } impl<'path> PartialEq> for str { fn eq(&self, other: &Path<'path>) -> bool { other == self.as_bytes() } } impl<'a> PartialEq<&'a str> for Path<'_> { fn eq(&self, other: &&'a str) -> bool { self == other.as_bytes() } } impl<'a, 'path> PartialEq> for &'a str { fn eq(&self, other: &Path<'path>) -> bool { other == self.as_bytes() } } impl<'path> TryFrom<&'path [u8]> for Path<'path> { type Error = PathError; fn try_from(value: &'path [u8]) -> Result { let (path, rest) = parse_path(value)?; if rest.is_empty() { Ok(path) } else { Err(PathError::InvalidCharacter) } } } impl<'path> TryFrom<&'path str> for Path<'path> { type Error = PathError; fn try_from(value: &'path str) -> Result { Path::try_from(value.as_bytes()) } } /// A segment of a path. /// /// Segments are separated from other segments with the `'/'` delimiter. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Segment<'segment> { /// Whether the segment is normalized. normalized: bool, /// The internal segment source that is either owned or borrowed. segment: Cow<'segment, str>, } impl Segment<'_> { /// Returns a new segment which is identical but has as lifetime tied to this segment. pub fn as_borrowed(&self) -> Segment { use self::Cow::*; let segment = match &self.segment { Borrowed(borrowed) => *borrowed, Owned(owned) => owned.as_str(), }; Segment { normalized: self.normalized, segment: Cow::Borrowed(segment), } } /// Returns a `str` representation of the segment. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Segment; /// /// let segment = Segment::try_from("segment").unwrap(); /// assert_eq!(segment.as_str(), "segment"); /// ``` pub fn as_str(&self) -> &str { &self.segment } /// Constructs a segment that is empty. /// /// # Examples /// /// ``` /// use uriparse::Segment; /// /// assert_eq!(Segment::empty(), ""); /// ``` pub fn empty() -> Segment<'static> { Segment { normalized: true, segment: Cow::from(""), } } /// Converts the [`Segment`] into an owned copy. /// /// If you construct the segment from a source with a non-static lifetime, you may run into /// lifetime problems due to the way the struct is designed. Calling this function will ensure /// that the returned value has a static lifetime. /// /// This is different from just cloning. Cloning the segment will just copy the references, and /// thus the lifetime will remain the same. pub fn into_owned(self) -> Segment<'static> { Segment { normalized: self.normalized, segment: Cow::from(self.segment.into_owned()), } } /// Returns whether the segment is a dot segment, i.e., is `"."` or `".."`. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Segment; /// /// let segment = Segment::try_from("segment").unwrap(); /// assert!(!segment.is_dot_segment()); /// /// let segment = Segment::try_from(".").unwrap(); /// assert!(segment.is_dot_segment()); /// /// let segment = Segment::try_from("..").unwrap(); /// assert!(segment.is_dot_segment()); /// ``` pub fn is_dot_segment(&self) -> bool { self == "." || self == ".." } /// Returns whether the segment is a dot segment, i.e., is `".."`. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Segment; /// /// let segment = Segment::try_from("segment").unwrap(); /// assert!(!segment.is_double_dot_segment()); /// /// let segment = Segment::try_from(".").unwrap(); /// assert!(!segment.is_double_dot_segment()); /// /// let segment = Segment::try_from("..").unwrap(); /// assert!(segment.is_double_dot_segment()); /// ``` pub fn is_double_dot_segment(&self) -> bool { self == ".." } /// Returns whether the segment is normalized. /// /// A normalized segment will have no bytes that are in the unreserved character set /// percent-encoded and all alphabetical characters in percent-encodings will be uppercase. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Segment; /// /// let segment = Segment::try_from("segment").unwrap(); /// assert!(segment.is_normalized()); /// /// let mut segment = Segment::try_from("%ff%ff").unwrap(); /// assert!(!segment.is_normalized()); /// segment.normalize(); /// assert!(segment.is_normalized()); /// ``` pub fn is_normalized(&self) -> bool { self.normalized } /// Returns whether the segment is a dot segment, i.e., is `"."`. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Segment; /// /// let segment = Segment::try_from("segment").unwrap(); /// assert!(!segment.is_single_dot_segment()); /// /// let segment = Segment::try_from(".").unwrap(); /// assert!(segment.is_single_dot_segment()); /// /// let segment = Segment::try_from("..").unwrap(); /// assert!(!segment.is_single_dot_segment()); /// ``` pub fn is_single_dot_segment(&self) -> bool { self == "." } /// Normalizes the segment such that it will have no bytes that are in the unreserved character /// set percent-encoded and all alphabetical characters in percent-encodings will be uppercase. /// /// If the segment is already normalized, the function will return immediately. Otherwise, if /// the segment is not owned, this function will perform an allocation to clone it. The /// normalization itself though, is done in-place with no extra memory allocations required. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Segment; /// /// let mut segment = Segment::try_from("segment").unwrap(); /// segment.normalize(); /// assert_eq!(segment, "segment"); /// /// let mut segment = Segment::try_from("%ff%41").unwrap(); /// assert_eq!(segment, "%ff%41"); /// segment.normalize(); /// assert_eq!(segment, "%FFA"); /// ``` pub fn normalize(&mut self) { if !self.normalized { // Unsafe: Paths must be valid ASCII-US, so this is safe. unsafe { normalize_string(&mut self.segment.to_mut(), true) }; self.normalized = true; } } } impl AsRef<[u8]> for Segment<'_> { fn as_ref(&self) -> &[u8] { self.segment.as_bytes() } } impl AsRef for Segment<'_> { fn as_ref(&self) -> &str { &self.segment } } impl Deref for Segment<'_> { type Target = str; fn deref(&self) -> &Self::Target { &self.segment } } impl Display for Segment<'_> { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { formatter.write_str(&self.segment) } } impl Eq for Segment<'_> {} impl<'segment> From> for String { fn from(value: Segment<'segment>) -> Self { value.to_string() } } impl Hash for Segment<'_> { fn hash(&self, state: &mut H) where H: Hasher, { percent_encoded_hash(self.segment.as_bytes(), state, true); } } impl PartialEq for Segment<'_> { fn eq(&self, other: &Segment) -> bool { *self == *other.as_bytes() } } impl PartialEq<[u8]> for Segment<'_> { fn eq(&self, other: &[u8]) -> bool { percent_encoded_equality(self.segment.as_bytes(), other, true) } } impl<'segment> PartialEq> for [u8] { fn eq(&self, other: &Segment<'segment>) -> bool { other == self } } impl<'a> PartialEq<&'a [u8]> for Segment<'_> { fn eq(&self, other: &&'a [u8]) -> bool { self == *other } } impl<'a, 'segment> PartialEq> for &'a [u8] { fn eq(&self, other: &Segment<'segment>) -> bool { other == *self } } impl PartialEq for Segment<'_> { fn eq(&self, other: &str) -> bool { self == other.as_bytes() } } impl<'segment> PartialEq> for str { fn eq(&self, other: &Segment<'segment>) -> bool { other == self.as_bytes() } } impl<'a> PartialEq<&'a str> for Segment<'_> { fn eq(&self, other: &&'a str) -> bool { self == other.as_bytes() } } impl<'a, 'segment> PartialEq> for &'a str { fn eq(&self, other: &Segment<'segment>) -> bool { other == self.as_bytes() } } impl<'segment> TryFrom<&'segment [u8]> for Segment<'segment> { type Error = PathError; fn try_from(value: &'segment [u8]) -> Result { let mut bytes = value.iter(); let mut normalized = true; while let Some(&byte) = bytes.next() { match PATH_CHAR_MAP[byte as usize] { 0 => return Err(PathError::InvalidCharacter), b'%' => { match get_percent_encoded_value(bytes.next().cloned(), bytes.next().cloned()) { Ok((hex_value, uppercase)) => { if !uppercase || UNRESERVED_CHAR_MAP[hex_value as usize] != 0 { normalized = false; } } Err(_) => return Err(PathError::InvalidPercentEncoding), } } _ => (), } } // Unsafe: The loop above makes sure the byte string is valid ASCII-US. let segment = Segment { normalized, segment: Cow::Borrowed(unsafe { str::from_utf8_unchecked(value) }), }; Ok(segment) } } impl<'segment> TryFrom<&'segment str> for Segment<'segment> { type Error = PathError; fn try_from(value: &'segment str) -> Result { Segment::try_from(value.as_bytes()) } } /// An error representing an invalid path. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum PathError { /// The path exceeded the maximum length allowed. Due to implementation reasons, the maximum /// length a path can be is 2^16 or 65536 characters. ExceededMaximumLength, /// The path contained an invalid character. InvalidCharacter, /// The path contained an invalid percent encoding (e.g. `"%ZZ"`). InvalidPercentEncoding, } impl Display for PathError { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { use self::PathError::*; match self { ExceededMaximumLength => write!(formatter, "exceeded maximum path length"), InvalidCharacter => write!(formatter, "invalid path character"), InvalidPercentEncoding => write!(formatter, "invalid path percent encoding"), } } } impl Error for PathError {} impl From for PathError { fn from(_: Infallible) -> Self { PathError::InvalidCharacter } } /// Parses the path from the given byte string. pub(crate) fn parse_path(value: &[u8]) -> Result<(Path, &[u8]), PathError> { struct SegmentInfo { absolute: bool, double_dot_segment_count: u16, index: u16, last_double_dot_segment: Option, leading_double_dot_segment_count: u16, normalized: bool, single_dot_segment_count: u16, unnormalized_count: u16, } impl SegmentInfo { fn into_path<'path>(self, segments: Vec>) -> Path<'path> { Path { absolute: self.absolute, double_dot_segment_count: self.double_dot_segment_count, leading_double_dot_segment_count: self.leading_double_dot_segment_count, segments, single_dot_segment_count: self.single_dot_segment_count, unnormalized_count: self.unnormalized_count, } } } #[allow(clippy::too_many_arguments)] fn new_segment<'segment>( segment: &'segment [u8], segment_info: &mut SegmentInfo, ) -> Segment<'segment> { if !segment_info.normalized { segment_info.unnormalized_count += 1; } if segment == b"." { segment_info.single_dot_segment_count += 1; } if segment == b".." { let index = segment_info.index - 1; segment_info.double_dot_segment_count += 1; if index == 0 || segment_info.last_double_dot_segment == Some(index - 1) { segment_info.leading_double_dot_segment_count += 1; segment_info.last_double_dot_segment = Some(index); } } // Unsafe: The loop above makes sure the byte string is valid ASCII-US. Segment { normalized: segment_info.normalized, segment: Cow::from(unsafe { str::from_utf8_unchecked(segment) }), } } let (value, absolute) = if value.starts_with(b"/") { (&value[1..], true) } else { (value, false) }; let mut bytes = value.iter(); let mut segment_info = SegmentInfo { absolute, double_dot_segment_count: 0, index: 1, last_double_dot_segment: None, leading_double_dot_segment_count: 0, normalized: true, single_dot_segment_count: 0, unnormalized_count: 0, }; let mut segment_end_index = 0; let mut segment_start_index = 0; // Set some moderate initial capacity. This seems to help with performance a bit. let mut segments = Vec::with_capacity(10); while let Some(&byte) = bytes.next() { match PATH_CHAR_MAP[byte as usize] { 0 if byte == b'?' || byte == b'#' => { let segment = new_segment( &value[segment_start_index..segment_end_index], &mut segment_info, ); segments.push(segment); let path = segment_info.into_path(segments); return Ok((path, &value[segment_end_index..])); } 0 if byte == b'/' => { let segment = new_segment( &value[segment_start_index..segment_end_index], &mut segment_info, ); segments.push(segment); segment_end_index += 1; segment_start_index = segment_end_index; segment_info.index = segment_info .index .checked_add(1) .ok_or(PathError::ExceededMaximumLength)?; segment_info.normalized = true; } 0 => return Err(PathError::InvalidCharacter), b'%' => match get_percent_encoded_value(bytes.next().cloned(), bytes.next().cloned()) { Ok((hex_value, uppercase)) => { if !uppercase || UNRESERVED_CHAR_MAP[hex_value as usize] != 0 { segment_info.normalized = false; } segment_end_index += 3; } Err(_) => return Err(PathError::InvalidPercentEncoding), }, _ => segment_end_index += 1, } } let segment = new_segment(&value[segment_start_index..], &mut segment_info); segments.push(segment); let path = segment_info.into_path(segments); Ok((path, b"")) } #[cfg(test)] mod test { use super::*; #[test] fn test_path_equals() { assert_eq!( Path::try_from("segment").unwrap(), Path::try_from("s%65gment").unwrap() ); } #[test] fn test_path_normalize() { fn test_case(value: &str, expected: &str, as_reference: bool) { let mut path = Path::try_from(value).unwrap(); path.normalize(as_reference); let expected_single_dot_segment_count = if expected.starts_with("./") { 1 } else { 0 }; let expected_double_dot_segment_count = expected .split('/') .filter(|&segment| segment == "..") .count() as u16; assert!(!path.segments().is_empty()); assert!(path.is_normalized(as_reference)); assert_eq!( path.single_dot_segment_count, expected_single_dot_segment_count ); assert_eq!( path.double_dot_segment_count, expected_double_dot_segment_count ); assert_eq!( path.leading_double_dot_segment_count, expected_double_dot_segment_count ); assert_eq!(path.to_string(), expected); } test_case("", "", true); test_case(".", "", true); test_case("..", "..", true); test_case("../", "../", true); test_case("/.", "/", true); test_case("./././././././.", "", true); test_case("././././././././", "", true); test_case("/..", "/", true); test_case("../..", "../..", true); test_case("../a/../..", "../..", true); test_case("a", "a", true); test_case("a/..", "", true); test_case("a/../", "", true); test_case("a/../..", "..", true); test_case("./a:b", "./a:b", true); test_case("./a:b/..", "", true); test_case("./a:b/../c:d", "./c:d", true); test_case("./../a:b", "../a:b", true); test_case("../a/../", "../", true); test_case("../../.././.././../../../.", "../../../../../../..", true); test_case("a/.././a:b", "./a:b", true); test_case("", "", false); test_case(".", "", false); test_case("..", "", false); test_case("../", "", false); test_case("/.", "/", false); test_case("/..", "/", false); test_case("../../.././.././../../../.", "", false); test_case("a/../..", "/", false); test_case("a/../../", "/", false); test_case("/a/../../../../", "/", false); test_case("/a/./././././././c", "/a/c", false); test_case("/a/.", "/a/", false); test_case("/a/./", "/a/", false); test_case("/a/..", "/", false); test_case("/a/b/./..", "/a/", false); test_case("/a/b/./../", "/a/", false); test_case("/a/b/c/./../../g", "/a/g", false); test_case("mid/content=5/../6", "mid/6", false); test_case("this/is/a/t%65st/path/%ff", "this/is/a/test/path/%FF", true); test_case( "this/is/a/t%65st/path/%ff", "this/is/a/test/path/%FF", false, ); } #[test] fn test_path_parse() { use self::PathError::*; let slash = "/".to_string(); assert_eq!(Path::try_from("").unwrap(), ""); assert_eq!(Path::try_from("/").unwrap(), "/"); assert_eq!( Path::try_from("/tHiS/iS/a/PaTh").unwrap(), "/tHiS/iS/a/PaTh" ); assert_eq!(Path::try_from("%ff%ff%ff%41").unwrap(), "%ff%ff%ff%41"); assert!(Path::try_from(&*slash.repeat(65535)).is_ok()); assert_eq!( Path::try_from(&*slash.repeat(65536)), Err(ExceededMaximumLength) ); assert_eq!(Path::try_from(" "), Err(InvalidCharacter)); assert_eq!(Path::try_from("#"), Err(InvalidCharacter)); assert_eq!(Path::try_from("%"), Err(InvalidPercentEncoding)); assert_eq!(Path::try_from("%f"), Err(InvalidPercentEncoding)); assert_eq!(Path::try_from("%zz"), Err(InvalidPercentEncoding)); } #[test] fn test_path_remove_dot_segments() { fn test_case(value: &str, expected: &str) { let mut path = Path::try_from(value).unwrap(); path.remove_dot_segments(); assert!(!path.segments().is_empty()); assert_eq!(path.single_dot_segment_count, 0); assert_eq!(path.double_dot_segment_count, 0); assert_eq!(path.leading_double_dot_segment_count, 0); assert_eq!(path.to_string(), expected); } test_case("", ""); test_case(".", ""); test_case("..", ""); test_case("../", ""); test_case("/.", "/"); test_case("/..", "/"); test_case("../../.././.././../../../.", ""); test_case("a/../..", "/"); test_case("a/../../", "/"); test_case("/a/../../../..", "/"); test_case("/a/../../../../", "/"); test_case("/a/./././././././c", "/a/c"); test_case("/a/.", "/a/"); test_case("/a/./", "/a/"); test_case("/a/..", "/"); test_case("/a/b/./..", "/a/"); test_case("/a/b/./../", "/a/"); test_case("/a/b/c/./../../g", "/a/g"); test_case("mid/content=5/../6", "mid/6"); } #[test] fn test_segment_normalize() { fn test_case(value: &str, expected: &str) { let mut segment = Segment::try_from(value).unwrap(); segment.normalize(); assert_eq!(segment, expected); } test_case("", ""); test_case("%ff", "%FF"); test_case("%41", "A"); } #[test] fn test_segment_parse() { use self::PathError::*; assert_eq!(Segment::try_from("").unwrap(), ""); assert_eq!(Segment::try_from("segment").unwrap(), "segment"); assert_eq!(Segment::try_from("sEgMeNt").unwrap(), "sEgMeNt"); assert_eq!(Segment::try_from("%ff%ff%ff%41").unwrap(), "%ff%ff%ff%41"); assert_eq!(Segment::try_from(" "), Err(InvalidCharacter)); assert_eq!(Segment::try_from("/"), Err(InvalidCharacter)); assert_eq!(Segment::try_from("%"), Err(InvalidPercentEncoding)); assert_eq!(Segment::try_from("%f"), Err(InvalidPercentEncoding)); assert_eq!(Segment::try_from("%zz"), Err(InvalidPercentEncoding)); } } uriparse-0.6.4/src/query.rs000064400000000000000000000310300072674642500140140ustar 00000000000000//! Query Component //! //! See [[RFC3986, Section 3.4](https://tools.ietf.org/html/rfc3986#section-3.4)]. //! //! This crate does not do query string parsing, it will simply make sure that it is a valid query //! string as defined by the RFC. You will need to use another crate (e.g. //! [queryst](https://github.com/rustless/queryst)) if you want it parsed. use std::borrow::Cow; use std::convert::{Infallible, TryFrom}; use std::error::Error; use std::fmt::{self, Display, Formatter}; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::str; use crate::utility::{ get_percent_encoded_value, normalize_string, percent_encoded_equality, percent_encoded_hash, UNRESERVED_CHAR_MAP, }; /// A map of byte characters that determines if a character is a valid query character. #[rustfmt::skip] const QUERY_CHAR_MAP: [u8; 256] = [ // 0 1 2 3 4 5 6 7 8 9 A B C D E F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 0, b'!', 0, 0, b'$', b'%', b'&',b'\'', b'(', b')', b'*', b'+', b',', b'-', b'.', b'/', // 2 b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', b';', 0, b'=', 0, b'?', // 3 b'@', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 4 b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', 0, 0, 0, 0, b'_', // 5 0, b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', // 6 b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', 0, 0, 0, b'~', 0, // 7 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F ]; /// The query component as defined in /// [[RFC3986, Section 3.4](https://tools.ietf.org/html/rfc3986#section-3.4)]. /// /// The query is case-sensitive. Furthermore, percent-encoding plays no role in equality checking /// for characters in the unreserved character set meaning that `"query"` and `"que%72y"` are /// identical. Both of these attributes are reflected in the equality and hash functions. /// /// However, be aware that just because percent-encoding plays no role in equality checking does not /// mean that the query is normalized. If the query needs to be normalized, use the /// [`Query::normalize`] function. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Query<'query> { /// Whether the query is normalized. normalized: bool, /// The internal query source that is either owned or borrowed. query: Cow<'query, str>, } impl Query<'_> { /// Returns a new query which is identical but has a lifetime tied to this query. pub fn as_borrowed(&self) -> Query { use self::Cow::*; let query = match &self.query { Borrowed(borrowed) => *borrowed, Owned(owned) => owned.as_str(), }; Query { normalized: self.normalized, query: Cow::Borrowed(query), } } /// Returns a `str` representation of the query. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Query; /// /// let query = Query::try_from("query").unwrap(); /// assert_eq!(query.as_str(), "query"); /// ``` pub fn as_str(&self) -> &str { &self.query } /// Converts the [`Query`] into an owned copy. /// /// If you construct the query from a source with a non-static lifetime, you may run into /// lifetime problems due to the way the struct is designed. Calling this function will ensure /// that the returned value has a static lifetime. /// /// This is different from just cloning. Cloning the query will just copy the references, and /// thus the lifetime will remain the same. pub fn into_owned(self) -> Query<'static> { Query { normalized: self.normalized, query: Cow::from(self.query.into_owned()), } } /// Returns whether the query is normalized. /// /// A normalized query will have no bytes that are in the unreserved character set /// percent-encoded and all alphabetical characters in percent-encodings will be uppercase. /// /// This function runs in constant-time. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Query; /// /// let query = Query::try_from("query").unwrap(); /// assert!(query.is_normalized()); /// /// let mut query = Query::try_from("%ff%ff").unwrap(); /// assert!(!query.is_normalized()); /// query.normalize(); /// assert!(query.is_normalized()); /// ``` pub fn is_normalized(&self) -> bool { self.normalized } /// Normalizes the query such that it will have no bytes that are in the unreserved character /// set percent-encoded and all alphabetical characters in percent-encodings will be uppercase. /// /// If the query is already normalized, the function will return immediately. Otherwise, if the /// query is not owned, this function will perform an allocation to clone it. The normalization /// itself though, is done in-place with no extra memory allocations required. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Query; /// /// let mut query = Query::try_from("query").unwrap(); /// query.normalize(); /// assert_eq!(query, "query"); /// /// let mut query = Query::try_from("%ff%41").unwrap(); /// assert_eq!(query, "%ff%41"); /// query.normalize(); /// assert_eq!(query, "%FFA"); /// ``` pub fn normalize(&mut self) { if !self.normalized { // Unsafe: Queries must be valid ASCII-US, so this is safe. unsafe { normalize_string(&mut self.query.to_mut(), true) }; self.normalized = true; } } } impl AsRef<[u8]> for Query<'_> { fn as_ref(&self) -> &[u8] { self.query.as_bytes() } } impl AsRef for Query<'_> { fn as_ref(&self) -> &str { &self.query } } impl Deref for Query<'_> { type Target = str; fn deref(&self) -> &Self::Target { &self.query } } impl Display for Query<'_> { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { formatter.write_str(&self.query) } } impl Eq for Query<'_> {} impl<'query> From> for String { fn from(value: Query<'query>) -> Self { value.to_string() } } impl Hash for Query<'_> { fn hash(&self, state: &mut H) where H: Hasher, { percent_encoded_hash(self.query.as_bytes(), state, true); } } impl PartialEq for Query<'_> { fn eq(&self, other: &Query) -> bool { *self == *other.as_bytes() } } impl PartialEq<[u8]> for Query<'_> { fn eq(&self, other: &[u8]) -> bool { percent_encoded_equality(self.query.as_bytes(), other, true) } } impl<'query> PartialEq> for [u8] { fn eq(&self, other: &Query<'query>) -> bool { other == self } } impl<'a> PartialEq<&'a [u8]> for Query<'_> { fn eq(&self, other: &&'a [u8]) -> bool { self == *other } } impl<'a, 'query> PartialEq> for &'a [u8] { fn eq(&self, other: &Query<'query>) -> bool { other == *self } } impl PartialEq for Query<'_> { fn eq(&self, other: &str) -> bool { self == other.as_bytes() } } impl<'query> PartialEq> for str { fn eq(&self, other: &Query<'query>) -> bool { other == self.as_bytes() } } impl<'a> PartialEq<&'a str> for Query<'_> { fn eq(&self, other: &&'a str) -> bool { self == other.as_bytes() } } impl<'a, 'query> PartialEq> for &'a str { fn eq(&self, other: &Query<'query>) -> bool { other == self.as_bytes() } } impl<'query> TryFrom<&'query [u8]> for Query<'query> { type Error = QueryError; fn try_from(value: &'query [u8]) -> Result { let (query, rest) = parse_query(value)?; if rest.is_empty() { Ok(query) } else { Err(QueryError::InvalidCharacter) } } } impl<'query> TryFrom<&'query str> for Query<'query> { type Error = QueryError; fn try_from(value: &'query str) -> Result { Query::try_from(value.as_bytes()) } } /// An error representing an invalid query. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum QueryError { /// The fragment contained an invalid character. InvalidCharacter, /// The fragment contained an invalid percent encoding (e.g. `"%ZZ"`). InvalidPercentEncoding, } impl Display for QueryError { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { use self::QueryError::*; match self { InvalidCharacter => write!(formatter, "invalid query character"), InvalidPercentEncoding => write!(formatter, "invalid query percent encoding"), } } } impl Error for QueryError {} impl From for QueryError { fn from(_: Infallible) -> Self { QueryError::InvalidCharacter } } /// Parses the query from the given byte string. pub(crate) fn parse_query(value: &[u8]) -> Result<(Query, &[u8]), QueryError> { let mut bytes = value.iter(); let mut end_index = 0; let mut normalized = true; while let Some(&byte) = bytes.next() { match QUERY_CHAR_MAP[byte as usize] { 0 if byte == b'#' => break, 0 => return Err(QueryError::InvalidCharacter), b'%' => match get_percent_encoded_value(bytes.next().cloned(), bytes.next().cloned()) { Ok((hex_value, uppercase)) => { if !uppercase || UNRESERVED_CHAR_MAP[hex_value as usize] != 0 { normalized = false; } end_index += 3; } Err(_) => return Err(QueryError::InvalidPercentEncoding), }, _ => end_index += 1, } } let (value, rest) = value.split_at(end_index); // Unsafe: The loop above makes sure the byte string is valid ASCII-US. let query = Query { normalized, query: Cow::from(unsafe { str::from_utf8_unchecked(value) }), }; Ok((query, rest)) } #[cfg(test)] mod test { use super::*; #[test] fn test_query_normalize() { fn test_case(value: &str, expected: &str) { let mut query = Query::try_from(value).unwrap(); query.normalize(); assert_eq!(query, expected); } test_case("", ""); test_case("%ff", "%FF"); test_case("%41", "A"); } #[test] fn test_query_parse() { use self::QueryError::*; assert_eq!(Query::try_from("").unwrap(), ""); assert_eq!(Query::try_from("query").unwrap(), "query"); assert_eq!(Query::try_from("qUeRy").unwrap(), "qUeRy"); assert_eq!(Query::try_from("%ff%ff%ff%41").unwrap(), "%ff%ff%ff%41"); assert_eq!(Query::try_from(" "), Err(InvalidCharacter)); assert_eq!(Query::try_from("#"), Err(InvalidCharacter)); assert_eq!(Query::try_from("%"), Err(InvalidPercentEncoding)); assert_eq!(Query::try_from("%f"), Err(InvalidPercentEncoding)); assert_eq!(Query::try_from("%zz"), Err(InvalidPercentEncoding)); } } uriparse-0.6.4/src/relative_reference.rs000064400000000000000000001244170072674642500165140ustar 00000000000000use std::convert::{Infallible, TryFrom}; use std::error::Error; use std::fmt::{self, Display, Formatter}; use crate::authority::{Authority, AuthorityError, Host, Password, Username}; use crate::fragment::{Fragment, FragmentError}; use crate::path::{Path, PathError}; use crate::query::{Query, QueryError}; use crate::scheme::Scheme; use crate::uri_reference::{URIReference, URIReferenceBuilder, URIReferenceError}; /// A relative reference as defined in /// [[RFC3986, Section 4.1]](https://tools.ietf.org/html/rfc3986#section-4.1). /// /// Specifically, a relative reference is a URI reference without a scheme. #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct RelativeReference<'uri> { /// All relative references are also URI references, so we just maintain a [`URIReference`] /// underneath. uri_reference: URIReference<'uri>, } impl<'uri> RelativeReference<'uri> { pub fn as_uri_reference(&self) -> &URIReference<'uri> { &self.uri_reference } /// Returns the authority, if present, of the relative reference. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("//example.com/my/path").unwrap(); /// assert_eq!(reference.authority().unwrap().to_string(), "example.com"); /// ``` pub fn authority(&self) -> Option<&Authority<'uri>> { self.uri_reference.authority() } /// Constructs a default builder for a relative reference. /// /// This provides an alternative means of constructing a relative reference besides parsing and /// [`RelativeReference::from_parts`]. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Fragment, Path, RelativeReference}; /// /// let reference = RelativeReference::builder() /// .with_path(Path::try_from("/my/path").unwrap()) /// .with_fragment(Some(Fragment::try_from("fragment").unwrap())) /// .build() /// .unwrap(); /// assert_eq!(reference.to_string(), "/my/path#fragment"); /// ``` pub fn builder<'new_uri>() -> RelativeReferenceBuilder<'new_uri> { RelativeReferenceBuilder::new() } /// Constructs a new [`RelativeReference`] from the individual parts: authority, path, query, /// and fragment. /// /// The lifetime used by the resulting value will be the lifetime of the part that is most /// restricted in scope. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Scheme, RelativeReference}; /// /// let reference = RelativeReference::from_parts( /// Some("example.com"), /// "/my/path", /// Some("query"), /// Some("fragment") /// ).unwrap(); /// assert_eq!(reference.to_string(), "//example.com/my/path?query#fragment"); /// ``` pub fn from_parts< 'new_uri, TAuthority, TPath, TQuery, TFragment, TAuthorityError, TPathError, TQueryError, TFragmentError, >( authority: Option, path: TPath, query: Option, fragment: Option, ) -> Result, RelativeReferenceError> where Authority<'new_uri>: TryFrom, Path<'new_uri>: TryFrom, Query<'new_uri>: TryFrom, Fragment<'new_uri>: TryFrom, URIReferenceError: From + From + From + From, { let uri_reference = URIReference::from_parts(None::, authority, path, query, fragment) .map_err(|error| RelativeReferenceError::try_from(error).unwrap())?; Ok(RelativeReference { uri_reference }) } /// Returns the fragment, if present, of the relative reference. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("//example.com#fragment").unwrap(); /// assert_eq!(reference.fragment().unwrap(), "fragment"); /// ``` pub fn fragment(&self) -> Option<&Fragment<'uri>> { self.uri_reference.fragment() } /// Returns whether the relative reference has an authority component. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("//example.com").unwrap(); /// assert!(reference.has_authority()); /// /// let reference = RelativeReference::try_from("").unwrap(); /// assert!(!reference.has_authority()); /// ``` pub fn has_authority(&self) -> bool { self.uri_reference.has_authority() } /// Returns whether the relative reference has a fragment component. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("#test").unwrap(); /// assert!(reference.has_fragment()); /// /// let reference = RelativeReference::try_from("/").unwrap(); /// assert!(!reference.has_fragment()); /// ``` pub fn has_fragment(&self) -> bool { self.uri_reference.has_fragment() } /// Returns whether the relative reference has a password component. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("//user:pass@127.0.0.1").unwrap(); /// assert!(reference.has_password()); /// /// let reference = RelativeReference::try_from("//user@127.0.0.1").unwrap(); /// assert!(!reference.has_password()); /// ``` pub fn has_password(&self) -> bool { self.uri_reference.has_password() } /// Returns whether the relative reference has a port. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("//127.0.0.1:8080").unwrap(); /// assert!(reference.has_port()); /// /// let reference = RelativeReference::try_from("//127.0.0.1").unwrap(); /// assert!(!reference.has_port()); /// ``` pub fn has_port(&self) -> bool { self.uri_reference.has_port() } /// Returns whether the relative reference has a query component. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("/?my=query").unwrap(); /// assert!(reference.has_query()); /// /// let reference = RelativeReference::try_from("/my/path").unwrap(); /// assert!(!reference.has_query()); /// ``` pub fn has_query(&self) -> bool { self.uri_reference.has_query() } /// Returns whether the relative reference has a username component. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("//username@example.com").unwrap(); /// assert!(reference.has_username()); /// /// let reference = RelativeReference::try_from("").unwrap(); /// assert!(!reference.has_username()); /// ``` pub fn has_username(&self) -> bool { self.uri_reference.has_username() } /// Returns the host, if present, of the relative reference. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("//username@example.com").unwrap(); /// assert_eq!(reference.host().unwrap().to_string(), "example.com"); /// ``` pub fn host(&self) -> Option<&Host<'uri>> { self.uri_reference.host() } /// Consumes the relative reference and converts it into a builder with the same values. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Fragment, Query, RelativeReference}; /// /// let reference = RelativeReference::try_from("//example.com/path?query#fragment").unwrap(); /// let mut builder = reference.into_builder(); /// builder.query(None::).fragment(None::); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "//example.com/path"); /// ``` pub fn into_builder(self) -> RelativeReferenceBuilder<'uri> { let (_, authority, path, query, fragment) = self.uri_reference.into_parts(); let mut builder = RelativeReferenceBuilder::new(); builder .authority(authority) .path(path) .query(query) .fragment(fragment); builder } /// Converts the [`RelativeReference`] into an owned copy. /// /// If you construct the relative reference from a source with a non-static lifetime, you may /// run into lifetime problems due to the way the struct is designed. Calling this function will /// ensure that the returned value has a static lifetime. /// /// This is different from just cloning. Cloning the relative reference will just copy the /// references, and thus the lifetime will remain the same. pub fn into_owned(self) -> RelativeReference<'static> { RelativeReference { uri_reference: self.uri_reference.into_owned(), } } /// Consumes the [`RelativeReference`] and returns its parts: authority, path, query, and /// fragment. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from( /// "/my/path?my=query#fragment", /// ).unwrap(); /// let (authority, path, query, fragment) = reference.into_parts(); /// /// assert_eq!(authority, None); /// assert_eq!(path, "/my/path"); /// assert_eq!(query.unwrap(), "my=query"); /// assert_eq!(fragment.unwrap(), "fragment"); /// ``` pub fn into_parts( self, ) -> ( Option>, Path<'uri>, Option>, Option>, ) { let (_, authority, path, query, fragment) = self.uri_reference.into_parts(); (authority, path, query, fragment) } /// Returns whether the relative reference is an absolute path reference. /// /// A URI reference is an absolute path reference if it is a relative reference that begins with /// a single `'/'`. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("/my/path").unwrap(); /// assert!(reference.is_absolute_path_reference()); /// ``` pub fn is_absolute_path_reference(&self) -> bool { self.uri_reference.is_absolute_path_reference() } /// Returns whether the relative reference is a network path reference. /// /// A relative reference is a network path reference if it is a relative reference that begins /// with two `'/'`. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("//example.com").unwrap(); /// assert!(reference.is_network_path_reference()); /// ``` pub fn is_network_path_reference(&self) -> bool { self.uri_reference.is_network_path_reference() } /// Returns whether the relative reference is normalized. /// /// A normalized relative reference will have all of its components normalized. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("/?a=b").unwrap(); /// assert!(reference.is_normalized()); /// /// let mut reference = RelativeReference::try_from("/././?a=b").unwrap(); /// assert!(!reference.is_normalized()); /// reference.normalize(); /// assert!(reference.is_normalized()); /// ``` pub fn is_normalized(&self) -> bool { self.uri_reference.is_normalized() } /// Returns whether the relative reference is a relative path reference. /// /// A relative reference is a relative path reference if it is a relative reference that does /// not begin with a `'/'`. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("my/path").unwrap(); /// assert!(reference.is_relative_path_reference()); /// ``` pub fn is_relative_path_reference(&self) -> bool { self.uri_reference.is_relative_path_reference() } /// Maps the authority using the given map function. /// /// This function will panic if, as a result of the authority change, the relative reference /// becomes invalid. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, RelativeReference}; /// /// let mut reference = RelativeReference::try_from("").unwrap(); /// reference.map_authority(|_| Some(Authority::try_from("127.0.0.1").unwrap())); /// assert_eq!(reference.to_string(), "//127.0.0.1/"); /// ``` pub fn map_authority(&mut self, mapper: TMapper) -> Option<&Authority<'uri>> where TMapper: FnOnce(Option>) -> Option>, { self.uri_reference.map_authority(mapper) } /// Maps the fragment using the given map function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Fragment, RelativeReference}; /// /// let mut reference = RelativeReference::try_from("/").unwrap(); /// reference.map_fragment(|_| Some(Fragment::try_from("fragment").unwrap())); /// assert_eq!(reference.to_string(), "/#fragment"); /// ``` pub fn map_fragment(&mut self, mapper: TMapper) -> Option<&Fragment<'uri>> where TMapper: FnOnce(Option>) -> Option>, { self.uri_reference.map_fragment(mapper) } /// Maps the path using the given map function. /// /// This function will panic if, as a result of the path change, the relative reference becomes /// invalid. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, URIReference}; /// /// let mut reference = URIReference::try_from("").unwrap(); /// reference.map_path(|mut path| { /// path.push("test").unwrap(); /// path.push("path").unwrap(); /// path /// }); /// assert_eq!(reference.to_string(), "test/path"); /// ``` pub fn map_path(&mut self, mapper: TMapper) -> &Path<'uri> where TMapper: FnOnce(Path<'uri>) -> Path<'uri>, { self.uri_reference.map_path(mapper) } /// Maps the query using the given map function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Query, RelativeReference}; /// /// let mut reference = RelativeReference::try_from("/path").unwrap(); /// reference.map_query(|_| Some(Query::try_from("query").unwrap())); /// assert_eq!(reference.to_string(), "/path?query"); /// ``` pub fn map_query(&mut self, mapper: TMapper) -> Option<&Query<'uri>> where TMapper: FnOnce(Option>) -> Option>, { self.uri_reference.map_query(mapper) } /// Normalizes the relative reference. /// /// A normalized relative reference will have all of its components normalized. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let mut reference = RelativeReference::try_from("/?a=b").unwrap(); /// reference.normalize(); /// assert_eq!(reference.to_string(), "/?a=b"); /// /// let mut reference = RelativeReference::try_from("/././?a=b").unwrap(); /// assert_eq!(reference.to_string(), "/././?a=b"); /// reference.normalize(); /// assert_eq!(reference.to_string(), "/?a=b"); /// ``` pub fn normalize(&mut self) { self.uri_reference.normalize(); } /// Returns the path of the relative reference. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("/my/path").unwrap(); /// assert_eq!(reference.path(), "/my/path"); /// ``` pub fn path(&self) -> &Path<'uri> { self.uri_reference.path() } /// Returns the password, if present, of the relative reference. /// /// Usage of a password in URI and URI references is deprecated. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("//user:pass@example.com").unwrap(); /// assert_eq!(reference.password().unwrap(), "pass"); /// ``` pub fn password(&self) -> Option<&Password<'uri>> { self.uri_reference.password() } /// Returns the port, if present, of the relative reference. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("//example.com:8080/").unwrap(); /// assert_eq!(reference.port().unwrap(), 8080); /// ``` pub fn port(&self) -> Option { self.uri_reference.port() } /// Returns the query, if present, of the relative reference. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("?my=query").unwrap(); /// assert_eq!(reference.query().unwrap(), "my=query"); /// ``` pub fn query(&self) -> Option<&Query<'uri>> { self.uri_reference.query() } /// Sets the authority of the relative reference. /// /// An error will be returned if the conversion to an [`Authority`] fails. /// /// The existing path will be set to absolute (i.e. starts with a `'/'`). /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let mut reference = RelativeReference::try_from("//example.com").unwrap(); /// reference.set_authority(Some("user@example.com:80")); /// assert_eq!(reference.to_string(), "//user@example.com:80/"); /// ``` pub fn set_authority( &mut self, authority: Option, ) -> Result>, RelativeReferenceError> where Authority<'uri>: TryFrom, URIReferenceError: From, { self.uri_reference .set_authority(authority) .map_err(|error| RelativeReferenceError::try_from(error).unwrap()) } /// Sets the fragment of the relative reference. /// /// An error will be returned if the conversion to a [`Fragment`] fails. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let mut reference = RelativeReference::try_from("/my/path").unwrap(); /// reference.set_fragment(Some("fragment")); /// assert_eq!(reference.to_string(), "/my/path#fragment"); /// ``` pub fn set_fragment( &mut self, fragment: Option, ) -> Result>, RelativeReferenceError> where Fragment<'uri>: TryFrom, URIReferenceError: From, { self.uri_reference .set_fragment(fragment) .map_err(|error| RelativeReferenceError::try_from(error).unwrap()) } /// Sets the path of the relative reference. /// /// An error will be returned in one of two cases: /// - The conversion to [`Path`] failed. /// - The path was set to a value that resulted in an invalid URI reference. /// /// Regardless of whether the given path was set as absolute or relative, if the relative /// reference currently has an authority, the path will be forced to be absolute. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let mut reference = RelativeReference::try_from("").unwrap(); /// reference.set_path("my/path"); /// assert_eq!(reference.to_string(), "my/path"); /// ``` pub fn set_path( &mut self, path: TPath, ) -> Result<&Path<'uri>, RelativeReferenceError> where Path<'uri>: TryFrom, URIReferenceError: From, { self.uri_reference .set_path(path) .map_err(|error| RelativeReferenceError::try_from(error).unwrap()) } /// Sets the query of the relative reference. /// /// An error will be returned if the conversion to a [`Query`] fails. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let mut reference = RelativeReference::try_from("").unwrap(); /// reference.set_query(Some("myquery")); /// assert_eq!(reference.to_string(), "?myquery"); /// ``` pub fn set_query( &mut self, query: Option, ) -> Result>, RelativeReferenceError> where Query<'uri>: TryFrom, URIReferenceError: From, { self.uri_reference .set_query(query) .map_err(|error| RelativeReferenceError::try_from(error).unwrap()) } /// Returns a new relative reference which is identical but has a lifetime tied to this relative /// reference. /// /// This function will perform a memory allocation. pub fn to_borrowed(&self) -> RelativeReference { RelativeReference { uri_reference: self.uri_reference.to_borrowed(), } } /// Returns the username, if present, of the relative reference. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::RelativeReference; /// /// let reference = RelativeReference::try_from("//username@example.com").unwrap(); /// assert_eq!(reference.username().unwrap(), "username"); /// ``` pub fn username(&self) -> Option<&Username<'uri>> { self.uri_reference.username() } } impl Display for RelativeReference<'_> { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { self.uri_reference.fmt(formatter) } } impl<'uri> From> for String { fn from(value: RelativeReference<'uri>) -> Self { value.to_string() } } impl<'uri> From> for URIReference<'uri> { fn from(value: RelativeReference<'uri>) -> Self { value.uri_reference } } impl<'uri> TryFrom<&'uri [u8]> for RelativeReference<'uri> { type Error = RelativeReferenceError; fn try_from(value: &'uri [u8]) -> Result { let uri_reference = URIReference::try_from(value) .map_err(|error| RelativeReferenceError::try_from(error).unwrap())?; if uri_reference.is_uri() { Err(RelativeReferenceError::NotRelativeReference) } else { Ok(RelativeReference { uri_reference }) } } } impl<'uri> TryFrom<&'uri str> for RelativeReference<'uri> { type Error = RelativeReferenceError; fn try_from(value: &'uri str) -> Result { RelativeReference::try_from(value.as_bytes()) } } impl<'uri> TryFrom> for RelativeReference<'uri> { type Error = RelativeReferenceError; fn try_from(value: URIReference<'uri>) -> Result { if value.is_relative_reference() { Ok(RelativeReference { uri_reference: value, }) } else { Err(RelativeReferenceError::NotRelativeReference) } } } /// A builder type for [`RelativeReference]`. /// /// You must use the [`RelativeReferenceBuilder::path`] function before building as relative /// references always have a path. Everything else is optional. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct RelativeReferenceBuilder<'uri> { /// All relative references are also URI references, so we just maintain a /// [`URIReferenceBuilder`] underneath. uri_reference_builder: URIReferenceBuilder<'uri>, } impl<'uri> RelativeReferenceBuilder<'uri> { /// Sets the authority part of the relative reference. /// /// It is optional to specify a authority. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Path, RelativeReferenceBuilder}; /// /// let mut builder = RelativeReferenceBuilder::new(); /// builder /// .authority(Some(Authority::try_from("example.com").unwrap())) /// .path(Path::try_from("/my/path").unwrap()); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "//example.com/my/path"); /// ``` pub fn authority(&mut self, authority: Option>) -> &mut Self { self.uri_reference_builder.authority(authority); self } /// Consumes the builder and tries to build a [`RelativeReference`]. /// /// This function will error in one of two situations: /// - A path was not specified in the builder. /// - While all individual components were valid, their combination as a relative reference was /// invalid. /// /// # Examples /// /// First error type (path not specified): /// /// ``` /// use uriparse::RelativeReferenceBuilder; /// /// let result = RelativeReferenceBuilder::new().build(); /// assert!(result.is_err()); /// ``` /// /// Second error type (first segment in schemeless path cannot contain a `':'`): /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Path, RelativeReferenceBuilder}; /// /// let result = RelativeReferenceBuilder::new() /// .with_path(Path::try_from("my:/path").unwrap()) /// .build(); /// assert!(result.is_err()); /// ``` pub fn build(self) -> Result, RelativeReferenceError> { Ok(RelativeReference { uri_reference: self .uri_reference_builder .build() .map_err(|error| RelativeReferenceError::try_from(error).unwrap())?, }) } /// Sets the fragment part of the relative reference. /// /// It is optional to specify a fragment. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Fragment, Path, RelativeReferenceBuilder}; /// /// let mut builder = RelativeReferenceBuilder::new(); /// builder /// .path(Path::try_from("/my/path").unwrap()) /// .fragment(Some(Fragment::try_from("fragment").unwrap())); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "/my/path#fragment"); /// ``` pub fn fragment(&mut self, fragment: Option>) -> &mut Self { self.uri_reference_builder.fragment(fragment); self } /// Constructs a new builder with nothing set. pub fn new() -> Self { RelativeReferenceBuilder::default() } /// Sets the path part of the relative reference. /// /// It is required to specify a path. Not doing so will result in an error during the /// [`RelativeReferenceBuilder::build`] function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Path, RelativeReferenceBuilder}; /// /// let mut builder = RelativeReferenceBuilder::new(); /// builder /// .path(Path::try_from("/my/path").unwrap()); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "/my/path"); /// ``` pub fn path(&mut self, path: Path<'uri>) -> &mut Self { self.uri_reference_builder.path(path); self } /// Sets the query part of the relative reference. /// /// It is optional to specify a query. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Path, Query, RelativeReferenceBuilder}; /// /// let mut builder = RelativeReferenceBuilder::new(); /// builder /// .path(Path::try_from("/my/path").unwrap()) /// .query(Some(Query::try_from("query").unwrap())); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "/my/path?query"); /// ``` pub fn query(&mut self, query: Option>) -> &mut Self { self.uri_reference_builder.query(query); self } /// Sets the authority part of the relative reference. /// /// If the given authority is not a valid authority (i.e. the conversion fails), an error is /// stored internally and checked during the [`RelativeReferenceBuilder::build`] function. The /// error state will be rewritten for any following calls to this function. /// /// It is optional to specify an authority. /// /// # Examples /// /// ``` /// use uriparse::RelativeReferenceBuilder; /// /// let mut builder = RelativeReferenceBuilder::new(); /// builder /// .try_authority(Some("example.com")) /// .unwrap() /// .try_path("/my/path") /// .unwrap(); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "//example.com/my/path"); /// ``` pub fn try_authority( &mut self, authority: Option, ) -> Result<&mut Self, TAuthorityError> where Authority<'uri>: TryFrom, AuthorityError: From, { self.uri_reference_builder.try_authority(authority)?; Ok(self) } /// Sets the fragment part of the relative reference. /// /// If the given fragment is not a valid fragment (i.e. the conversion fails), an error is /// stored internally and checked during the [`RelativeReferenceBuilder::build`] function. The /// error state will be rewritten for any following calls to this function. /// /// It is optional to specify a fragment. /// /// # Examples /// /// ``` /// use uriparse::RelativeReferenceBuilder; /// /// let mut builder = RelativeReferenceBuilder::new(); /// builder /// .try_path("/my/path") /// .unwrap() /// .try_fragment(Some("fragment")) /// .unwrap(); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "/my/path#fragment"); /// ``` pub fn try_fragment( &mut self, fragment: Option, ) -> Result<&mut Self, FragmentError> where Fragment<'uri>: TryFrom, FragmentError: From, { self.uri_reference_builder.try_fragment(fragment)?; Ok(self) } /// Sets the path part of the relative reference. /// /// If the given path is not a valid path (i.e. the conversion fails), an error is stored /// internally and checked during the [`RelativeReferenceBuilder::build`] function. The error /// state will be rewritten for any following calls to this function. /// /// It is required to specify an path. Not doing so will result in an error during the /// [`RelativeReferenceBuilder::build`] function. /// /// # Examples /// /// ``` /// use uriparse::RelativeReferenceBuilder; /// /// let mut builder = RelativeReferenceBuilder::new(); /// builder /// .try_path("/my/path") /// .unwrap(); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "/my/path"); /// ``` pub fn try_path(&mut self, path: TPath) -> Result<&mut Self, PathError> where Path<'uri>: TryFrom, PathError: From, { self.uri_reference_builder.try_path(path)?; Ok(self) } /// Sets the query part of the relative reference. /// /// If the given query is not a valid query (i.e. the conversion fails), an error is stored /// internally and checked during the [`RelativeReferenceBuilder::build`] function. The error /// state will be rewritten for any following calls to this function. /// /// It is optional to specify a query. /// /// # Examples /// /// ``` /// use uriparse::RelativeReferenceBuilder; /// /// let mut builder = RelativeReferenceBuilder::new(); /// builder /// .try_path("/my/path") /// .unwrap() /// .try_query(Some("query")) /// .unwrap(); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "/my/path?query"); /// ``` pub fn try_query( &mut self, query: Option, ) -> Result<&mut Self, QueryError> where Query<'uri>: TryFrom, QueryError: From, { self.uri_reference_builder.try_query(query)?; Ok(self) } /// Consumes the builder and sets the authority part of the relative reference. /// /// It is optional to specify an authority. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Path, RelativeReferenceBuilder}; /// /// let reference = RelativeReferenceBuilder::new() /// .with_authority(Some(Authority::try_from("example.com").unwrap())) /// .with_path(Path::try_from("/").unwrap()) /// .build() /// .unwrap(); /// assert_eq!(reference.to_string(), "//example.com/") /// ``` pub fn with_authority(mut self, authority: Option>) -> Self { self.authority(authority); self } /// Consumes the builder and sets the fragment part of the relative reference. /// /// It is optional to specify a fragment. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Fragment, Path, RelativeReferenceBuilder}; /// /// let reference = RelativeReferenceBuilder::new() /// .with_path(Path::try_from("/").unwrap()) /// .with_fragment(Some(Fragment::try_from("fragment").unwrap())) /// .build() /// .unwrap(); /// assert_eq!(reference.to_string(), "/#fragment") /// ``` pub fn with_fragment(mut self, fragment: Option>) -> Self { self.fragment(fragment); self } /// Consumes the builder and sets the path part of the relative reference. /// /// It is required to specify a path. Not doing so will result in an error during the /// [`RelativeReferenceBuilder::build`] function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Path, RelativeReferenceBuilder}; /// /// let reference = RelativeReferenceBuilder::new() /// .with_path(Path::try_from("/").unwrap()) /// .build() /// .unwrap(); /// assert_eq!(reference.to_string(), "/") /// ``` pub fn with_path(mut self, path: Path<'uri>) -> Self { self.path(path); self } /// Consumes the builder and sets the query part of the relative reference. /// /// It is optional to specify a query. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Path, Query, RelativeReferenceBuilder}; /// /// let reference = RelativeReferenceBuilder::new() /// .with_path(Path::try_from("/").unwrap()) /// .with_query(Some(Query::try_from("query").unwrap())) /// .build() /// .unwrap(); /// assert_eq!(reference.to_string(), "/?query") /// ``` pub fn with_query(mut self, query: Option>) -> Self { self.query(query); self } } /// An error representing an invalid relative reference. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum RelativeReferenceError { /// Represents the case where there is no authority, but the first path segment starts with /// `"//"`. This is not allowed because it would be interpreted as an authority component. /// /// This can only occur when using creation functions that act on individual parts (e.g. /// [`RelativeReference::from_parts`]). AbsolutePathStartsWithTwoSlashes, /// The authority component of the relative reference was invalid. Authority(AuthorityError), /// The fragment component of the relative reference was invalid. Fragment(FragmentError), /// The path component of the relative reference was invalid. Path(PathError), /// The query component of the relative reference was invalid. Query(QueryError), /// This error occurs when you do not specify a path component on the builder. /// /// This can only occur when using [`RelativeReferenceBuilder`]. MissingPath, /// When parsing from some byte string source, if the source ends up being a URI, then it is /// obviously not a relative reference. /// /// This can only occur when parsing from a byte string source. NotRelativeReference, /// Represents the case where the first path segment contains a `':'`. This is not allowed /// because it would be interpreted as a scheme component. /// /// This can only occur when using creation functions that act on individual parts (e.g. /// [`RelativeReference::from_parts`]). SchemelessPathStartsWithColonSegment, } impl Display for RelativeReferenceError { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { use self::RelativeReferenceError::*; match self { AbsolutePathStartsWithTwoSlashes => { write!(formatter, "absolute path starts with two slashes") } Authority(error) => error.fmt(formatter), Fragment(error) => error.fmt(formatter), NotRelativeReference => write!(formatter, "not relative reference"), Path(error) => error.fmt(formatter), Query(error) => error.fmt(formatter), MissingPath => write!(formatter, "missing path"), SchemelessPathStartsWithColonSegment => write!( formatter, "relative reference schemeless path starts with colon segment" ), } } } impl Error for RelativeReferenceError {} impl From for RelativeReferenceError { fn from(_: Infallible) -> Self { RelativeReferenceError::MissingPath } } impl From for RelativeReferenceError { fn from(value: AuthorityError) -> Self { RelativeReferenceError::Authority(value) } } impl From for RelativeReferenceError { fn from(value: FragmentError) -> Self { RelativeReferenceError::Fragment(value) } } impl From for RelativeReferenceError { fn from(value: PathError) -> Self { RelativeReferenceError::Path(value) } } impl From for RelativeReferenceError { fn from(value: QueryError) -> Self { RelativeReferenceError::Query(value) } } impl TryFrom for RelativeReferenceError { type Error = (); fn try_from(value: URIReferenceError) -> Result { use self::RelativeReferenceError::*; match value { URIReferenceError::AbsolutePathStartsWithTwoSlashes => { Ok(AbsolutePathStartsWithTwoSlashes) } URIReferenceError::Authority(error) => Ok(Authority(error)), URIReferenceError::Fragment(error) => Ok(Fragment(error)), URIReferenceError::Path(error) => Ok(Path(error)), URIReferenceError::Query(error) => Ok(Query(error)), URIReferenceError::MissingPath => Ok(MissingPath), URIReferenceError::SchemelessPathStartsWithColonSegment => { Ok(SchemelessPathStartsWithColonSegment) } URIReferenceError::Scheme(_) => Err(()), } } } uriparse-0.6.4/src/scheme.rs000064400000000000000000001213620072674642500141230ustar 00000000000000#![allow(clippy::string_lit_as_bytes)] //! Scheme Component //! //! See [[RFC3986, Section 3.5](https://tools.ietf.org/html/rfc3986#section-3.5)]. For a list of //! the listed schemes, see //! [iana.org](https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml). use fnv::FnvBuildHasher; use lazy_static::lazy_static; use std::borrow::Cow; use std::collections::HashMap; use std::convert::{Infallible, TryFrom}; use std::error::Error; use std::fmt::{self, Display, Formatter}; use std::hash::{Hash, Hasher}; use std::str; use crate::utility::normalize_string; /// The length of the longest currently registered scheme. This is used internally for parsing. Make /// sure to check this whenever adding a new scheme. const MAX_REGISTERED_SCHEME_LENGTH: usize = 36; /// The number of registered schemes. Make sure to update this whenever adding a new scheme. const NUMBER_OF_SCHEMES: usize = 304; /// A map of byte characters that determines if a character is a valid scheme character. #[rustfmt::skip] const SCHEME_CHAR_MAP: [u8; 256] = [ // 0 1 2 3 4 5 6 7 8 9 A B C D E F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, b'+', 0, b'-', b'.', 0, // 2 b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', 0, 0, 0, 0, 0, 0, // 3 0, b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 4 b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', 0, 0, 0, 0, 0, // 5 0, b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', // 6 b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', 0, 0, 0, 0, 0, // 7 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F ]; macro_rules! schemes { ( $( ($variant:ident, $name:expr, $status:expr); )+ ) => { lazy_static! { /// An immutable hashmap mapping scheme names to their corresponding [`Scheme`] /// variants. static ref SCHEME_NAME_MAP: HashMap<&'static [u8], Scheme<'static>, FnvBuildHasher> = { let mut map = HashMap::with_capacity_and_hasher( NUMBER_OF_SCHEMES, FnvBuildHasher::default() ); $( assert!(map.insert($name.as_bytes(), Scheme::$variant).is_none()); )+ map }; } /// The scheme component as defined in /// [[RFC3986, Section 3.5](https://tools.ietf.org/html/rfc3986#section-3.5)]. The schemes /// listed here come from /// [iana.org](https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml). Any scheme /// not listed there is considered unregistered and will be contained in /// [`Scheme::UnregisteredScheme`]. /// /// An unregistered scheme is case-insensitive. Furthermore, percent-encoding is not allowed /// in schemes. #[derive(Clone, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[non_exhaustive] pub enum Scheme<'scheme> { $( $variant, )+ Unregistered(UnregisteredScheme<'scheme>) } impl<'scheme> Scheme<'scheme> { /// Returns a new scheme which is identical but has a lifetime tied to this scheme. pub fn as_borrowed(&self) -> Scheme { use self::Scheme::*; match self { $( $variant => $variant, )+ Unregistered(scheme) => Unregistered(scheme.as_borrowed()) } } /// Returns a `str` representation of the scheme. /// /// The case of the scheme will be lowercase if it was a registered scheme. Otherwise, /// the string representation will be exactly that of the original string including /// case-sensitivity. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Scheme; /// /// assert_eq!(Scheme::HTTP.as_str(), "http"); /// /// let scheme = Scheme::try_from("TEST-scheme").unwrap(); /// assert_eq!(scheme.as_str(), "TEST-scheme"); /// ``` pub fn as_str(&self) -> &str { use self::Scheme::*; match self { $( $variant => $name, )+ Unregistered(scheme) => scheme.as_str() } } /// Converts the [`Scheme`] into an owned copy. /// /// If you construct the scheme from a source with a non-static lifetime, you may run /// into lifetime problems due to the way it is designed. Calling this function will /// ensure that the returned value has a static lifetime. /// /// This is different from just cloning. Cloning the scheme will just copy the /// references (in the case of an unregistered scheme), and thus the lifetime will /// remain the same. pub fn into_owned(self) -> Scheme<'static> { use self::Scheme::*; match self { $( $variant => $variant, )+ Unregistered(scheme) => Unregistered(scheme.into_owned()) } } /// Returns the registration status of the scheme. /// /// # Examples /// /// ``` /// use uriparse::{Scheme, SchemeStatus}; /// /// assert_eq!(Scheme::HTTP.status(), SchemeStatus::Permanent); /// ``` pub fn status(&self) -> SchemeStatus { use self::Scheme::*; match self { $( $variant => $status, )+ Unregistered(_) => SchemeStatus::Unregistered } } } /// Parses the scheme from the given byte string. pub(crate) fn parse_scheme(value: &[u8]) -> Result<(Scheme, &[u8]), SchemeError> { fn unregistered_scheme(value: &[u8], normalized: bool) -> Scheme { // Unsafe: The loop below makes sure the byte string is valid ASCII-US. let scheme = unsafe { str::from_utf8_unchecked(value) }; Scheme::Unregistered(UnregisteredScheme{ normalized, scheme:Cow::from(scheme) }) } if !value.iter().next().ok_or(SchemeError::Empty)?.is_ascii_alphabetic() { return Err(SchemeError::StartsWithNonAlphabetic); } let mut end_index = 0; let mut lowercase_scheme = [0; MAX_REGISTERED_SCHEME_LENGTH]; let mut normalized = true; for &byte in value.iter() { match SCHEME_CHAR_MAP[byte as usize] { 0 if byte == b':' => break, 0 => return Err(SchemeError::InvalidCharacter), _ => { if byte >= b'A' && byte <= b'Z' { normalized = false; } if end_index + 1 < MAX_REGISTERED_SCHEME_LENGTH { lowercase_scheme[end_index] = byte.to_ascii_lowercase(); } end_index += 1; } } } let (value, rest) = value.split_at(end_index); // It is important to make sure that [`MAX_REGISTERED_SCHEME_LENGTH`] is correctly // maintained, or registered schemes may be set as unregistered. if end_index > MAX_REGISTERED_SCHEME_LENGTH { return Ok((unregistered_scheme(value, normalized), rest)); } let scheme = SCHEME_NAME_MAP .get(&lowercase_scheme[..end_index]) .cloned() .unwrap_or_else(|| unregistered_scheme(value, normalized)); Ok((scheme, rest)) } } } impl Scheme<'_> { /// Returns whether the scheme is normalized. /// /// A normalized scheme will be all lowercase. All standardized schemes are always considered /// normalized regardless of what source they were parsed from. /// /// This function returns in constant-time. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Scheme; /// /// let scheme = Scheme::try_from("http").unwrap(); /// assert!(scheme.is_normalized()); /// /// let scheme = Scheme::try_from("HTTP").unwrap(); /// assert!(scheme.is_normalized()); /// /// let mut scheme = Scheme::try_from("MyScHeMe").unwrap(); /// assert!(!scheme.is_normalized()); /// scheme.normalize(); /// assert!(scheme.is_normalized()); /// ``` pub fn is_normalized(&self) -> bool { match self { Scheme::Unregistered(scheme) => scheme.is_normalized(), _ => true, } } /// Normalizes the scheme so that it is all lowercase. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Scheme; /// /// let mut scheme = Scheme::try_from("http").unwrap(); /// scheme.normalize(); /// assert_eq!(scheme, "http"); /// /// let mut scheme = Scheme::try_from("MyScHeMe").unwrap(); /// scheme.normalize(); /// assert_eq!(scheme, "myscheme"); /// ``` pub fn normalize(&mut self) { if let Scheme::Unregistered(scheme) = self { scheme.normalize(); } } } impl AsRef<[u8]> for Scheme<'_> { fn as_ref(&self) -> &[u8] { self.as_str().as_bytes() } } impl AsRef for Scheme<'_> { fn as_ref(&self) -> &str { self.as_str() } } impl Display for Scheme<'_> { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { formatter.write_str(self.as_str()) } } impl<'scheme> From> for String { fn from(value: Scheme<'scheme>) -> Self { value.to_string() } } impl PartialEq<[u8]> for Scheme<'_> { fn eq(&self, other: &[u8]) -> bool { self.as_str().as_bytes().eq_ignore_ascii_case(other) } } impl<'scheme> PartialEq> for [u8] { fn eq(&self, other: &Scheme<'scheme>) -> bool { other == self } } impl<'a> PartialEq<&'a [u8]> for Scheme<'_> { fn eq(&self, other: &&'a [u8]) -> bool { self == *other } } impl<'a, 'scheme> PartialEq> for &'a [u8] { fn eq(&self, other: &Scheme<'scheme>) -> bool { other == *self } } impl PartialEq for Scheme<'_> { fn eq(&self, other: &str) -> bool { self == other.as_bytes() } } impl<'scheme> PartialEq> for str { fn eq(&self, other: &Scheme<'scheme>) -> bool { other == self.as_bytes() } } impl<'a> PartialEq<&'a str> for Scheme<'_> { fn eq(&self, other: &&'a str) -> bool { self == other.as_bytes() } } impl<'a, 'scheme> PartialEq> for &'a str { fn eq(&self, other: &Scheme<'scheme>) -> bool { other == self.as_bytes() } } impl<'scheme> TryFrom<&'scheme [u8]> for Scheme<'scheme> { type Error = SchemeError; fn try_from(value: &'scheme [u8]) -> Result, Self::Error> { let (scheme, rest) = parse_scheme(value)?; if rest.is_empty() { Ok(scheme) } else { Err(SchemeError::InvalidCharacter) } } } impl<'scheme> TryFrom<&'scheme str> for Scheme<'scheme> { type Error = SchemeError; fn try_from(value: &'scheme str) -> Result, Self::Error> { Scheme::try_from(value.as_bytes()) } } /// A scheme that is not in the /// [registered schemes](https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml). /// /// This is case-insensitive, and this is reflected in the equality and hash functions. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct UnregisteredScheme<'scheme> { /// Whether the fragment is normalized. normalized: bool, /// The internal scheme source that is either owned or borrowed. scheme: Cow<'scheme, str>, } impl UnregisteredScheme<'_> { /// Returns a new unregistered scheme which is identical but has a lifetime tied to this /// unregistered scheme. pub fn as_borrowed(&self) -> UnregisteredScheme { use self::Cow::*; let scheme = match &self.scheme { Borrowed(borrowed) => *borrowed, Owned(owned) => owned.as_str(), }; UnregisteredScheme { normalized: self.normalized, scheme: Cow::Borrowed(scheme), } } /// Returns a `str` representation of the scheme. /// /// The case-sensitivity of the original string is preserved. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::UnregisteredScheme; /// /// let scheme = UnregisteredScheme::try_from("TEST-scheme").unwrap(); /// assert_eq!(scheme.as_str(), "TEST-scheme"); /// ``` pub fn as_str(&self) -> &str { &self.scheme } /// Converts the [`UnregisteredScheme`] into an owned copy. /// /// If you construct the scheme from a source with a non-static lifetime, you may run into /// lifetime problems due to the way the struct is designed. Calling this function will ensure /// that the returned value has a static lifetime. /// /// This is different from just cloning. Cloning the scheme will just copy the references, and /// thus the lifetime will remain the same. pub fn into_owned(self) -> UnregisteredScheme<'static> { UnregisteredScheme { normalized: self.normalized, scheme: Cow::from(self.scheme.into_owned()), } } /// Returns whether the scheme is normalized. /// /// A normalized scheme will be all lowercase. /// /// This function runs in constant-time. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::UnregisteredScheme; /// /// let scheme = UnregisteredScheme::try_from("myscheme").unwrap(); /// assert!(scheme.is_normalized()); /// /// let mut scheme = UnregisteredScheme::try_from("MyScHeMe").unwrap(); /// assert!(!scheme.is_normalized()); /// scheme.normalize(); /// assert!(scheme.is_normalized()); /// ``` pub fn is_normalized(&self) -> bool { self.normalized } /// Normalizes the scheme so that it is all lowercase. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::UnregisteredScheme; /// /// let mut scheme = UnregisteredScheme::try_from("myscheme").unwrap(); /// scheme.normalize(); /// assert_eq!(scheme, "myscheme"); /// /// let mut scheme = UnregisteredScheme::try_from("MyScHeMe").unwrap(); /// scheme.normalize(); /// assert_eq!(scheme, "myscheme"); /// ``` pub fn normalize(&mut self) { if !self.normalized { // Unsafe: Schemes must be valid ASCII-US, so this is safe. unsafe { normalize_string(&mut self.scheme.to_mut(), true) }; self.normalized = true; } } } impl AsRef<[u8]> for UnregisteredScheme<'_> { fn as_ref(&self) -> &[u8] { self.scheme.as_bytes() } } impl AsRef for UnregisteredScheme<'_> { fn as_ref(&self) -> &str { &self.scheme } } impl Display for UnregisteredScheme<'_> { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { formatter.write_str(&self.scheme) } } impl Eq for UnregisteredScheme<'_> {} impl<'scheme> From> for String { fn from(value: UnregisteredScheme<'scheme>) -> Self { value.to_string() } } impl Hash for UnregisteredScheme<'_> { fn hash(&self, state: &mut H) where H: Hasher, { self.scheme.to_lowercase().hash(state) } } impl PartialEq for UnregisteredScheme<'_> { fn eq(&self, other: &UnregisteredScheme) -> bool { *self == *other.scheme.as_bytes() } } impl PartialEq<[u8]> for UnregisteredScheme<'_> { fn eq(&self, other: &[u8]) -> bool { self.scheme.as_bytes().eq_ignore_ascii_case(&other) } } impl<'scheme> PartialEq> for [u8] { fn eq(&self, other: &UnregisteredScheme<'scheme>) -> bool { other == self } } impl<'a> PartialEq<&'a [u8]> for UnregisteredScheme<'_> { fn eq(&self, other: &&'a [u8]) -> bool { self == *other } } impl<'a, 'scheme> PartialEq> for &'a [u8] { fn eq(&self, other: &UnregisteredScheme<'scheme>) -> bool { other == *self } } impl PartialEq for UnregisteredScheme<'_> { fn eq(&self, other: &str) -> bool { self == other.as_bytes() } } impl<'scheme> PartialEq> for str { fn eq(&self, other: &UnregisteredScheme<'scheme>) -> bool { other == self.as_bytes() } } impl<'a> PartialEq<&'a str> for UnregisteredScheme<'_> { fn eq(&self, other: &&'a str) -> bool { self == other.as_bytes() } } impl<'a, 'scheme> PartialEq> for &'a str { fn eq(&self, other: &UnregisteredScheme<'scheme>) -> bool { other == self.as_bytes() } } impl<'scheme> TryFrom<&'scheme [u8]> for UnregisteredScheme<'scheme> { type Error = UnregisteredSchemeError; fn try_from(value: &'scheme [u8]) -> Result { match Scheme::try_from(value) { Ok(Scheme::Unregistered(scheme)) => Ok(scheme), _ => Err(UnregisteredSchemeError), } } } impl<'scheme> TryFrom<&'scheme str> for UnregisteredScheme<'scheme> { type Error = UnregisteredSchemeError; fn try_from(value: &'scheme str) -> Result { UnregisteredScheme::try_from(value.as_bytes()) } } /// An error representing an invalid scheme. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum SchemeError { /// The scheme component was empty. Empty, /// The scheme contained an invalid scheme character. InvalidCharacter, /// The scheme did not start with an alphabetic character. StartsWithNonAlphabetic, } impl Display for SchemeError { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { use self::SchemeError::*; match self { Empty => write!(formatter, "scheme is empty"), InvalidCharacter => write!(formatter, "invalid scheme character"), StartsWithNonAlphabetic => { write!(formatter, "scheme starts with non-alphabetic character") } } } } impl Error for SchemeError {} impl From for SchemeError { fn from(_: Infallible) -> Self { SchemeError::InvalidCharacter } } /// An error representing that the unregistered scheme was an invalid scheme, or it was actually /// a registered scheme. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct UnregisteredSchemeError; impl Display for UnregisteredSchemeError { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { write!(formatter, "invalid unregistered scheme") } } impl Error for UnregisteredSchemeError {} /// The registration status of a scheme. See [RFC 7595](https://tools.ietf.org/html/rfc7595) for /// more information. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum SchemeStatus { /// A scheme registered due to historical use. Generally, it is no longer in common use or is /// not recommended. Historical, /// A scheme that has been expertly reviewed. Permanent, /// A scheme that was registered on a first come first served basis. Provisional, /// A scheme that is not currently registerd under /// [iana.org](https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml). Unregistered, } impl SchemeStatus { /// Returns whether the scheme status is historical. /// /// # Examples /// /// ``` /// use uriparse::Scheme; /// /// assert_eq!(Scheme::Fax.status().is_historical(), true); /// assert_eq!(Scheme::HTTP.status().is_historical(), false); /// ``` pub fn is_historical(self) -> bool { match self { SchemeStatus::Historical => true, _ => false, } } /// Returns whether the scheme status is historical. /// /// # Examples /// /// ``` /// use uriparse::Scheme; /// /// assert_eq!(Scheme::HTTP.status().is_permanent(), true); /// assert_eq!(Scheme::IRC.status().is_permanent(), false); /// ``` pub fn is_permanent(self) -> bool { match self { SchemeStatus::Permanent => true, _ => false, } } /// Returns whether the scheme status is historical. /// /// # Examples /// /// ``` /// use uriparse::Scheme; /// /// assert_eq!(Scheme::Git.status().is_provisional(), true); /// assert_eq!(Scheme::RTSP.status().is_provisional(), false); /// ``` pub fn is_provisional(self) -> bool { match self { SchemeStatus::Provisional => true, _ => false, } } /// Returns whether the scheme status is historical. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::Scheme; /// /// let scheme = Scheme::try_from("test-scheme").unwrap(); /// assert_eq!(scheme.status().is_unregistered(), true); /// assert_eq!(Scheme::HTTPS.status().is_unregistered(), false); /// ``` pub fn is_unregistered(self) -> bool { match self { SchemeStatus::Unregistered => true, _ => false, } } } schemes! { (AAA, "aaa", SchemeStatus::Permanent); (AAAS, "aaas", SchemeStatus::Permanent); (About, "about", SchemeStatus::Permanent); (ACAP, "acap", SchemeStatus::Permanent); (ACCT, "acat", SchemeStatus::Permanent); (ACR, "acr", SchemeStatus::Provisional); (AdiumXtra, "adiumxtra", SchemeStatus::Provisional); (AFP, "afp", SchemeStatus::Provisional); (AFS, "afs", SchemeStatus::Provisional); (AIM, "aim", SchemeStatus::Provisional); (AMSS, "amss", SchemeStatus::Provisional); (Android, "android", SchemeStatus::Provisional); (AppData, "appdata", SchemeStatus::Provisional); (APT, "apt", SchemeStatus::Provisional); (Attachment, "attachment", SchemeStatus::Provisional); (AW, "aw", SchemeStatus::Provisional); (Barion, "barion", SchemeStatus::Provisional); (BeShare, "beshare", SchemeStatus::Provisional); (Bitcoin, "bitcoin", SchemeStatus::Provisional); (BitcoinCash, "bitcoincash", SchemeStatus::Provisional); (Blob, "blob", SchemeStatus::Provisional); (Bolo, "bolo", SchemeStatus::Provisional); (BrowserExt, "browserext", SchemeStatus::Provisional); (Calculator, "calculator", SchemeStatus::Provisional); (CallTo, "callto", SchemeStatus::Provisional); (CAP, "cap", SchemeStatus::Permanent); (Cast, "cast", SchemeStatus::Provisional); (Casts, "casts", SchemeStatus::Provisional); (Chrome, "chrome", SchemeStatus::Provisional); (ChromeExtension, "chrome-extension", SchemeStatus::Provisional); (CID, "cid", SchemeStatus::Permanent); (CoAP, "coap", SchemeStatus::Permanent); (CoAPTCP, "coap+tcp", SchemeStatus::Permanent); (CoAPWS, "coap+ws", SchemeStatus::Permanent); (CoAPS, "coaps", SchemeStatus::Permanent); (CoAPSTCP, "coaps+tcp", SchemeStatus::Permanent); (CoAPSWS, "coaps+ws", SchemeStatus::Permanent); (ComEventBriteAttendee, "com-eventbrite-attendee", SchemeStatus::Provisional); (Content, "content", SchemeStatus::Provisional); (Conti, "conti", SchemeStatus::Provisional); (CRID, "crid", SchemeStatus::Permanent); (CVS, "cvs", SchemeStatus::Provisional); (DAB, "dab", SchemeStatus::Provisional); (Data, "data", SchemeStatus::Permanent); (DAV, "dav", SchemeStatus::Permanent); (Diaspora, "diaspora", SchemeStatus::Provisional); (DICT, "dict", SchemeStatus::Permanent); (DID, "did", SchemeStatus::Provisional); (DIS, "dis", SchemeStatus::Provisional); (DLNAPlayContainer, "dlna-playcontainer", SchemeStatus::Provisional); (DLNAPlaySingle, "dlna-playsingle", SchemeStatus::Provisional); (DNS, "dns", SchemeStatus::Permanent); (DNTP, "dntp", SchemeStatus::Provisional); (DPP, "dpp", SchemeStatus::Provisional); (DRM, "drm", SchemeStatus::Provisional); (Drop, "drop", SchemeStatus::Provisional); (DTN, "dtn", SchemeStatus::Provisional); (DVB, "dvb", SchemeStatus::Provisional); (ED2K, "ed2k", SchemeStatus::Provisional); (ELSI, "elsi", SchemeStatus::Provisional); (Example, "example", SchemeStatus::Permanent); (FaceTime, "facetime", SchemeStatus::Provisional); (Fax, "fax", SchemeStatus::Historical); (Feed, "feed", SchemeStatus::Provisional); (FeedReady, "feedready", SchemeStatus::Provisional); (File, "file", SchemeStatus::Permanent); (FileSystem, "filesystem", SchemeStatus::Historical); (Finger, "finger", SchemeStatus::Provisional); (Fish, "fish", SchemeStatus::Provisional); (FM, "fm", SchemeStatus::Provisional); (FTP, "ftp", SchemeStatus::Permanent); (FuchsiaPkg, "fuchsia-pkg", SchemeStatus::Provisional); (Geo, "geo", SchemeStatus::Permanent); (GG, "gg", SchemeStatus::Provisional); (Git, "git", SchemeStatus::Provisional); (GizmoProject, "gizmoproject", SchemeStatus::Provisional); (Go, "go", SchemeStatus::Permanent); (Gopher, "gopher", SchemeStatus::Permanent); (Graph, "graph", SchemeStatus::Provisional); (GTalk, "gtalk", SchemeStatus::Provisional); (H323, "h323", SchemeStatus::Permanent); (HAM, "ham", SchemeStatus::Provisional); (HCAP, "hcap", SchemeStatus::Provisional); (HCP, "hcp", SchemeStatus::Provisional); (HTTP, "http", SchemeStatus::Permanent); (HTTPS, "https", SchemeStatus::Permanent); (HXXP, "hxxp", SchemeStatus::Provisional); (HXXPS, "hxxps", SchemeStatus::Provisional); (HydraZone, "hydrazone", SchemeStatus::Provisional); (IAX, "iax", SchemeStatus::Permanent); (ICAP, "icap", SchemeStatus::Permanent); (Icon, "icon", SchemeStatus::Provisional); (IM, "im", SchemeStatus::Permanent); (IMAP, "imap", SchemeStatus::Permanent); (Info, "info", SchemeStatus::Permanent); (IoTDisc, "iotdisc", SchemeStatus::Provisional); (IPN, "ipn", SchemeStatus::Provisional); (IPP, "ipp", SchemeStatus::Permanent); (IPPS, "ipps", SchemeStatus::Permanent); (IRC, "irc", SchemeStatus::Provisional); (IRC6, "irc6", SchemeStatus::Provisional); (IRCS, "ircs", SchemeStatus::Provisional); (IRIS, "iris", SchemeStatus::Permanent); (IRISBEEP, "iris.beep", SchemeStatus::Permanent); (IRISLWZ, "iris.lwz", SchemeStatus::Permanent); (IRISXPC, "iris.xpc", SchemeStatus::Permanent); (IRISXPCS, "iris.xpcs", SchemeStatus::Permanent); (IsoStore, "isostore", SchemeStatus::Provisional); (ITMS, "itms", SchemeStatus::Provisional); (Jabber, "jabber", SchemeStatus::Permanent); (JAR, "jar", SchemeStatus::Provisional); (JMS, "jms", SchemeStatus::Provisional); (KeyParc, "keyparc", SchemeStatus::Provisional); (LastFM, "lastfm", SchemeStatus::Provisional); (LDAP, "ldap", SchemeStatus::Permanent); (LDAPS, "ldaps", SchemeStatus::Provisional); (LoRaWAN, "lorawan", SchemeStatus::Provisional); (LVLT, "lvlt", SchemeStatus::Provisional); (Magnet, "magnet", SchemeStatus::Provisional); (MailServer, "mailserver", SchemeStatus::Historical); (MailTo, "mailto", SchemeStatus::Permanent); (Maps, "maps", SchemeStatus::Provisional); (Market, "market", SchemeStatus::Provisional); (Message, "message", SchemeStatus::Provisional); (MicrosoftWindowsCamera, "microsoft.windows.camera", SchemeStatus::Provisional); (MicrosoftWindowsCameraMultiPicker, "microsoft.windows.camera.multipicker", SchemeStatus::Provisional); (MicrosoftWindowsCameraPicker, "microsoft.windows.camera.picker", SchemeStatus::Provisional); (MID, "mid", SchemeStatus::Permanent); (MMS, "mms", SchemeStatus::Provisional); (Modem, "modem", SchemeStatus::Historical); (MongoDB, "mongodb", SchemeStatus::Provisional); (Moz, "moz", SchemeStatus::Provisional); (MSAccess, "ms-access", SchemeStatus::Provisional); (MSBrowserExtension, "ms-browser-extension", SchemeStatus::Provisional); (MSCalculator, "ms-calculator", SchemeStatus::Provisional); (MSDriveTo, "ms-drive-to", SchemeStatus::Provisional); (MSEnrollment, "ms-enrollment", SchemeStatus::Provisional); (MSExcel, "ms-excel", SchemeStatus::Provisional); (MSEyeControlSpeech, "ms-eyecontrolspeech", SchemeStatus::Provisional); (MSGameBarServices, "ms-gamebaresrvices", SchemeStatus::Provisional); (MSGamingOverlay, "ms-gamingoverlay", SchemeStatus::Provisional); (MSGetOffice, "ms-getoffice", SchemeStatus::Provisional); (MSHelp, "ms-help", SchemeStatus::Provisional); (MSInfoPath, "ms-infopath", SchemeStatus::Provisional); (MSInputApp, "ms-inputapp", SchemeStatus::Provisional); (MSLockScreenComponentConfig, "ms-lockscreencomponent-config", SchemeStatus::Provisional); (MSMediaStreamID, "ms-media-stream-id", SchemeStatus::Provisional); (MSMixedRealityCapture, "ms-mixedrealitycapture", SchemeStatus::Provisional); (MSOfficeApp, "ms-officeapp", SchemeStatus::Provisional); (MSPeople, "ms-people", SchemeStatus::Provisional); (MSProject, "ms-project", SchemeStatus::Provisional); (MSPowerPoint, "ms-powerpoint", SchemeStatus::Provisional); (MSPublisher, "ms-publisher", SchemeStatus::Provisional); (MSRestoreTabCompanion, "ms-restoretabcompanion", SchemeStatus::Provisional); (MSS, "mss", SchemeStatus::Provisional); (MSScreenClip, "ms-screenclip", SchemeStatus::Provisional); (MSScreenSketch, "ms-screensketch", SchemeStatus::Provisional); (MSSearch, "ms-search", SchemeStatus::Provisional); (MSSearchRepair, "ms-search-repair", SchemeStatus::Provisional); (MSSecondaryScreenController, "ms-secondary-screen-controller", SchemeStatus::Provisional); (MSSeocndaryScreenSetup, "ms-secondary-screen-setup", SchemeStatus::Provisional); (MSSettings, "ms-settings", SchemeStatus::Provisional); (MSSettingsAirplaneMode, "ms-settings-airplanemode", SchemeStatus::Provisional); (MSSettingsBluetooth, "ms-settings-bluetooth", SchemeStatus::Provisional); (MSSettingsCamera, "ms-settings-camera", SchemeStatus::Provisional); (MSSettingsCellular, "ms-settings-cellular", SchemeStatus::Provisional); (MSSettingsCloudStorage, "ms-settings-cloudstorage", SchemeStatus::Provisional); (MSSettingsConnectableDevices, "ms-settings-connectabledevices", SchemeStatus::Provisional); (MSSettingsDisplaysTopology, "ms-settings-displays-topology", SchemeStatus::Provisional); (MSSettingsEmailAndAccounts, "ms-settings-emailandaccounts", SchemeStatus::Provisional); (MSSettingsLanguage, "ms-settings-language", SchemeStatus::Provisional); (MSSettingsLocation, "ms-settings-location", SchemeStatus::Provisional); (MSSettingsLock, "ms-settings-lock", SchemeStatus::Provisional); (MSSettingsNFCTransactions, "ms-settings-nfctransactions", SchemeStatus::Provisional); (MSSettingsNotifications, "ms-settings-notifications", SchemeStatus::Provisional); (MSSettingsPower, "ms-settings-power", SchemeStatus::Provisional); (MSSettingsPrivacy, "ms-settings-privacy", SchemeStatus::Provisional); (MSSettingsProximity, "ms-settings-proximity", SchemeStatus::Provisional); (MSSettingsScreenRotation, "ms-settings-screenrotation", SchemeStatus::Provisional); (MSSettingsWiFi, "ms-settings-wifi", SchemeStatus::Provisional); (MSSettingsWorkplace, "ms-settings-workplace", SchemeStatus::Provisional); (MSSPD, "ms-spd", SchemeStatus::Provisional); (MSSTTOverlay, "ms-sttoverlay", SchemeStatus::Provisional); (MSTransitTo, "ms-transit-to", SchemeStatus::Provisional); (MSUserActivitySet, "ms-useractivityset", SchemeStatus::Provisional); (MSVirtualTouchPad, "ms-virtualtouchpad", SchemeStatus::Provisional); (MSVisio, "ms-visio", SchemeStatus::Provisional); (MSWalkTo, "ms-walk-to", SchemeStatus::Provisional); (MSWhiteboard, "ms-whiteboard", SchemeStatus::Provisional); (MSWhiteboardCMD, "ms-whiteboard-cmd", SchemeStatus::Provisional); (MSWord, "ms-word", SchemeStatus::Provisional); (MSNIM, "msnim", SchemeStatus::Provisional); (MSRP, "msrp", SchemeStatus::Permanent); (MSRPS, "msrps", SchemeStatus::Permanent); (MTQP, "mtqp", SchemeStatus::Permanent); (Mumble, "mumble", SchemeStatus::Provisional); (MUpdate, "mupdate", SchemeStatus::Permanent); (MVN, "mvn", SchemeStatus::Provisional); (News, "news", SchemeStatus::Permanent); (NFS, "nfs", SchemeStatus::Permanent); (NI, "ni", SchemeStatus::Permanent); (NIH, "nih", SchemeStatus::Permanent); (NNTP, "nntp", SchemeStatus::Permanent); (Notes, "notes", SchemeStatus::Provisional); (OCF, "ocf", SchemeStatus::Provisional); (OID, "oid", SchemeStatus::Provisional); (OneNote, "onenote", SchemeStatus::Provisional); (OneNoteCMD, "onenote-cmd", SchemeStatus::Provisional); (OpaqueLockToken, "opaquelocktoken", SchemeStatus::Permanent); (OpenPGP4FPR, "openpgp4fpr", SchemeStatus::Provisional); (Pack, "pack", SchemeStatus::Historical); (Palm, "palm", SchemeStatus::Provisional); (Paparazzi, "paparazzi", SchemeStatus::Provisional); (PKCS11, "pkcs11", SchemeStatus::Permanent); (Platform, "platform", SchemeStatus::Provisional); (POP, "pop", SchemeStatus::Permanent); (Pres, "pres", SchemeStatus::Permanent); (Prospero, "prospero", SchemeStatus::Historical); (Proxy, "proxy", SchemeStatus::Provisional); (PWID, "pwid", SchemeStatus::Provisional); (PSYC, "psyc", SchemeStatus::Provisional); (QB, "qb", SchemeStatus::Provisional); (Query, "query", SchemeStatus::Provisional); (Redis, "redis", SchemeStatus::Provisional); (RedisS, "rediss", SchemeStatus::Provisional); (Reload, "reload", SchemeStatus::Permanent); (Res, "res", SchemeStatus::Provisional); (Resource, "resource", SchemeStatus::Provisional); (RMI, "rmi", SchemeStatus::Provisional); (RSync, "rsync", SchemeStatus::Provisional); (RTMFP, "rtmfp", SchemeStatus::Provisional); (RTMP, "rtmp", SchemeStatus::Provisional); (RTSP, "rtsp", SchemeStatus::Permanent); (RTSPS, "rtsps", SchemeStatus::Permanent); (RTSPU, "rtspu", SchemeStatus::Permanent); (SecondLife, "secondlife", SchemeStatus::Provisional); (Service, "service", SchemeStatus::Permanent); (Session, "session", SchemeStatus::Permanent); (SFTP, "sftp", SchemeStatus::Provisional); (SGN, "sgn", SchemeStatus::Provisional); (SHTTP, "shttp", SchemeStatus::Permanent); (Sieve, "sieve", SchemeStatus::Permanent); (SIP, "sip", SchemeStatus::Permanent); (SIPS, "sips", SchemeStatus::Permanent); (SimpleLedger, "simpleledger", SchemeStatus::Provisional); (Skype, "skype", SchemeStatus::Provisional); (SMB, "smb", SchemeStatus::Provisional); (SMS, "sms", SchemeStatus::Permanent); (SMTP, "smtp", SchemeStatus::Provisional); (SNews, "snews", SchemeStatus::Historical); (SNMP, "snmp", SchemeStatus::Permanent); (SOAPBEEP, "soap.beep", SchemeStatus::Permanent); (SOAPBEEPS, "soap.beeps", SchemeStatus::Permanent); (Soldat, "soldat", SchemeStatus::Provisional); (SPIFFE, "spiffe", SchemeStatus::Provisional); (Spotify, "spotify", SchemeStatus::Provisional); (SSH, "ssh", SchemeStatus::Provisional); (Steam, "steam", SchemeStatus::Provisional); (STUN, "stun", SchemeStatus::Permanent); (STUNS, "stuns", SchemeStatus::Permanent); (Submit, "submit", SchemeStatus::Provisional); (SVN, "svn", SchemeStatus::Provisional); (Tag, "tag", SchemeStatus::Permanent); (TeamSpeak, "teamspeak", SchemeStatus::Provisional); (Tel, "tel", SchemeStatus::Permanent); (TeliaEID, "teliaeid", SchemeStatus::Provisional); (Telnet, "telnet", SchemeStatus::Permanent); (TFTP, "tftp", SchemeStatus::Permanent); (Things, "things", SchemeStatus::Provisional); (ThisMessage, "thismessage", SchemeStatus::Permanent); (TIP, "tip", SchemeStatus::Permanent); (TN3270, "tn3270", SchemeStatus::Permanent); (Tool, "tool", SchemeStatus::Provisional); (TURN, "turn", SchemeStatus::Permanent); (TURNS, "turns", SchemeStatus::Permanent); (TV, "tv", SchemeStatus::Permanent); (UDP, "udp", SchemeStatus::Provisional); (Unreal, "unreal", SchemeStatus::Provisional); (URN, "urn", SchemeStatus::Permanent); (UT2004, "ut2004", SchemeStatus::Provisional); (VEvent, "v-event", SchemeStatus::Provisional); (VEMMI, "vemmi", SchemeStatus::Permanent); (Ventrilo, "ventrilo", SchemeStatus::Provisional); (Videotex, "videotex", SchemeStatus::Historical); (VNC, "vnc", SchemeStatus::Permanent); (ViewSource, "view-source", SchemeStatus::Provisional); (WAIS, "wais", SchemeStatus::Historical); (Webcal, "webcal", SchemeStatus::Provisional); (WPID, "wpid", SchemeStatus::Historical); (WS, "ws", SchemeStatus::Permanent); (WSS, "wss", SchemeStatus::Permanent); (WTAI, "wtai", SchemeStatus::Provisional); (WYCIWYG, "wyciwyg", SchemeStatus::Provisional); (XCON, "xcon", SchemeStatus::Permanent); (XCONUserID, "xcon-userid", SchemeStatus::Permanent); (Xfire, "xfire", SchemeStatus::Provisional); (XMLRPCBEEP, "xmlrpc.beep", SchemeStatus::Permanent); (XMLRPCBEEPS, "xmlrpc.beeps", SchemeStatus::Permanent); (XMPP, "xmpp", SchemeStatus::Permanent); (XRI, "xri", SchemeStatus::Provisional); (YMSGR, "ymsgr", SchemeStatus::Provisional); (Z3950, "z39.50", SchemeStatus::Historical); (Z3950R, "z39.50r", SchemeStatus::Permanent); (Z3950S, "z39.50s", SchemeStatus::Permanent); } #[cfg(test)] mod test { use super::*; #[test] fn test_scheme_normalize() { fn test_case(value: &str, expected: &str) { let mut scheme = Scheme::try_from(value).unwrap(); scheme.normalize(); assert_eq!(scheme, expected); } test_case("http", "http"); test_case("SCHEME", "scheme"); } #[test] fn test_scheme_parse() { use self::SchemeError::*; assert_eq!(Scheme::try_from("scheme").unwrap(), "scheme"); assert_eq!(Scheme::try_from("HTTP").unwrap(), "http"); assert_eq!(Scheme::try_from("SCHEME").unwrap(), "SCHEME"); assert_eq!(Scheme::try_from(""), Err(Empty)); assert_eq!(Scheme::try_from("a:"), Err(InvalidCharacter)); assert_eq!(Scheme::try_from("1"), Err(StartsWithNonAlphabetic)); } } uriparse-0.6.4/src/uri.rs000064400000000000000000001464720072674642500134670ustar 00000000000000//! URIs, Relative References, and URI References //! //! See [RFC3986](https://tools.ietf.org/html/rfc3986). //! //! This module is composed of three primary types [`URI`], [`RelativeReference`], and //! [`URIReference`] that are all very similar. The first thing to note is that URIs and relative //! references are types of URI references. They differ in only one way: URIs have schemes, while //! relative references do not. //! //! As a result, choose the type that best fits your use case. If you need absolute URIs, you should //! use [`URI`], but if you want relative references (e.g. `"/"` in a GET request) use //! [`RelativeReference`]. If you can accept both, then use [`URIReference`]. use std::convert::{Infallible, TryFrom}; use std::error::Error; use std::fmt::{self, Display, Formatter}; use crate::authority::{Authority, AuthorityError, Host, Password, Username}; use crate::fragment::{Fragment, FragmentError}; use crate::path::{Path, PathError}; use crate::query::{Query, QueryError}; use crate::scheme::{Scheme, SchemeError}; use crate::uri_reference::{URIReference, URIReferenceBuilder, URIReferenceError}; /// A Uniform Resource Identifier (URI) as defined in /// [RFC3986](https://tools.ietf.org/html/rfc3986). /// /// A URI is a URI reference, one with a scheme. #[derive(Clone, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct URI<'uri> { /// All URIs are also URI references, so we just maintain a [`URIReference`] underneath. uri_reference: URIReference<'uri>, } impl<'uri> URI<'uri> { pub fn as_uri_reference(&self) -> &URIReference<'uri> { &self.uri_reference } /// Returns the authority, if present, of the URI. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://example.com:80/my/path").unwrap(); /// assert_eq!(uri.authority().unwrap().to_string(), "example.com:80"); /// ``` pub fn authority(&self) -> Option<&Authority<'uri>> { self.uri_reference.authority() } /// Constructs a default builder for a URI. /// /// This provides an alternative means of constructing a URI besides parsing and /// [`URI::from_parts`]. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Path, Scheme, URI}; /// /// let uri = URI::builder() /// .with_scheme(Scheme::HTTP) /// .with_authority(Some(Authority::try_from("example.com").unwrap())) /// .with_path(Path::try_from("/my/path").unwrap()) /// .build() /// .unwrap(); /// assert_eq!(uri.to_string(), "http://example.com/my/path"); /// ``` pub fn builder<'new_uri>() -> URIBuilder<'new_uri> { URIBuilder::new() } /// Returns whether the URI can act as a base URI. /// /// A URI can be a base if it is absolute (i.e. it has no fragment component). /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://example.com/my/path").unwrap(); /// assert!(uri.can_be_a_base()); /// /// let uri = URI::try_from("ftp://127.0.0.1#fragment").unwrap(); /// assert!(!uri.can_be_a_base()); /// ``` pub fn can_be_a_base(&self) -> bool { !self.uri_reference.has_fragment() } /// Constructs a new [`URI`] from the individual parts: scheme, authority, path, query, and /// fragment. /// /// The lifetime used by the resulting value will be the lifetime of the part that is most /// restricted in scope. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Fragment, URI}; /// /// let uri = URI::from_parts( /// "http", /// Some("example.com"), /// "", /// Some("query"), /// None:: /// ).unwrap(); /// assert_eq!(uri.to_string(), "http://example.com/?query"); /// ``` pub fn from_parts< 'new_uri, TScheme, TAuthority, TPath, TQuery, TFragment, TSchemeError, TAuthorityError, TPathError, TQueryError, TFragmentError, >( scheme: TScheme, authority: Option, path: TPath, query: Option, fragment: Option, ) -> Result, URIError> where Scheme<'new_uri>: TryFrom, Authority<'new_uri>: TryFrom, Path<'new_uri>: TryFrom, Query<'new_uri>: TryFrom, Fragment<'new_uri>: TryFrom, URIReferenceError: From + From + From + From + From, { let uri_reference = URIReference::from_parts(Some(scheme), authority, path, query, fragment) .map_err(|error| URIError::try_from(error).unwrap())?; Ok(URI { uri_reference }) } /// Returns the fragment, if present, of the URI. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://example.com#fragment").unwrap(); /// assert_eq!(uri.fragment().unwrap(), "fragment"); /// ``` pub fn fragment(&self) -> Option<&Fragment<'uri>> { self.uri_reference.fragment() } /// Returns whether the URI has an authority component. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://example.com").unwrap(); /// assert!(uri.has_authority()); /// /// let uri = URI::try_from("urn:test").unwrap(); /// assert!(!uri.has_authority()); /// ``` pub fn has_authority(&self) -> bool { self.uri_reference.has_authority() } /// Returns whether the URI has a fragment component. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://example.com#test").unwrap(); /// assert!(uri.has_fragment()); /// /// let uri = URI::try_from("http://example.com").unwrap(); /// assert!(!uri.has_fragment()); /// ``` pub fn has_fragment(&self) -> bool { self.uri_reference.has_fragment() } /// Returns whether the URI has a password component. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://user:pass@127.0.0.1").unwrap(); /// assert!(uri.has_password()); /// /// let uri = URI::try_from("http://user@127.0.0.1").unwrap(); /// assert!(!uri.has_password()); /// ``` pub fn has_password(&self) -> bool { self.uri_reference.has_password() } /// Returns whether the URI has a port. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://127.0.0.1:8080").unwrap(); /// assert!(uri.has_port()); /// /// let uri = URI::try_from("http://127.0.0.1").unwrap(); /// assert!(!uri.has_port()); /// ``` pub fn has_port(&self) -> bool { self.uri_reference.has_port() } /// Returns whether the URI has a query component. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://example.com/my/path?my=query").unwrap(); /// assert!(uri.has_query()); /// /// let uri = URI::try_from("http://example.com/my/path").unwrap(); /// assert!(!uri.has_query()); /// ``` pub fn has_query(&self) -> bool { self.uri_reference.has_query() } /// Returns whether the URI has a username component. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://username@example.com").unwrap(); /// assert!(uri.has_username()); /// /// let uri = URI::try_from("http://example.com").unwrap(); /// assert!(!uri.has_username()); /// ``` pub fn has_username(&self) -> bool { self.uri_reference.has_username() } /// Returns the host, if present, of the URI. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://username@example.com").unwrap(); /// assert_eq!(uri.host().unwrap().to_string(), "example.com"); /// ``` pub fn host(&self) -> Option<&Host<'uri>> { self.uri_reference.host() } /// Converts the URI into a base URI (i.e. the fragment component is removed). /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://example.com#fragment").unwrap(); /// assert_eq!(uri.to_string(), "http://example.com/#fragment"); /// let uri = uri.into_base_uri(); /// assert_eq!(uri.to_string(), "http://example.com/"); /// ``` pub fn into_base_uri(self) -> URI<'uri> { let (scheme, authority, path, query, _) = self.uri_reference.into_parts(); let uri_reference = URIReference::from_parts(scheme, authority, path, query, None::).unwrap(); URI { uri_reference } } /// Consumes the URI and converts it into a builder with the same values. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Fragment, Query, URI}; /// /// let uri = URI::try_from("http://example.com/path?query#fragment").unwrap(); /// let mut builder = uri.into_builder(); /// builder.query(None::).fragment(None::); /// let uri = builder.build().unwrap(); /// assert_eq!(uri.to_string(), "http://example.com/path"); /// ``` pub fn into_builder(self) -> URIBuilder<'uri> { let (scheme, authority, path, query, fragment) = self.uri_reference.into_parts(); let mut builder = URIBuilder::new(); builder .scheme(scheme.unwrap()) .authority(authority) .path(path) .query(query) .fragment(fragment); builder } /// Converts the [`URI`] into an owned copy. /// /// If you construct the URI from a source with a non-static lifetime, you may run into /// lifetime problems due to the way the struct is designed. Calling this function will ensure /// that the returned value has a static lifetime. /// /// This is different from just cloning. Cloning the URI will just copy the references, and thus /// the lifetime will remain the same. pub fn into_owned(self) -> URI<'static> { URI { uri_reference: self.uri_reference.into_owned(), } } /// Consumes the [`URI`] and returns its parts: scheme, authority, path, query, and fragment. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from( /// "http://username:password@example.com:80/my/path?my=query#fragment", /// ).unwrap(); /// let (scheme, authority, path, query, fragment) = uri.into_parts(); /// /// assert_eq!(scheme, "http"); /// assert_eq!(authority.unwrap().to_string(), "username:password@example.com:80"); /// assert_eq!(path, "/my/path"); /// assert_eq!(query.unwrap(), "my=query"); /// assert_eq!(fragment.unwrap(), "fragment"); /// ``` pub fn into_parts( self, ) -> ( Scheme<'uri>, Option>, Path<'uri>, Option>, Option>, ) { let (scheme, authority, path, query, fragment) = self.uri_reference.into_parts(); (scheme.unwrap(), authority, path, query, fragment) } /// Returns whether the URI is normalized. /// /// A normalized URI will have all of its components normalized. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://example.com/?a=b").unwrap(); /// assert!(uri.is_normalized()); /// /// let mut uri = URI::try_from("http://EXAMPLE.com/?a=b").unwrap(); /// assert!(!uri.is_normalized()); /// uri.normalize(); /// assert!(uri.is_normalized()); /// ``` pub fn is_normalized(&self) -> bool { self.uri_reference.is_normalized() } /// Maps the authority using the given map function. /// /// This function will panic if, as a result of the authority change, the URI reference becomes /// invalid. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, URI}; /// /// let mut uri = URI::try_from("http://example.com").unwrap(); /// uri.map_authority(|_| Some(Authority::try_from("127.0.0.1").unwrap())); /// assert_eq!(uri.to_string(), "http://127.0.0.1/"); /// ``` pub fn map_authority(&mut self, mapper: TMapper) -> Option<&Authority<'uri>> where TMapper: FnOnce(Option>) -> Option>, { self.uri_reference.map_authority(mapper) } /// Maps the fragment using the given map function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Fragment, URI}; /// /// let mut uri = URI::try_from("http://example.com").unwrap(); /// uri.map_fragment(|_| Some(Fragment::try_from("fragment").unwrap())); /// assert_eq!(uri.to_string(), "http://example.com/#fragment"); /// ``` pub fn map_fragment(&mut self, mapper: TMapper) -> Option<&Fragment<'uri>> where TMapper: FnOnce(Option>) -> Option>, { self.uri_reference.map_fragment(mapper) } /// Maps the path using the given map function. /// /// This function will panic if, as a result of the path change, the URI becomes invalid. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let mut uri = URI::try_from("http://example.com").unwrap(); /// uri.map_path(|mut path| { /// path.push("test").unwrap(); /// path.push("path").unwrap(); /// path /// }); /// assert_eq!(uri.to_string(), "http://example.com/test/path"); /// ``` pub fn map_path(&mut self, mapper: TMapper) -> &Path<'uri> where TMapper: FnOnce(Path<'uri>) -> Path<'uri>, { self.uri_reference.map_path(mapper) } /// Maps the query using the given map function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Query, URI}; /// /// let mut uri = URI::try_from("http://example.com").unwrap(); /// uri.map_query(|_| Some(Query::try_from("query").unwrap())); /// assert_eq!(uri.to_string(), "http://example.com/?query"); /// ``` pub fn map_query(&mut self, mapper: TMapper) -> Option<&Query<'uri>> where TMapper: FnOnce(Option>) -> Option>, { self.uri_reference.map_query(mapper) } /// Maps the scheme using the given map function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Scheme, URI}; /// /// let mut uri = URI::try_from("http://example.com").unwrap(); /// uri.map_scheme(|_| Scheme::try_from("https").unwrap()); /// assert_eq!(uri.to_string(), "https://example.com/"); /// ``` pub fn map_scheme(&mut self, mapper: TMapper) -> Option<&Scheme<'uri>> where TMapper: FnOnce(Scheme<'uri>) -> Scheme<'uri>, { self.uri_reference .map_scheme(|scheme| Some(mapper(scheme.unwrap()))) } /// Normalizes the URI. /// /// A normalized URI will have all of its components normalized. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let mut uri = URI::try_from("http://example.com/?a=b").unwrap(); /// uri.normalize(); /// assert_eq!(uri.to_string(), "http://example.com/?a=b"); /// /// let mut uri = URI::try_from("http://EXAMPLE.com/?a=b").unwrap(); /// assert_eq!(uri.to_string(), "http://EXAMPLE.com/?a=b"); /// uri.normalize(); /// assert_eq!(uri.to_string(), "http://example.com/?a=b"); /// ``` pub fn normalize(&mut self) { self.uri_reference.normalize(); } /// Returns the path of the URI. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://127.0.0.1/my/path").unwrap(); /// assert_eq!(uri.path(), "/my/path"); /// ``` pub fn path(&self) -> &Path<'uri> { self.uri_reference.path() } /// Returns the password, if present, of the URI. /// /// Usage of a password in URIs is deprecated. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://user:pass@example.com").unwrap(); /// assert_eq!(uri.password().unwrap(), "pass"); /// ``` pub fn password(&self) -> Option<&Password<'uri>> { self.uri_reference.password() } /// Returns the port, if present, of the URI. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://example.com:8080/").unwrap(); /// assert_eq!(uri.port().unwrap(), 8080); /// ``` pub fn port(&self) -> Option { self.uri_reference.port() } /// Returns the query, if present, of the URI. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://127.0.0.1?my=query").unwrap(); /// assert_eq!(uri.query().unwrap(), "my=query"); /// ``` pub fn query(&self) -> Option<&Query<'uri>> { self.uri_reference.query() } /// Creates a new URI which is created by resolving the given reference against this URI. /// /// The algorithm used for resolving the reference is described in /// [[RFC3986, Section 5.2.2](https://tools.ietf.org/html/rfc3986#section-5.2.2)]. pub fn resolve(&self, reference: &'uri URIReference<'uri>) -> URI<'uri> { let mut builder = URIBuilder::new(); if let Some(scheme) = reference.scheme() { let mut path = reference.path().clone(); path.remove_dot_segments(); builder .scheme(scheme.clone()) .authority(reference.authority().cloned()) .path(path) .query(reference.query().cloned()); } else { if reference.authority().is_some() { let mut path = reference.path().clone(); path.remove_dot_segments(); builder .authority(reference.authority().cloned()) .path(path) .query(reference.query().cloned()); } else { if reference.path().is_relative() && reference.path().segments().len() == 1 && reference.path().segments()[0].is_empty() { let mut path = self.path().clone(); path.remove_dot_segments(); builder.path(path); if reference.query().is_some() { builder.query(reference.query().cloned()); } else { builder.query(self.query().cloned()); } } else { if reference.path().is_absolute() { let mut path = reference.path().clone(); path.remove_dot_segments(); builder.path(path); } else { let mut path = if self.authority().is_some() && self.path().segments().len() == 1 && self.path().segments()[0].is_empty() { let mut path = reference.path().clone(); path.set_absolute(true); path } else { let mut path = self.path().clone(); path.pop(); for segment in reference.path().segments() { path.push(segment.clone()).unwrap(); } path }; path.remove_dot_segments(); builder.path(path); } builder.query(reference.query().cloned()); } builder.authority(self.authority().cloned()); } builder.scheme(self.scheme().clone()); } builder.fragment(reference.fragment().cloned()); builder.build().unwrap() } /// Returns the scheme of the URI. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://127.0.0.1/").unwrap(); /// assert_eq!(uri.scheme(), "http"); /// ``` pub fn scheme(&self) -> &Scheme<'uri> { self.uri_reference.scheme().unwrap() } /// Sets the authority of the URI. /// /// An error will be returned if the conversion to an [`Authority`] fails. /// /// The existing path will be set to absolute (i.e. starts with a `'/'`). /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let mut uri = URI::try_from("http://example.com").unwrap(); /// uri.set_authority(Some("user@example.com:80")); /// assert_eq!(uri.to_string(), "http://user@example.com:80/"); /// ``` pub fn set_authority( &mut self, authority: Option, ) -> Result>, URIError> where Authority<'uri>: TryFrom, URIReferenceError: From, { self.uri_reference .set_authority(authority) .map_err(|error| URIError::try_from(error).unwrap()) } /// Sets the fragment of the URI. /// /// An error will be returned if the conversion to a [`Fragment`] fails. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let mut uri = URI::try_from("http://example.com").unwrap(); /// uri.set_fragment(Some("fragment")); /// assert_eq!(uri.to_string(), "http://example.com/#fragment"); /// ``` pub fn set_fragment( &mut self, fragment: Option, ) -> Result>, URIError> where Fragment<'uri>: TryFrom, URIReferenceError: From, { self.uri_reference .set_fragment(fragment) .map_err(|error| URIError::try_from(error).unwrap()) } /// Sets the path of the URI. /// /// An error will be returned in one of two cases: /// - The conversion to [`Path`] failed. /// - The path was set to a value that resulted in an invalid URI. /// /// Regardless of whether the given path was set as absolute or relative, if the URI /// reference currently has an authority, the path will be forced to be absolute. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let mut uri = URI::try_from("http://example.com").unwrap(); /// uri.set_path("my/path"); /// assert_eq!(uri.to_string(), "http://example.com/my/path"); /// ``` pub fn set_path(&mut self, path: TPath) -> Result<&Path<'uri>, URIError> where Path<'uri>: TryFrom, URIReferenceError: From, { self.uri_reference .set_path(path) .map_err(|error| URIError::try_from(error).unwrap()) } /// Sets the query of the URI. /// /// An error will be returned if the conversion to a [`Query`] fails. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let mut uri = URI::try_from("http://example.com").unwrap(); /// uri.set_query(Some("myquery")); /// assert_eq!(uri.to_string(), "http://example.com/?myquery"); /// ``` pub fn set_query( &mut self, query: Option, ) -> Result>, URIError> where Query<'uri>: TryFrom, URIReferenceError: From, { self.uri_reference .set_query(query) .map_err(|error| URIError::try_from(error).unwrap()) } /// Sets the scheme of the URI. /// /// An error will be returned if the conversion to a [`Scheme`] fails. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let mut uri = URI::try_from("http://example.com").unwrap(); /// uri.set_scheme("https"); /// assert_eq!(uri.to_string(), "https://example.com/"); /// ``` pub fn set_scheme( &mut self, scheme: TScheme, ) -> Result<&Scheme<'uri>, URIError> where Scheme<'uri>: TryFrom, URIReferenceError: From, { self.uri_reference .set_scheme(Some(scheme)) .map_err(|error| URIError::try_from(error).unwrap())?; Ok(self.scheme()) } /// Returns a new URI which is identical but has a lifetime tied to this URI. /// /// This function will perform a memory allocation. pub fn to_borrowed(&self) -> URI { URI { uri_reference: self.uri_reference.to_borrowed(), } } /// Returns the username, if present, of the URI. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URI; /// /// let uri = URI::try_from("http://username@example.com").unwrap(); /// assert_eq!(uri.username().unwrap(), "username"); /// ``` pub fn username(&self) -> Option<&Username<'uri>> { self.uri_reference.username() } } impl Display for URI<'_> { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { self.uri_reference.fmt(formatter) } } impl<'uri> From> for String { fn from(value: URI<'uri>) -> Self { value.to_string() } } impl<'uri> From> for URIReference<'uri> { fn from(value: URI<'uri>) -> Self { value.uri_reference } } impl<'uri> TryFrom<&'uri [u8]> for URI<'uri> { type Error = URIError; fn try_from(value: &'uri [u8]) -> Result { let uri_reference = URIReference::try_from(value).map_err(|error| URIError::try_from(error).unwrap())?; if uri_reference.is_relative_reference() { Err(URIError::NotURI) } else { Ok(URI { uri_reference }) } } } impl<'uri> TryFrom<&'uri str> for URI<'uri> { type Error = URIError; fn try_from(value: &'uri str) -> Result { URI::try_from(value.as_bytes()) } } impl<'uri> TryFrom> for URI<'uri> { type Error = URIError; fn try_from(value: URIReference<'uri>) -> Result { if value.is_uri() { Ok(URI { uri_reference: value, }) } else { Err(URIError::NotURI) } } } /// A builder type for [`URI]`. /// /// You must use the [`URI::scheme`] and [`URI::path`] functions before building as URIs always /// have a scheme and path. Everything else is optional. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct URIBuilder<'uri> { /// All URIs are also URI references, so we just maintain a [`URIReferenceBuilder`] underneath. uri_reference_builder: URIReferenceBuilder<'uri>, } impl<'uri> URIBuilder<'uri> { /// Sets the authority part of the URI. /// /// It is optional to specify a authority. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Path, Scheme, URIBuilder}; /// /// let mut builder = URIBuilder::new(); /// builder /// .scheme(Scheme::HTTP) /// .authority(Some(Authority::try_from("example.com").unwrap())) /// .path(Path::try_from("/my/path").unwrap()); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "http://example.com/my/path"); /// ``` pub fn authority(&mut self, authority: Option>) -> &mut Self { self.uri_reference_builder.authority(authority); self } /// Consumes the builder and tries to build a [`URI`]. /// /// This function will error in one of three situations: /// - A scheme and path were not specified in the builder. /// - While all individual components were valid, their combination as a URI was invalid. /// /// # Examples /// /// First error type (scheme and/or path were not specified): /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Path, URIBuilder}; /// /// let result = URIBuilder::new() /// .with_authority(Some(Authority::try_from("example.com").unwrap())) /// .with_path(Path::try_from("/my/path").unwrap()) /// .build(); /// assert!(result.is_err()); /// ``` /// /// Second error type (URI with no authority cannot have path starting with `"//"`): /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Scheme, Path, URIBuilder}; /// /// let result = URIBuilder::new() /// .with_scheme(Scheme::URN) /// .with_path(Path::try_from("//path").unwrap()) /// .build(); /// assert!(result.is_err()); /// ``` pub fn build(self) -> Result, URIError> { let uri_reference = self .uri_reference_builder .build() .map_err(|error| URIError::try_from(error).unwrap())?; if !uri_reference.has_scheme() { return Err(URIError::MissingScheme); } Ok(URI { uri_reference }) } /// Sets the fragment part of the URI. /// /// It is optional to specify a fragment. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Fragment, Path, Scheme, URIBuilder}; /// /// let mut builder = URIBuilder::new(); /// builder /// .scheme(Scheme::URN) /// .path(Path::try_from("path").unwrap()) /// .fragment(Some(Fragment::try_from("fragment").unwrap())); /// let uri = builder.build().unwrap(); /// assert_eq!(uri.to_string(), "urn:path#fragment"); /// ``` pub fn fragment(&mut self, fragment: Option>) -> &mut Self { self.uri_reference_builder.fragment(fragment); self } /// Constructs a new builder with nothing set. pub fn new() -> Self { URIBuilder::default() } /// Sets the path part of the URI. /// /// It is required to specify a path. Not doing so will result in an error during the /// [`URIBuilder::build`] function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Path, Scheme, URIBuilder}; /// /// let mut builder = URIBuilder::new(); /// builder /// .scheme(Scheme::URN) /// .path(Path::try_from("path").unwrap()); /// let uri = builder.build().unwrap(); /// assert_eq!(uri.to_string(), "urn:path"); /// ``` pub fn path(&mut self, path: Path<'uri>) -> &mut Self { self.uri_reference_builder.path(path); self } /// Sets the query part of the URI reference. /// /// It is optional to specify a query. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Path, Query, Scheme, URIBuilder}; /// /// let mut builder = URIBuilder::new(); /// builder /// .scheme(Scheme::URN) /// .path(Path::try_from("path").unwrap()) /// .query(Some(Query::try_from("query").unwrap())); /// let uri = builder.build().unwrap(); /// assert_eq!(uri.to_string(), "urn:path?query"); /// ``` pub fn query(&mut self, query: Option>) -> &mut Self { self.uri_reference_builder.query(query); self } /// Sets the scheme part of the URI reference. /// /// It is required to specify a scheme. Not doing so will result in an error during the /// [`URIBuilder::build`] function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Path, Scheme, URIBuilder}; /// /// let mut builder = URIBuilder::new(); /// builder /// .scheme(Scheme::HTTP) /// .authority(Some(Authority::try_from("example.com").unwrap())) /// .path(Path::try_from("/my/path").unwrap()); /// let uri = builder.build().unwrap(); /// assert_eq!(uri.to_string(), "http://example.com/my/path"); /// ``` pub fn scheme(&mut self, scheme: Scheme<'uri>) -> &mut Self { self.uri_reference_builder.scheme(Some(scheme)); self } /// Sets the authority part of the URI.1 /// /// If the given authority is not a valid authority (i.e. the conversion fails), an error is /// stored internally and checked during the [`URIBuilder::build`] function. The error state /// will be rewritten for any following calls to this function. /// /// It is optional to specify an authority. /// /// # Examples /// /// ``` /// use uriparse::URIBuilder; /// /// let mut builder = URIBuilder::new(); /// builder /// .try_scheme("http") /// .unwrap() /// .try_authority(Some("example.com")) /// .unwrap() /// .try_path("/my/path") /// .unwrap(); /// let uri = builder.build().unwrap(); /// assert_eq!(uri.to_string(), "http://example.com/my/path"); /// ``` pub fn try_authority( &mut self, authority: Option, ) -> Result<&mut Self, AuthorityError> where Authority<'uri>: TryFrom, AuthorityError: From, { self.uri_reference_builder.try_authority(authority)?; Ok(self) } /// Sets the fragment part of the URI. /// /// If the given fragment is not a valid fragment (i.e. the conversion fails), an error is /// stored internally and checked during the [`URIBuilder::build`] function. The error state /// will be rewritten for any following calls to this function. /// /// It is optional to specify a fragment. /// /// # Examples /// /// ``` /// use uriparse::URIBuilder; /// /// let mut builder = URIBuilder::new(); /// builder /// .try_scheme("urn") /// .unwrap() /// .try_path("path") /// .unwrap() /// .try_fragment(Some("fragment")) /// .unwrap(); /// let uri = builder.build().unwrap(); /// assert_eq!(uri.to_string(), "urn:path#fragment"); /// ``` pub fn try_fragment( &mut self, fragment: Option, ) -> Result<&mut Self, FragmentError> where Fragment<'uri>: TryFrom, FragmentError: From, { self.uri_reference_builder.try_fragment(fragment)?; Ok(self) } /// Sets the path part of the URI. /// /// If the given path is not a valid path (i.e. the conversion fails), an error is stored /// internally and checked during the [`URIBuilder::build`] function. The error state will be /// rewritten for any following calls to this function. /// /// It is required to specify a path. /// /// # Examples /// /// ``` /// use uriparse::URIBuilder; /// /// let mut builder = URIBuilder::new(); /// builder /// .try_scheme("urn") /// .unwrap() /// .try_path("path") /// .unwrap(); /// let uri = builder.build().unwrap(); /// assert_eq!(uri.to_string(), "urn:path"); /// ``` pub fn try_path(&mut self, path: TPath) -> Result<&mut Self, PathError> where Path<'uri>: TryFrom, PathError: From, { self.uri_reference_builder.try_path(path)?; Ok(self) } /// Sets the query part of the URI. /// /// If the given query is not a valid query (i.e. the conversion fails), an error is stored /// internally and checked during the [`URIBuilder::build`] function. The error state will be /// rewritten for any following calls to this function. /// /// It is optional to specify a query. /// /// # Examples /// /// ``` /// use uriparse::URIBuilder; /// /// let mut builder = URIBuilder::new(); /// builder /// .try_scheme("urn") /// .unwrap() /// .try_path("path") /// .unwrap() /// .try_query(Some("query")) /// .unwrap(); /// let uri = builder.build().unwrap(); /// assert_eq!(uri.to_string(), "urn:path?query"); /// ``` pub fn try_query( &mut self, query: Option, ) -> Result<&mut Self, QueryError> where Query<'uri>: TryFrom, QueryError: From, { self.uri_reference_builder.try_query(query)?; Ok(self) } /// Sets the scheme part of the URI. /// /// If the given scheme is not a valid scheme (i.e. the conversion fails), an error is stored /// internally and checked during the [`URIBuilder::build`] function. The error state will be /// rewritten for any following calls to this function. /// /// It is required to specify a scheme. Not doing so will result in an error during the /// [`URIBuilder::build`] function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Path, Scheme, URIBuilder}; /// /// let mut builder = URIBuilder::new(); /// builder /// .try_scheme("urn") /// .unwrap() /// .try_path("path") /// .unwrap(); /// let uri = builder.build().unwrap(); /// assert_eq!(uri.to_string(), "urn:path"); /// ``` pub fn try_scheme( &mut self, scheme: TScheme, ) -> Result<&mut Self, SchemeError> where Scheme<'uri>: TryFrom, SchemeError: From, { self.uri_reference_builder.try_scheme(Some(scheme))?; Ok(self) } /// Consumes the builder and sets the authority part of the URI. /// /// It is optional to specify an authority. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Path, Scheme, URIBuilder}; /// /// let uri = URIBuilder::new() /// .with_scheme(Scheme::HTTP) /// .with_authority(Some(Authority::try_from("example.com").unwrap())) /// .with_path(Path::try_from("/").unwrap()) /// .build() /// .unwrap(); /// assert_eq!(uri.to_string(), "http://example.com/") /// ``` pub fn with_authority(mut self, authority: Option>) -> Self { self.authority(authority); self } /// Consumes the builder and sets the fragment part of the URI. /// /// It is optional to specify a fragment. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Fragment, Path, Scheme, URIBuilder}; /// /// let uri = URIBuilder::new() /// .with_scheme(Scheme::URN) /// .with_path(Path::try_from("").unwrap()) /// .with_fragment(Some(Fragment::try_from("fragment").unwrap())) /// .build() /// .unwrap(); /// assert_eq!(uri.to_string(), "urn:#fragment") /// ``` pub fn with_fragment(mut self, fragment: Option>) -> Self { self.fragment(fragment); self } /// Consumes the builder and sets the path part of the URI. /// /// It is required to specify a path. Not doing so will result in an error during the /// [`URIBuilder::build`] function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Path, Scheme, URIBuilder}; /// /// let reference = URIBuilder::new() /// .with_scheme(Scheme::HTTP) /// .with_authority(Some(Authority::try_from("example.com").unwrap())) /// .with_path(Path::try_from("/").unwrap()) /// .build() /// .unwrap(); /// assert_eq!(reference.to_string(), "http://example.com/") /// ``` pub fn with_path(mut self, path: Path<'uri>) -> Self { self.path(path); self } /// Consumes the builder and sets the query part of the URI. /// /// It is optional to specify a query. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Path, Query, Scheme, URIBuilder}; /// /// let uri = URIBuilder::new() /// .with_scheme(Scheme::URN) /// .with_path(Path::try_from("").unwrap()) /// .with_query(Some(Query::try_from("query").unwrap())) /// .build() /// .unwrap(); /// assert_eq!(uri.to_string(), "urn:?query") /// ``` pub fn with_query(mut self, query: Option>) -> Self { self.query(query); self } /// Consumes the builder and sets the scheme part of the URI. /// /// It is required to specify a scheme. Not doing so will result in an error during the /// [`URIBuilder::build`] function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Path, Scheme, URIBuilder}; /// /// let reference = URIBuilder::new() /// .with_scheme(Scheme::HTTP) /// .with_authority(Some(Authority::try_from("example.com").unwrap())) /// .with_path(Path::try_from("/").unwrap()) /// .build() /// .unwrap(); /// assert_eq!(reference.to_string(), "http://example.com/") /// ``` pub fn with_scheme(mut self, scheme: Scheme<'uri>) -> Self { self.scheme(scheme); self } } /// An error representing an invalid URI. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum URIError { /// Represents the case when there is no authority, but the first path segment starts with /// `"//"`. This is not allowed because it would be interpreted as an authority component. /// /// This can only occur when using creation functions that act on individual parts (e.g. /// [`URI::from_parts`]). AbsolutePathStartsWithTwoSlashes, /// The authority component of the relative reference was invalid. Authority(AuthorityError), /// The fragment component of the relative reference was invalid. Fragment(FragmentError), /// This error occurs when you do not specify a path component on the builder. /// /// This can only occur when using [`URIBuilder`]. MissingPath, /// This error occurs when you do not specify a scheme component on the builder. /// /// This can only occur when using [`URIBuilder`]. MissingScheme, /// When parsing from some byte string source, if the source ends up being a relative reference, /// then it is obviously not a URI. /// /// This can only occur when parsing from a byte string source. NotURI, /// The path component of the relative reference was invalid. Path(PathError), /// The query component of the relative reference was invalid. Query(QueryError), /// The scheme component of the relative reference was invalid. Scheme(SchemeError), } impl Display for URIError { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { use self::URIError::*; match self { AbsolutePathStartsWithTwoSlashes => { write!(formatter, "absolute path URI starts with two slashes") } Authority(error) => error.fmt(formatter), Fragment(error) => error.fmt(formatter), MissingPath => write!(formatter, "missing path"), MissingScheme => write!(formatter, "missing scheme"), NotURI => write!(formatter, "not URI"), Path(error) => error.fmt(formatter), Query(error) => error.fmt(formatter), Scheme(error) => error.fmt(formatter), } } } impl Error for URIError {} impl From for URIError { fn from(_: Infallible) -> Self { URIError::AbsolutePathStartsWithTwoSlashes } } impl From for URIError { fn from(value: AuthorityError) -> Self { URIError::Authority(value) } } impl From for URIError { fn from(value: FragmentError) -> Self { URIError::Fragment(value) } } impl From for URIError { fn from(value: PathError) -> Self { URIError::Path(value) } } impl From for URIError { fn from(value: QueryError) -> Self { URIError::Query(value) } } impl From for URIError { fn from(value: SchemeError) -> Self { URIError::Scheme(value) } } impl TryFrom for URIError { type Error = (); fn try_from(value: URIReferenceError) -> Result { use self::URIError::*; match value { URIReferenceError::AbsolutePathStartsWithTwoSlashes => { Ok(AbsolutePathStartsWithTwoSlashes) } URIReferenceError::Authority(error) => Ok(Authority(error)), URIReferenceError::Fragment(error) => Ok(Fragment(error)), URIReferenceError::MissingPath => Ok(MissingPath), URIReferenceError::Path(error) => Ok(Path(error)), URIReferenceError::Query(error) => Ok(Query(error)), URIReferenceError::Scheme(error) => Ok(Scheme(error)), URIReferenceError::SchemelessPathStartsWithColonSegment => Err(()), } } } #[cfg(test)] mod test { use super::*; #[test] fn test_resolve() { fn test_case(value: &str, expected: &str) { let base_uri = URI::try_from("http://a/b/c/d;p?q").unwrap(); let reference = URIReference::try_from(value).unwrap(); assert_eq!(base_uri.resolve(&reference).to_string(), expected); } test_case("g:h", "g:h"); test_case("g", "http://a/b/c/g"); test_case("./g", "http://a/b/c/g"); test_case("g/", "http://a/b/c/g/"); test_case("/g", "http://a/g"); test_case("//g", "http://g/"); test_case("?y", "http://a/b/c/d;p?y"); test_case("g?y", "http://a/b/c/g?y"); test_case("#s", "http://a/b/c/d;p?q#s"); test_case("g#s", "http://a/b/c/g#s"); test_case("g?y#s", "http://a/b/c/g?y#s"); test_case(";x", "http://a/b/c/;x"); test_case("g;x", "http://a/b/c/g;x"); test_case("g;x?y#s", "http://a/b/c/g;x?y#s"); test_case("", "http://a/b/c/d;p?q"); test_case(".", "http://a/b/c/"); test_case("./", "http://a/b/c/"); test_case("..", "http://a/b/"); test_case("../", "http://a/b/"); test_case("../g", "http://a/b/g"); test_case("../..", "http://a/"); test_case("../../", "http://a/"); test_case("../../g", "http://a/g"); test_case("../../../g", "http://a/g"); test_case("../../../g", "http://a/g"); test_case("/./g", "http://a/g"); test_case("/../g", "http://a/g"); test_case("g.", "http://a/b/c/g."); test_case(".g", "http://a/b/c/.g"); test_case("g..", "http://a/b/c/g.."); test_case("..g", "http://a/b/c/..g"); test_case("./../g", "http://a/b/g"); test_case("./g/.", "http://a/b/c/g/"); test_case("g/./h", "http://a/b/c/g/h"); test_case("g/../h", "http://a/b/c/h"); test_case("g;x=1/./y", "http://a/b/c/g;x=1/y"); test_case("g;x=1/../y", "http://a/b/c/y"); test_case("g?y/./x", "http://a/b/c/g?y/./x"); test_case("g?y/../x", "http://a/b/c/g?y/../x"); test_case("g#s/./x", "http://a/b/c/g#s/./x"); test_case("g#s/../x", "http://a/b/c/g#s/../x"); test_case("http:g", "http:g"); } #[cfg(feature = "serde")] #[test] fn test_serde() { let uri = URI::try_from("http://a/b/c/d;p?q").unwrap(); // Perform serialization let json_string = serde_json::to_string(&uri).unwrap(); // Perform deserialization let uri2 = serde_json::from_str(&json_string).unwrap(); assert_eq!( uri, uri2, "Information lost in serialization/deserialization" ); } } uriparse-0.6.4/src/uri_reference.rs000064400000000000000000001702350072674642500154770ustar 00000000000000use std::convert::{Infallible, TryFrom}; use std::error::Error; use std::fmt::{self, Display, Formatter, Write}; use std::mem; use crate::authority::{parse_authority, Authority, AuthorityError, Host, Password, Username}; use crate::fragment::{Fragment, FragmentError}; use crate::path::{parse_path, Path, PathError}; use crate::query::{parse_query, Query, QueryError}; use crate::scheme::{parse_scheme, Scheme, SchemeError}; /// A URI reference as defined in /// [[RFC3986, Section 4.1]](https://tools.ietf.org/html/rfc3986#section-4.1). /// /// Specifically, a URI reference is either a URI or a relative reference (a schemeless URI). #[derive(Clone, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct URIReference<'uri> { /// The authority component of the URI reference as defined in /// [[RFC3986, Section 3.2]](https://tools.ietf.org/html/rfc3986#section-3.2). authority: Option>, /// The fragment component of the URI reference as defined in /// [[RFC3986, Section 3.5]](https://tools.ietf.org/html/rfc3986#section-3.5). fragment: Option>, /// The path component of the URI reference as defined in /// [[RFC3986, Section 3.3]](https://tools.ietf.org/html/rfc3986#section-3.3). path: Path<'uri>, /// The query component of the URI reference as defined in /// [[RFC3986, Section 3.4]](https://tools.ietf.org/html/rfc3986#section-3.4). query: Option>, /// The scheme component of the URI reference as defined in /// [[RFC3986, Section 3.1](https://tools.ietf.org/html/rfc3986#section-3.1). scheme: Option>, } impl<'uri> URIReference<'uri> { /// Returns the authority, if present, of the URI reference. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("//example.com/my/path").unwrap(); /// assert_eq!(reference.authority().unwrap().to_string(), "example.com"); /// ``` pub fn authority(&self) -> Option<&Authority<'uri>> { self.authority.as_ref() } /// Constructs a default builder for a URI reference. /// /// This provides an alternative means of constructing a URI reference besides parsing and /// [`URIReference::from_parts`]. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Path, Scheme, URIReference}; /// /// let reference = URIReference::builder() /// .with_scheme(Some(Scheme::HTTP)) /// .with_authority(Some(Authority::try_from("example.com").unwrap())) /// .with_path(Path::try_from("/my/path").unwrap()) /// .build() /// .unwrap(); /// assert_eq!(reference.to_string(), "http://example.com/my/path"); /// ``` pub fn builder<'new_uri>() -> URIReferenceBuilder<'new_uri> { URIReferenceBuilder::default() } /// Returns whether the URI reference can act as a base URI. /// /// A URI can be a base if it is absolute (i.e. it has no fragment component). /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("http://example.com/my/path").unwrap(); /// assert!(reference.can_be_a_base()); /// /// let reference = URIReference::try_from("ftp://127.0.0.1#fragment").unwrap(); /// assert!(!reference.can_be_a_base()); /// ``` pub fn can_be_a_base(&self) -> bool { self.has_scheme() && !self.has_fragment() } /// Constructs a new [`URIReference`] from the individual parts: scheme, authority, path, query, /// and fragment. /// /// The lifetime used by the resulting value will be the lifetime of the part that is most /// restricted in scope. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Scheme, URIReference}; /// /// let reference = URIReference::from_parts( /// None::, /// Some("example.com"), /// "/my/path", /// Some("query"), /// Some("fragment") /// ).unwrap(); /// assert_eq!(reference.to_string(), "//example.com/my/path?query#fragment"); /// ``` pub fn from_parts< 'new_uri, TScheme, TAuthority, TPath, TQuery, TFragment, TSchemeError, TAuthorityError, TPathError, TQueryError, TFragmentError, >( scheme: Option, authority: Option, path: TPath, query: Option, fragment: Option, ) -> Result, URIReferenceError> where Scheme<'new_uri>: TryFrom, Authority<'new_uri>: TryFrom, Path<'new_uri>: TryFrom, Query<'new_uri>: TryFrom, Fragment<'new_uri>: TryFrom, URIReferenceError: From + From + From + From + From, { let scheme = match scheme { Some(scheme) => Some(Scheme::try_from(scheme)?), None => None, }; let authority = match authority { Some(authority) => Some(Authority::try_from(authority)?), None => None, }; let mut path = Path::try_from(path)?; if authority.is_some() { path.set_absolute(true); } validate_absolute_path(authority.as_ref(), &path)?; validate_schemeless_path(scheme.as_ref(), authority.as_ref(), &path)?; let query = match query { Some(query) => Some(Query::try_from(query)?), None => None, }; let fragment = match fragment { Some(fragment) => Some(Fragment::try_from(fragment)?), None => None, }; Ok(URIReference { authority, fragment, path, query, scheme, }) } /// Returns the fragment, if present, of the URI reference. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("http://example.com#fragment").unwrap(); /// assert_eq!(reference.fragment().unwrap(), "fragment"); /// ``` pub fn fragment(&self) -> Option<&Fragment<'uri>> { self.fragment.as_ref() } /// Returns whether the URI reference has an authority component. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("http://example.com").unwrap(); /// assert!(reference.has_authority()); /// /// let reference = URIReference::try_from("").unwrap(); /// assert!(!reference.has_authority()); /// ``` pub fn has_authority(&self) -> bool { self.authority.is_some() } /// Returns whether the URI reference has a fragment component. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("#test").unwrap(); /// assert!(reference.has_fragment()); /// /// let reference = URIReference::try_from("http://example.com").unwrap(); /// assert!(!reference.has_fragment()); /// ``` pub fn has_fragment(&self) -> bool { self.fragment.is_some() } /// Returns whether the URI reference has a password component. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("http://user:pass@127.0.0.1").unwrap(); /// assert!(reference.has_password()); /// /// let reference = URIReference::try_from("http://user@127.0.0.1").unwrap(); /// assert!(!reference.has_password()); /// ``` pub fn has_password(&self) -> bool { if let Some(ref authority) = self.authority { authority.has_password() } else { false } } /// Returns whether the URI reference has a port. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("http://127.0.0.1:8080").unwrap(); /// assert!(reference.has_port()); /// /// let reference = URIReference::try_from("http://127.0.0.1").unwrap(); /// assert!(!reference.has_port()); /// ``` pub fn has_port(&self) -> bool { if let Some(ref authority) = self.authority { authority.has_port() } else { false } } /// Returns whether the URI reference has a query component. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("/?my=query").unwrap(); /// assert!(reference.has_query()); /// /// let reference = URIReference::try_from("http://example.com/my/path").unwrap(); /// assert!(!reference.has_query()); /// ``` pub fn has_query(&self) -> bool { self.query.is_some() } /// Returns whether the URI reference has a scheme component. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("http://example.com?my=query").unwrap(); /// assert!(reference.has_scheme()); /// /// let reference = URIReference::try_from("/my/path").unwrap(); /// assert!(!reference.has_scheme()); /// ``` pub fn has_scheme(&self) -> bool { self.scheme.is_some() } /// Returns whether the URI reference has a username component. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("//username@example.com").unwrap(); /// assert!(reference.has_username()); /// /// let reference = URIReference::try_from("http://example.com").unwrap(); /// assert!(!reference.has_username()); /// ``` pub fn has_username(&self) -> bool { if let Some(ref authority) = self.authority { authority.has_username() } else { false } } /// Returns the host, if present, of the URI reference. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("http://username@example.com").unwrap(); /// assert_eq!(reference.host().unwrap().to_string(), "example.com"); /// ``` pub fn host(&self) -> Option<&Host<'uri>> { if let Some(ref authority) = self.authority { Some(authority.host()) } else { None } } /// Consumes the URI reference and converts it into a builder with the same values. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Fragment, Query, URIReference}; /// /// let reference = URIReference::try_from("//example.com/path?query#fragment").unwrap(); /// let mut builder = reference.into_builder(); /// builder.query(None::).fragment(None::); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "//example.com/path"); /// ``` pub fn into_builder(self) -> URIReferenceBuilder<'uri> { let mut builder = URIReferenceBuilder::new(); builder .scheme(self.scheme) .authority(self.authority) .path(self.path) .query(self.query) .fragment(self.fragment); builder } /// Converts the [`URIReference`] into an owned copy. /// /// If you construct the URI reference from a source with a non-static lifetime, you may run /// into lifetime problems due to the way the struct is designed. Calling this function will /// ensure that the returned value has a static lifetime. /// /// This is different from just cloning. Cloning the URI reference will just copy the /// references, and thus the lifetime will remain the same. pub fn into_owned(self) -> URIReference<'static> { let scheme = self.scheme.map(Scheme::into_owned); let authority = self.authority.map(Authority::into_owned); let path = self.path.into_owned(); let query = self.query.map(Query::into_owned); let fragment = self.fragment.map(Fragment::into_owned); URIReference { authority, fragment, path, query, scheme, } } /// Consumes the [`URIReference`] and returns its parts: scheme, authority, path, query, and /// fragment. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from( /// "http://username:password@example.com:80/my/path?my=query#fragment", /// ).unwrap(); /// let (scheme, authority, path, query, fragment) = reference.into_parts(); /// /// assert_eq!(scheme.unwrap(), "http"); /// assert_eq!(authority.unwrap().to_string(), "username:password@example.com:80"); /// assert_eq!(path, "/my/path"); /// assert_eq!(query.unwrap(), "my=query"); /// assert_eq!(fragment.unwrap(), "fragment"); /// ``` pub fn into_parts( self, ) -> ( Option>, Option>, Path<'uri>, Option>, Option>, ) { ( self.scheme, self.authority, self.path, self.query, self.fragment, ) } /// Returns whether the URI reference is an absolute path reference. /// /// A URI reference is an absolute path reference if it is a relative reference that begins with /// a single `'/'`. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("/my/path").unwrap(); /// assert!(reference.is_absolute_path_reference()); /// ``` pub fn is_absolute_path_reference(&self) -> bool { self.scheme.is_none() && self.authority.is_none() && self.path.is_absolute() } /// Returns whether the URI reference is a network path reference. /// /// A URI reference is a network path reference if it is a relative reference that begins with /// two `'/'`. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("//example.com").unwrap(); /// assert!(reference.is_network_path_reference()); /// ``` pub fn is_network_path_reference(&self) -> bool { self.scheme.is_none() && self.authority.is_some() } /// Returns whether the URI reference is normalized. /// /// A normalized URI reference will have all of its components normalized. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("http://example.com/?a=b").unwrap(); /// assert!(reference.is_normalized()); /// /// let mut reference = URIReference::try_from("http://EXAMPLE.com/?a=b").unwrap(); /// assert!(!reference.is_normalized()); /// reference.normalize(); /// assert!(reference.is_normalized()); /// ``` pub fn is_normalized(&self) -> bool { if let Some(scheme) = self.scheme.as_ref() { if !scheme.is_normalized() { return false; } } if let Some(authority) = self.authority.as_ref() { if !authority.is_normalized() { return false; } } if !self.path.is_normalized(self.scheme.is_none()) { return false; } if let Some(query) = self.query.as_ref() { if !query.is_normalized() { return false; } } if let Some(fragment) = self.fragment.as_ref() { if !fragment.is_normalized() { return false; } } true } /// Returns whether the URI reference is a relative path reference. /// /// A URI reference is a relative path reference if it is a relative reference that does not /// begin with a `'/'`. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("my/path").unwrap(); /// assert!(reference.is_relative_path_reference()); /// ``` pub fn is_relative_path_reference(&self) -> bool { self.scheme.is_none() && self.authority.is_none() && !self.path.is_absolute() } /// Returns whether the URI reference is a relative reference. /// /// A URI reference is a relative reference if it has no scheme. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("/my/path").unwrap(); /// assert!(reference.is_relative_reference()); /// ``` pub fn is_relative_reference(&self) -> bool { self.scheme.is_none() } /// Returns whether the URI reference is a URI. /// /// A URI reference is a URI if it has a scheme. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("http://example.com").unwrap(); /// assert!(reference.is_uri()); /// ``` pub fn is_uri(&self) -> bool { self.scheme.is_some() } /// Maps the authority using the given map function. /// /// This function will panic if, as a result of the authority change, the URI reference becomes /// invalid. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, URIReference}; /// /// let mut reference = URIReference::try_from("http://example.com").unwrap(); /// reference.map_authority(|_| Some(Authority::try_from("127.0.0.1").unwrap())); /// assert_eq!(reference.to_string(), "http://127.0.0.1/"); /// ``` pub fn map_authority(&mut self, mapper: TMapper) -> Option<&Authority<'uri>> where TMapper: FnOnce(Option>) -> Option>, { let authority = mapper(self.authority.take()); self.set_authority(authority) .expect("mapped authority resulted in invalid state") } /// Maps the fragment using the given map function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Fragment, URIReference}; /// /// let mut reference = URIReference::try_from("http://example.com").unwrap(); /// reference.map_fragment(|_| Some(Fragment::try_from("fragment").unwrap())); /// assert_eq!(reference.to_string(), "http://example.com/#fragment"); /// ``` pub fn map_fragment(&mut self, mapper: TMapper) -> Option<&Fragment<'uri>> where TMapper: FnOnce(Option>) -> Option>, { let fragment = mapper(self.fragment.take()); self.set_fragment(fragment) .expect("mapped fragment resulted in invalid state") } /// Maps the path using the given map function. /// /// This function will panic if, as a result of the path change, the URI reference becomes /// invalid. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, URIReference}; /// /// let mut reference = URIReference::try_from("http://example.com").unwrap(); /// reference.map_path(|mut path| { /// path.push("test").unwrap(); /// path.push("path").unwrap(); /// path /// }); /// assert_eq!(reference.to_string(), "http://example.com/test/path"); /// ``` pub fn map_path(&mut self, mapper: TMapper) -> &Path<'uri> where TMapper: FnOnce(Path<'uri>) -> Path<'uri>, { // Unsafe: We're creating an invalid path just as a temporary sentinel value, but it is // replaced shortly after. let temp_path = unsafe { Path::new_with_no_segments(true) }; let path = mapper(mem::replace(&mut self.path, temp_path)); self.set_path(path) .expect("mapped path resulted in invalid state") } /// Maps the query using the given map function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Query, URIReference}; /// /// let mut reference = URIReference::try_from("http://example.com").unwrap(); /// reference.map_query(|_| Some(Query::try_from("query").unwrap())); /// assert_eq!(reference.to_string(), "http://example.com/?query"); /// ``` pub fn map_query(&mut self, mapper: TMapper) -> Option<&Query<'uri>> where TMapper: FnOnce(Option>) -> Option>, { let query = mapper(self.query.take()); self.set_query(query) .expect("mapped query resulted in invalid state") } /// Maps the scheme using the given map function. /// /// This function will panic if, as a result of the scheme change, the URI reference becomes /// invalid. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Scheme, URIReference}; /// /// let mut reference = URIReference::try_from("http://example.com").unwrap(); /// reference.map_scheme(|_| Some(Scheme::try_from("https").unwrap())); /// assert_eq!(reference.to_string(), "https://example.com/"); /// ``` pub fn map_scheme(&mut self, mapper: TMapper) -> Option<&Scheme<'uri>> where TMapper: FnOnce(Option>) -> Option>, { let scheme = mapper(self.scheme.take()); self.set_scheme(scheme) .expect("mapped scheme resulted in invalid state") } /// Normalizes the URI reference. /// /// A normalized URI reference will have all of its components normalized. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let mut reference = URIReference::try_from("http://example.com/?a=b").unwrap(); /// reference.normalize(); /// assert_eq!(reference.to_string(), "http://example.com/?a=b"); /// /// let mut reference = URIReference::try_from("http://EXAMPLE.com/?a=b").unwrap(); /// assert_eq!(reference.to_string(), "http://EXAMPLE.com/?a=b"); /// reference.normalize(); /// assert_eq!(reference.to_string(), "http://example.com/?a=b"); /// ``` pub fn normalize(&mut self) { if let Some(scheme) = self.scheme.as_mut() { scheme.normalize(); } if let Some(authority) = self.authority.as_mut() { authority.normalize(); } self.path.normalize(self.scheme.is_none()); if let Some(query) = self.query.as_mut() { query.normalize(); } if let Some(fragment) = self.fragment.as_mut() { fragment.normalize(); } } /// Returns the path of the URI reference. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("http://127.0.0.1/my/path").unwrap(); /// assert_eq!(reference.path(), "/my/path"); /// ``` pub fn path(&self) -> &Path<'uri> { &self.path } /// Returns the password, if present, of the URI reference. /// /// Usage of a password in URI and URI references is deprecated. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("http://user:pass@example.com").unwrap(); /// assert_eq!(reference.password().unwrap(), "pass"); /// ``` pub fn password(&self) -> Option<&Password<'uri>> { if let Some(ref authority) = self.authority { authority.password() } else { None } } /// Returns the port, if present, of the URI reference. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("http://example.com:8080/").unwrap(); /// assert_eq!(reference.port().unwrap(), 8080); /// ``` pub fn port(&self) -> Option { if let Some(ref authority) = self.authority { authority.port() } else { None } } /// Returns the query, if present, of the URI reference. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("http://127.0.0.1?my=query").unwrap(); /// assert_eq!(reference.query().unwrap(), "my=query"); /// ``` pub fn query(&self) -> Option<&Query<'uri>> { self.query.as_ref() } /// Returns the scheme, if present, of the URI reference. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("http://127.0.0.1/").unwrap(); /// assert_eq!(reference.scheme().unwrap(), "http"); /// ``` pub fn scheme(&self) -> Option<&Scheme<'uri>> { self.scheme.as_ref() } /// Sets the authority of the URI reference. /// /// An error will be returned if the conversion to an [`Authority`] fails. /// /// The existing path will be set to absolute (i.e. starts with a `'/'`). /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let mut reference = URIReference::try_from("http://example.com").unwrap(); /// reference.set_authority(Some("user@example.com:80")); /// assert_eq!(reference.to_string(), "http://user@example.com:80/"); /// ``` pub fn set_authority( &mut self, authority: Option, ) -> Result>, URIReferenceError> where Authority<'uri>: TryFrom, URIReferenceError: From, { self.authority = match authority { Some(authority) => { self.path.set_absolute(true); Some(Authority::try_from(authority)?) } None => { validate_absolute_path(None, &self.path)?; None } }; Ok(self.authority()) } /// Sets the fragment of the URI reference. /// /// An error will be returned if the conversion to a [`Fragment`] fails. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let mut reference = URIReference::try_from("http://example.com").unwrap(); /// reference.set_fragment(Some("fragment")); /// assert_eq!(reference.to_string(), "http://example.com/#fragment"); /// ``` pub fn set_fragment( &mut self, fragment: Option, ) -> Result>, URIReferenceError> where Fragment<'uri>: TryFrom, URIReferenceError: From, { self.fragment = match fragment { Some(fragment) => Some(Fragment::try_from(fragment)?), None => None, }; Ok(self.fragment()) } /// Sets the path of the URI reference. /// /// An error will be returned in one of two cases: /// - The conversion to [`Path`] failed. /// - The path was set to a value that resulted in an invalid URI reference. /// /// Regardless of whether the given path was set as absolute or relative, if the URI /// reference currently has an authority, the path will be forced to be absolute. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let mut reference = URIReference::try_from("http://example.com").unwrap(); /// reference.set_path("my/path"); /// assert_eq!(reference.to_string(), "http://example.com/my/path"); /// ``` pub fn set_path( &mut self, path: TPath, ) -> Result<&Path<'uri>, URIReferenceError> where Path<'uri>: TryFrom, URIReferenceError: From, { let mut path = Path::try_from(path)?; validate_absolute_path(self.authority.as_ref(), &path)?; validate_schemeless_path(self.scheme.as_ref(), self.authority.as_ref(), &path)?; if self.authority.is_some() { path.set_absolute(true); } self.path = path; Ok(self.path()) } /// Sets the query of the URI reference. /// /// An error will be returned if the conversion to a [`Query`] fails. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let mut reference = URIReference::try_from("http://example.com").unwrap(); /// reference.set_query(Some("myquery")); /// assert_eq!(reference.to_string(), "http://example.com/?myquery"); /// ``` pub fn set_query( &mut self, query: Option, ) -> Result>, URIReferenceError> where Query<'uri>: TryFrom, URIReferenceError: From, { self.query = match query { Some(query) => Some(Query::try_from(query)?), None => None, }; Ok(self.query()) } /// Sets the scheme of the URI reference. /// /// An error will be returned in one of two cases: /// - The conversion to [`Scheme`] failed. /// - The scheme was set to `None`, but the resulting URI reference has an invalid schemeless /// path. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let mut reference = URIReference::try_from("http://example.com").unwrap(); /// reference.set_scheme(Some("https")); /// assert_eq!(reference.to_string(), "https://example.com/"); /// ``` pub fn set_scheme( &mut self, scheme: Option, ) -> Result>, URIReferenceError> where Scheme<'uri>: TryFrom, URIReferenceError: From, { self.scheme = match scheme { Some(scheme) => Some(Scheme::try_from(scheme)?), None => { validate_schemeless_path(None, self.authority.as_ref(), &self.path)?; None } }; Ok(self.scheme()) } /// Returns a new URI reference which is identical but has a lifetime tied to this URI /// reference. /// /// This function will perform a memory allocation. pub fn to_borrowed(&self) -> URIReference { let scheme = self.scheme.as_ref().map(Scheme::as_borrowed); let authority = self.authority.as_ref().map(Authority::as_borrowed); let path = self.path.to_borrowed(); let query = self.query.as_ref().map(Query::as_borrowed); let fragment = self.fragment.as_ref().map(Fragment::as_borrowed); URIReference { authority, fragment, path, query, scheme, } } /// Returns the username, if present, of the URI reference. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::URIReference; /// /// let reference = URIReference::try_from("http://username@example.com").unwrap(); /// assert_eq!(reference.username().unwrap(), "username"); /// ``` pub fn username(&self) -> Option<&Username<'uri>> { if let Some(ref authority) = self.authority { authority.username() } else { None } } } impl Display for URIReference<'_> { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { if let Some(ref scheme) = self.scheme { formatter.write_str(scheme.as_str())?; formatter.write_char(':')?; } if let Some(ref authority) = self.authority { formatter.write_str("//")?; formatter.write_str(&authority.to_string())?; } formatter.write_str(&self.path.to_string())?; if let Some(ref query) = self.query { formatter.write_char('?')?; formatter.write_str(query.as_str())?; } if let Some(ref fragment) = self.fragment { formatter.write_char('#')?; formatter.write_str(fragment.as_str())?; } Ok(()) } } impl<'uri> From> for String { fn from(value: URIReference<'uri>) -> Self { value.to_string() } } impl<'uri> TryFrom<&'uri [u8]> for URIReference<'uri> { type Error = URIReferenceError; fn try_from(value: &'uri [u8]) -> Result { let (scheme, value) = match parse_scheme(value) { Ok((scheme, rest)) => { if rest.starts_with(b":") { (Some(scheme), &rest[1..]) } else { (None, value) } } _ => (None, value), }; let (authority, value) = match value.get(0..2) { Some(b"//") => { let (authority, value) = parse_authority(&value[2..])?; (Some(authority), value) } _ => (None, value), }; let (mut path, value) = parse_path(value)?; if authority.is_some() { path.set_absolute(true); } validate_schemeless_path(scheme.as_ref(), authority.as_ref(), &path)?; let (query, value) = if value.starts_with(b"?") { let (query, value) = parse_query(&value[1..])?; (Some(query), value) } else { (None, value) }; let fragment = if value.starts_with(b"#") { Some(Fragment::try_from(&value[1..])?) } else { None }; Ok(URIReference { authority, fragment, path, query, scheme, }) } } impl<'uri> TryFrom<&'uri str> for URIReference<'uri> { type Error = URIReferenceError; fn try_from(value: &'uri str) -> Result { URIReference::try_from(value.as_bytes()) } } /// A builder type for [`URIReference]`. /// /// You must use the [`URIReference::path`] function before building as URI references always have /// have a path. Everything else is optional. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct URIReferenceBuilder<'uri> { /// The authority component of the URI reference as defined in /// [[RFC3986, Section 3.2]](https://tools.ietf.org/html/rfc3986#section-3.2). authority: Option>, /// The fragment component of the URI reference as defined in /// [[RFC3986, Section 3.5]](https://tools.ietf.org/html/rfc3986#section-3.5). fragment: Option>, /// The path component of the URI reference as defined in /// [[RFC3986, Section 3.3]](https://tools.ietf.org/html/rfc3986#section-3.3). path: Option>, /// The query component of the URI reference as defined in /// [[RFC3986, Section 3.4]](https://tools.ietf.org/html/rfc3986#section-3.4). query: Option>, /// The scheme component of the URI reference as defined in /// [[RFC3986, Section 3.1]](https://tools.ietf.org/html/rfc3986#section-3.1). scheme: Option>, } impl<'uri> URIReferenceBuilder<'uri> { /// Sets the authority part of the URI reference. /// /// It is optional to specify a authority. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Path, URIReferenceBuilder}; /// /// let mut builder = URIReferenceBuilder::new(); /// builder /// .authority(Some(Authority::try_from("example.com").unwrap())) /// .path(Path::try_from("/my/path").unwrap()); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "//example.com/my/path"); /// ``` pub fn authority(&mut self, authority: Option>) -> &mut Self { self.authority = authority; self } /// Consumes the builder and tries to build a [`URIReference`]. /// /// This function will error in one of two situations: /// - A path was not specified in the builder. /// - While all individual components were valid, their combination as a URI reference was /// invalid. /// /// # Examples /// /// First error type (path not specified): /// /// ``` /// use uriparse::URIReferenceBuilder; /// /// let result = URIReferenceBuilder::new().build(); /// assert!(result.is_err()); /// ``` /// /// Second error type (first segment in schemeless path cannot contain a `':'`): /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Path, URIReferenceBuilder}; /// /// let result = URIReferenceBuilder::new() /// .with_path(Path::try_from("my:/path").unwrap()) /// .build(); /// assert!(result.is_err()); /// ``` pub fn build(self) -> Result, URIReferenceError> { let path = match self.path { Some(path) => path, None => return Err(URIReferenceError::MissingPath), }; URIReference::from_parts(self.scheme, self.authority, path, self.query, self.fragment) } /// Sets the fragment part of the URI reference. /// /// It is optional to specify a fragment. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Fragment, Path, URIReferenceBuilder}; /// /// let mut builder = URIReferenceBuilder::new(); /// builder /// .path(Path::try_from("/my/path").unwrap()) /// .fragment(Some(Fragment::try_from("fragment").unwrap())); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "/my/path#fragment"); /// ``` pub fn fragment(&mut self, fragment: Option>) -> &mut Self { self.fragment = fragment; self } /// Constructs a new builder with nothing set. pub fn new() -> Self { URIReferenceBuilder::default() } /// Sets the path part of the URI reference. /// /// It is required to specify a path. Not doing so will result in an error during the /// [`URIReferenceBuilder::build`] function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Path, URIReferenceBuilder}; /// /// let mut builder = URIReferenceBuilder::new(); /// builder /// .path(Path::try_from("/my/path").unwrap()); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "/my/path"); /// ``` pub fn path(&mut self, path: Path<'uri>) -> &mut Self { self.path = Some(path); self } /// Sets the query part of the URI reference. /// /// It is optional to specify a query. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Path, Query, URIReferenceBuilder}; /// /// let mut builder = URIReferenceBuilder::new(); /// builder /// .path(Path::try_from("/my/path").unwrap()) /// .query(Some(Query::try_from("query").unwrap())); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "/my/path?query"); /// ``` pub fn query(&mut self, query: Option>) -> &mut Self { self.query = query; self } /// Sets the scheme part of the URI reference. /// /// It is optional to specify a scheme. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Path, Scheme, URIReferenceBuilder}; /// /// let mut builder = URIReferenceBuilder::new(); /// builder /// .scheme(Some(Scheme::HTTP)) /// .authority(Some(Authority::try_from("example.com").unwrap())) /// .path(Path::try_from("/my/path").unwrap()); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "http://example.com/my/path"); /// ``` pub fn scheme(&mut self, scheme: Option>) -> &mut Self { self.scheme = scheme; self } /// Sets the authority part of the URI reference. /// /// If the given authority is not a valid authority (i.e. the conversion fails), an error is /// return. /// /// It is optional to specify an authority. /// /// # Examples /// /// ``` /// use uriparse::URIReferenceBuilder; /// /// let mut builder = URIReferenceBuilder::new(); /// builder /// .try_authority(Some("example.com")) /// .unwrap() /// .try_path("/my/path") /// .unwrap(); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "//example.com/my/path"); /// ``` pub fn try_authority( &mut self, authority: Option, ) -> Result<&mut Self, TAuthorityError> where Authority<'uri>: TryFrom, AuthorityError: From, { self.authority = match authority { Some(authority) => Some(Authority::try_from(authority).map_err(|error| error)?), None => None, }; Ok(self) } /// Sets the fragment part of the URI reference. /// /// If the given fragment is not a valid fragment (i.e. the conversion fails), an error is /// returned. /// /// It is optional to specify a fragment. /// /// # Examples /// /// ``` /// use uriparse::URIReferenceBuilder; /// /// let mut builder = URIReferenceBuilder::new(); /// builder /// .try_path("/my/path") /// .unwrap() /// .try_fragment(Some("fragment")) /// .unwrap(); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "/my/path#fragment"); /// ``` pub fn try_fragment( &mut self, fragment: Option, ) -> Result<&mut Self, FragmentError> where Fragment<'uri>: TryFrom, FragmentError: From, { self.fragment = match fragment { Some(fragment) => Some(Fragment::try_from(fragment).map_err(|error| error)?), None => None, }; Ok(self) } /// Sets the path part of the URI reference. /// /// If the given path is not a valid path (i.e. the conversion fails), an error is returned. /// /// It is required to specify a path. Not doing so will result in an error during the /// [`URIReferenceBuilder::build`] function. /// /// # Examples /// /// ``` /// use uriparse::URIReferenceBuilder; /// /// let mut builder = URIReferenceBuilder::new(); /// builder /// .try_path("/my/path") /// .unwrap(); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "/my/path"); /// ``` pub fn try_path(&mut self, path: TPath) -> Result<&mut Self, PathError> where Path<'uri>: TryFrom, PathError: From, { self.path = Some(Path::try_from(path).map_err(|error| error)?); Ok(self) } /// Sets the query part of the URI reference. /// /// If the given query is not a valid query (i.e. the conversion fails), an error is returned. /// /// It is optional to specify a query. /// /// # Examples /// /// ``` /// use uriparse::URIReferenceBuilder; /// /// let mut builder = URIReferenceBuilder::new(); /// builder /// .try_path("/my/path") /// .unwrap() /// .try_query(Some("query")) /// .unwrap(); /// let reference = builder.build().unwrap(); /// assert_eq!(reference.to_string(), "/my/path?query"); /// ``` pub fn try_query( &mut self, query: Option, ) -> Result<&mut Self, QueryError> where Query<'uri>: TryFrom, QueryError: From, { self.query = match query { Some(query) => Some(Query::try_from(query).map_err(|error| error)?), None => None, }; Ok(self) } /// Sets the scheme part of the URI reference. /// /// If the given scheme is not a valid scheme (i.e. the conversion fails), an error is returned. /// /// It is optional to specify a scheme. /// /// # Examples /// /// ``` /// use uriparse::URIReferenceBuilder; /// /// let mut builder = URIReferenceBuilder::new(); /// builder /// .try_scheme(Some("urn")) /// .unwrap() /// .try_path("path") /// .unwrap(); /// let uri = builder.build().unwrap(); /// assert_eq!(uri.to_string(), "urn:path"); /// ``` pub fn try_scheme( &mut self, scheme: Option, ) -> Result<&mut Self, SchemeError> where Scheme<'uri>: TryFrom, SchemeError: From, { self.scheme = match scheme { Some(scheme) => Some(Scheme::try_from(scheme).map_err(|error| error)?), None => None, }; Ok(self) } /// Consumes the builder and sets the authority part of the URI reference. /// /// It is optional to specify an authority. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Path, URIReferenceBuilder}; /// /// let reference = URIReferenceBuilder::new() /// .with_authority(Some(Authority::try_from("example.com").unwrap())) /// .with_path(Path::try_from("/").unwrap()) /// .build() /// .unwrap(); /// assert_eq!(reference.to_string(), "//example.com/") /// ``` pub fn with_authority(mut self, authority: Option>) -> Self { self.authority(authority); self } /// Consumes the builder and sets the fragment part of the URI reference. /// /// It is optional to specify a fragment. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Fragment, Path, URIReferenceBuilder}; /// /// let reference = URIReferenceBuilder::new() /// .with_path(Path::try_from("/").unwrap()) /// .with_fragment(Some(Fragment::try_from("fragment").unwrap())) /// .build() /// .unwrap(); /// assert_eq!(reference.to_string(), "/#fragment") /// ``` pub fn with_fragment(mut self, fragment: Option>) -> Self { self.fragment(fragment); self } /// Consumes the builder and sets the path part of the URI reference. /// /// It is required to specify a path. Not doing so will result in an error during the /// [`URIReferenceBuilder::build`] function. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Path, URIReferenceBuilder}; /// /// let reference = URIReferenceBuilder::new() /// .with_path(Path::try_from("/").unwrap()) /// .build() /// .unwrap(); /// assert_eq!(reference.to_string(), "/") /// ``` pub fn with_path(mut self, path: Path<'uri>) -> Self { self.path(path); self } /// Consumes the builder and sets the query part of the URI reference. /// /// It is optional to specify a query. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Path, Query, URIReferenceBuilder}; /// /// let reference = URIReferenceBuilder::new() /// .with_path(Path::try_from("/").unwrap()) /// .with_query(Some(Query::try_from("query").unwrap())) /// .build() /// .unwrap(); /// assert_eq!(reference.to_string(), "/?query") /// ``` pub fn with_query(mut self, query: Option>) -> Self { self.query(query); self } /// Consumes the builder and sets the scheme part of the URI reference. /// /// It is optional to specify a scheme. /// /// # Examples /// /// ``` /// use std::convert::TryFrom; /// /// use uriparse::{Authority, Path, Scheme, URIReferenceBuilder}; /// /// let reference = URIReferenceBuilder::new() /// .with_scheme(Some(Scheme::HTTP)) /// .with_authority(Some(Authority::try_from("example.com").unwrap())) /// .with_path(Path::try_from("/").unwrap()) /// .build() /// .unwrap(); /// assert_eq!(reference.to_string(), "http://example.com/") /// ``` pub fn with_scheme(mut self, scheme: Option>) -> Self { self.scheme(scheme); self } } /// An error representing an invalid URI reference. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum URIReferenceError { /// Represents the case when there is no authority, but the first path segment starts with /// `"//"`. This is not allowed because it would be interpreted as an authority component. /// /// This can only occur when using creation functions that act on individual parts (e.g. /// [`URIReference::from_parts`]). AbsolutePathStartsWithTwoSlashes, /// The authority component of the relative reference was invalid. Authority(AuthorityError), /// The fragment component of the relative reference was invalid. Fragment(FragmentError), /// This error occurs when you do not specify a path component on the builder. /// /// This can only occur when using [`URIReferenceBuilder`]. MissingPath, /// The path component of the relative reference was invalid. Path(PathError), /// The query component of the relative reference was invalid. Query(QueryError), /// The scheme component of the relative reference was invalid. Scheme(SchemeError), /// Represents the case when there is no authority, but the first path segment starts with /// `"//"`. This is not allowed because it would be interpreted as an authority component. /// /// This can only occur when using creation functions that act on individual parts (e.g. /// [`URIReference::from_parts`]). SchemelessPathStartsWithColonSegment, } impl Display for URIReferenceError { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { use self::URIReferenceError::*; match self { AbsolutePathStartsWithTwoSlashes => write!( formatter, "absolute path URI reference starts with two slashes" ), Authority(error) => error.fmt(formatter), Fragment(error) => error.fmt(formatter), Path(error) => error.fmt(formatter), Query(error) => error.fmt(formatter), Scheme(error) => error.fmt(formatter), MissingPath => write!(formatter, "URI reference missing path"), SchemelessPathStartsWithColonSegment => write!( formatter, "schemeless path URI reference starts with colon segment" ), } } } impl Error for URIReferenceError {} impl From for URIReferenceError { fn from(_: Infallible) -> Self { URIReferenceError::AbsolutePathStartsWithTwoSlashes } } impl From for URIReferenceError { fn from(value: AuthorityError) -> Self { URIReferenceError::Authority(value) } } impl From for URIReferenceError { fn from(value: FragmentError) -> Self { URIReferenceError::Fragment(value) } } impl From for URIReferenceError { fn from(value: PathError) -> Self { URIReferenceError::Path(value) } } impl From for URIReferenceError { fn from(value: QueryError) -> Self { URIReferenceError::Query(value) } } impl From for URIReferenceError { fn from(value: SchemeError) -> Self { URIReferenceError::Scheme(value) } } fn validate_absolute_path( authority: Option<&Authority>, path: &Path, ) -> Result<(), URIReferenceError> { if authority.is_some() || path.is_relative() || path.segments().len() == 1 || !path.segments().first().unwrap().is_empty() { Ok(()) } else { Err(URIReferenceError::AbsolutePathStartsWithTwoSlashes) } } fn validate_schemeless_path( scheme: Option<&Scheme>, authority: Option<&Authority>, path: &Path, ) -> Result<(), URIReferenceError> { if scheme.is_some() || authority.is_some() || !path .segments() .first() .unwrap() .bytes() .any(|byte| byte == b':') { Ok(()) } else { Err(URIReferenceError::SchemelessPathStartsWithColonSegment) } } #[cfg(test)] mod test { use super::*; #[test] fn test_parse_uri_reference() { let actual = URIReference::try_from("http://example.com").unwrap(); let expected = URIReference::from_parts( Some(Scheme::HTTP), Some("example.com"), "/", None::, None::, ) .unwrap(); assert_eq!(actual, expected); let actual = URIReference::try_from("http://example.com/").unwrap(); let expected = URIReference::from_parts( Some(Scheme::HTTP), Some("example.com"), "/", None::, None::, ) .unwrap(); assert_eq!(actual, expected); let actual = URIReference::try_from("http://example.com").unwrap(); let expected = URIReference::from_parts( Some(Scheme::HTTP), Some("example.com"), "", None::, None::, ) .unwrap(); assert_eq!(actual, expected); let actual = URIReference::try_from("http://example.com/").unwrap(); let expected = URIReference::from_parts( Some(Scheme::HTTP), Some("example.com"), "", None::, None::, ) .unwrap(); assert_eq!(actual, expected); let actual = URIReference::try_from("http:").unwrap(); let expected = URIReference::from_parts( Some(Scheme::HTTP), None::, "", None::, None::, ) .unwrap(); assert_eq!(actual, expected); let actual = URIReference::try_from("http:/").unwrap(); let expected = URIReference::from_parts( Some(Scheme::HTTP), None::, "/", None::, None::, ) .unwrap(); assert_eq!(actual, expected); let actual = URIReference::try_from("http:/path").unwrap(); let expected = URIReference::from_parts( Some(Scheme::HTTP), None::, "/path", None::, None::, ) .unwrap(); assert_eq!(actual, expected); let actual = URIReference::try_from("//example.com/").unwrap(); let expected = URIReference::from_parts( None::, Some("example.com"), "/", None::, None::, ) .unwrap(); assert_eq!(actual, expected); let actual = URIReference::try_from("").unwrap(); let expected = URIReference::from_parts( None::, None::, "", None::, None::, ) .unwrap(); assert_eq!(actual, expected); let actual = URIReference::try_from("*").unwrap(); let expected = URIReference::from_parts( None::, None::, "*", None::, None::, ) .unwrap(); assert_eq!(actual, expected); let actual = URIReference::try_from("/").unwrap(); let expected = URIReference::from_parts( None::, None::, "/", None::, None::, ) .unwrap(); assert_eq!(actual, expected); let actual = URIReference::try_from("test/path").unwrap(); let expected = URIReference::from_parts( None::, None::, "test/path", None::, None::, ) .unwrap(); assert_eq!(actual, expected); } #[test] fn test_parse_uri_reference_error() { assert_eq!( URIReference::try_from("://www.example.com/"), Err(URIReferenceError::SchemelessPathStartsWithColonSegment) ); } } uriparse-0.6.4/src/utility.rs000064400000000000000000000246370072674642500143710ustar 00000000000000use std::hash::{Hash, Hasher}; #[rustfmt::skip] pub const UNRESERVED_CHAR_MAP: [u8; 256] = [ // 0 1 2 3 4 5 6 7 8 9 A B C D E F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, b'-', b'.', 0, // 2 b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', 0, 0, 0, 0, 0, 0, // 3 0, b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 4 b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', 0, 0, 0, 0, b'_', // 5 0, b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', // 6 b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', 0, 0, 0, b'~', 0, // 7 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F ]; pub fn get_percent_encoded_value( first_digit: Option, second_digit: Option, ) -> Result<(u8, bool), ()> { match (first_digit, second_digit) { (Some(first_digit), Some(second_digit)) => { let first_digit = hex_digit_to_decimal(first_digit)?; let second_digit = hex_digit_to_decimal(second_digit)?; let hex_value = first_digit.0 * 16 + second_digit.0; let uppercase = first_digit.1 && second_digit.1; Ok((hex_value, uppercase)) } _ => Err(()), } } fn hex_digit_to_decimal(digit: u8) -> Result<(u8, bool), ()> { match digit { _ if digit >= b'A' && digit <= b'F' => Ok((digit - b'A' + 10, true)), _ if digit >= b'a' && digit <= b'f' => Ok((digit - b'a' + 10, false)), _ if digit.is_ascii_digit() => Ok((digit - b'0', true)), _ => Err(()), } } /// This function is unsafe because it makes the assumption that the given string is valid ASCII-US. pub unsafe fn normalize_string(string: &mut String, case_sensitive: bool) { let bytes = string.as_mut_vec(); let mut read_index = 0; let mut write_index = 0; while read_index < bytes.len() { let byte = bytes[read_index]; read_index += 1; if byte == b'%' { let first_digit = bytes.get(read_index).cloned(); let second_digit = bytes.get(read_index + 1).cloned(); let (hex_value, _) = get_percent_encoded_value(first_digit, second_digit).unwrap(); read_index += 2; if UNRESERVED_CHAR_MAP[hex_value as usize] != 0 { bytes[write_index] = hex_value; write_index += 1; } else { bytes[write_index] = b'%'; bytes[write_index + 1] = first_digit.unwrap().to_ascii_uppercase(); bytes[write_index + 2] = second_digit.unwrap().to_ascii_uppercase(); write_index += 3; } } else { if !case_sensitive { bytes[write_index] = byte.to_ascii_lowercase(); } else { bytes[write_index] = byte; } write_index += 1; } } bytes.truncate(write_index); } pub fn percent_encoded_hash(value: &[u8], state: &mut H, case_sensitive: bool) where H: Hasher, { let mut bytes = value.iter(); let mut length = 0; while let Some(byte) = bytes.next() { length += 1; match byte { b'%' => { let first_digit = bytes.next().cloned(); let second_digit = bytes.next().cloned(); let (hex_value, _) = get_percent_encoded_value(first_digit, second_digit).unwrap(); if UNRESERVED_CHAR_MAP[hex_value as usize] == 0 { b'%'.hash(state); first_digit.unwrap().to_ascii_uppercase().hash(state); second_digit.unwrap().to_ascii_uppercase().hash(state); } else if case_sensitive { hex_value.hash(state); } else { hex_value.to_ascii_lowercase().hash(state); } } _ => { if case_sensitive { byte.hash(state) } else { byte.to_ascii_lowercase().hash(state) } } } } length.hash(state); } fn percent_encoded_equality_helper( byte: u8, first_digit: Option, second_digit: Option, case_sensitive: bool, ) -> bool { if UNRESERVED_CHAR_MAP[byte as usize] == 0 { return false; } match get_percent_encoded_value(first_digit, second_digit) { Ok((hex_value, _)) => { if case_sensitive { hex_value == byte } else { hex_value.eq_ignore_ascii_case(&byte) } } Err(_) => false, } } pub fn percent_encoded_equality(left: &[u8], right: &[u8], case_sensitive: bool) -> bool { let mut left_bytes = left.iter(); let mut right_bytes = right.iter(); loop { match (left_bytes.next(), right_bytes.next()) { (Some(b'%'), Some(b'%')) => (), (Some(b'%'), Some(&right_byte)) => { if !percent_encoded_equality_helper( right_byte, left_bytes.next().cloned(), left_bytes.next().cloned(), case_sensitive, ) { return false; } } (Some(&left_byte), Some(b'%')) => { if !percent_encoded_equality_helper( left_byte, right_bytes.next().cloned(), right_bytes.next().cloned(), case_sensitive, ) { return false; } } (Some(left_byte), Some(right_byte)) => { let equal = if case_sensitive { left_byte == right_byte } else { left_byte.eq_ignore_ascii_case(&right_byte) }; if !equal { return false; } } (None, None) => return true, _ => return false, } } } #[cfg(test)] mod test { use std::collections::hash_map::RandomState; use std::hash::BuildHasher; use super::*; #[test] fn test_equality() { // Case sensitive assert!(percent_encoded_equality(b"abc", b"abc", true)); assert!(percent_encoded_equality(b"abc", b"%61bc", true)); assert!(percent_encoded_equality(b"MNO", b"%4DNO", true)); assert!(percent_encoded_equality(b"MNO", b"%4dNO", true)); assert!(!percent_encoded_equality(b"abc", b"xyz", true)); assert!(!percent_encoded_equality(b"abc", b"Abc", true)); assert!(!percent_encoded_equality(b"abc", b"%41bc", true)); assert!(!percent_encoded_equality(b"/", b"%2F", true)); // Case insensitive assert!(percent_encoded_equality(b"abc", b"abc", false)); assert!(percent_encoded_equality(b"abc", b"ABC", false)); assert!(percent_encoded_equality(b"MNO", b"%4DNO", false)); assert!(percent_encoded_equality(b"MNO", b"%4dNO", false)); assert!(percent_encoded_equality(b"abc", b"%61bc", false)); assert!(percent_encoded_equality(b"abc", b"%41bc", false)); assert!(!percent_encoded_equality(b"abc", b"xyz", false)); assert!(!percent_encoded_equality(b"/", b"%2F", false)); } #[test] fn test_hash() { fn hash(value: &[u8], state: &State, case_sensitive: bool) -> u64 where State: BuildHasher, { let mut hasher = state.build_hasher(); percent_encoded_hash(value, &mut hasher, case_sensitive); hasher.finish() } fn compare_hashes( left: &[u8], right: &[u8], state: &State, case_sensitive: bool, ) -> bool where State: BuildHasher, { let left_hash = hash(left, state, case_sensitive); let right_hash = hash(right, state, case_sensitive); left_hash == right_hash } let state = RandomState::new(); // Case sensitive assert!(compare_hashes(b"abc", b"abc", &state, true)); assert!(compare_hashes(b"abc", b"%61bc", &state, true)); assert!(compare_hashes(b"MNO", b"%4DNO", &state, true)); assert!(compare_hashes(b"MNO", b"%4dNO", &state, true)); assert!(!compare_hashes(b"abc", b"xyz", &state, true)); assert!(!compare_hashes(b"abc", b"Abc", &state, true)); assert!(!compare_hashes(b"abc", b"%41bc", &state, true)); assert!(!compare_hashes(b"/", b"%2F", &state, true)); // Case insensitive assert!(compare_hashes(b"abc", b"abc", &state, false)); assert!(compare_hashes(b"abc", b"ABC", &state, false)); assert!(compare_hashes(b"MNO", b"%4DNO", &state, false)); assert!(compare_hashes(b"MNO", b"%4dNO", &state, false)); assert!(compare_hashes(b"abc", b"%61bc", &state, false)); assert!(compare_hashes(b"abc", b"%41bc", &state, false)); assert!(!compare_hashes(b"abc", b"xyz", &state, false)); assert!(!compare_hashes(b"/", b"%2F", &state, false)); } }