Rocket

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit ca5623c6414bdfa953a4499c0a17f3419de8fc38
parent 104429f3983e9964bd49fa9fe183ab310fd79190
Author: Sergio Benitez <sb@sergio.bz>
Date:   Wed, 12 Dec 2018 17:37:51 -0800

Type encoding to fix encoding issues once and for all.

Fixes #849.

Diffstat:
Mcore/codegen/src/attribute/segments.rs | 30+++++++++++++++++++++---------
Mcore/codegen/src/bang/uri.rs | 6+++---
Mcore/codegen/src/http_codegen.rs | 20++++++--------------
Mcore/codegen/tests/typed-uris.rs | 1+
Mcore/codegen/tests/ui-fail/route-path-bad-syntax.rs | 13++++++-------
Mcore/codegen/tests/ui-fail/route-path-bad-syntax.stderr | 135++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mcore/http/src/lib.rs | 2+-
Mcore/http/src/parse/uri/mod.rs | 5+++--
Mcore/http/src/route.rs | 83++++++++++++++++++++++++++++++++++++++++---------------------------------------
Acore/http/src/uri/encoding.rs | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/http/src/uri/mod.rs | 4++++
Mcore/http/src/uri/uri.rs | 26+++++---------------------
Mcore/lib/src/router/route.rs | 12++++++------
13 files changed, 238 insertions(+), 174 deletions(-)

diff --git a/core/codegen/src/attribute/segments.rs b/core/codegen/src/attribute/segments.rs @@ -3,6 +3,7 @@ use std::hash::{Hash, Hasher}; use devise::syn; use proc_macro::{Span, Diagnostic}; +use http::uri::{UriPart, Path}; use http::route::RouteSegment; use proc_macro_ext::{Diagnostics, StringLit, PResult, DResult}; @@ -18,8 +19,14 @@ crate struct Segment { } impl Segment { - fn from(segment: RouteSegment, span: Span) -> Segment { - let (kind, source, index) = (segment.kind, segment.source, segment.index); + fn from<P: UriPart>(segment: RouteSegment<P>, span: Span) -> Segment { + let source = match P::DELIMITER { + '/' => Source::Path, + '&' => Source::Query, + _ => unreachable!("only paths and queries") + }; + + let (kind, index) = (segment.kind, segment.index); Segment { span, kind, source, index, name: segment.name.into_owned() } } } @@ -95,7 +102,8 @@ fn into_diagnostic( } Error::Uri => { seg_span.error("component contains invalid URI characters") - .note("components cannot contain '%' and '+' characters") + .note("components cannot contain reserved characters") + .help("reserved characters include: '%', '+', '&', etc.") } Error::Trailing(multi) => { let multi_span = subspan(multi, source, span).expect("mutli_span"); @@ -107,21 +115,25 @@ fn into_diagnostic( } } -crate fn parse_segment(segment: &str, span: Span) -> PResult<Segment> { - RouteSegment::parse_one(segment) - .map(|segment| Segment::from(segment, span)) +crate fn parse_data_segment(segment: &str, span: Span) -> PResult<Segment> { + <RouteSegment<Path>>::parse_one(segment) + .map(|segment| { + let mut seg = Segment::from(segment, span); + seg.source = Source::Data; + seg.index = Some(0); + seg + }) .map_err(|e| into_diagnostic(segment, segment, span, &e)) } -crate fn parse_segments( +crate fn parse_segments<P: UriPart>( string: &str, - source: Source, span: Span ) -> DResult<Vec<Segment>> { let mut segments = vec![]; let mut diags = Diagnostics::new(); - for result in RouteSegment::parse_many(string, source) { + for result in <RouteSegment<P>>::parse_many(string) { if let Err((segment_string, error)) = result { diags.push(into_diagnostic(segment_string, string, span, &error)); if let Error::Trailing(..) = error { diff --git a/core/codegen/src/bang/uri.rs b/core/codegen/src/bang/uri.rs @@ -4,7 +4,7 @@ use proc_macro2::TokenStream as TokenStream2; use devise::{syn, Result}; use devise::syn::{Expr, Ident, Type, spanned::Spanned}; -use http::{uri::Origin, ext::IntoOwned}; +use http::{uri::{Origin, Path, Query}, ext::IntoOwned}; use http::route::{RouteSegment, Kind, Source}; use http_codegen::Optional; @@ -132,7 +132,7 @@ fn explode_path<'a, I: Iterator<Item = (&'a Ident, &'a Type, &'a Expr)>>( } let uri_display = quote!(#uri_mod::UriDisplay<#uri_mod::Path>); - let dyn_exprs = RouteSegment::parse_path(uri).map(|segment| { + let dyn_exprs = <RouteSegment<Path>>::parse(uri).map(|segment| { let segment = segment.expect("segment okay; prechecked on parse"); match segment.kind { Kind::Static => { @@ -162,7 +162,7 @@ fn explode_query<'a, I: Iterator<Item = (&'a Ident, &'a Type, &'a ArgExpr)>>( let query_arg = quote!(#uri_mod::UriQueryArgument); let uri_display = quote!(#uri_mod::UriDisplay<#uri_mod::Query>); - let dyn_exprs = RouteSegment::parse_query(uri)?.filter_map(|segment| { + let dyn_exprs = <RouteSegment<Query>>::parse(uri)?.filter_map(|segment| { let segment = segment.expect("segment okay; prechecked on parse"); if segment.kind == Kind::Static { let string = &segment.string; diff --git a/core/codegen/src/http_codegen.rs b/core/codegen/src/http_codegen.rs @@ -2,7 +2,8 @@ use quote::ToTokens; use proc_macro2::TokenStream as TokenStream2; use devise::{FromMeta, MetaItem, Result, ext::Split2}; use http::{self, ext::IntoOwned}; -use attribute::segments::{parse_segments, parse_segment, Segment, Kind, Source}; +use http::uri::{Path, Query}; +use attribute::segments::{parse_segments, parse_data_segment, Segment, Kind}; use proc_macro_ext::StringLit; @@ -188,27 +189,18 @@ impl FromMeta for Origin { } } -impl FromMeta for Segment { +impl FromMeta for DataSegment { fn from_meta(meta: MetaItem) -> Result<Self> { let string = StringLit::from_meta(meta)?; let span = string.subspan(1..(string.len() + 1)) .expect("segment"); - let segment = parse_segment(&string, span)?; + let segment = parse_data_segment(&string, span)?; if segment.kind != Kind::Single { return Err(span.error("malformed parameter") .help("parameter must be of the form '<param>'")); } - Ok(segment) - } -} - -impl FromMeta for DataSegment { - fn from_meta(meta: MetaItem) -> Result<Self> { - let mut segment = Segment::from_meta(meta)?; - segment.source = Source::Data; - segment.index = Some(0); Ok(DataSegment(segment)) } } @@ -217,7 +209,7 @@ impl FromMeta for RoutePath { fn from_meta(meta: MetaItem) -> Result<Self> { let (origin, string) = (Origin::from_meta(meta)?, StringLit::from_meta(meta)?); let path_span = string.subspan(1..origin.0.path().len() + 1).expect("path"); - let path = parse_segments(origin.0.path(), Source::Path, path_span); + let path = parse_segments::<Path>(origin.0.path(), path_span); let query = origin.0.query() .map(|q| { @@ -228,7 +220,7 @@ impl FromMeta for RoutePath { // TODO: Show a help message with what's expected. Err(query_span.error("query cannot contain empty segments").into()) } else { - parse_segments(q, Source::Query, query_span) + parse_segments::<Query>(q, query_span) } }).transpose(); diff --git a/core/codegen/tests/typed-uris.rs b/core/codegen/tests/typed-uris.rs @@ -120,6 +120,7 @@ fn check_simple_unnamed() { uri!(simple3: 1349) => "/?id=1349", uri!(simple4: 100, "bob") => "/?id=100&name=bob", uri!(simple4: 1349, "Bob Anderson") => "/?id=1349&name=Bob%20Anderson", + uri!(simple4: -2, "@M+s&OU=") => "/?id=-2&name=@M%2Bs%26OU%3D", uri!(simple4_flipped: 100, "bob") => "/?id=100&name=bob", uri!(simple4_flipped: 1349, "Bob Anderson") => "/?id=1349&name=Bob%20Anderson", } diff --git a/core/codegen/tests/ui-fail/route-path-bad-syntax.rs b/core/codegen/tests/ui-fail/route-path-bad-syntax.rs @@ -32,24 +32,23 @@ fn f6() {} // Check that paths contain only valid URI characters -#[get("/?????")] //~ ERROR invalid URI characters -//~^ NOTE cannot contain -fn g0() {} - #[get("/!@#$%^&*()")] //~ ERROR invalid path URI //~^ HELP origin form fn g1() {} #[get("/a%20b")] //~ ERROR invalid URI characters -//~^ NOTE cannot contain +//~^ NOTE cannot contain reserved +//~^^ HELP reserved characters include fn g2() {} #[get("/a?a%20b")] //~ ERROR invalid URI characters -//~^ NOTE cannot contain +//~^ NOTE cannot contain reserved +//~^^ HELP reserved characters include fn g3() {} #[get("/a?a+b")] //~ ERROR invalid URI characters -//~^ NOTE cannot contain +//~^ NOTE cannot contain reserved +//~^^ HELP reserved characters include fn g4() {} // Check that all declared parameters are accounted for diff --git a/core/codegen/tests/ui-fail/route-path-bad-syntax.stderr b/core/codegen/tests/ui-fail/route-path-bad-syntax.stderr @@ -50,222 +50,217 @@ error: paths cannot contain empty segments | = note: expected '/a/b', found '/a/b//' -error: component contains invalid URI characters - --> $DIR/route-path-bad-syntax.rs:35:10 - | -35 | #[get("/?????")] //~ ERROR invalid URI characters - | ^^^^ - | - = note: components cannot contain '%' and '+' characters - error: invalid path URI: expected EOF but found '#' at index 3 - --> $DIR/route-path-bad-syntax.rs:39:11 + --> $DIR/route-path-bad-syntax.rs:35:11 | -39 | #[get("/!@#$%^&*()")] //~ ERROR invalid path URI +35 | #[get("/!@#$%^&*()")] //~ ERROR invalid path URI | ^^^^^^^^^ | = help: expected path in origin form: "/path/<param>" error: component contains invalid URI characters - --> $DIR/route-path-bad-syntax.rs:43:9 + --> $DIR/route-path-bad-syntax.rs:39:9 | -43 | #[get("/a%20b")] //~ ERROR invalid URI characters +39 | #[get("/a%20b")] //~ ERROR invalid URI characters | ^^^^^ | - = note: components cannot contain '%' and '+' characters + = note: components cannot contain reserved characters + = help: reserved characters include: '%', '+', '&', etc. error: component contains invalid URI characters - --> $DIR/route-path-bad-syntax.rs:47:11 + --> $DIR/route-path-bad-syntax.rs:44:11 | -47 | #[get("/a?a%20b")] //~ ERROR invalid URI characters +44 | #[get("/a?a%20b")] //~ ERROR invalid URI characters | ^^^^^ | - = note: components cannot contain '%' and '+' characters + = note: components cannot contain reserved characters + = help: reserved characters include: '%', '+', '&', etc. error: component contains invalid URI characters - --> $DIR/route-path-bad-syntax.rs:51:11 + --> $DIR/route-path-bad-syntax.rs:49:11 | -51 | #[get("/a?a+b")] //~ ERROR invalid URI characters +49 | #[get("/a?a+b")] //~ ERROR invalid URI characters | ^^^ | - = note: components cannot contain '%' and '+' characters + = note: components cannot contain reserved characters + = help: reserved characters include: '%', '+', '&', etc. error: unused dynamic parameter - --> $DIR/route-path-bad-syntax.rs:57:9 + --> $DIR/route-path-bad-syntax.rs:56:9 | -57 | #[get("/<name>")] //~ ERROR unused dynamic parameter +56 | #[get("/<name>")] //~ ERROR unused dynamic parameter | ^^^^^^ | note: expected argument named `name` here - --> $DIR/route-path-bad-syntax.rs:58:7 + --> $DIR/route-path-bad-syntax.rs:57:7 | -58 | fn h0(_name: usize) {} //~ NOTE expected argument named `name` here +57 | fn h0(_name: usize) {} //~ NOTE expected argument named `name` here | ^^^^^^^^^^^^ error: unused dynamic parameter - --> $DIR/route-path-bad-syntax.rs:60:11 + --> $DIR/route-path-bad-syntax.rs:59:11 | -60 | #[get("/a?<r>")] //~ ERROR unused dynamic parameter +59 | #[get("/a?<r>")] //~ ERROR unused dynamic parameter | ^^^ | note: expected argument named `r` here - --> $DIR/route-path-bad-syntax.rs:61:1 + --> $DIR/route-path-bad-syntax.rs:60:1 | -61 | fn h1() {} //~ NOTE expected argument named `r` here +60 | fn h1() {} //~ NOTE expected argument named `r` here | ^^^^^^^^^^ error: unused dynamic parameter - --> $DIR/route-path-bad-syntax.rs:63:22 + --> $DIR/route-path-bad-syntax.rs:62:22 | -63 | #[post("/a", data = "<test>")] //~ ERROR unused dynamic parameter +62 | #[post("/a", data = "<test>")] //~ ERROR unused dynamic parameter | ^^^^^^ | note: expected argument named `test` here - --> $DIR/route-path-bad-syntax.rs:64:1 + --> $DIR/route-path-bad-syntax.rs:63:1 | -64 | fn h2() {} //~ NOTE expected argument named `test` here +63 | fn h2() {} //~ NOTE expected argument named `test` here | ^^^^^^^^^^ error: unused dynamic parameter - --> $DIR/route-path-bad-syntax.rs:66:9 + --> $DIR/route-path-bad-syntax.rs:65:9 | -66 | #[get("/<_r>")] //~ ERROR unused dynamic parameter +65 | #[get("/<_r>")] //~ ERROR unused dynamic parameter | ^^^^ | note: expected argument named `_r` here - --> $DIR/route-path-bad-syntax.rs:67:1 + --> $DIR/route-path-bad-syntax.rs:66:1 | -67 | fn h3() {} //~ NOTE expected argument named `_r` here +66 | fn h3() {} //~ NOTE expected argument named `_r` here | ^^^^^^^^^^ error: unused dynamic parameter - --> $DIR/route-path-bad-syntax.rs:69:9 + --> $DIR/route-path-bad-syntax.rs:68:9 | -69 | #[get("/<_r>/<b>")] //~ ERROR unused dynamic parameter +68 | #[get("/<_r>/<b>")] //~ ERROR unused dynamic parameter | ^^^^ | note: expected argument named `_r` here - --> $DIR/route-path-bad-syntax.rs:71:1 + --> $DIR/route-path-bad-syntax.rs:70:1 | -71 | fn h4() {} //~ NOTE expected argument named `_r` here +70 | fn h4() {} //~ NOTE expected argument named `_r` here | ^^^^^^^^^^ error: unused dynamic parameter - --> $DIR/route-path-bad-syntax.rs:69:14 + --> $DIR/route-path-bad-syntax.rs:68:14 | -69 | #[get("/<_r>/<b>")] //~ ERROR unused dynamic parameter +68 | #[get("/<_r>/<b>")] //~ ERROR unused dynamic parameter | ^^^ | note: expected argument named `b` here - --> $DIR/route-path-bad-syntax.rs:71:1 + --> $DIR/route-path-bad-syntax.rs:70:1 | -71 | fn h4() {} //~ NOTE expected argument named `_r` here +70 | fn h4() {} //~ NOTE expected argument named `_r` here | ^^^^^^^^^^ error: `foo_.` is not a valid identifier - --> $DIR/route-path-bad-syntax.rs:76:9 + --> $DIR/route-path-bad-syntax.rs:75:9 | -76 | #[get("/<foo_.>")] //~ ERROR `foo_.` is not a valid identifier +75 | #[get("/<foo_.>")] //~ ERROR `foo_.` is not a valid identifier | ^^^^^^^ | = help: parameter names must be valid identifiers error: `foo*` is not a valid identifier - --> $DIR/route-path-bad-syntax.rs:80:9 + --> $DIR/route-path-bad-syntax.rs:79:9 | -80 | #[get("/<foo*>")] //~ ERROR `foo*` is not a valid identifier +79 | #[get("/<foo*>")] //~ ERROR `foo*` is not a valid identifier | ^^^^^^ | = help: parameter names must be valid identifiers error: `!` is not a valid identifier - --> $DIR/route-path-bad-syntax.rs:84:9 + --> $DIR/route-path-bad-syntax.rs:83:9 | -84 | #[get("/<!>")] //~ ERROR `!` is not a valid identifier +83 | #[get("/<!>")] //~ ERROR `!` is not a valid identifier | ^^^ | = help: parameter names must be valid identifiers error: `name>:<id` is not a valid identifier - --> $DIR/route-path-bad-syntax.rs:88:9 + --> $DIR/route-path-bad-syntax.rs:87:9 | -88 | #[get("/<name>:<id>")] //~ ERROR `name>:<id` is not a valid identifier +87 | #[get("/<name>:<id>")] //~ ERROR `name>:<id` is not a valid identifier | ^^^^^^^^^^^ | = help: parameter names must be valid identifiers error: malformed parameter - --> $DIR/route-path-bad-syntax.rs:94:20 + --> $DIR/route-path-bad-syntax.rs:93:20 | -94 | #[get("/", data = "foo")] //~ ERROR malformed parameter +93 | #[get("/", data = "foo")] //~ ERROR malformed parameter | ^^^ | = help: parameter must be of the form '<param>' error: malformed parameter - --> $DIR/route-path-bad-syntax.rs:98:20 + --> $DIR/route-path-bad-syntax.rs:97:20 | -98 | #[get("/", data = "<foo..>")] //~ ERROR malformed parameter +97 | #[get("/", data = "<foo..>")] //~ ERROR malformed parameter | ^^^^^^^ | = help: parameter must be of the form '<param>' error: parameter is missing a closing bracket - --> $DIR/route-path-bad-syntax.rs:102:20 + --> $DIR/route-path-bad-syntax.rs:101:20 | -102 | #[get("/", data = "<foo")] //~ ERROR missing a closing bracket +101 | #[get("/", data = "<foo")] //~ ERROR missing a closing bracket | ^^^^ | = help: did you mean '<foo>'? error: `test ` is not a valid identifier - --> $DIR/route-path-bad-syntax.rs:106:20 + --> $DIR/route-path-bad-syntax.rs:105:20 | -106 | #[get("/", data = "<test >")] //~ ERROR `test ` is not a valid identifier +105 | #[get("/", data = "<test >")] //~ ERROR `test ` is not a valid identifier | ^^^^^^^ | = help: parameter names must be valid identifiers error: parameters must be named - --> $DIR/route-path-bad-syntax.rs:112:9 + --> $DIR/route-path-bad-syntax.rs:111:9 | -112 | #[get("/<_>")] //~ ERROR must be named +111 | #[get("/<_>")] //~ ERROR must be named | ^^^ | = help: use a name such as `_guard` or `_param` error: parameter names cannot be empty - --> $DIR/route-path-bad-syntax.rs:117:9 + --> $DIR/route-path-bad-syntax.rs:116:9 | -117 | #[get("/<>")] //~ ERROR cannot be empty +116 | #[get("/<>")] //~ ERROR cannot be empty | ^^ error: malformed parameter or identifier - --> $DIR/route-path-bad-syntax.rs:120:9 + --> $DIR/route-path-bad-syntax.rs:119:9 | -120 | #[get("/<id><")] //~ ERROR malformed parameter +119 | #[get("/<id><")] //~ ERROR malformed parameter | ^^^^^ | = help: parameters must be of the form '<param>' = help: identifiers cannot contain '<' or '>' error: malformed parameter or identifier - --> $DIR/route-path-bad-syntax.rs:125:9 + --> $DIR/route-path-bad-syntax.rs:124:9 | -125 | #[get("/<<<<id><")] //~ ERROR malformed parameter +124 | #[get("/<<<<id><")] //~ ERROR malformed parameter | ^^^^^^^^ | = help: parameters must be of the form '<param>' = help: identifiers cannot contain '<' or '>' error: malformed parameter or identifier - --> $DIR/route-path-bad-syntax.rs:130:9 + --> $DIR/route-path-bad-syntax.rs:129:9 | -130 | #[get("/<>name><")] //~ ERROR malformed parameter +129 | #[get("/<>name><")] //~ ERROR malformed parameter | ^^^^^^^^ | = help: parameters must be of the form '<param>' = help: identifiers cannot contain '<' or '>' -error: aborting due to 31 previous errors +error: aborting due to 30 previous errors diff --git a/core/http/src/lib.rs b/core/http/src/lib.rs @@ -14,7 +14,7 @@ //! [#17]: https://github.com/SergioBenitez/Rocket/issues/17 #[macro_use] extern crate pear; -#[macro_use] extern crate percent_encoding; +extern crate percent_encoding; extern crate smallvec; extern crate cookie; extern crate time; diff --git a/core/http/src/parse/uri/mod.rs b/core/http/src/parse/uri/mod.rs @@ -1,13 +1,14 @@ mod parser; mod error; mod tables; -#[cfg(test)] -mod tests; + +#[cfg(test)] mod tests; use uri::{Uri, Origin, Absolute, Authority}; use parse::indexed::IndexedInput; use self::parser::{uri, origin, authority_only, absolute_only, rocket_route_origin}; +crate use self::tables::is_pchar; pub use self::error::Error; type RawInput<'a> = IndexedInput<'a, [u8]>; diff --git a/core/http/src/route.rs b/core/http/src/route.rs @@ -1,8 +1,10 @@ use std::borrow::Cow; +use std::marker::PhantomData; use unicode_xid::UnicodeXID; use ext::IntoOwned; -use uri::{Uri, Origin}; +use uri::{Origin, UriPart, Path, Query}; +use uri::encoding::unsafe_percent_encode; use self::Error::*; @@ -22,25 +24,25 @@ pub enum Source { } #[derive(Debug, Clone)] -pub struct RouteSegment<'a> { +pub struct RouteSegment<'a, P: UriPart> { pub string: Cow<'a, str>, pub kind: Kind, - pub source: Source, pub name: Cow<'a, str>, pub index: Option<usize>, + _part: PhantomData<P>, } -impl<'a> IntoOwned for RouteSegment<'a> { - type Owned = RouteSegment<'static>; +impl<'a, P: UriPart + 'static> IntoOwned for RouteSegment<'a, P> { + type Owned = RouteSegment<'static, P>; #[inline] fn into_owned(self) -> Self::Owned { RouteSegment { string: IntoOwned::into_owned(self.string), kind: self.kind, - source: self.source, name: IntoOwned::into_owned(self.name), index: self.index, + _part: PhantomData } } } @@ -56,7 +58,7 @@ pub enum Error<'a> { Trailing(&'a str) } -pub type SResult<'a> = Result<RouteSegment<'a>, (&'a str, Error<'a>)>; +pub type SResult<'a, P> = Result<RouteSegment<'a, P>, (&'a str, Error<'a>)>; #[inline] fn is_ident_start(c: char) -> bool { @@ -83,9 +85,9 @@ fn is_valid_ident(string: &str) -> bool { } } -impl<'a> RouteSegment<'a> { - pub fn parse_one(segment: &str) -> Result<RouteSegment, Error> { - let (string, source, index) = (segment.into(), Source::Unknown, None); +impl<'a, P: UriPart> RouteSegment<'a, P> { + pub fn parse_one(segment: &'a str) -> Result<Self, Error> { + let (string, index) = (segment.into(), None); // Check if this is a dynamic param. If so, check its well-formedness. if segment.starts_with('<') && segment.ends_with('>') { @@ -105,7 +107,7 @@ impl<'a> RouteSegment<'a> { } let name = name.into(); - return Ok(RouteSegment { string, source, name, kind, index }); + return Ok(RouteSegment { string, name, kind, index, _part: PhantomData }); } else if segment.is_empty() { return Err(Empty); } else if segment.starts_with('<') && segment.len() > 1 @@ -113,50 +115,49 @@ impl<'a> RouteSegment<'a> { return Err(MissingClose); } else if segment.contains('>') || segment.contains('<') { return Err(Malformed); - } else if Uri::percent_encode(segment) != segment - || Uri::percent_decode_lossy(segment.as_bytes()) != segment - || segment.contains('+') { + } else if unsafe_percent_encode::<P>(segment) != segment { return Err(Uri); } Ok(RouteSegment { - string, source, index, + string, index, name: segment.into(), kind: Kind::Static, + _part: PhantomData }) } pub fn parse_many( - string: &str, - source: Source, - ) -> impl Iterator<Item = SResult> { - let sep = match source { - Source::Query => '&', - _ => '/', - }; - + string: &'a str, + ) -> impl Iterator<Item = SResult<P>> { let mut last_multi_seg: Option<&str> = None; - string.split(sep).filter(|s| !s.is_empty()).enumerate().map(move |(i, seg)| { - if let Some(multi_seg) = last_multi_seg { - return Err((seg, Trailing(multi_seg))); - } - - let mut parsed = Self::parse_one(seg).map_err(|e| (seg, e))?; - if parsed.kind == Kind::Multi { - last_multi_seg = Some(seg); - } - - parsed.index = Some(i); - parsed.source = source; - Ok(parsed) - }) + string.split(P::DELIMITER) + .filter(|s| !s.is_empty()) + .enumerate() + .map(move |(i, seg)| { + if let Some(multi_seg) = last_multi_seg { + return Err((seg, Trailing(multi_seg))); + } + + let mut parsed = Self::parse_one(seg).map_err(|e| (seg, e))?; + if parsed.kind == Kind::Multi { + last_multi_seg = Some(seg); + } + + parsed.index = Some(i); + Ok(parsed) + }) } +} - pub fn parse_path(uri: &'a Origin) -> impl Iterator<Item = SResult<'a>> { - Self::parse_many(uri.path(), Source::Path) +impl<'a> RouteSegment<'a, Path> { + pub fn parse(uri: &'a Origin) -> impl Iterator<Item = SResult<'a, Path>> { + Self::parse_many(uri.path()) } +} - pub fn parse_query(uri: &'a Origin) -> Option<impl Iterator<Item = SResult<'a>>> { - uri.query().map(|q| Self::parse_many(q, Source::Query)) +impl<'a> RouteSegment<'a, Query> { + pub fn parse(uri: &'a Origin) -> Option<impl Iterator<Item = SResult<'a, Query>>> { + uri.query().map(|q| Self::parse_many(q)) } } diff --git a/core/http/src/uri/encoding.rs b/core/http/src/uri/encoding.rs @@ -0,0 +1,75 @@ +use std::marker::PhantomData; +use std::borrow::Cow; + +use percent_encoding::{EncodeSet, utf8_percent_encode}; + +use uri::{UriPart, Path, Query}; +use parse::uri::is_pchar; + +#[derive(Clone, Copy)] +#[allow(non_camel_case_types)] +crate struct UNSAFE_ENCODE_SET<P: UriPart>(PhantomData<P>); + +impl<P: UriPart> Default for UNSAFE_ENCODE_SET<P> { + #[inline(always)] + fn default() -> Self { UNSAFE_ENCODE_SET(PhantomData) } +} + +impl EncodeSet for UNSAFE_ENCODE_SET<Path> { + #[inline(always)] + fn contains(&self, byte: u8) -> bool { + !is_pchar(byte) || byte == b'%' + } +} + +impl EncodeSet for UNSAFE_ENCODE_SET<Query> { + #[inline(always)] + fn contains(&self, byte: u8) -> bool { + (!is_pchar(byte) && (byte != b'?')) || byte == b'%' || byte == b'+' + } +} + +#[derive(Clone, Copy)] +#[allow(non_camel_case_types)] +crate struct ENCODE_SET<P: UriPart>(PhantomData<P>); + +impl EncodeSet for ENCODE_SET<Path> { + #[inline(always)] + fn contains(&self, byte: u8) -> bool { + <UNSAFE_ENCODE_SET<Path>>::default().contains(byte) || byte == b'/' + } +} + +impl EncodeSet for ENCODE_SET<Query> { + #[inline(always)] + fn contains(&self, byte: u8) -> bool { + <UNSAFE_ENCODE_SET<Query>>::default().contains(byte) || match byte { + b'&' | b'=' => true, + _ => false + } + } +} + +#[derive(Default, Clone, Copy)] +#[allow(non_camel_case_types)] +crate struct DEFAULT_ENCODE_SET; + +impl EncodeSet for DEFAULT_ENCODE_SET { + #[inline(always)] + fn contains(&self, byte: u8) -> bool { + ENCODE_SET::<Path>(PhantomData).contains(byte) || + ENCODE_SET::<Query>(PhantomData).contains(byte) + } +} + +crate fn unsafe_percent_encode<P: UriPart>(string: &str) -> Cow<str> { + match P::DELIMITER { + '/' => percent_encode::<UNSAFE_ENCODE_SET<Path>>(string), + '&' => percent_encode::<UNSAFE_ENCODE_SET<Query>>(string), + _ => percent_encode::<DEFAULT_ENCODE_SET>(string) + } +} + +crate fn percent_encode<S: EncodeSet + Default>(string: &str) -> Cow<str> { + utf8_percent_encode(string, S::default()).into() +} diff --git a/core/http/src/uri/mod.rs b/core/http/src/uri/mod.rs @@ -9,6 +9,8 @@ mod authority; mod absolute; mod segments; +crate mod encoding; + pub use parse::uri::Error; pub use self::uri::*; @@ -65,6 +67,7 @@ pub trait UriPart: private::Sealed { /// ``` /// /// [`UriPart`]: uri::UriPart +#[derive(Debug, Clone, Copy)] pub enum Path { } /// Marker type indicating use of a type for the query [`UriPart`] of a URI. @@ -77,6 +80,7 @@ pub enum Path { } /// ``` /// /// [`UriPart`]: uri::UriPart +#[derive(Debug, Clone, Copy)] pub enum Query { } impl UriPart for Path { diff --git a/core/http/src/uri/uri.rs b/core/http/src/uri/uri.rs @@ -7,6 +7,7 @@ use std::convert::TryFrom; use ext::IntoOwned; use parse::Indexed; use uri::{Origin, Authority, Absolute, Error}; +use uri::encoding::{percent_encode, DEFAULT_ENCODE_SET}; /// An `enum` encapsulating any of the possible URI variants. /// @@ -59,22 +60,6 @@ pub enum Uri<'a> { Asterisk, } -/// This encode set is used for strings where '/' characters are known to be -/// safe; all other special path segment characters are encoded. -define_encode_set! { - #[doc(hidden)] - pub UNSAFE_PATH_ENCODE_SET = [::percent_encoding::DEFAULT_ENCODE_SET] | { - '%', '[', '\\', ']', '^', '|' - } -} - -/// This encode set should be used for path segments (components) of a -/// `/`-separated path. It encodes as much as possible. -define_encode_set! { - #[doc(hidden)] - pub DEFAULT_ENCODE_SET = [UNSAFE_PATH_ENCODE_SET] | { '/' } -} - impl<'a> Uri<'a> { #[inline] crate unsafe fn raw_absolute( @@ -175,9 +160,8 @@ impl<'a> Uri<'a> { } } - /// Returns a URL-encoded version of the string. Any characters outside of - /// visible ASCII-range are encoded as well as ' ', '"', '#', '<', '>', '`', - /// '?', '{', '}', '%', '/', '[', '\\', ']', '^', and '|'. + /// Returns a URL-encoded version of the string. Any reserved characters are + /// percent-encoded. /// /// # Examples /// @@ -186,10 +170,10 @@ impl<'a> Uri<'a> { /// use rocket::http::uri::Uri; /// /// let encoded = Uri::percent_encode("hello?a=<b>hi</b>"); - /// assert_eq!(encoded, "hello%3Fa=%3Cb%3Ehi%3C%2Fb%3E"); + /// assert_eq!(encoded, "hello%3Fa%3D%3Cb%3Ehi%3C%2Fb%3E"); /// ``` pub fn percent_encode(string: &str) -> Cow<str> { - ::percent_encoding::utf8_percent_encode(string, DEFAULT_ENCODE_SET).into() + percent_encode::<DEFAULT_ENCODE_SET>(string) } /// Returns a URL-decoded version of the string. If the percent encoded diff --git a/core/lib/src/router/route.rs b/core/lib/src/router/route.rs @@ -9,7 +9,7 @@ use http::{Method, MediaType}; use http::route::{RouteSegment, Kind}; use error::RouteUriError; use http::ext::IntoOwned; -use http::uri::Origin; +use http::uri::{Origin, Path, Query}; /// A route: a method, its handler, path, rank, and format/media type. #[derive(Clone)] @@ -35,18 +35,18 @@ pub struct Route { #[derive(Debug, Default, Clone)] crate struct Metadata { - crate path_segments: Vec<RouteSegment<'static>>, - crate query_segments: Option<Vec<RouteSegment<'static>>>, + crate path_segments: Vec<RouteSegment<'static, Path>>, + crate query_segments: Option<Vec<RouteSegment<'static, Query>>>, crate fully_dynamic_query: bool, } impl Metadata { fn from(route: &Route) -> Result<Metadata, RouteUriError> { - let path_segments = RouteSegment::parse_path(&route.uri) + let path_segments = <RouteSegment<Path>>::parse(&route.uri) .map(|res| res.map(|s| s.into_owned())) .collect::<Result<Vec<_>, _>>()?; - let (query_segments, dyn) = match RouteSegment::parse_query(&route.uri) { + let (query_segments, dyn) = match <RouteSegment<Query>>::parse(&route.uri) { Some(results) => { let segments = results.map(|res| res.map(|s| s.into_owned())) .collect::<Result<Vec<_>, _>>()?; @@ -264,7 +264,7 @@ impl Route { path: Origin<'a> ) -> Result<(), RouteUriError> { base.clear_query(); - for segment in RouteSegment::parse_path(&base) { + for segment in <RouteSegment<Path>>::parse(&base) { if segment?.kind != Kind::Static { return Err(RouteUriError::DynamicBase); }