2017-02-01 11:12:24 +00:00
|
|
|
use super::Route;
|
|
|
|
|
Overhaul URI types.
This is fairly large commit with several entangled logical changes.
The primary change in this commit is to completely overhaul how URI
handling in Rocket works. Prior to this commit, the `Uri` type acted as
an origin API. Its parser was minimal and lenient, allowing URIs that
were invalid according to RFC 7230. By contrast, the new `Uri` type
brings with it a strict RFC 7230 compliant parser. The `Uri` type now
represents any kind of valid URI, not simply `Origin` types. Three new
URI types were introduced:
* `Origin` - represents valid origin URIs
* `Absolute` - represents valid absolute URIs
* `Authority` - represents valid authority URIs
The `Origin` type replaces `Uri` in many cases:
* As fields and method inputs of `Route`
* The `&Uri` request guard is now `&Origin`
* The `uri!` macro produces an `Origin` instead of a `Uri`
The strict nature of URI parsing cascaded into the following changes:
* Several `Route` methods now `panic!` on invalid URIs
* The `Rocket::mount()` method is (correctly) stricter with URIs
* The `Redirect` constructors take a `TryInto<Uri>` type
* Dispatching of a `LocalRequest` correctly validates URIs
Overall, URIs are now properly and uniformly handled throughout Rocket's
codebase, resulting in a more reliable and correct system.
In addition to these URI changes, the following changes are also part of
this commit:
* The `LocalRequest::cloned_dispatch()` method was removed in favor of
chaining `.clone().dispatch()`.
* The entire Rocket codebase uses `crate` instead of `pub(crate)` as a
visibility modifier.
* Rocket uses the `crate_visibility_modifier` and `try_from` features.
A note on unsafety: this commit introduces many uses of `unsafe` in the
URI parser. All of these uses are a result of unsafely transforming byte
slices (`&[u8]` or similar) into strings (`&str`). The parser ensures
that these casts are safe, but of course, we must label their use
`unsafe`. The parser was written to be as generic and efficient as
possible and thus can parse directly from byte sources. Rocket, however,
does not make use of this fact and so would be able to remove all uses
of `unsafe` by parsing from an existing `&str`. This should be
considered in the future.
Fixes #443.
Resolves #263.
2018-07-29 01:26:15 +00:00
|
|
|
use http::uri::Origin;
|
2017-03-28 07:12:59 +00:00
|
|
|
use http::MediaType;
|
2017-02-01 11:12:24 +00:00
|
|
|
use request::Request;
|
|
|
|
|
2018-09-25 02:44:25 +00:00
|
|
|
impl Route {
|
|
|
|
/// Determines if two routes can match against some request. That is, if two
|
|
|
|
/// routes `collide`, there exists a request that can match against both
|
|
|
|
/// routes.
|
|
|
|
///
|
|
|
|
/// This implementation is used at initialization to check if two user
|
|
|
|
/// routes collide before launching. Format collisions works like this:
|
|
|
|
///
|
|
|
|
/// * If route specifies a format, it only gets requests for that format.
|
|
|
|
/// * If route doesn't specify a format, it gets requests for any format.
|
|
|
|
///
|
|
|
|
/// Query collisions work like this:
|
|
|
|
///
|
|
|
|
/// * If routes specify a query, they only gets request that have queries.
|
|
|
|
/// * If routes don't specify a query, requests with queries also match.
|
|
|
|
///
|
|
|
|
/// As a result, as long as everything else collides, whether a route has a
|
|
|
|
/// query or not is irrelevant: it will collide.
|
|
|
|
pub fn collides_with(&self, other: &Route) -> bool {
|
|
|
|
self.method == other.method
|
|
|
|
&& self.rank == other.rank
|
|
|
|
&& paths_collide(&self.uri, &other.uri)
|
|
|
|
&& match (self.format.as_ref(), other.format.as_ref()) {
|
|
|
|
(Some(a), Some(b)) => media_types_collide(a, b),
|
|
|
|
(Some(_), None) => true,
|
|
|
|
(None, Some(_)) => true,
|
|
|
|
(None, None) => true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Determines if this route matches against the given request. This means
|
|
|
|
/// that:
|
|
|
|
///
|
|
|
|
/// * The route's method matches that of the incoming request.
|
|
|
|
/// * The route's format (if any) matches that of the incoming request.
|
|
|
|
/// - If route specifies format, it only gets requests for that format.
|
|
|
|
/// - If route doesn't specify format, it gets requests for any format.
|
|
|
|
/// * All static components in the route's path match the corresponding
|
|
|
|
/// components in the same position in the incoming request.
|
|
|
|
/// * If the route specifies a query, the request must have a query as
|
|
|
|
/// well. If the route doesn't specify a query, requests with and
|
|
|
|
/// without queries match.
|
|
|
|
///
|
|
|
|
/// In the future, query handling will work as follows:
|
|
|
|
///
|
|
|
|
/// * All static components in the route's query string are also in the
|
|
|
|
/// request query string, though in any position, and there exists a
|
|
|
|
/// query parameter named exactly like each non-multi dynamic component
|
|
|
|
/// in the route's query that wasn't matched against a static component.
|
|
|
|
/// - If no query in route, requests with/without queries match.
|
|
|
|
pub fn matches(&self, req: &Request) -> bool {
|
|
|
|
self.method == req.method()
|
|
|
|
&& paths_collide(&self.uri, req.uri())
|
|
|
|
&& queries_collide(self, req)
|
|
|
|
&& match self.format {
|
|
|
|
Some(ref a) => match req.format() {
|
|
|
|
Some(ref b) => media_types_collide(a, b),
|
|
|
|
None => false
|
|
|
|
},
|
|
|
|
None => true
|
|
|
|
}
|
|
|
|
}
|
2016-03-18 02:43:58 +00:00
|
|
|
}
|
|
|
|
|
2017-04-24 08:33:00 +00:00
|
|
|
#[inline(always)]
|
2018-02-26 01:17:03 +00:00
|
|
|
fn iters_match_until<A, B>(break_c: u8, mut a: A, mut b: B) -> bool
|
2018-02-25 14:45:42 +00:00
|
|
|
where A: Iterator<Item = u8>, B: Iterator<Item = u8>
|
|
|
|
{
|
|
|
|
loop {
|
|
|
|
match (a.next(), b.next()) {
|
|
|
|
(None, Some(_)) => return false,
|
|
|
|
(Some(_), None) => return false,
|
|
|
|
(None, None) => return true,
|
|
|
|
(Some(c1), Some(c2)) if c1 == break_c || c2 == break_c => return true,
|
|
|
|
(Some(c1), Some(c2)) if c1 != c2 => return false,
|
|
|
|
(Some(_), Some(_)) => continue
|
2016-03-19 02:05:29 +00:00
|
|
|
}
|
|
|
|
}
|
2016-03-22 05:04:39 +00:00
|
|
|
}
|
|
|
|
|
2018-09-25 02:44:25 +00:00
|
|
|
fn segments_collide(first: &str, other: &str) -> bool {
|
|
|
|
let a_iter = first.as_bytes().iter().cloned();
|
|
|
|
let b_iter = other.as_bytes().iter().cloned();
|
|
|
|
iters_match_until(b'<', a_iter.clone(), b_iter.clone())
|
|
|
|
&& iters_match_until(b'>', a_iter.rev(), b_iter.rev())
|
2016-04-01 23:54:53 +00:00
|
|
|
}
|
|
|
|
|
2018-09-25 02:44:25 +00:00
|
|
|
fn paths_collide(first: &Origin, other: &Origin) -> bool {
|
|
|
|
for (seg_a, seg_b) in first.segments().zip(other.segments()) {
|
|
|
|
if seg_a.ends_with("..>") || seg_b.ends_with("..>") {
|
|
|
|
return true;
|
2017-02-01 11:12:24 +00:00
|
|
|
}
|
|
|
|
|
2018-09-25 02:44:25 +00:00
|
|
|
if !segments_collide(seg_a, seg_b) {
|
2017-02-01 11:12:24 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-25 02:44:25 +00:00
|
|
|
if first.segment_count() != other.segment_count() {
|
|
|
|
return false;
|
2017-02-01 11:12:24 +00:00
|
|
|
}
|
2018-09-25 02:44:25 +00:00
|
|
|
|
|
|
|
true
|
2017-02-01 11:12:24 +00:00
|
|
|
}
|
|
|
|
|
2018-09-25 02:44:25 +00:00
|
|
|
fn queries_collide(route: &Route, req: &Request) -> bool {
|
|
|
|
route.uri.query().map_or(true, |_| req.uri().query().is_some())
|
2017-02-01 11:12:24 +00:00
|
|
|
}
|
|
|
|
|
2018-09-25 02:44:25 +00:00
|
|
|
fn media_types_collide(first: &MediaType, other: &MediaType) -> bool {
|
|
|
|
let collide = |a, b| a == "*" || b == "*" || a == b;
|
|
|
|
collide(first.top(), other.top()) && collide(first.sub(), other.sub())
|
2017-02-01 11:12:24 +00:00
|
|
|
}
|
|
|
|
|
2016-03-19 02:05:29 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2016-08-26 08:55:11 +00:00
|
|
|
use std::str::FromStr;
|
2016-10-04 00:09:13 +00:00
|
|
|
|
2018-09-25 02:44:25 +00:00
|
|
|
use super::*;
|
2017-06-06 20:41:04 +00:00
|
|
|
use rocket::Rocket;
|
|
|
|
use config::Config;
|
2016-10-25 11:24:07 +00:00
|
|
|
use request::Request;
|
2017-09-28 21:22:03 +00:00
|
|
|
use router::{dummy_handler, route::Route};
|
2017-03-29 11:08:53 +00:00
|
|
|
use http::{Method, MediaType, ContentType, Accept};
|
Overhaul URI types.
This is fairly large commit with several entangled logical changes.
The primary change in this commit is to completely overhaul how URI
handling in Rocket works. Prior to this commit, the `Uri` type acted as
an origin API. Its parser was minimal and lenient, allowing URIs that
were invalid according to RFC 7230. By contrast, the new `Uri` type
brings with it a strict RFC 7230 compliant parser. The `Uri` type now
represents any kind of valid URI, not simply `Origin` types. Three new
URI types were introduced:
* `Origin` - represents valid origin URIs
* `Absolute` - represents valid absolute URIs
* `Authority` - represents valid authority URIs
The `Origin` type replaces `Uri` in many cases:
* As fields and method inputs of `Route`
* The `&Uri` request guard is now `&Origin`
* The `uri!` macro produces an `Origin` instead of a `Uri`
The strict nature of URI parsing cascaded into the following changes:
* Several `Route` methods now `panic!` on invalid URIs
* The `Rocket::mount()` method is (correctly) stricter with URIs
* The `Redirect` constructors take a `TryInto<Uri>` type
* Dispatching of a `LocalRequest` correctly validates URIs
Overall, URIs are now properly and uniformly handled throughout Rocket's
codebase, resulting in a more reliable and correct system.
In addition to these URI changes, the following changes are also part of
this commit:
* The `LocalRequest::cloned_dispatch()` method was removed in favor of
chaining `.clone().dispatch()`.
* The entire Rocket codebase uses `crate` instead of `pub(crate)` as a
visibility modifier.
* Rocket uses the `crate_visibility_modifier` and `try_from` features.
A note on unsafety: this commit introduces many uses of `unsafe` in the
URI parser. All of these uses are a result of unsafely transforming byte
slices (`&[u8]` or similar) into strings (`&str`). The parser ensures
that these casts are safe, but of course, we must label their use
`unsafe`. The parser was written to be as generic and efficient as
possible and thus can parse directly from byte sources. Rocket, however,
does not make use of this fact and so would be able to remove all uses
of `unsafe` by parsing from an existing `&str`. This should be
considered in the future.
Fixes #443.
Resolves #263.
2018-07-29 01:26:15 +00:00
|
|
|
use http::uri::Origin;
|
2016-10-04 00:09:13 +00:00
|
|
|
use http::Method::*;
|
2016-03-19 02:05:29 +00:00
|
|
|
|
2016-03-21 04:27:17 +00:00
|
|
|
type SimpleRoute = (Method, &'static str);
|
|
|
|
|
|
|
|
fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool {
|
2016-04-02 07:51:40 +00:00
|
|
|
let route_a = Route::new(a.0, a.1.to_string(), dummy_handler);
|
|
|
|
route_a.collides_with(&Route::new(b.0, b.1.to_string(), dummy_handler))
|
2016-03-19 02:05:29 +00:00
|
|
|
}
|
|
|
|
|
2016-04-02 08:46:41 +00:00
|
|
|
fn unranked_collide(a: &'static str, b: &'static str) -> bool {
|
|
|
|
let route_a = Route::ranked(0, Get, a.to_string(), dummy_handler);
|
Overhaul URI types.
This is fairly large commit with several entangled logical changes.
The primary change in this commit is to completely overhaul how URI
handling in Rocket works. Prior to this commit, the `Uri` type acted as
an origin API. Its parser was minimal and lenient, allowing URIs that
were invalid according to RFC 7230. By contrast, the new `Uri` type
brings with it a strict RFC 7230 compliant parser. The `Uri` type now
represents any kind of valid URI, not simply `Origin` types. Three new
URI types were introduced:
* `Origin` - represents valid origin URIs
* `Absolute` - represents valid absolute URIs
* `Authority` - represents valid authority URIs
The `Origin` type replaces `Uri` in many cases:
* As fields and method inputs of `Route`
* The `&Uri` request guard is now `&Origin`
* The `uri!` macro produces an `Origin` instead of a `Uri`
The strict nature of URI parsing cascaded into the following changes:
* Several `Route` methods now `panic!` on invalid URIs
* The `Rocket::mount()` method is (correctly) stricter with URIs
* The `Redirect` constructors take a `TryInto<Uri>` type
* Dispatching of a `LocalRequest` correctly validates URIs
Overall, URIs are now properly and uniformly handled throughout Rocket's
codebase, resulting in a more reliable and correct system.
In addition to these URI changes, the following changes are also part of
this commit:
* The `LocalRequest::cloned_dispatch()` method was removed in favor of
chaining `.clone().dispatch()`.
* The entire Rocket codebase uses `crate` instead of `pub(crate)` as a
visibility modifier.
* Rocket uses the `crate_visibility_modifier` and `try_from` features.
A note on unsafety: this commit introduces many uses of `unsafe` in the
URI parser. All of these uses are a result of unsafely transforming byte
slices (`&[u8]` or similar) into strings (`&str`). The parser ensures
that these casts are safe, but of course, we must label their use
`unsafe`. The parser was written to be as generic and efficient as
possible and thus can parse directly from byte sources. Rocket, however,
does not make use of this fact and so would be able to remove all uses
of `unsafe` by parsing from an existing `&str`. This should be
considered in the future.
Fixes #443.
Resolves #263.
2018-07-29 01:26:15 +00:00
|
|
|
let route_b = Route::ranked(0, Get, b.to_string(), dummy_handler);
|
|
|
|
eprintln!("Checking {} against {}.", route_a, route_b);
|
|
|
|
route_a.collides_with(&route_b)
|
2016-03-19 02:05:29 +00:00
|
|
|
}
|
|
|
|
|
2016-04-01 23:54:53 +00:00
|
|
|
fn s_s_collide(a: &'static str, b: &'static str) -> bool {
|
2018-09-25 02:44:25 +00:00
|
|
|
let a = Origin::parse_route(a).unwrap();
|
|
|
|
let b = Origin::parse_route(b).unwrap();
|
|
|
|
paths_collide(&a, &b)
|
2016-04-01 23:54:53 +00:00
|
|
|
}
|
|
|
|
|
2016-03-19 02:05:29 +00:00
|
|
|
#[test]
|
|
|
|
fn simple_collisions() {
|
2016-04-02 08:46:41 +00:00
|
|
|
assert!(unranked_collide("/a", "/a"));
|
|
|
|
assert!(unranked_collide("/hello", "/hello"));
|
|
|
|
assert!(unranked_collide("/hello", "/hello/"));
|
|
|
|
assert!(unranked_collide("/hello/there/how/ar", "/hello/there/how/ar"));
|
|
|
|
assert!(unranked_collide("/hello/there", "/hello/there/"));
|
2016-03-19 02:05:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn simple_param_collisions() {
|
2016-04-02 08:46:41 +00:00
|
|
|
assert!(unranked_collide("/hello/<name>", "/hello/<person>"));
|
|
|
|
assert!(unranked_collide("/hello/<name>/hi", "/hello/<person>/hi"));
|
|
|
|
assert!(unranked_collide("/hello/<name>/hi/there", "/hello/<person>/hi/there"));
|
|
|
|
assert!(unranked_collide("/<name>/hi/there", "/<person>/hi/there"));
|
|
|
|
assert!(unranked_collide("/<name>/hi/there", "/dude/<name>/there"));
|
|
|
|
assert!(unranked_collide("/<name>/<a>/<b>", "/<a>/<b>/<c>"));
|
|
|
|
assert!(unranked_collide("/<name>/<a>/<b>/", "/<a>/<b>/<c>/"));
|
2016-09-08 07:02:17 +00:00
|
|
|
assert!(unranked_collide("/<a..>", "/hi"));
|
|
|
|
assert!(unranked_collide("/<a..>", "/hi/hey"));
|
|
|
|
assert!(unranked_collide("/<a..>", "/hi/hey/hayo"));
|
|
|
|
assert!(unranked_collide("/a/<a..>", "/a/hi/hey/hayo"));
|
|
|
|
assert!(unranked_collide("/a/<b>/<a..>", "/a/hi/hey/hayo"));
|
|
|
|
assert!(unranked_collide("/a/<b>/<c>/<a..>", "/a/hi/hey/hayo"));
|
|
|
|
assert!(unranked_collide("/<b>/<c>/<a..>", "/a/hi/hey/hayo"));
|
|
|
|
assert!(unranked_collide("/<b>/<c>/hey/hayo", "/a/hi/hey/hayo"));
|
2016-03-19 02:05:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn medium_param_collisions() {
|
2016-04-02 08:46:41 +00:00
|
|
|
assert!(unranked_collide("/hello/<name>", "/hello/bob"));
|
|
|
|
assert!(unranked_collide("/<name>", "//bob"));
|
2016-03-19 02:05:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn hard_param_collisions() {
|
2016-04-02 08:46:41 +00:00
|
|
|
assert!(unranked_collide("/<name>bob", "/<name>b"));
|
|
|
|
assert!(unranked_collide("/a<b>c", "/abc"));
|
|
|
|
assert!(unranked_collide("/a<b>c", "/azooc"));
|
2018-02-25 14:45:42 +00:00
|
|
|
assert!(unranked_collide("/a<b>", "/ab"));
|
2016-04-02 08:46:41 +00:00
|
|
|
assert!(unranked_collide("/<b>", "/a"));
|
|
|
|
assert!(unranked_collide("/<a>/<b>", "/a/b<c>"));
|
|
|
|
assert!(unranked_collide("/<a>/bc<b>", "/a/b<c>"));
|
|
|
|
assert!(unranked_collide("/<a>/bc<b>d", "/a/b<c>"));
|
2016-09-08 07:02:17 +00:00
|
|
|
assert!(unranked_collide("/<a..>", "///a///"));
|
2016-03-19 02:05:29 +00:00
|
|
|
}
|
|
|
|
|
2017-02-05 09:43:53 +00:00
|
|
|
#[test]
|
|
|
|
fn query_collisions() {
|
|
|
|
assert!(unranked_collide("/?<a>", "/?<a>"));
|
|
|
|
assert!(unranked_collide("/a/?<a>", "/a/?<a>"));
|
|
|
|
assert!(unranked_collide("/a?<a>", "/a?<a>"));
|
|
|
|
assert!(unranked_collide("/<r>?<a>", "/<r>?<a>"));
|
|
|
|
assert!(unranked_collide("/a/b/c?<a>", "/a/b/c?<a>"));
|
|
|
|
assert!(unranked_collide("/<a>/b/c?<d>", "/a/b/<c>?<d>"));
|
2017-03-27 10:52:26 +00:00
|
|
|
assert!(unranked_collide("/?<a>", "/"));
|
|
|
|
assert!(unranked_collide("/a?<a>", "/a"));
|
|
|
|
assert!(unranked_collide("/a?<a>", "/a"));
|
|
|
|
assert!(unranked_collide("/a/b?<a>", "/a/b"));
|
|
|
|
assert!(unranked_collide("/a/b", "/a/b?<c>"));
|
2017-02-05 09:43:53 +00:00
|
|
|
}
|
|
|
|
|
2016-03-19 02:05:29 +00:00
|
|
|
#[test]
|
|
|
|
fn non_collisions() {
|
2018-02-25 14:45:42 +00:00
|
|
|
assert!(!unranked_collide("/<a>", "/"));
|
2016-04-02 08:46:41 +00:00
|
|
|
assert!(!unranked_collide("/a", "/b"));
|
|
|
|
assert!(!unranked_collide("/a/b", "/a"));
|
|
|
|
assert!(!unranked_collide("/a/b", "/a/c"));
|
|
|
|
assert!(!unranked_collide("/a/hello", "/a/c"));
|
|
|
|
assert!(!unranked_collide("/hello", "/a/c"));
|
|
|
|
assert!(!unranked_collide("/hello/there", "/hello/there/guy"));
|
|
|
|
assert!(!unranked_collide("/b<a>/there", "/hi/there"));
|
|
|
|
assert!(!unranked_collide("/<a>/<b>c", "/hi/person"));
|
|
|
|
assert!(!unranked_collide("/<a>/<b>cd", "/hi/<a>e"));
|
|
|
|
assert!(!unranked_collide("/a<a>/<b>", "/b<b>/<a>"));
|
|
|
|
assert!(!unranked_collide("/a/<b>", "/b/<b>"));
|
|
|
|
assert!(!unranked_collide("/a<a>/<b>", "/b/<b>"));
|
2016-09-08 07:02:17 +00:00
|
|
|
assert!(!unranked_collide("/<a..>", "/"));
|
|
|
|
assert!(!unranked_collide("/hi/<a..>", "/hi"));
|
|
|
|
assert!(!unranked_collide("/hi/<a..>", "/hi/"));
|
|
|
|
assert!(!unranked_collide("/<a..>", "//////"));
|
2018-02-25 14:45:42 +00:00
|
|
|
assert!(!unranked_collide("/t", "/test"));
|
|
|
|
assert!(!unranked_collide("/a", "/aa"));
|
|
|
|
assert!(!unranked_collide("/a", "/aaa"));
|
|
|
|
assert!(!unranked_collide("/", "/a"));
|
2016-03-19 02:05:29 +00:00
|
|
|
}
|
|
|
|
|
2017-02-05 09:43:53 +00:00
|
|
|
#[test]
|
|
|
|
fn query_non_collisions() {
|
2017-03-27 10:52:26 +00:00
|
|
|
assert!(!unranked_collide("/a?<b>", "/b"));
|
|
|
|
assert!(!unranked_collide("/a/b", "/a?<b>"));
|
|
|
|
assert!(!unranked_collide("/a/b/c?<d>", "/a/b/c/d"));
|
|
|
|
assert!(!unranked_collide("/a/hello", "/a/?<hello>"));
|
2017-02-05 09:43:53 +00:00
|
|
|
assert!(!unranked_collide("/?<a>", "/hi"));
|
|
|
|
}
|
|
|
|
|
2016-03-19 02:05:29 +00:00
|
|
|
#[test]
|
|
|
|
fn method_dependent_non_collisions() {
|
|
|
|
assert!(!m_collide((Get, "/"), (Post, "/")));
|
|
|
|
assert!(!m_collide((Post, "/"), (Put, "/")));
|
|
|
|
assert!(!m_collide((Put, "/a"), (Put, "/")));
|
|
|
|
assert!(!m_collide((Post, "/a"), (Put, "/")));
|
|
|
|
assert!(!m_collide((Get, "/a"), (Put, "/")));
|
|
|
|
assert!(!m_collide((Get, "/hello"), (Put, "/hello")));
|
|
|
|
}
|
2016-03-21 04:27:17 +00:00
|
|
|
|
Overhaul URI types.
This is fairly large commit with several entangled logical changes.
The primary change in this commit is to completely overhaul how URI
handling in Rocket works. Prior to this commit, the `Uri` type acted as
an origin API. Its parser was minimal and lenient, allowing URIs that
were invalid according to RFC 7230. By contrast, the new `Uri` type
brings with it a strict RFC 7230 compliant parser. The `Uri` type now
represents any kind of valid URI, not simply `Origin` types. Three new
URI types were introduced:
* `Origin` - represents valid origin URIs
* `Absolute` - represents valid absolute URIs
* `Authority` - represents valid authority URIs
The `Origin` type replaces `Uri` in many cases:
* As fields and method inputs of `Route`
* The `&Uri` request guard is now `&Origin`
* The `uri!` macro produces an `Origin` instead of a `Uri`
The strict nature of URI parsing cascaded into the following changes:
* Several `Route` methods now `panic!` on invalid URIs
* The `Rocket::mount()` method is (correctly) stricter with URIs
* The `Redirect` constructors take a `TryInto<Uri>` type
* Dispatching of a `LocalRequest` correctly validates URIs
Overall, URIs are now properly and uniformly handled throughout Rocket's
codebase, resulting in a more reliable and correct system.
In addition to these URI changes, the following changes are also part of
this commit:
* The `LocalRequest::cloned_dispatch()` method was removed in favor of
chaining `.clone().dispatch()`.
* The entire Rocket codebase uses `crate` instead of `pub(crate)` as a
visibility modifier.
* Rocket uses the `crate_visibility_modifier` and `try_from` features.
A note on unsafety: this commit introduces many uses of `unsafe` in the
URI parser. All of these uses are a result of unsafely transforming byte
slices (`&[u8]` or similar) into strings (`&str`). The parser ensures
that these casts are safe, but of course, we must label their use
`unsafe`. The parser was written to be as generic and efficient as
possible and thus can parse directly from byte sources. Rocket, however,
does not make use of this fact and so would be able to remove all uses
of `unsafe` by parsing from an existing `&str`. This should be
considered in the future.
Fixes #443.
Resolves #263.
2018-07-29 01:26:15 +00:00
|
|
|
#[test]
|
|
|
|
fn query_dependent_non_collisions() {
|
|
|
|
assert!(!m_collide((Get, "/"), (Get, "/?a")));
|
|
|
|
assert!(!m_collide((Get, "/"), (Get, "/?<a>")));
|
|
|
|
assert!(!m_collide((Get, "/a/<b>"), (Get, "/a/<b>?d")));
|
|
|
|
}
|
|
|
|
|
2016-03-21 04:27:17 +00:00
|
|
|
#[test]
|
|
|
|
fn test_str_non_collisions() {
|
2016-04-01 23:54:53 +00:00
|
|
|
assert!(!s_s_collide("/a", "/b"));
|
|
|
|
assert!(!s_s_collide("/a/b", "/a"));
|
|
|
|
assert!(!s_s_collide("/a/b", "/a/c"));
|
|
|
|
assert!(!s_s_collide("/a/hello", "/a/c"));
|
|
|
|
assert!(!s_s_collide("/hello", "/a/c"));
|
|
|
|
assert!(!s_s_collide("/hello/there", "/hello/there/guy"));
|
|
|
|
assert!(!s_s_collide("/b<a>/there", "/hi/there"));
|
|
|
|
assert!(!s_s_collide("/<a>/<b>c", "/hi/person"));
|
|
|
|
assert!(!s_s_collide("/<a>/<b>cd", "/hi/<a>e"));
|
|
|
|
assert!(!s_s_collide("/a<a>/<b>", "/b<b>/<a>"));
|
|
|
|
assert!(!s_s_collide("/a/<b>", "/b/<b>"));
|
|
|
|
assert!(!s_s_collide("/a<a>/<b>", "/b/<b>"));
|
2016-09-08 07:02:17 +00:00
|
|
|
assert!(!s_s_collide("/a", "/b"));
|
|
|
|
assert!(!s_s_collide("/a/b", "/a"));
|
|
|
|
assert!(!s_s_collide("/a/b", "/a/c"));
|
|
|
|
assert!(!s_s_collide("/a/hello", "/a/c"));
|
|
|
|
assert!(!s_s_collide("/hello", "/a/c"));
|
|
|
|
assert!(!s_s_collide("/hello/there", "/hello/there/guy"));
|
|
|
|
assert!(!s_s_collide("/b<a>/there", "/hi/there"));
|
|
|
|
assert!(!s_s_collide("/<a>/<b>c", "/hi/person"));
|
|
|
|
assert!(!s_s_collide("/<a>/<b>cd", "/hi/<a>e"));
|
|
|
|
assert!(!s_s_collide("/a<a>/<b>", "/b<b>/<a>"));
|
|
|
|
assert!(!s_s_collide("/a/<b>", "/b/<b>"));
|
|
|
|
assert!(!s_s_collide("/a<a>/<b>", "/b/<b>"));
|
|
|
|
assert!(!s_s_collide("/a", "/b"));
|
|
|
|
assert!(!s_s_collide("/a/b", "/a"));
|
|
|
|
assert!(!s_s_collide("/a/b", "/a/c"));
|
|
|
|
assert!(!s_s_collide("/a/hello", "/a/c"));
|
|
|
|
assert!(!s_s_collide("/hello", "/a/c"));
|
|
|
|
assert!(!s_s_collide("/hello/there", "/hello/there/guy"));
|
|
|
|
assert!(!s_s_collide("/b<a>/there", "/hi/there"));
|
|
|
|
assert!(!s_s_collide("/<a>/<b>c", "/hi/person"));
|
|
|
|
assert!(!s_s_collide("/<a>/<b>cd", "/hi/<a>e"));
|
|
|
|
assert!(!s_s_collide("/a<a>/<b>", "/b<b>/<a>"));
|
|
|
|
assert!(!s_s_collide("/a/<b>", "/b/<b>"));
|
|
|
|
assert!(!s_s_collide("/a<a>/<b>", "/b/<b>"));
|
|
|
|
assert!(!s_s_collide("/<a..>", "/"));
|
|
|
|
assert!(!s_s_collide("/hi/<a..>", "/hi/"));
|
|
|
|
assert!(!s_s_collide("/a/hi/<a..>", "/a/hi/"));
|
2018-02-25 14:45:42 +00:00
|
|
|
assert!(!s_s_collide("/t", "/test"));
|
|
|
|
assert!(!s_s_collide("/a", "/aa"));
|
|
|
|
assert!(!s_s_collide("/a", "/aaa"));
|
|
|
|
assert!(!s_s_collide("/", "/a"));
|
2016-03-21 04:27:17 +00:00
|
|
|
}
|
2016-08-26 08:55:11 +00:00
|
|
|
|
2017-03-28 07:12:59 +00:00
|
|
|
fn mt_mt_collide(mt1: &str, mt2: &str) -> bool {
|
|
|
|
let mt_a = MediaType::from_str(mt1).expect(mt1);
|
|
|
|
let mt_b = MediaType::from_str(mt2).expect(mt2);
|
2018-09-25 02:44:25 +00:00
|
|
|
media_types_collide(&mt_a, &mt_b)
|
2016-08-26 08:55:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_content_type_colliions() {
|
2017-03-28 07:12:59 +00:00
|
|
|
assert!(mt_mt_collide("application/json", "application/json"));
|
|
|
|
assert!(mt_mt_collide("*/json", "application/json"));
|
|
|
|
assert!(mt_mt_collide("*/*", "application/json"));
|
|
|
|
assert!(mt_mt_collide("application/*", "application/json"));
|
|
|
|
assert!(mt_mt_collide("application/*", "*/json"));
|
|
|
|
assert!(mt_mt_collide("something/random", "something/random"));
|
|
|
|
|
|
|
|
assert!(!mt_mt_collide("text/*", "application/*"));
|
|
|
|
assert!(!mt_mt_collide("*/text", "*/json"));
|
|
|
|
assert!(!mt_mt_collide("*/text", "application/test"));
|
|
|
|
assert!(!mt_mt_collide("something/random", "something_else/random"));
|
|
|
|
assert!(!mt_mt_collide("something/random", "*/else"));
|
|
|
|
assert!(!mt_mt_collide("*/random", "*/else"));
|
|
|
|
assert!(!mt_mt_collide("something/*", "random/else"));
|
2016-08-26 08:55:11 +00:00
|
|
|
}
|
|
|
|
|
2017-03-28 07:12:59 +00:00
|
|
|
fn r_mt_mt_collide<S1, S2>(m1: Method, mt1: S1, m2: Method, mt2: S2) -> bool
|
2017-02-01 11:12:24 +00:00
|
|
|
where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>>
|
|
|
|
{
|
|
|
|
let mut route_a = Route::new(m1, "/", dummy_handler);
|
2017-03-28 07:12:59 +00:00
|
|
|
if let Some(mt_str) = mt1.into() {
|
|
|
|
route_a.format = Some(mt_str.parse::<MediaType>().unwrap());
|
2017-02-01 11:12:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let mut route_b = Route::new(m2, "/", dummy_handler);
|
2017-03-28 07:12:59 +00:00
|
|
|
if let Some(mt_str) = mt2.into() {
|
|
|
|
route_b.format = Some(mt_str.parse::<MediaType>().unwrap());
|
2017-02-01 11:12:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
route_a.collides_with(&route_b)
|
2016-08-26 08:55:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_route_content_type_colliions() {
|
2017-03-28 07:12:59 +00:00
|
|
|
assert!(r_mt_mt_collide(Get, "application/json", Get, "application/json"));
|
|
|
|
assert!(r_mt_mt_collide(Get, "*/json", Get, "application/json"));
|
|
|
|
assert!(r_mt_mt_collide(Get, "*/json", Get, "application/*"));
|
|
|
|
assert!(r_mt_mt_collide(Get, "text/html", Get, "text/*"));
|
|
|
|
assert!(r_mt_mt_collide(Get, "any/thing", Get, "*/*"));
|
|
|
|
|
|
|
|
assert!(r_mt_mt_collide(Get, None, Get, "text/*"));
|
|
|
|
assert!(r_mt_mt_collide(Get, None, Get, "text/html"));
|
|
|
|
assert!(r_mt_mt_collide(Get, None, Get, "*/*"));
|
|
|
|
assert!(r_mt_mt_collide(Get, "text/html", Get, None));
|
|
|
|
assert!(r_mt_mt_collide(Get, "*/*", Get, None));
|
|
|
|
assert!(r_mt_mt_collide(Get, "application/json", Get, None));
|
|
|
|
|
|
|
|
assert!(!r_mt_mt_collide(Get, "text/html", Get, "application/*"));
|
|
|
|
assert!(!r_mt_mt_collide(Get, "application/html", Get, "text/*"));
|
|
|
|
assert!(!r_mt_mt_collide(Get, "*/json", Get, "text/html"));
|
|
|
|
assert!(!r_mt_mt_collide(Get, "text/html", Get, "text/css"));
|
2017-02-01 11:12:24 +00:00
|
|
|
}
|
|
|
|
|
2017-03-29 11:08:53 +00:00
|
|
|
fn req_route_mt_collide<S1, S2>(m: Method, mt1: S1, mt2: S2) -> bool
|
2017-02-01 11:12:24 +00:00
|
|
|
where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>>
|
|
|
|
{
|
2018-07-03 20:47:17 +00:00
|
|
|
let rocket = Rocket::custom(Config::development().unwrap());
|
Overhaul URI types.
This is fairly large commit with several entangled logical changes.
The primary change in this commit is to completely overhaul how URI
handling in Rocket works. Prior to this commit, the `Uri` type acted as
an origin API. Its parser was minimal and lenient, allowing URIs that
were invalid according to RFC 7230. By contrast, the new `Uri` type
brings with it a strict RFC 7230 compliant parser. The `Uri` type now
represents any kind of valid URI, not simply `Origin` types. Three new
URI types were introduced:
* `Origin` - represents valid origin URIs
* `Absolute` - represents valid absolute URIs
* `Authority` - represents valid authority URIs
The `Origin` type replaces `Uri` in many cases:
* As fields and method inputs of `Route`
* The `&Uri` request guard is now `&Origin`
* The `uri!` macro produces an `Origin` instead of a `Uri`
The strict nature of URI parsing cascaded into the following changes:
* Several `Route` methods now `panic!` on invalid URIs
* The `Rocket::mount()` method is (correctly) stricter with URIs
* The `Redirect` constructors take a `TryInto<Uri>` type
* Dispatching of a `LocalRequest` correctly validates URIs
Overall, URIs are now properly and uniformly handled throughout Rocket's
codebase, resulting in a more reliable and correct system.
In addition to these URI changes, the following changes are also part of
this commit:
* The `LocalRequest::cloned_dispatch()` method was removed in favor of
chaining `.clone().dispatch()`.
* The entire Rocket codebase uses `crate` instead of `pub(crate)` as a
visibility modifier.
* Rocket uses the `crate_visibility_modifier` and `try_from` features.
A note on unsafety: this commit introduces many uses of `unsafe` in the
URI parser. All of these uses are a result of unsafely transforming byte
slices (`&[u8]` or similar) into strings (`&str`). The parser ensures
that these casts are safe, but of course, we must label their use
`unsafe`. The parser was written to be as generic and efficient as
possible and thus can parse directly from byte sources. Rocket, however,
does not make use of this fact and so would be able to remove all uses
of `unsafe` by parsing from an existing `&str`. This should be
considered in the future.
Fixes #443.
Resolves #263.
2018-07-29 01:26:15 +00:00
|
|
|
let mut req = Request::new(&rocket, m, Origin::dummy());
|
2017-03-28 07:12:59 +00:00
|
|
|
if let Some(mt_str) = mt1.into() {
|
2017-03-29 11:08:53 +00:00
|
|
|
if m.supports_payload() {
|
|
|
|
req.replace_header(mt_str.parse::<ContentType>().unwrap());
|
|
|
|
} else {
|
|
|
|
req.replace_header(mt_str.parse::<Accept>().unwrap());
|
|
|
|
}
|
2017-02-01 11:12:24 +00:00
|
|
|
}
|
|
|
|
|
2017-03-29 11:08:53 +00:00
|
|
|
let mut route = Route::new(m, "/", dummy_handler);
|
2017-03-28 07:12:59 +00:00
|
|
|
if let Some(mt_str) = mt2.into() {
|
|
|
|
route.format = Some(mt_str.parse::<MediaType>().unwrap());
|
2017-02-01 11:12:24 +00:00
|
|
|
}
|
|
|
|
|
2018-09-25 02:44:25 +00:00
|
|
|
route.matches(&req)
|
2017-02-01 11:12:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2017-03-28 07:12:59 +00:00
|
|
|
fn test_req_route_mt_collisions() {
|
2017-03-29 11:08:53 +00:00
|
|
|
assert!(req_route_mt_collide(Post, "application/json", "application/json"));
|
|
|
|
assert!(req_route_mt_collide(Post, "application/json", "application/*"));
|
|
|
|
assert!(req_route_mt_collide(Post, "application/json", "*/json"));
|
|
|
|
assert!(req_route_mt_collide(Post, "text/html", "*/*"));
|
|
|
|
|
|
|
|
assert!(req_route_mt_collide(Get, "application/json", "application/json"));
|
|
|
|
assert!(req_route_mt_collide(Get, "text/html", "text/html"));
|
|
|
|
assert!(req_route_mt_collide(Get, "text/html", "*/*"));
|
|
|
|
assert!(req_route_mt_collide(Get, None, "text/html"));
|
|
|
|
assert!(req_route_mt_collide(Get, None, "*/*"));
|
|
|
|
assert!(req_route_mt_collide(Get, None, "application/json"));
|
|
|
|
|
|
|
|
assert!(req_route_mt_collide(Post, "text/html", None));
|
|
|
|
assert!(req_route_mt_collide(Post, "application/json", None));
|
|
|
|
assert!(req_route_mt_collide(Post, "x-custom/anything", None));
|
|
|
|
assert!(req_route_mt_collide(Post, None, None));
|
|
|
|
|
|
|
|
assert!(req_route_mt_collide(Get, "text/html", None));
|
|
|
|
assert!(req_route_mt_collide(Get, "application/json", None));
|
|
|
|
assert!(req_route_mt_collide(Get, "x-custom/anything", None));
|
|
|
|
assert!(req_route_mt_collide(Get, None, None));
|
|
|
|
|
|
|
|
assert!(req_route_mt_collide(Get, "text/html, text/plain", "text/html"));
|
|
|
|
assert!(req_route_mt_collide(Get, "text/html; q=0.5, text/xml", "text/xml"));
|
|
|
|
|
|
|
|
assert!(!req_route_mt_collide(Post, "application/json", "text/html"));
|
|
|
|
assert!(!req_route_mt_collide(Post, "application/json", "text/*"));
|
|
|
|
assert!(!req_route_mt_collide(Post, "application/json", "*/xml"));
|
|
|
|
assert!(!req_route_mt_collide(Get, "application/json", "text/html"));
|
|
|
|
assert!(!req_route_mt_collide(Get, "application/json", "text/*"));
|
|
|
|
assert!(!req_route_mt_collide(Get, "application/json", "*/xml"));
|
|
|
|
|
|
|
|
assert!(!req_route_mt_collide(Post, None, "text/html"));
|
|
|
|
assert!(!req_route_mt_collide(Post, None, "*/*"));
|
|
|
|
assert!(!req_route_mt_collide(Post, None, "application/json"));
|
2017-03-27 10:52:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn req_route_path_collide(a: &'static str, b: &'static str) -> bool {
|
2018-07-03 20:47:17 +00:00
|
|
|
let rocket = Rocket::custom(Config::development().unwrap());
|
Overhaul URI types.
This is fairly large commit with several entangled logical changes.
The primary change in this commit is to completely overhaul how URI
handling in Rocket works. Prior to this commit, the `Uri` type acted as
an origin API. Its parser was minimal and lenient, allowing URIs that
were invalid according to RFC 7230. By contrast, the new `Uri` type
brings with it a strict RFC 7230 compliant parser. The `Uri` type now
represents any kind of valid URI, not simply `Origin` types. Three new
URI types were introduced:
* `Origin` - represents valid origin URIs
* `Absolute` - represents valid absolute URIs
* `Authority` - represents valid authority URIs
The `Origin` type replaces `Uri` in many cases:
* As fields and method inputs of `Route`
* The `&Uri` request guard is now `&Origin`
* The `uri!` macro produces an `Origin` instead of a `Uri`
The strict nature of URI parsing cascaded into the following changes:
* Several `Route` methods now `panic!` on invalid URIs
* The `Rocket::mount()` method is (correctly) stricter with URIs
* The `Redirect` constructors take a `TryInto<Uri>` type
* Dispatching of a `LocalRequest` correctly validates URIs
Overall, URIs are now properly and uniformly handled throughout Rocket's
codebase, resulting in a more reliable and correct system.
In addition to these URI changes, the following changes are also part of
this commit:
* The `LocalRequest::cloned_dispatch()` method was removed in favor of
chaining `.clone().dispatch()`.
* The entire Rocket codebase uses `crate` instead of `pub(crate)` as a
visibility modifier.
* Rocket uses the `crate_visibility_modifier` and `try_from` features.
A note on unsafety: this commit introduces many uses of `unsafe` in the
URI parser. All of these uses are a result of unsafely transforming byte
slices (`&[u8]` or similar) into strings (`&str`). The parser ensures
that these casts are safe, but of course, we must label their use
`unsafe`. The parser was written to be as generic and efficient as
possible and thus can parse directly from byte sources. Rocket, however,
does not make use of this fact and so would be able to remove all uses
of `unsafe` by parsing from an existing `&str`. This should be
considered in the future.
Fixes #443.
Resolves #263.
2018-07-29 01:26:15 +00:00
|
|
|
let req = Request::new(&rocket, Get, Origin::parse(a).expect("valid URI"));
|
2017-03-27 10:52:26 +00:00
|
|
|
let route = Route::ranked(0, Get, b.to_string(), dummy_handler);
|
2018-09-25 02:44:25 +00:00
|
|
|
route.matches(&req)
|
2017-03-27 10:52:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_req_route_query_collisions() {
|
|
|
|
assert!(req_route_path_collide("/a/b?a=b", "/a/b?<c>"));
|
|
|
|
assert!(req_route_path_collide("/a/b?a=b", "/<a>/b?<c>"));
|
|
|
|
assert!(req_route_path_collide("/a/b?a=b", "/<a>/<b>?<c>"));
|
|
|
|
assert!(req_route_path_collide("/a/b?a=b", "/a/<b>?<c>"));
|
|
|
|
assert!(req_route_path_collide("/?b=c", "/?<b>"));
|
|
|
|
|
|
|
|
assert!(req_route_path_collide("/a/b?a=b", "/a/b"));
|
|
|
|
assert!(req_route_path_collide("/a/b", "/a/b"));
|
|
|
|
assert!(req_route_path_collide("/a/b/c/d?", "/a/b/c/d"));
|
|
|
|
assert!(req_route_path_collide("/a/b/c/d?v=1&v=2", "/a/b/c/d"));
|
|
|
|
|
|
|
|
assert!(!req_route_path_collide("/a/b", "/a/b?<c>"));
|
|
|
|
assert!(!req_route_path_collide("/a/b/c", "/a/b?<c>"));
|
|
|
|
assert!(!req_route_path_collide("/a?b=c", "/a/b?<c>"));
|
|
|
|
assert!(!req_route_path_collide("/?b=c", "/a/b?<c>"));
|
|
|
|
assert!(!req_route_path_collide("/?b=c", "/a?<c>"));
|
2016-08-26 08:55:11 +00:00
|
|
|
}
|
2016-03-19 02:05:29 +00:00
|
|
|
}
|