mirror of https://github.com/rwf2/Rocket.git
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.
This commit is contained in:
parent
c04655f290
commit
56c6a96f6a
|
@ -1,4 +1,5 @@
|
||||||
#![feature(use_extern_macros)]
|
#![feature(use_extern_macros)]
|
||||||
|
#![feature(crate_visibility_modifier)]
|
||||||
|
|
||||||
// TODO: Version URLs.
|
// TODO: Version URLs.
|
||||||
#![doc(html_root_url = "https://api.rocket.rs")]
|
#![doc(html_root_url = "https://api.rocket.rs")]
|
||||||
|
|
|
@ -62,12 +62,12 @@ pub struct Engines {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Engines {
|
impl Engines {
|
||||||
pub(crate) const ENABLED_EXTENSIONS: &'static [&'static str] = &[
|
crate const ENABLED_EXTENSIONS: &'static [&'static str] = &[
|
||||||
#[cfg(feature = "tera_templates")] Tera::EXT,
|
#[cfg(feature = "tera_templates")] Tera::EXT,
|
||||||
#[cfg(feature = "handlebars_templates")] Handlebars::EXT,
|
#[cfg(feature = "handlebars_templates")] Handlebars::EXT,
|
||||||
];
|
];
|
||||||
|
|
||||||
pub(crate) fn init(templates: &HashMap<String, TemplateInfo>) -> Option<Engines> {
|
crate fn init(templates: &HashMap<String, TemplateInfo>) -> Option<Engines> {
|
||||||
fn inner<E: Engine>(templates: &HashMap<String, TemplateInfo>) -> Option<E> {
|
fn inner<E: Engine>(templates: &HashMap<String, TemplateInfo>) -> Option<E> {
|
||||||
let named_templates = templates.iter()
|
let named_templates = templates.iter()
|
||||||
.filter(|&(_, i)| i.extension == E::EXT)
|
.filter(|&(_, i)| i.extension == E::EXT)
|
||||||
|
@ -91,7 +91,7 @@ impl Engines {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn render<C: Serialize>(
|
crate fn render<C: Serialize>(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
info: &TemplateInfo,
|
info: &TemplateInfo,
|
||||||
|
|
|
@ -260,9 +260,9 @@ impl RouteParams {
|
||||||
).expect("consistent uri macro item")
|
).expect("consistent uri macro item")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn explode(&self, ecx: &ExtCtxt) -> (LocalInternedString, &str, Path, P<Expr>, P<Expr>) {
|
fn explode(&self, ecx: &ExtCtxt) -> (LocalInternedString, String, Path, P<Expr>, P<Expr>) {
|
||||||
let name = self.annotated_fn.ident().name.as_str();
|
let name = self.annotated_fn.ident().name.as_str();
|
||||||
let path = &self.uri.node.as_str();
|
let path = self.uri.node.to_string();
|
||||||
let method = method_to_path(ecx, self.method.node);
|
let method = method_to_path(ecx, self.method.node);
|
||||||
let format = self.format.as_ref().map(|kv| kv.value().clone());
|
let format = self.format.as_ref().map(|kv| kv.value().clone());
|
||||||
let media_type = option_as_expr(ecx, &media_type_to_expr(ecx, format));
|
let media_type = option_as_expr(ecx, &media_type_to_expr(ecx, format));
|
||||||
|
|
|
@ -88,8 +88,9 @@
|
||||||
//! other: String
|
//! other: String
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! Each field's type is required to implement [`FromFormValue`]. The derive
|
//! Each field's type is required to implement [`FromFormValue`].
|
||||||
//! accepts one field attribute: `form`, with the following syntax:
|
//!
|
||||||
|
//! The derive accepts one field attribute: `form`, with the following syntax:
|
||||||
//!
|
//!
|
||||||
//! <pre>
|
//! <pre>
|
||||||
//! form := 'field' '=' '"' IDENT '"'
|
//! form := 'field' '=' '"' IDENT '"'
|
||||||
|
@ -113,8 +114,8 @@
|
||||||
//! implementation succeeds only when all of the field parses succeed.
|
//! implementation succeeds only when all of the field parses succeed.
|
||||||
//!
|
//!
|
||||||
//! The `form` field attribute can be used to direct that a different incoming
|
//! The `form` field attribute can be used to direct that a different incoming
|
||||||
//! field name is expected. In this case, the attribute's field name is used
|
//! field name is expected. In this case, the `field` name in the attribute is
|
||||||
//! instead of the structure's field name when parsing a form.
|
//! used instead of the structure's actual field name when parsing a form.
|
||||||
//!
|
//!
|
||||||
//! [`FromForm`]: /rocket/request/trait.FromForm.html
|
//! [`FromForm`]: /rocket/request/trait.FromForm.html
|
||||||
//! [`FromFormValue`]: /rocket/request/trait.FromFormValue.html
|
//! [`FromFormValue`]: /rocket/request/trait.FromFormValue.html
|
||||||
|
@ -138,7 +139,9 @@
|
||||||
//! ### Typed URIs: `uri!`
|
//! ### Typed URIs: `uri!`
|
||||||
//!
|
//!
|
||||||
//! The `uri!` macro creates a type-safe URI given a route and values for the
|
//! The `uri!` macro creates a type-safe URI given a route and values for the
|
||||||
//! route's URI parameters.
|
//! route's URI parameters. The inputs to the macro are the path to a route, a
|
||||||
|
//! colon, and one argument for each dynamic parameter (parameters in `<>`) in
|
||||||
|
//! the route's path.
|
||||||
//!
|
//!
|
||||||
//! For example, for the following route:
|
//! For example, for the following route:
|
||||||
//!
|
//!
|
||||||
|
@ -152,10 +155,10 @@
|
||||||
//! A URI can be created as follows:
|
//! A URI can be created as follows:
|
||||||
//!
|
//!
|
||||||
//! ```rust,ignore
|
//! ```rust,ignore
|
||||||
//! // with unnamed parameters
|
//! // with unnamed parameters, in route path declaration order
|
||||||
//! let mike = uri!(person: "Mike", 28);
|
//! let mike = uri!(person: "Mike", 28);
|
||||||
//!
|
//!
|
||||||
//! // with named parameters
|
//! // with named parameters, order irrelevant
|
||||||
//! let mike = uri!(person: name = "Mike", age = 28);
|
//! let mike = uri!(person: name = "Mike", age = 28);
|
||||||
//! let mike = uri!(person: age = 28, name = "Mike");
|
//! let mike = uri!(person: age = 28, name = "Mike");
|
||||||
//!
|
//!
|
||||||
|
@ -183,11 +186,14 @@
|
||||||
//!
|
//!
|
||||||
//! #### Semantics
|
//! #### Semantics
|
||||||
//!
|
//!
|
||||||
//! The `uri!` macro returns a `Uri` structure with the URI of the supplied
|
//! The `uri!` macro returns an [`Origin`](rocket::uri::Origin) structure with
|
||||||
//! route with the given values. A `uri!` invocation only succeeds if the type
|
//! the URI of the supplied route interpolated with the given values. Note that
|
||||||
//! of every value in the invocation matches the type declared for the parameter
|
//! `Origin` implements `Into<Uri>` (and by extension, `TryInto<Uri>`), so it
|
||||||
//! in the given route.
|
//! can be converted into a [`Uri`](rocket::uri::Uri) using `.into()` as needed.
|
||||||
//!
|
//!
|
||||||
|
//!
|
||||||
|
//! A `uri!` invocation only typechecks if the type of every value in the
|
||||||
|
//! invocation matches the type declared for the parameter in the given route.
|
||||||
//! The [`FromUriParam`] trait is used to typecheck and perform a conversion for
|
//! The [`FromUriParam`] trait is used to typecheck and perform a conversion for
|
||||||
//! each value. If a `FromUriParam<S>` implementation exists for a type `T`,
|
//! each value. If a `FromUriParam<S>` implementation exists for a type `T`,
|
||||||
//! then a value of type `S` can be used in `uri!` macro for a route URI
|
//! then a value of type `S` can be used in `uri!` macro for a route URI
|
||||||
|
@ -221,7 +227,6 @@
|
||||||
//! ROCKET_CODEGEN_DEBUG=1 cargo build
|
//! ROCKET_CODEGEN_DEBUG=1 cargo build
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
|
||||||
extern crate syntax;
|
extern crate syntax;
|
||||||
extern crate syntax_ext;
|
extern crate syntax_ext;
|
||||||
extern crate syntax_pos;
|
extern crate syntax_pos;
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::fmt::Display;
|
||||||
use syntax::codemap::Span;
|
use syntax::codemap::Span;
|
||||||
use syntax::ext::base::{DummyResult, ExtCtxt, MacEager, MacResult};
|
use syntax::ext::base::{DummyResult, ExtCtxt, MacEager, MacResult};
|
||||||
use syntax::tokenstream::{TokenStream, TokenTree};
|
use syntax::tokenstream::{TokenStream, TokenTree};
|
||||||
use syntax::ast::{self, GenericArg, MacDelimiter, Ident};
|
use syntax::ast::{self, Expr, GenericArg, MacDelimiter, Ident};
|
||||||
use syntax::symbol::Symbol;
|
use syntax::symbol::Symbol;
|
||||||
use syntax::parse::PResult;
|
use syntax::parse::PResult;
|
||||||
use syntax::ext::build::AstBuilder;
|
use syntax::ext::build::AstBuilder;
|
||||||
|
@ -11,9 +11,14 @@ use syntax::ptr::P;
|
||||||
|
|
||||||
use URI_INFO_MACRO_PREFIX;
|
use URI_INFO_MACRO_PREFIX;
|
||||||
use super::prefix_path;
|
use super::prefix_path;
|
||||||
use utils::{IdentExt, split_idents, ExprExt};
|
use utils::{IdentExt, split_idents, ExprExt, option_as_expr};
|
||||||
use parser::{UriParams, InternalUriParams, Validation};
|
use parser::{UriParams, InternalUriParams, Validation};
|
||||||
|
|
||||||
|
use rocket_http::uri::Origin;
|
||||||
|
use rocket_http::ext::IntoOwned;
|
||||||
|
|
||||||
|
// What gets called when `uri!` is invoked. This just invokes the internal URI
|
||||||
|
// macro which calls the `uri_internal` function below.
|
||||||
pub fn uri(
|
pub fn uri(
|
||||||
ecx: &mut ExtCtxt,
|
ecx: &mut ExtCtxt,
|
||||||
sp: Span,
|
sp: Span,
|
||||||
|
@ -89,49 +94,35 @@ fn extract_exprs<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
// Validates the mount path and the URI and returns a single Origin URI with
|
||||||
pub fn uri_internal(
|
// both paths concatinated. Validation should always succeed since this macro
|
||||||
ecx: &mut ExtCtxt,
|
// can only be called if the route attribute succeed, which implies that the
|
||||||
sp: Span,
|
// route URI was valid.
|
||||||
tt: &[TokenTree],
|
fn extract_origin<'a>(
|
||||||
) -> Box<MacResult + 'static> {
|
ecx: &ExtCtxt<'a>,
|
||||||
// Parse the internal invocation and the user's URI param expressions.
|
internal: &InternalUriParams,
|
||||||
let mut parser = ecx.new_parser_from_tts(tt);
|
) -> PResult<'a, Origin<'static>> {
|
||||||
let internal = try_parse!(sp, InternalUriParams::parse(ecx, &mut parser));
|
let base_uri = match internal.uri_params.mount_point {
|
||||||
let exprs = try_parse!(sp, extract_exprs(ecx, &internal));
|
Some(base) => Origin::parse(&base.node)
|
||||||
|
.map_err(|_| ecx.struct_span_err(base.span, "invalid path URI"))?
|
||||||
|
.into_owned(),
|
||||||
|
None => Origin::dummy()
|
||||||
|
};
|
||||||
|
|
||||||
// Generate the statements to typecheck each parameter. First, the mount.
|
Origin::parse_route(&format!("{}/{}", base_uri, internal.uri.node))
|
||||||
let mut argument_stmts = vec![];
|
.map(|o| o.to_normalized().into_owned())
|
||||||
let mut format_assign_tokens = vec![];
|
.map_err(|_| ecx.struct_span_err(internal.uri.span, "invalid route URI"))
|
||||||
let mut fmt_string = internal.uri_fmt_string();
|
}
|
||||||
if let Some(mount_point) = internal.uri_params.mount_point {
|
|
||||||
// generating: let mount: &str = $mount_string;
|
|
||||||
let mount_string = mount_point.node;
|
|
||||||
argument_stmts.push(ecx.stmt_let_typed(
|
|
||||||
mount_point.span,
|
|
||||||
false,
|
|
||||||
Ident::from_str("mount"),
|
|
||||||
quote_ty!(ecx, &str),
|
|
||||||
quote_expr!(ecx, $mount_string),
|
|
||||||
));
|
|
||||||
|
|
||||||
// generating: format string arg for `mount`
|
fn explode<I>(ecx: &ExtCtxt, route_str: &str, items: I) -> P<Expr>
|
||||||
let mut tokens = quote_tokens!(ecx, mount = mount,);
|
where I: Iterator<Item = (ast::Ident, P<ast::Ty>, P<Expr>)>
|
||||||
tokens.iter_mut().for_each(|tree| tree.set_span(mount_point.span));
|
{
|
||||||
format_assign_tokens.push(tokens);
|
// Generate the statements to typecheck each parameter.
|
||||||
|
|
||||||
// Ensure the `format!` string contains the `{mount}` parameter.
|
|
||||||
fmt_string = "{mount}".to_string() + &fmt_string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now the user's parameters.
|
|
||||||
// Building <$T as ::rocket::http::uri::FromUriParam<_>>::from_uri_param($e).
|
// Building <$T as ::rocket::http::uri::FromUriParam<_>>::from_uri_param($e).
|
||||||
for (i, &(mut ident, ref ty)) in internal.fn_args.iter().enumerate() {
|
let mut let_bindings = vec![];
|
||||||
let (span, mut expr) = (exprs[i].span, exprs[i].clone());
|
let mut fmt_exprs = vec![];
|
||||||
|
for (mut ident, ty, expr) in items {
|
||||||
// Format argument names cannot begin with `_`, but a function parameter
|
let (span, mut expr) = (expr.span, expr.clone());
|
||||||
// might, so we prefix each parameter with the letters `fmt`.
|
|
||||||
ident.name = Symbol::intern(&format!("fmt{}", ident.name));
|
|
||||||
ident.span = span;
|
ident.span = span;
|
||||||
|
|
||||||
// path for call: <T as FromUriParam<_>>::from_uri_param
|
// path for call: <T as FromUriParam<_>>::from_uri_param
|
||||||
|
@ -150,7 +141,7 @@ pub fn uri_internal(
|
||||||
if !inner.is_location() {
|
if !inner.is_location() {
|
||||||
let tmp_ident = ident.append("_tmp");
|
let tmp_ident = ident.append("_tmp");
|
||||||
let tmp_stmt = ecx.stmt_let(span, false, tmp_ident, inner);
|
let tmp_stmt = ecx.stmt_let(span, false, tmp_ident, inner);
|
||||||
argument_stmts.push(tmp_stmt);
|
let_bindings.push(tmp_stmt);
|
||||||
expr = ecx.expr_ident(span, tmp_ident);
|
expr = ecx.expr_ident(span, tmp_ident);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,19 +151,63 @@ pub fn uri_internal(
|
||||||
let call = ecx.expr_call(span, path_expr, vec![expr]);
|
let call = ecx.expr_call(span, path_expr, vec![expr]);
|
||||||
let stmt = ecx.stmt_let(span, false, ident, call);
|
let stmt = ecx.stmt_let(span, false, ident, call);
|
||||||
debug!("Emitting URI typecheck statement: {:?}", stmt);
|
debug!("Emitting URI typecheck statement: {:?}", stmt);
|
||||||
argument_stmts.push(stmt);
|
let_bindings.push(stmt);
|
||||||
|
|
||||||
// generating: arg assignment tokens for format string
|
// generating: arg tokens for format string
|
||||||
let uri_display = quote_path!(ecx, ::rocket::http::uri::UriDisplay);
|
let mut tokens = quote_tokens!(ecx, &$ident as &::rocket::http::uri::UriDisplay,);
|
||||||
let mut tokens = quote_tokens!(ecx, $ident = &$ident as &$uri_display,);
|
|
||||||
tokens.iter_mut().for_each(|tree| tree.set_span(span));
|
tokens.iter_mut().for_each(|tree| tree.set_span(span));
|
||||||
format_assign_tokens.push(tokens);
|
fmt_exprs.push(tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
let expr = quote_expr!(ecx, {
|
// Convert all of the '<...>' into '{}'.
|
||||||
$argument_stmts
|
let mut inside = false;
|
||||||
::rocket::http::uri::Uri::from(format!($fmt_string, $format_assign_tokens))
|
let fmt_string: String = route_str.chars().filter_map(|c| {
|
||||||
});
|
Some(match c {
|
||||||
|
'<' => { inside = true; '{' }
|
||||||
|
'>' => { inside = false; '}' }
|
||||||
|
_ if !inside => c,
|
||||||
|
_ => return None
|
||||||
|
})
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
// Don't allocate if there are no formatting expressions.
|
||||||
|
if fmt_exprs.is_empty() {
|
||||||
|
quote_expr!(ecx, $fmt_string.into())
|
||||||
|
} else {
|
||||||
|
quote_expr!(ecx, { $let_bindings format!($fmt_string, $fmt_exprs).into() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
pub fn uri_internal(
|
||||||
|
ecx: &mut ExtCtxt,
|
||||||
|
sp: Span,
|
||||||
|
tt: &[TokenTree],
|
||||||
|
) -> Box<MacResult + 'static> {
|
||||||
|
// Parse the internal invocation and the user's URI param expressions.
|
||||||
|
let mut parser = ecx.new_parser_from_tts(tt);
|
||||||
|
let internal = try_parse!(sp, InternalUriParams::parse(ecx, &mut parser));
|
||||||
|
let exprs = try_parse!(sp, extract_exprs(ecx, &internal));
|
||||||
|
let origin = try_parse!(sp, extract_origin(ecx, &internal));
|
||||||
|
|
||||||
|
// Determine how many parameters there are in the URI path.
|
||||||
|
let path_param_count = origin.path().matches('<').count();
|
||||||
|
|
||||||
|
// Create an iterator over the `ident`, `ty`, and `expr` triple.
|
||||||
|
let mut arguments = internal.fn_args
|
||||||
|
.into_iter()
|
||||||
|
.zip(exprs.into_iter())
|
||||||
|
.map(|((ident, ty), expr)| (ident, ty, expr));
|
||||||
|
|
||||||
|
// Generate an expression for both the path and query.
|
||||||
|
let path = explode(ecx, origin.path(), arguments.by_ref().take(path_param_count));
|
||||||
|
let query = option_as_expr(ecx, &origin.query().map(|q| explode(ecx, q, arguments)));
|
||||||
|
|
||||||
|
// Generate the final `Origin` expression.
|
||||||
|
let expr = quote_expr!(ecx, ::rocket::http::uri::Origin::new::<
|
||||||
|
::std::borrow::Cow<'static, str>,
|
||||||
|
::std::borrow::Cow<'static, str>,
|
||||||
|
>($path, $query));
|
||||||
|
|
||||||
debug!("Emitting URI expression: {:?}", expr);
|
debug!("Emitting URI expression: {:?}", expr);
|
||||||
MacEager::expr(expr)
|
MacEager::expr(expr)
|
||||||
|
|
|
@ -10,7 +10,7 @@ use super::Function;
|
||||||
use super::keyvalue::KVSpanned;
|
use super::keyvalue::KVSpanned;
|
||||||
use super::uri::validate_uri;
|
use super::uri::validate_uri;
|
||||||
use rocket_http::{Method, MediaType};
|
use rocket_http::{Method, MediaType};
|
||||||
use rocket_http::uri::Uri;
|
use rocket_http::uri::Origin;
|
||||||
|
|
||||||
/// This structure represents the parsed `route` attribute.
|
/// This structure represents the parsed `route` attribute.
|
||||||
///
|
///
|
||||||
|
@ -22,7 +22,7 @@ use rocket_http::uri::Uri;
|
||||||
pub struct RouteParams {
|
pub struct RouteParams {
|
||||||
pub annotated_fn: Function,
|
pub annotated_fn: Function,
|
||||||
pub method: Spanned<Method>,
|
pub method: Spanned<Method>,
|
||||||
pub uri: Spanned<Uri<'static>>,
|
pub uri: Spanned<Origin<'static>>,
|
||||||
pub data_param: Option<KVSpanned<Ident>>,
|
pub data_param: Option<KVSpanned<Ident>>,
|
||||||
pub query_param: Option<Spanned<Ident>>,
|
pub query_param: Option<Spanned<Ident>>,
|
||||||
pub format: Option<KVSpanned<MediaType>>,
|
pub format: Option<KVSpanned<MediaType>>,
|
||||||
|
@ -185,9 +185,10 @@ fn parse_method(ecx: &ExtCtxt, meta_item: &NestedMetaItem) -> Spanned<Method> {
|
||||||
dummy_spanned(Method::Get)
|
dummy_spanned(Method::Get)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_path(ecx: &ExtCtxt,
|
fn parse_path(
|
||||||
meta_item: &NestedMetaItem)
|
ecx: &ExtCtxt,
|
||||||
-> (Spanned<Uri<'static>>, Option<Spanned<Ident>>) {
|
meta_item: &NestedMetaItem
|
||||||
|
) -> (Spanned<Origin<'static>>, Option<Spanned<Ident>>) {
|
||||||
let sp = meta_item.span();
|
let sp = meta_item.span();
|
||||||
if let Some((name, lit)) = meta_item.name_value() {
|
if let Some((name, lit)) = meta_item.name_value() {
|
||||||
if name != "path" {
|
if name != "path" {
|
||||||
|
@ -207,7 +208,7 @@ fn parse_path(ecx: &ExtCtxt,
|
||||||
.emit();
|
.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
(dummy_spanned(Uri::new("")), None)
|
(dummy_spanned(Origin::dummy()), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_opt<O, T, F>(ecx: &ExtCtxt, kv: &KVSpanned<T>, f: F) -> Option<KVSpanned<O>>
|
fn parse_opt<O, T, F>(ecx: &ExtCtxt, kv: &KVSpanned<T>, f: F) -> Option<KVSpanned<O>>
|
||||||
|
|
|
@ -2,7 +2,8 @@ use syntax::ast::*;
|
||||||
use syntax::codemap::{Span, Spanned, dummy_spanned};
|
use syntax::codemap::{Span, Spanned, dummy_spanned};
|
||||||
use syntax::ext::base::ExtCtxt;
|
use syntax::ext::base::ExtCtxt;
|
||||||
|
|
||||||
use rocket_http::uri::Uri;
|
use rocket_http::ext::IntoOwned;
|
||||||
|
use rocket_http::uri::{Uri, Origin};
|
||||||
use super::route::param_to_ident;
|
use super::route::param_to_ident;
|
||||||
use utils::{span, SpanExt, is_valid_ident};
|
use utils::{span, SpanExt, is_valid_ident};
|
||||||
|
|
||||||
|
@ -11,24 +12,18 @@ use utils::{span, SpanExt, is_valid_ident};
|
||||||
// stripped out at runtime. So, to avoid any confusion, we issue an error at
|
// stripped out at runtime. So, to avoid any confusion, we issue an error at
|
||||||
// compile-time for empty segments. At the moment, this disallows trailing
|
// compile-time for empty segments. At the moment, this disallows trailing
|
||||||
// slashes as well, since then the last segment is empty.
|
// slashes as well, since then the last segment is empty.
|
||||||
fn valid_path(ecx: &ExtCtxt, uri: &Uri, sp: Span) -> bool {
|
fn valid_path(ecx: &ExtCtxt, uri: &Origin, sp: Span) -> bool {
|
||||||
let cleaned = uri.to_string();
|
if !uri.is_normalized() {
|
||||||
if !uri.as_str().starts_with('/') {
|
let normalized = uri.to_normalized();
|
||||||
ecx.struct_span_err(sp, "route paths must be absolute")
|
|
||||||
.note(&format!("expected {:?}, found {:?}", cleaned, uri.as_str()))
|
|
||||||
.emit()
|
|
||||||
} else if cleaned != uri.as_str() {
|
|
||||||
ecx.struct_span_err(sp, "paths cannot contain empty segments")
|
ecx.struct_span_err(sp, "paths cannot contain empty segments")
|
||||||
.note(&format!("expected {:?}, found {:?}", cleaned, uri.as_str()))
|
.note(&format!("expected '{}', found '{}'", normalized, uri))
|
||||||
.emit()
|
.emit();
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
uri.is_normalized()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn valid_segments(ecx: &ExtCtxt, uri: &Uri, sp: Span) -> bool {
|
fn valid_segments(ecx: &ExtCtxt, uri: &Origin, sp: Span) -> bool {
|
||||||
let mut validated = true;
|
let mut validated = true;
|
||||||
let mut segments_span = None;
|
let mut segments_span = None;
|
||||||
for segment in uri.segments() {
|
for segment in uri.segments() {
|
||||||
|
@ -97,18 +92,32 @@ fn valid_segments(ecx: &ExtCtxt, uri: &Uri, sp: Span) -> bool {
|
||||||
validated
|
validated
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_uri(ecx: &ExtCtxt,
|
pub fn validate_uri(
|
||||||
string: &str,
|
ecx: &ExtCtxt,
|
||||||
sp: Span)
|
string: &str,
|
||||||
-> (Spanned<Uri<'static>>, Option<Spanned<Ident>>) {
|
sp: Span,
|
||||||
let uri = Uri::from(string.to_string());
|
) -> (Spanned<Origin<'static>>, Option<Spanned<Ident>>) {
|
||||||
let query_param = string.find('?')
|
let query_param = string.find('?')
|
||||||
.map(|i| span(&string[(i + 1)..], sp.trim_left(i + 1)))
|
.map(|i| span(&string[(i + 1)..], sp.trim_left(i + 1)))
|
||||||
.and_then(|spanned_q_param| param_to_ident(ecx, spanned_q_param));
|
.and_then(|spanned_q_param| param_to_ident(ecx, spanned_q_param));
|
||||||
|
|
||||||
if valid_segments(ecx, &uri, sp) && valid_path(ecx, &uri, sp) {
|
let dummy = (dummy_spanned(Origin::dummy()), query_param);
|
||||||
(span(uri, sp), query_param)
|
match Origin::parse_route(string) {
|
||||||
} else {
|
Ok(uri) => {
|
||||||
(dummy_spanned(Uri::new("")), query_param)
|
let uri = uri.into_owned();
|
||||||
|
if valid_segments(ecx, &uri, sp) && valid_path(ecx, &uri, sp) {
|
||||||
|
return (span(uri, sp), query_param)
|
||||||
|
}
|
||||||
|
|
||||||
|
dummy
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
ecx.struct_span_err(sp, &format!("invalid path URI: {}", e))
|
||||||
|
.note("expected path URI in origin form")
|
||||||
|
.help("example: /path/<route>")
|
||||||
|
.emit();
|
||||||
|
|
||||||
|
dummy
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,12 @@ pub enum Args {
|
||||||
Named(Vec<(Spanned<Ident>, P<Expr>)>),
|
Named(Vec<(Spanned<Ident>, P<Expr>)>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For an invocation that looks like:
|
||||||
|
// uri!("/mount/point", this::route: e1, e2, e3);
|
||||||
|
// ^-------------| ^----------| ^---------|
|
||||||
|
// uri_params.mount_point | uri_params.arguments
|
||||||
|
// uri_params.route_path
|
||||||
|
//
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UriParams {
|
pub struct UriParams {
|
||||||
pub mount_point: Option<Spanned<LocalInternedString>>,
|
pub mount_point: Option<Spanned<LocalInternedString>>,
|
||||||
|
@ -31,6 +37,9 @@ pub struct UriParams {
|
||||||
pub arguments: Option<Spanned<Args>>,
|
pub arguments: Option<Spanned<Args>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `fn_args` are the URI arguments (excluding guards) from the original route's
|
||||||
|
// handler in the order they were declared in the URI (`<first>/<second>`).
|
||||||
|
// `uri` is the full URI used in the origin route's attribute
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct InternalUriParams {
|
pub struct InternalUriParams {
|
||||||
pub uri: Spanned<String>,
|
pub uri: Spanned<String>,
|
||||||
|
@ -252,11 +261,4 @@ impl InternalUriParams {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uri_fmt_string(&self) -> String {
|
|
||||||
self.uri.node
|
|
||||||
.replace('<', "{fmt")
|
|
||||||
.replace("..>", "}")
|
|
||||||
.replace('>', "}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
#![feature(plugin, decl_macro)]
|
#![feature(plugin, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
#![plugin(rocket_codegen)]
|
||||||
|
|
||||||
#[get("a")] //~ ERROR absolute
|
#[get("a")] //~ ERROR invalid
|
||||||
fn get() -> &'static str { "hi" }
|
fn get() -> &'static str { "hi" }
|
||||||
|
|
||||||
#[get("")] //~ ERROR absolute
|
#[get("")] //~ ERROR invalid
|
||||||
fn get1(id: usize) -> &'static str { "hi" }
|
fn get1(id: usize) -> &'static str { "hi" }
|
||||||
|
|
||||||
#[get("a/b/c")] //~ ERROR absolute
|
#[get("a/b/c")] //~ ERROR invalid
|
||||||
fn get2(id: usize) -> &'static str { "hi" }
|
fn get2(id: usize) -> &'static str { "hi" }
|
||||||
|
|
||||||
fn main() { }
|
fn main() { }
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use rocket::http::uri::SegmentError;
|
use rocket::request::SegmentError;
|
||||||
|
|
||||||
#[post("/<a>/<b..>")]
|
#[post("/<a>/<b..>")]
|
||||||
fn get(a: String, b: PathBuf) -> String {
|
fn get(a: String, b: PathBuf) -> String {
|
||||||
|
|
|
@ -8,7 +8,7 @@ use std::fmt;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use rocket::http::{RawStr, Cookies};
|
use rocket::http::{RawStr, Cookies};
|
||||||
use rocket::http::uri::{Uri, UriDisplay, FromUriParam};
|
use rocket::http::uri::{Origin, UriDisplay, FromUriParam};
|
||||||
use rocket::request::Form;
|
use rocket::request::Form;
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
|
@ -85,7 +85,7 @@ fn param_and_segments(path: PathBuf, id: usize) -> &'static str { "" }
|
||||||
fn guarded_segments(cookies: Cookies, path: PathBuf, id: usize) -> &'static str { "" }
|
fn guarded_segments(cookies: Cookies, path: PathBuf, id: usize) -> &'static str { "" }
|
||||||
|
|
||||||
macro assert_uri_eq($($uri:expr => $expected:expr,)+) {
|
macro assert_uri_eq($($uri:expr => $expected:expr,)+) {
|
||||||
$(assert_eq!($uri, Uri::from($expected));)+
|
$(assert_eq!($uri, Origin::parse($expected).expect("valid origin URI"));)+
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -21,7 +21,7 @@ impl MemberExt for Member {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait FieldsExt {
|
pub trait FieldsExt {
|
||||||
fn len(&self) -> usize;
|
fn len(&self) -> usize;
|
||||||
fn is_empty(&self) -> bool;
|
fn is_empty(&self) -> bool;
|
||||||
fn named(&self) -> Option<&FieldsNamed>;
|
fn named(&self) -> Option<&FieldsNamed>;
|
||||||
|
|
|
@ -24,6 +24,7 @@ hyper = { version = "0.10.13", default-features = false }
|
||||||
time = "0.1"
|
time = "0.1"
|
||||||
indexmap = "1.0"
|
indexmap = "1.0"
|
||||||
rustls = { version = "0.13", optional = true }
|
rustls = { version = "0.13", optional = true }
|
||||||
|
state = "0.4"
|
||||||
|
|
||||||
[dependencies.cookie]
|
[dependencies.cookie]
|
||||||
git = "https://github.com/alexcrichton/cookie-rs"
|
git = "https://github.com/alexcrichton/cookie-rs"
|
||||||
|
|
|
@ -163,7 +163,7 @@ impl PartialEq for AcceptParams {
|
||||||
/// let response = Response::build().header(Accept::JSON).finalize();
|
/// let response = Response::build().header(Accept::JSON).finalize();
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Accept(pub(crate) AcceptParams);
|
pub struct Accept(crate AcceptParams);
|
||||||
|
|
||||||
macro_rules! accept_constructor {
|
macro_rules! accept_constructor {
|
||||||
($($name:ident ($check:ident): $str:expr, $t:expr,
|
($($name:ident ($check:ident): $str:expr, $t:expr,
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
|
//! Extension traits implemented by several HTTP types.
|
||||||
|
|
||||||
use smallvec::{Array, SmallVec};
|
use smallvec::{Array, SmallVec};
|
||||||
|
|
||||||
// TODO: It would be nice if we could somehow have one trait that could give us
|
// TODO: It would be nice if we could somehow have one trait that could give us
|
||||||
// either SmallVec or Vec.
|
// either SmallVec or Vec.
|
||||||
|
/// Trait implemented by types that can be converted into a collection.
|
||||||
pub trait IntoCollection<T> {
|
pub trait IntoCollection<T> {
|
||||||
|
/// Converts `self` into a collection.
|
||||||
fn into_collection<A: Array<Item=T>>(self) -> SmallVec<A>;
|
fn into_collection<A: Array<Item=T>>(self) -> SmallVec<A>;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
fn mapped<U, F: FnMut(T) -> U, A: Array<Item=U>>(self, f: F) -> SmallVec<A>;
|
fn mapped<U, F: FnMut(T) -> U, A: Array<Item=U>>(self, f: F) -> SmallVec<A>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,3 +68,33 @@ impl_for_slice!(; 7);
|
||||||
impl_for_slice!(; 8);
|
impl_for_slice!(; 8);
|
||||||
impl_for_slice!(; 9);
|
impl_for_slice!(; 9);
|
||||||
impl_for_slice!(; 10);
|
impl_for_slice!(; 10);
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
/// Trait implemented by types that can be converted into owned versions of
|
||||||
|
/// themselves.
|
||||||
|
pub trait IntoOwned {
|
||||||
|
/// The owned version of the type.
|
||||||
|
type Owned: 'static;
|
||||||
|
|
||||||
|
/// Converts `self` into an owned version of itself.
|
||||||
|
fn into_owned(self) -> Self::Owned;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: IntoOwned> IntoOwned for Option<T> {
|
||||||
|
type Owned = Option<T::Owned>;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn into_owned(self) -> Self::Owned {
|
||||||
|
self.map(|inner| inner.into_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, B: 'static + ToOwned + ?Sized> IntoOwned for Cow<'a, B> {
|
||||||
|
type Owned = Cow<'static, B>;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn into_owned(self) -> Self::Owned {
|
||||||
|
Cow::Owned(self.into_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#![feature(specialization)]
|
#![feature(specialization)]
|
||||||
#![feature(proc_macro_non_items, use_extern_macros)]
|
#![feature(proc_macro_non_items, use_extern_macros)]
|
||||||
#![feature(const_fn)]
|
#![feature(const_fn)]
|
||||||
|
#![feature(try_from)]
|
||||||
|
#![feature(crate_visibility_modifier)]
|
||||||
#![recursion_limit="256"]
|
#![recursion_limit="256"]
|
||||||
|
|
||||||
//! Types that map to concepts in HTTP.
|
//! Types that map to concepts in HTTP.
|
||||||
|
@ -19,10 +21,13 @@ extern crate percent_encoding;
|
||||||
extern crate cookie;
|
extern crate cookie;
|
||||||
extern crate time;
|
extern crate time;
|
||||||
extern crate indexmap;
|
extern crate indexmap;
|
||||||
|
extern crate state;
|
||||||
|
|
||||||
pub mod hyper;
|
pub mod hyper;
|
||||||
pub mod uri;
|
pub mod uri;
|
||||||
|
pub mod ext;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
pub mod tls;
|
pub mod tls;
|
||||||
|
|
||||||
|
@ -38,16 +43,20 @@ mod status;
|
||||||
mod header;
|
mod header;
|
||||||
mod accept;
|
mod accept;
|
||||||
mod raw_str;
|
mod raw_str;
|
||||||
mod ext;
|
|
||||||
|
|
||||||
pub(crate) mod parse;
|
crate mod parse;
|
||||||
|
|
||||||
|
pub mod uncased;
|
||||||
|
|
||||||
// We need to export these for codegen, but otherwise it's unnecessary.
|
// We need to export these for codegen, but otherwise it's unnecessary.
|
||||||
// TODO: Expose a `const fn` from ContentType when possible. (see RFC#1817)
|
// TODO: Expose a `const fn` from ContentType when possible. (see RFC#1817)
|
||||||
pub mod uncased;
|
// FIXME(rustc): These show up in the rexported module.
|
||||||
#[doc(hidden)] pub use self::parse::Indexed;
|
#[doc(hidden)] pub use self::parse::Indexed;
|
||||||
#[doc(hidden)] pub use self::media_type::{MediaParams, Source};
|
#[doc(hidden)] pub use self::media_type::{MediaParams, Source};
|
||||||
|
|
||||||
|
// This one we need to expose for core.
|
||||||
|
#[doc(hidden)] pub use self::cookies::{Key, CookieJar};
|
||||||
|
|
||||||
pub use self::method::Method;
|
pub use self::method::Method;
|
||||||
pub use self::content_type::ContentType;
|
pub use self::content_type::ContentType;
|
||||||
pub use self::accept::{Accept, QMediaType};
|
pub use self::accept::{Accept, QMediaType};
|
||||||
|
@ -57,6 +66,3 @@ pub use self::raw_str::RawStr;
|
||||||
|
|
||||||
pub use self::media_type::MediaType;
|
pub use self::media_type::MediaType;
|
||||||
pub use self::cookies::{Cookie, SameSite, Cookies};
|
pub use self::cookies::{Cookie, SameSite, Cookies};
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub use self::cookies::{Key, CookieJar};
|
|
||||||
|
|
|
@ -2,10 +2,12 @@ use pear::parser;
|
||||||
use pear::parsers::*;
|
use pear::parsers::*;
|
||||||
|
|
||||||
use {Accept, QMediaType};
|
use {Accept, QMediaType};
|
||||||
use parse::{Input, Result};
|
|
||||||
use parse::checkers::is_whitespace;
|
use parse::checkers::is_whitespace;
|
||||||
use parse::media_type::media_type;
|
use parse::media_type::media_type;
|
||||||
|
|
||||||
|
type Input<'a> = ::parse::IndexedInput<'a, str>;
|
||||||
|
type Result<'a, T> = ::pear::Result<T, Input<'a>>;
|
||||||
|
|
||||||
#[parser]
|
#[parser]
|
||||||
fn weighted_media_type<'a>(input: &mut Input<'a>) -> Result<'a, QMediaType> {
|
fn weighted_media_type<'a>(input: &mut Input<'a>) -> Result<'a, QMediaType> {
|
||||||
let media_type = media_type()?;
|
let media_type = media_type()?;
|
||||||
|
|
|
@ -6,7 +6,11 @@ use std::fmt::{self, Debug};
|
||||||
|
|
||||||
use pear::{Input, Length};
|
use pear::{Input, Length};
|
||||||
|
|
||||||
|
use ext::IntoOwned;
|
||||||
|
|
||||||
pub type IndexedString = Indexed<'static, str>;
|
pub type IndexedString = Indexed<'static, str>;
|
||||||
|
pub type IndexedStr<'a> = Indexed<'a, str>;
|
||||||
|
pub type IndexedBytes<'a> = Indexed<'a, [u8]>;
|
||||||
|
|
||||||
pub trait AsPtr {
|
pub trait AsPtr {
|
||||||
fn as_ptr(&self) -> *const u8;
|
fn as_ptr(&self) -> *const u8;
|
||||||
|
@ -48,9 +52,7 @@ impl<'a, T: ?Sized + ToOwned + 'a> Indexed<'a, T> {
|
||||||
_ => panic!("cannot convert indexed T to U unless indexed")
|
_ => panic!("cannot convert indexed T to U unless indexed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: ?Sized + ToOwned + 'a> Indexed<'a, T> {
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn coerce_lifetime<'b>(self) -> Indexed<'b, T> {
|
pub fn coerce_lifetime<'b>(self) -> Indexed<'b, T> {
|
||||||
match self {
|
match self {
|
||||||
|
@ -60,6 +62,17 @@ impl<'a, T: ?Sized + ToOwned + 'a> Indexed<'a, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, T: 'static + ?Sized + ToOwned> IntoOwned for Indexed<'a, T> {
|
||||||
|
type Owned = Indexed<'static, T>;
|
||||||
|
|
||||||
|
fn into_owned(self) -> Indexed<'static, T> {
|
||||||
|
match self {
|
||||||
|
Indexed::Indexed(a, b) => Indexed::Indexed(a, b),
|
||||||
|
Indexed::Concrete(cow) => Indexed::Concrete(IntoOwned::into_owned(cow))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
|
|
||||||
impl<'a, T: ?Sized + ToOwned + 'a> Add for Indexed<'a, T> {
|
impl<'a, T: ?Sized + ToOwned + 'a> Add for Indexed<'a, T> {
|
||||||
|
@ -303,12 +316,12 @@ pub struct Context {
|
||||||
impl ::std::fmt::Display for Context {
|
impl ::std::fmt::Display for Context {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
const LIMIT: usize = 7;
|
const LIMIT: usize = 7;
|
||||||
write!(f, "{}", self.offset)?;
|
write!(f, "[{}:]", self.offset)?;
|
||||||
|
|
||||||
if self.string.len() > LIMIT {
|
if self.string.len() > LIMIT {
|
||||||
write!(f, " ({}..)", &self.string[..LIMIT])
|
write!(f, " {}..", &self.string[..LIMIT])
|
||||||
} else if !self.string.is_empty() {
|
} else if !self.string.is_empty() {
|
||||||
write!(f, " ({})", &self.string)
|
write!(f, " {}", &self.string)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,13 @@ use pear::parsers::*;
|
||||||
|
|
||||||
use {MediaType, Source};
|
use {MediaType, Source};
|
||||||
use parse::checkers::{is_whitespace, is_valid_token};
|
use parse::checkers::{is_whitespace, is_valid_token};
|
||||||
use parse::{Input, Slice, Result};
|
use parse::IndexedStr;
|
||||||
|
|
||||||
|
type Input<'a> = ::parse::IndexedInput<'a, str>;
|
||||||
|
type Result<'a, T> = ::pear::Result<T, Input<'a>>;
|
||||||
|
|
||||||
#[parser]
|
#[parser]
|
||||||
fn quoted_string<'a>(input: &mut Input<'a>) -> Result<'a, Slice<'a>> {
|
fn quoted_string<'a>(input: &mut Input<'a>) -> Result<'a, IndexedStr<'a>> {
|
||||||
eat('"')?;
|
eat('"')?;
|
||||||
|
|
||||||
let mut is_escaped = false;
|
let mut is_escaped = false;
|
||||||
|
@ -23,7 +26,7 @@ fn quoted_string<'a>(input: &mut Input<'a>) -> Result<'a, Slice<'a>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[parser]
|
#[parser]
|
||||||
fn media_param<'a>(input: &mut Input<'a>) -> Result<'a, (Slice<'a>, Slice<'a>)> {
|
fn media_param<'a>(input: &mut Input<'a>) -> Result<'a, (IndexedStr<'a>, IndexedStr<'a>)> {
|
||||||
let key = (take_some_while_until(is_valid_token, '=')?, eat('=')?).0;
|
let key = (take_some_while_until(is_valid_token, '=')?, eat('=')?).0;
|
||||||
let value = switch! {
|
let value = switch! {
|
||||||
peek('"') => quoted_string()?,
|
peek('"') => quoted_string()?,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
mod media_type;
|
mod media_type;
|
||||||
mod accept;
|
mod accept;
|
||||||
mod indexed;
|
|
||||||
mod checkers;
|
mod checkers;
|
||||||
|
mod indexed;
|
||||||
|
|
||||||
pub use self::indexed::*;
|
|
||||||
pub use self::media_type::*;
|
pub use self::media_type::*;
|
||||||
pub use self::accept::*;
|
pub use self::accept::*;
|
||||||
|
|
||||||
pub type Input<'a> = IndexedInput<'a, str>;
|
pub mod uri;
|
||||||
pub type Slice<'a> = Indexed<'a, str>;
|
|
||||||
pub type Result<'a, T> = ::pear::Result<T, Input<'a>>;
|
// Exposed for codegen.
|
||||||
|
#[doc(hidden)] pub use self::indexed::*;
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
use std::fmt;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use pear::{ParseErr, Expected};
|
||||||
|
use parse::indexed::Context;
|
||||||
|
use parse::uri::RawInput;
|
||||||
|
use ext::IntoOwned;
|
||||||
|
|
||||||
|
/// Error emitted on URI parse failure.
|
||||||
|
///
|
||||||
|
/// Internally, the type includes information about where the parse error
|
||||||
|
/// occured (the error's context) and information about what went wrong.
|
||||||
|
/// Externally, this information can be retrieved (in textual form) through its
|
||||||
|
/// `Display` implementation. In other words, by printing a value of this type.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error<'a> {
|
||||||
|
expected: Expected<Or<char, u8>, Cow<'a, str>, String>,
|
||||||
|
context: Option<Context>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Or<L, R> {
|
||||||
|
A(L),
|
||||||
|
B(R)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Error<'a> {
|
||||||
|
crate fn from(src: &'a str, pear_error: ParseErr<RawInput<'a>>) -> Error<'a> {
|
||||||
|
let new_expected = pear_error.expected.map(|token| {
|
||||||
|
if token.is_ascii() && !token.is_ascii_control() {
|
||||||
|
Or::A(token as char)
|
||||||
|
} else {
|
||||||
|
Or::B(token)
|
||||||
|
}
|
||||||
|
}, String::from_utf8_lossy, |indexed| {
|
||||||
|
let src = Some(src.as_bytes());
|
||||||
|
String::from_utf8_lossy(indexed.from_source(src)).to_string()
|
||||||
|
});
|
||||||
|
|
||||||
|
Error { expected: new_expected, context: pear_error.context }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Or<char, u8> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
Or::A(left) => write!(f, "'{}'", left),
|
||||||
|
Or::B(right) => write!(f, "non-ASCII byte {}", right),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> fmt::Display for Error<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// This relies on specialization of the `Display` impl for `Expected`.
|
||||||
|
write!(f, "{}", self.expected)?;
|
||||||
|
|
||||||
|
if let Some(ref context) = self.context {
|
||||||
|
write!(f, " at index {}", context.offset)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoOwned for Error<'a> {
|
||||||
|
type Owned = Error<'static>;
|
||||||
|
|
||||||
|
fn into_owned(self) -> Self::Owned {
|
||||||
|
let expected = self.expected.map(|i| i, IntoOwned::into_owned, |i| i);
|
||||||
|
Error { expected, context: self.context }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use parse::uri::origin_from_str;
|
||||||
|
|
||||||
|
macro_rules! check_err {
|
||||||
|
($url:expr => $error:expr) => {{
|
||||||
|
let e = origin_from_str($url).unwrap_err();
|
||||||
|
assert_eq!(e.to_string(), $error.to_string())
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_display() {
|
||||||
|
check_err!("a" => "expected token '/' but found 'a' at index 0");
|
||||||
|
check_err!("?" => "expected token '/' but found '?' at index 0");
|
||||||
|
check_err!("这" => "expected token '/' but found non-ASCII byte 232 at index 0");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
mod parser;
|
||||||
|
mod error;
|
||||||
|
mod tables;
|
||||||
|
#[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};
|
||||||
|
|
||||||
|
pub use self::error::Error;
|
||||||
|
|
||||||
|
type RawInput<'a> = IndexedInput<'a, [u8]>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn from_str(string: &str) -> Result<Uri, Error> {
|
||||||
|
parse!(uri: &mut RawInput::from(string.as_bytes()))
|
||||||
|
.map_err(|e| Error::from(string, e))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn origin_from_str(string: &str) -> Result<Origin, Error> {
|
||||||
|
parse!(origin: &mut RawInput::from(string.as_bytes()))
|
||||||
|
.map_err(|e| Error::from(string, e))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn route_origin_from_str(string: &str) -> Result<Origin, Error> {
|
||||||
|
parse!(rocket_route_origin: &mut RawInput::from(string.as_bytes()))
|
||||||
|
.map_err(|e| Error::from(string, e))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn authority_from_str(string: &str) -> Result<Authority, Error> {
|
||||||
|
parse!(authority_only: &mut RawInput::from(string.as_bytes()))
|
||||||
|
.map_err(|e| Error::from(string, e))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn absolute_from_str(string: &str) -> Result<Absolute, Error> {
|
||||||
|
parse!(absolute_only: &mut RawInput::from(string.as_bytes()))
|
||||||
|
.map_err(|e| Error::from(string, e))
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
use pear::parsers::*;
|
||||||
|
use pear::{parser, switch};
|
||||||
|
|
||||||
|
use uri::{Uri, Origin, Authority, Absolute, Host};
|
||||||
|
use parse::uri::tables::{is_reg_name_char, is_pchar, is_pchar_or_rchar};
|
||||||
|
use parse::uri::RawInput;
|
||||||
|
use parse::IndexedBytes;
|
||||||
|
|
||||||
|
type Result<'a, T> = ::pear::Result<T, RawInput<'a>>;
|
||||||
|
|
||||||
|
#[parser]
|
||||||
|
crate fn uri<'a>(input: &mut RawInput<'a>) -> Result<'a, Uri<'a>> {
|
||||||
|
match input.len() {
|
||||||
|
0 => return Err(pear_error!("empty URI")),
|
||||||
|
1 => switch! {
|
||||||
|
eat(b'*') => Uri::Asterisk,
|
||||||
|
eat(b'/') => Uri::Origin(Origin::new::<_, &str>("/", None)),
|
||||||
|
_ => unsafe {
|
||||||
|
// the `is_reg_name_char` guarantees ASCII
|
||||||
|
let host = Host::Raw(take_n_if(1, is_reg_name_char)?);
|
||||||
|
Uri::Authority(Authority::raw(input.cow_source(), None, host, None))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => switch! {
|
||||||
|
peek(b'/') => Uri::Origin(origin()?),
|
||||||
|
_ => absolute_or_authority()?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[parser]
|
||||||
|
crate fn origin<'a>(input: &mut RawInput<'a>) -> Result<'a, Origin<'a>> {
|
||||||
|
(peek(b'/')?, path_and_query(is_pchar)?).1
|
||||||
|
}
|
||||||
|
|
||||||
|
#[parser]
|
||||||
|
crate fn rocket_route_origin<'a>(input: &mut RawInput<'a>) -> Result<'a, Origin<'a>> {
|
||||||
|
(peek(b'/')?, path_and_query(is_pchar_or_rchar)?).1
|
||||||
|
}
|
||||||
|
|
||||||
|
#[parser]
|
||||||
|
fn path_and_query<'a, F>(input: &mut RawInput<'a>, is_good_char: F) -> Result<'a, Origin<'a>>
|
||||||
|
where F: Fn(u8) -> bool + Copy
|
||||||
|
{
|
||||||
|
let path = take_while(is_good_char)?;
|
||||||
|
|
||||||
|
// FIXME(rustc): We should be able to use `pear_try`, but rustc...is broken.
|
||||||
|
let query = switch! {
|
||||||
|
eat(b'?') => Some(take_while(|c| is_good_char(c) || c == b'?')?),
|
||||||
|
_ => None
|
||||||
|
};
|
||||||
|
|
||||||
|
if path.is_empty() && query.is_none() {
|
||||||
|
Err(pear_error!("expected path or query, found neither"))
|
||||||
|
} else {
|
||||||
|
// We know the string is ASCII because of the `is_good_char` checks above.
|
||||||
|
Ok(unsafe { Origin::raw(input.cow_source(), path, query) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[parser]
|
||||||
|
fn port_from<'a>(input: &mut RawInput<'a>, bytes: &IndexedBytes<'a>) -> Result<'a, u16> {
|
||||||
|
let mut port_num: u32 = 0;
|
||||||
|
let source = Some(input.cow_source());
|
||||||
|
let string = bytes.from_cow_source(&source);
|
||||||
|
for (&b, i) in string.iter().rev().zip(&[1, 10, 100, 1000, 10000]) {
|
||||||
|
if b < b'0' || b > b'9' {
|
||||||
|
return Err(pear_error!("port byte is out of range"));
|
||||||
|
}
|
||||||
|
|
||||||
|
port_num += (b - b'0') as u32 * i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if port_num > u16::max_value() as u32 {
|
||||||
|
return Err(pear_error!("port value out of range: {}", port_num));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(port_num as u16)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[parser]
|
||||||
|
fn port<'a>(input: &mut RawInput<'a>) -> Result<'a, u16> {
|
||||||
|
let port_str = take_n_while(5, |c| c >= b'0' && c <= b'9')?;
|
||||||
|
port_from(&port_str)?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[parser]
|
||||||
|
fn authority<'a>(
|
||||||
|
input: &mut RawInput<'a>,
|
||||||
|
user_info: Option<IndexedBytes<'a>>
|
||||||
|
) -> Result<'a, Authority<'a>> {
|
||||||
|
let host = switch! {
|
||||||
|
peek(b'[') => Host::Bracketed(delimited(b'[', is_pchar, b']')?),
|
||||||
|
_ => Host::Raw(take_while(is_reg_name_char)?)
|
||||||
|
};
|
||||||
|
|
||||||
|
// The `is_pchar`,`is_reg_name_char`, and `port()` functions ensure ASCII.
|
||||||
|
let port = pear_try!(eat(b':') => port()?);
|
||||||
|
unsafe { Authority::raw(input.cow_source(), user_info, host, port) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callers must ensure that `scheme` is actually ASCII.
|
||||||
|
#[parser]
|
||||||
|
fn absolute<'a>(
|
||||||
|
input: &mut RawInput<'a>,
|
||||||
|
scheme: IndexedBytes<'a>
|
||||||
|
) -> Result<'a, Absolute<'a>> {
|
||||||
|
let (authority, path_and_query) = switch! {
|
||||||
|
eat_slice(b"://") => {
|
||||||
|
let left = take_while(|c| is_reg_name_char(c) || c == b':')?;
|
||||||
|
let authority = switch! {
|
||||||
|
eat(b'@') => authority(Some(left))?,
|
||||||
|
_ => {
|
||||||
|
input.backtrack(left.len())?;
|
||||||
|
authority(None)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let path_and_query = pear_try!(path_and_query(is_pchar));
|
||||||
|
(Some(authority), path_and_query)
|
||||||
|
},
|
||||||
|
eat(b':') => (None, Some(path_and_query(is_pchar)?)),
|
||||||
|
_ => return Err(pear_error!("expected ':' but none was found"))
|
||||||
|
};
|
||||||
|
|
||||||
|
// `authority` and `path_and_query` parsers ensure ASCII.
|
||||||
|
unsafe { Absolute::raw(input.cow_source(), scheme, authority, path_and_query) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[parser]
|
||||||
|
crate fn authority_only<'a>(input: &mut RawInput<'a>) -> Result<'a, Authority<'a>> {
|
||||||
|
if let Uri::Authority(authority) = absolute_or_authority()? {
|
||||||
|
Ok(authority)
|
||||||
|
} else {
|
||||||
|
Err(pear_error!("expected authority URI but found absolute URI"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[parser]
|
||||||
|
crate fn absolute_only<'a>(input: &mut RawInput<'a>) -> Result<'a, Absolute<'a>> {
|
||||||
|
if let Uri::Absolute(absolute) = absolute_or_authority()? {
|
||||||
|
Ok(absolute)
|
||||||
|
} else {
|
||||||
|
Err(pear_error!("expected absolute URI but found authority URI"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[parser]
|
||||||
|
fn absolute_or_authority<'a>(
|
||||||
|
input: &mut RawInput<'a>,
|
||||||
|
) -> Result<'a, Uri<'a>> {
|
||||||
|
let left = take_while(is_reg_name_char)?;
|
||||||
|
switch! {
|
||||||
|
peek_slice(b":/") => Uri::Absolute(absolute(left)?),
|
||||||
|
eat(b'@') => Uri::Authority(authority(Some(left))?),
|
||||||
|
colon@take_n_if(1, |b| b == b':') => {
|
||||||
|
// could be authority or an IP with ':' in it
|
||||||
|
let rest = take_while(|c| is_reg_name_char(c) || c == b':')?;
|
||||||
|
switch! {
|
||||||
|
eat(b'@') => Uri::Authority(authority(Some(left + colon + rest))?),
|
||||||
|
peek(b'/') => {
|
||||||
|
input.backtrack(rest.len() + 1)?;
|
||||||
|
Uri::Absolute(absolute(left)?)
|
||||||
|
},
|
||||||
|
_ => unsafe {
|
||||||
|
// Here we hit an ambiguity: `rest` could be a port in
|
||||||
|
// host:port or a host in scheme:host. Both are correct
|
||||||
|
// parses. To settle the ambiguity, we assume that if it
|
||||||
|
// looks like a port, it's a port. Otherwise a host. Unless
|
||||||
|
// we have a query, in which case it's definitely a host.
|
||||||
|
let query = pear_try!(eat(b'?') => take_while(is_pchar)?);
|
||||||
|
if query.is_some() || rest.is_empty() || rest.len() > 5 {
|
||||||
|
Uri::raw_absolute(input.cow_source(), left, rest, query)
|
||||||
|
} else if let Ok(port) = port_from(input, &rest) {
|
||||||
|
let host = Host::Raw(left);
|
||||||
|
let source = input.cow_source();
|
||||||
|
let port = Some(port);
|
||||||
|
Uri::Authority(Authority::raw(source, None, host, port))
|
||||||
|
} else {
|
||||||
|
Uri::raw_absolute(input.cow_source(), left, rest, query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
input.backtrack(left.len())?;
|
||||||
|
Uri::Authority(authority(None)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
RFC 7230 URI Grammar
|
||||||
|
|
||||||
|
request-target = origin-form / absolute-form / authority-form / asterisk-form
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
asterisk-form = "*"
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
origin-form = absolute-path [ "?" query ]
|
||||||
|
|
||||||
|
absolute-path = 1*( "/" segment )
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
authority-form = authority
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
1. look for ':', '@', '?'
|
||||||
|
2. if neither is found, you have an authority, text is `host`
|
||||||
|
3. if ':' is found, have either 'host', 'scheme', or 'userinfo'
|
||||||
|
* can only be host if: next four characters are port
|
||||||
|
* must be host if: text before ':' is empty, requires port
|
||||||
|
* if next (at most) four characters are numbers, then we have a host/port.
|
||||||
|
* if next character is '/' or there is none, then scheme
|
||||||
|
* otherwise try as scheme, fallback to userinfo if find '@'
|
||||||
|
4. if '?' is found, have either 'host', 'scheme', or 'userinfo'
|
||||||
|
5. if '@' is found, have 'userinfo'
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
absolute-form = absolute-URI
|
||||||
|
|
||||||
|
absolute-URI = scheme ":" hier-part [ "?" query ]
|
||||||
|
|
||||||
|
scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
||||||
|
|
||||||
|
hier-part = "//" authority path-abempty
|
||||||
|
/ path-absolute
|
||||||
|
/ path-rootless
|
||||||
|
/ path-empty
|
||||||
|
|
||||||
|
query = *( pchar / "/" / "?" )
|
||||||
|
|
||||||
|
authority = [ userinfo "@" ] host [ ":" port ]
|
||||||
|
userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
|
||||||
|
host = IP-literal / IPv4address / reg-name
|
||||||
|
port = *DIGIT
|
||||||
|
|
||||||
|
reg-name = *( unreserved / pct-encoded / sub-delims )
|
||||||
|
|
||||||
|
path-abempty = *( "/" segment )
|
||||||
|
|
||||||
|
path-absolute = "/" [ segment-nz *( "/" segment ) ]
|
||||||
|
path-noscheme = segment-nz-nc *( "/" segment )
|
||||||
|
path-rootless = segment-nz *( "/" segment )
|
||||||
|
path-empty = 0<pchar>
|
||||||
|
|
||||||
|
segment = *pchar
|
||||||
|
segment-nz = 1*pchar
|
||||||
|
|
||||||
|
pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||||
|
|
||||||
|
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||||
|
pct-encoded = "%" HEXDIG HEXDIG
|
||||||
|
sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||||
|
/ "*" / "+" / "," / ";" / "="
|
||||||
|
|
||||||
|
IP-literal = "[" ( IPv6address / IPvFuture ) "]"
|
||||||
|
|
||||||
|
IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
|
||||||
|
|
||||||
|
IPv6address = 6( h16 ":" ) ls32
|
||||||
|
/ "::" 5( h16 ":" ) ls32
|
||||||
|
/ [ h16 ] "::" 4( h16 ":" ) ls32
|
||||||
|
/ [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
|
||||||
|
/ [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
|
||||||
|
/ [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
|
||||||
|
/ [ *4( h16 ":" ) h16 ] "::" ls32
|
||||||
|
/ [ *5( h16 ":" ) h16 ] "::" h16
|
||||||
|
/ [ *6( h16 ":" ) h16 ] "::"
|
||||||
|
|
||||||
|
IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
|
||||||
|
|
||||||
|
dec-octet = DIGIT ; 0-9
|
||||||
|
/ %x31-39 DIGIT ; 10-99
|
||||||
|
/ "1" 2DIGIT ; 100-199
|
||||||
|
/ "2" %x30-34 DIGIT ; 200-249
|
||||||
|
/ "25" %x30-35 ; 250-255
|
||||||
|
|
||||||
|
ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
|
||||||
|
HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
|
||||||
|
DIGIT = %x30-39 ; 0-9
|
|
@ -0,0 +1,92 @@
|
||||||
|
const PATH_CHARS: [u8; 256] = [
|
||||||
|
// 0 1 2 3 4 5 6 7 8 9
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2x
|
||||||
|
0, 0, 0, b'!', 0, 0, b'$', b'%', b'&', b'\'', // 3x
|
||||||
|
b'(', b')', b'*', b'+', b',', b'-', b'.', b'/', b'0', b'1', // 4x
|
||||||
|
b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', b';', // 5x
|
||||||
|
// < > (1 used to indicate these are valid in route URIs only)
|
||||||
|
1, b'=', 1, 0, b'@', b'A', b'B', b'C', b'D', b'E', // 6x
|
||||||
|
b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 7x
|
||||||
|
b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', // 8x
|
||||||
|
b'Z', 0, 0, 0, 0, b'_', 0, b'a', b'b', b'c', // 9x
|
||||||
|
b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', // 10x
|
||||||
|
b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', // 11x
|
||||||
|
b'x', b'y', b'z', 0, 0, 0, b'~', 0, 0, 0, // 12x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 13x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 14x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 15x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 17x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 18x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 19x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 21x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 22x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 23x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 24x
|
||||||
|
0, 0, 0, 0, 0, 0 // 25x
|
||||||
|
];
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_pchar(c: u8) -> bool {
|
||||||
|
PATH_CHARS[c as usize] == c
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_pchar_or_rchar(c: u8) -> bool {
|
||||||
|
PATH_CHARS[c as usize] != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const REG_CHARS: [u8; 256] = [
|
||||||
|
// 0 1 2 3 4 5 6 7 8 9
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2x
|
||||||
|
0, 0, 0, b'!', 0, 0, b'$', 0, b'&', b'\'', // 3x
|
||||||
|
b'(', b')', b'*', b'+', b',', b'-', b'.', 0, b'0', b'1', // 4x
|
||||||
|
b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', 0, b';', // 5x
|
||||||
|
0, b'=', 0, 0, 0, b'A', b'B', b'C', b'D', b'E', // 6x
|
||||||
|
b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 7x
|
||||||
|
b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', // 8x
|
||||||
|
b'Z', 0, 0, 0, 0, b'_', 0, b'a', b'b', b'c', // 9x
|
||||||
|
b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', // 10x
|
||||||
|
b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', // 11x
|
||||||
|
b'x', b'y', b'z', 0, 0, 0, b'~', 0, 0, 0, // 12x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 13x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 14x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 15x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 17x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 18x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 19x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 21x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 22x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 23x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 24x
|
||||||
|
0, 0, 0, 0, 0, 0 // 25x
|
||||||
|
];
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_reg_name_char(c: u8) -> bool {
|
||||||
|
REG_CHARS[c as usize] != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
fn test_char_table(table: &[u8]) {
|
||||||
|
for (i, &v) in table.iter().enumerate() {
|
||||||
|
if v != 0 && v != 1 {
|
||||||
|
assert_eq!(i, v as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_tables() {
|
||||||
|
test_char_table(&super::PATH_CHARS[..]);
|
||||||
|
test_char_table(&super::REG_CHARS[..]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,228 @@
|
||||||
|
use uri::{Uri, Origin, Authority, Absolute};
|
||||||
|
use parse::uri::*;
|
||||||
|
use uri::Host::*;
|
||||||
|
|
||||||
|
macro_rules! assert_parse_eq {
|
||||||
|
($($from:expr => $to:expr),+) => (
|
||||||
|
$(
|
||||||
|
let expected = $to.into();
|
||||||
|
match from_str($from) {
|
||||||
|
Ok(output) => {
|
||||||
|
if output != expected {
|
||||||
|
println!("Failure on: {:?}", $from);
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("{:?} failed to parse!", $from);
|
||||||
|
panic!("Error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
);
|
||||||
|
|
||||||
|
($($from:expr => $to:expr),+,) => (assert_parse_eq!($($from => $to),+))
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! assert_no_parse {
|
||||||
|
($($from:expr),+) => (
|
||||||
|
$(
|
||||||
|
if let Ok(uri) = from_str($from) {
|
||||||
|
println!("{:?} parsed unexpectedly!", $from);
|
||||||
|
panic!("Parsed as: {:?}", uri);
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
);
|
||||||
|
|
||||||
|
($($from:expr),+,) => (assert_no_parse!($($from),+))
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! assert_displays_eq {
|
||||||
|
($($string:expr),+) => (
|
||||||
|
$(
|
||||||
|
let string = $string.into();
|
||||||
|
match from_str(string) {
|
||||||
|
Ok(output) => {
|
||||||
|
let output_string = output.to_string();
|
||||||
|
if output_string != string {
|
||||||
|
println!("Failure on: {:?}", $string);
|
||||||
|
println!("Got: {:?}", output_string);
|
||||||
|
println!("Parsed as: {:?}", output);
|
||||||
|
panic!("failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("{:?} failed to parse!", $string);
|
||||||
|
panic!("Error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
);
|
||||||
|
|
||||||
|
($($string:expr),+,) => (assert_parse_eq!($($string),+))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uri_origin<'a>(path: &'a str, query: Option<&'a str>) -> Uri<'a> {
|
||||||
|
Uri::Origin(Origin::new(path, query))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_assert_parse_eq() {
|
||||||
|
assert_parse_eq!("*" => uri_origin("*", None));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_assert_parse_eq_consecutive() {
|
||||||
|
assert_parse_eq!("/" => uri_origin("/", None), "/" => Uri::Asterisk);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_assert_no_parse() {
|
||||||
|
assert_no_parse!("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bad_parses() {
|
||||||
|
assert_no_parse!("://z7:77777777777777777777777777777`77777777777");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_byte() {
|
||||||
|
assert_parse_eq!(
|
||||||
|
"*" => Uri::Asterisk,
|
||||||
|
"/" => uri_origin("/", None),
|
||||||
|
"." => Authority::new(None, Raw("."), None),
|
||||||
|
"_" => Authority::new(None, Raw("_"), None),
|
||||||
|
"1" => Authority::new(None, Raw("1"), None),
|
||||||
|
"b" => Authority::new(None, Raw("b"), None),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_no_parse!("?", "#", "%");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn origin() {
|
||||||
|
assert_parse_eq!(
|
||||||
|
"/a/b/c" => uri_origin("/a/b/c", None),
|
||||||
|
"/a/b/c?" => uri_origin("/a/b/c", Some("")),
|
||||||
|
"/a/b/c?abc" => uri_origin("/a/b/c", Some("abc")),
|
||||||
|
"/a/b/c???" => uri_origin("/a/b/c", Some("??")),
|
||||||
|
"/a/b/c?a?b?" => uri_origin("/a/b/c", Some("a?b?")),
|
||||||
|
"/a/b/c?a?b?/c" => uri_origin("/a/b/c", Some("a?b?/c")),
|
||||||
|
"/?abc" => uri_origin("/", Some("abc")),
|
||||||
|
"/hi%20there?a=b&c=d" => uri_origin("/hi%20there", Some("a=b&c=d")),
|
||||||
|
"/c/d/fa/b/c?abc" => uri_origin("/c/d/fa/b/c", Some("abc")),
|
||||||
|
"/xn--ls8h?emoji=poop" => uri_origin("/xn--ls8h", Some("emoji=poop")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn authority() {
|
||||||
|
assert_parse_eq!(
|
||||||
|
"abc" => Authority::new(None, Raw("abc"), None),
|
||||||
|
"@abc" => Authority::new(Some(""), Raw("abc"), None),
|
||||||
|
"sergio:benitez@spark" => Authority::new(Some("sergio:benitez"), Raw("spark"), None),
|
||||||
|
"a:b:c@1.2.3:12121" => Authority::new(Some("a:b:c"), Raw("1.2.3"), Some(12121)),
|
||||||
|
"sergio@spark" => Authority::new(Some("sergio"), Raw("spark"), None),
|
||||||
|
"sergio@spark:230" => Authority::new(Some("sergio"), Raw("spark"), Some(230)),
|
||||||
|
"sergio@[1::]:230" => Authority::new(Some("sergio"), Bracketed("1::"), Some(230)),
|
||||||
|
"google.com:8000" => Authority::new(None, Raw("google.com"), Some(8000)),
|
||||||
|
"[1::2::3]:80" => Authority::new(None, Bracketed("1::2::3"), Some(80)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn absolute() {
|
||||||
|
assert_parse_eq! {
|
||||||
|
"http://foo.com:8000" => Absolute::new(
|
||||||
|
"http",
|
||||||
|
Some(Authority::new(None, Raw("foo.com"), Some(8000))),
|
||||||
|
None
|
||||||
|
),
|
||||||
|
"http://foo:8000" => Absolute::new(
|
||||||
|
"http",
|
||||||
|
Some(Authority::new(None, Raw("foo"), Some(8000))),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
"foo:bar" => Absolute::new(
|
||||||
|
"foo",
|
||||||
|
None,
|
||||||
|
Some(Origin::new::<_, &str>("bar", None)),
|
||||||
|
),
|
||||||
|
"http://sergio:pass@foo.com:8000" => Absolute::new(
|
||||||
|
"http",
|
||||||
|
Some(Authority::new(Some("sergio:pass"), Raw("foo.com"), Some(8000))),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
"foo:/sergio/pass?hi" => Absolute::new(
|
||||||
|
"foo",
|
||||||
|
None,
|
||||||
|
Some(Origin::new("/sergio/pass", Some("hi"))),
|
||||||
|
),
|
||||||
|
"bar:" => Absolute::new(
|
||||||
|
"bar",
|
||||||
|
None,
|
||||||
|
Some(Origin::new::<_, &str>("", None)),
|
||||||
|
),
|
||||||
|
"foo:?hi" => Absolute::new(
|
||||||
|
"foo",
|
||||||
|
None,
|
||||||
|
Some(Origin::new("", Some("hi"))),
|
||||||
|
),
|
||||||
|
"foo:a/b?hi" => Absolute::new(
|
||||||
|
"foo",
|
||||||
|
None,
|
||||||
|
Some(Origin::new("a/b", Some("hi"))),
|
||||||
|
),
|
||||||
|
"foo:a/b" => Absolute::new(
|
||||||
|
"foo",
|
||||||
|
None,
|
||||||
|
Some(Origin::new::<_, &str>("a/b", None)),
|
||||||
|
),
|
||||||
|
"foo:/a/b" => Absolute::new(
|
||||||
|
"foo",
|
||||||
|
None,
|
||||||
|
Some(Origin::new::<_, &str>("/a/b", None))
|
||||||
|
),
|
||||||
|
"abc://u:p@foo.com:123/a/b?key=value&key2=value2" => Absolute::new(
|
||||||
|
"abc",
|
||||||
|
Some(Authority::new(Some("u:p"), Raw("foo.com"), Some(123))),
|
||||||
|
Some(Origin::new("/a/b", Some("key=value&key2=value2"))),
|
||||||
|
),
|
||||||
|
"ftp://foo.com:21/abc" => Absolute::new(
|
||||||
|
"ftp",
|
||||||
|
Some(Authority::new(None, Raw("foo.com"), Some(21))),
|
||||||
|
Some(Origin::new::<_, &str>("/abc", None)),
|
||||||
|
),
|
||||||
|
"http://google.com/abc" => Absolute::new(
|
||||||
|
"http",
|
||||||
|
Some(Authority::new(None, Raw("google.com"), None)),
|
||||||
|
Some(Origin::new::<_, &str>("/abc", None)),
|
||||||
|
),
|
||||||
|
"http://google.com" => Absolute::new(
|
||||||
|
"http",
|
||||||
|
Some(Authority::new(None, Raw("google.com"), None)),
|
||||||
|
None
|
||||||
|
),
|
||||||
|
"http://foo.com?test" => Absolute::new(
|
||||||
|
"http",
|
||||||
|
Some(Authority::new(None, Raw("foo.com"), None,)),
|
||||||
|
Some(Origin::new("", Some("test"))),
|
||||||
|
),
|
||||||
|
"http://google.com/abc?hi" => Absolute::new(
|
||||||
|
"http",
|
||||||
|
Some(Authority::new(None, Raw("google.com"), None,)),
|
||||||
|
Some(Origin::new("/abc", Some("hi"))),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn display() {
|
||||||
|
assert_displays_eq! {
|
||||||
|
"abc", "@):0", "[a]"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
|
use ext::IntoOwned;
|
||||||
|
use parse::{Indexed, IndexedStr};
|
||||||
|
use uri::{Authority, Origin, Error, as_utf8_unchecked};
|
||||||
|
|
||||||
|
/// A URI with a scheme, authority, path, and query:
|
||||||
|
/// `http://user:pass@domain.com:4444/path?query`.
|
||||||
|
///
|
||||||
|
/// # Structure
|
||||||
|
///
|
||||||
|
/// The following diagram illustrates the syntactic structure of an absolute
|
||||||
|
/// URI with all optional parts:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// http://user:pass@domain.com:4444/path?query
|
||||||
|
/// |--| |-----------------------||---------|
|
||||||
|
/// scheme authority origin
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The scheme part of the absolute URI and at least one of authority or origin
|
||||||
|
/// are required.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Absolute<'a> {
|
||||||
|
source: Option<Cow<'a, str>>,
|
||||||
|
scheme: IndexedStr<'a>,
|
||||||
|
authority: Option<Authority<'a>>,
|
||||||
|
origin: Option<Origin<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoOwned for Absolute<'a> {
|
||||||
|
type Owned = Absolute<'static>;
|
||||||
|
|
||||||
|
fn into_owned(self) -> Self::Owned {
|
||||||
|
Absolute {
|
||||||
|
source: self.source.into_owned(),
|
||||||
|
scheme: self.scheme.into_owned(),
|
||||||
|
authority: self.authority.into_owned(),
|
||||||
|
origin: self.origin.into_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Absolute<'a> {
|
||||||
|
#[inline]
|
||||||
|
crate unsafe fn raw(
|
||||||
|
source: Cow<'a, [u8]>,
|
||||||
|
scheme: Indexed<'a, [u8]>,
|
||||||
|
authority: Option<Authority<'a>>,
|
||||||
|
origin: Option<Origin<'a>>,
|
||||||
|
) -> Absolute<'a> {
|
||||||
|
Absolute {
|
||||||
|
source: Some(as_utf8_unchecked(source)),
|
||||||
|
scheme: scheme.coerce(),
|
||||||
|
authority: authority,
|
||||||
|
origin: origin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
crate fn new(
|
||||||
|
scheme: &'a str,
|
||||||
|
authority: Option<Authority<'a>>,
|
||||||
|
origin: Option<Origin<'a>>
|
||||||
|
) -> Absolute<'a> {
|
||||||
|
Absolute {
|
||||||
|
source: None, scheme: scheme.into(), authority, origin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses the string `string` into an `Absolute`. Parsing will never
|
||||||
|
/// allocate. Returns an `Error` if `string` is not a valid absolute URI.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Absolute;
|
||||||
|
///
|
||||||
|
/// // Parse a valid authority URI.
|
||||||
|
/// let uri = Absolute::parse("http://google.com").expect("valid URI");
|
||||||
|
/// assert_eq!(uri.scheme(), "http");
|
||||||
|
/// assert_eq!(uri.authority().unwrap().host(), "google.com");
|
||||||
|
/// assert_eq!(uri.origin(), None);
|
||||||
|
/// ```
|
||||||
|
pub fn parse(string: &'a str) -> Result<Absolute<'a>, Error<'a>> {
|
||||||
|
::parse::uri::absolute_from_str(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the scheme part of the absolute URI.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Absolute;
|
||||||
|
///
|
||||||
|
/// let uri = Absolute::parse("ftp://127.0.0.1").expect("valid URI");
|
||||||
|
/// assert_eq!(uri.scheme(), "ftp");
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn scheme(&self) -> &str {
|
||||||
|
self.scheme.from_cow_source(&self.source)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the authority part of the absolute URI, if there is one.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Absolute;
|
||||||
|
///
|
||||||
|
/// let uri = Absolute::parse("https://rocket.rs:80").expect("valid URI");
|
||||||
|
/// assert_eq!(uri.scheme(), "https");
|
||||||
|
/// let authority = uri.authority().unwrap();
|
||||||
|
/// assert_eq!(authority.host(), "rocket.rs");
|
||||||
|
/// assert_eq!(authority.port(), Some(80));
|
||||||
|
///
|
||||||
|
/// let uri = Absolute::parse("file:/web/home").expect("valid URI");
|
||||||
|
/// assert_eq!(uri.authority(), None);
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn authority(&self) -> Option<&Authority<'a>> {
|
||||||
|
self.authority.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the origin part of the absolute URI, if there is one.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Absolute;
|
||||||
|
///
|
||||||
|
/// let uri = Absolute::parse("file:/web/home.html?new").expect("valid URI");
|
||||||
|
/// assert_eq!(uri.scheme(), "file");
|
||||||
|
/// let origin = uri.origin().unwrap();
|
||||||
|
/// assert_eq!(origin.path(), "/web/home.html");
|
||||||
|
/// assert_eq!(origin.query(), Some("new"));
|
||||||
|
///
|
||||||
|
/// let uri = Absolute::parse("https://rocket.rs").expect("valid URI");
|
||||||
|
/// assert_eq!(uri.origin(), None);
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn origin(&self) -> Option<&Origin<'a>> {
|
||||||
|
self.origin.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> PartialEq<Absolute<'b>> for Absolute<'a> {
|
||||||
|
fn eq(&self, other: &Absolute<'b>) -> bool {
|
||||||
|
self.scheme() == other.scheme()
|
||||||
|
&& self.authority() == other.authority()
|
||||||
|
&& self.origin() == other.origin()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for Absolute<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.scheme())?;
|
||||||
|
match self.authority {
|
||||||
|
Some(ref authority) => write!(f, "://{}", authority)?,
|
||||||
|
None => write!(f, ":")?
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref origin) = self.origin {
|
||||||
|
write!(f, "{}", origin)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,227 @@
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use ext::IntoOwned;
|
||||||
|
use parse::{Indexed, IndexedStr};
|
||||||
|
use uri::{as_utf8_unchecked, Error};
|
||||||
|
|
||||||
|
/// A URI with an authority only: `user:pass@host:8000`.
|
||||||
|
///
|
||||||
|
/// # Structure
|
||||||
|
///
|
||||||
|
/// The following diagram illustrates the syntactic structure of an authority
|
||||||
|
/// URI:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// username:password@some.host:8088
|
||||||
|
/// |---------------| |-------| |--|
|
||||||
|
/// user info host port
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Only the host part of the URI is required.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Authority<'a> {
|
||||||
|
source: Option<Cow<'a, str>>,
|
||||||
|
user_info: Option<IndexedStr<'a>>,
|
||||||
|
host: Host<IndexedStr<'a>>,
|
||||||
|
port: Option<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
crate enum Host<T> {
|
||||||
|
Bracketed(T),
|
||||||
|
Raw(T)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: IntoOwned> IntoOwned for Host<T> {
|
||||||
|
type Owned = Host<T::Owned>;
|
||||||
|
|
||||||
|
fn into_owned(self) -> Self::Owned {
|
||||||
|
self.map_inner(IntoOwned::into_owned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoOwned for Authority<'a> {
|
||||||
|
type Owned = Authority<'static>;
|
||||||
|
|
||||||
|
fn into_owned(self) -> Authority<'static> {
|
||||||
|
Authority {
|
||||||
|
source: self.source.into_owned(),
|
||||||
|
user_info: self.user_info.into_owned(),
|
||||||
|
host: self.host.into_owned(),
|
||||||
|
port: self.port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Authority<'a> {
|
||||||
|
crate unsafe fn raw(
|
||||||
|
source: Cow<'a, [u8]>,
|
||||||
|
user_info: Option<Indexed<'a, [u8]>>,
|
||||||
|
host: Host<Indexed<'a, [u8]>>,
|
||||||
|
port: Option<u16>
|
||||||
|
) -> Authority<'a> {
|
||||||
|
Authority {
|
||||||
|
source: Some(as_utf8_unchecked(source)),
|
||||||
|
user_info: user_info.map(|u| u.coerce()),
|
||||||
|
host: host.map_inner(|inner| inner.coerce()),
|
||||||
|
port: port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
crate fn new(
|
||||||
|
user_info: Option<&'a str>,
|
||||||
|
host: Host<&'a str>,
|
||||||
|
port: Option<u16>
|
||||||
|
) -> Authority<'a> {
|
||||||
|
Authority {
|
||||||
|
source: None,
|
||||||
|
user_info: user_info.map(|u| u.into()),
|
||||||
|
host: host.map_inner(|inner| inner.into()),
|
||||||
|
port: port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses the string `string` into an `Authority`. Parsing will never
|
||||||
|
/// allocate. Returns an `Error` if `string` is not a valid authority URI.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Authority;
|
||||||
|
///
|
||||||
|
/// // Parse a valid authority URI.
|
||||||
|
/// let uri = Authority::parse("user:pass@host").expect("valid URI");
|
||||||
|
/// assert_eq!(uri.user_info(), Some("user:pass"));
|
||||||
|
/// assert_eq!(uri.host(), "host");
|
||||||
|
/// assert_eq!(uri.port(), None);
|
||||||
|
///
|
||||||
|
/// // Invalid authority URIs fail to parse.
|
||||||
|
/// Authority::parse("http://google.com").expect_err("invalid authority");
|
||||||
|
/// ```
|
||||||
|
pub fn parse(string: &'a str) -> Result<Authority<'a>, Error<'a>> {
|
||||||
|
::parse::uri::authority_from_str(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the user info part of the authority URI, if there is one.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Authority;
|
||||||
|
///
|
||||||
|
/// let uri = Authority::parse("username:password@host").unwrap();
|
||||||
|
/// assert_eq!(uri.user_info(), Some("username:password"));
|
||||||
|
/// ```
|
||||||
|
pub fn user_info(&self) -> Option<&str> {
|
||||||
|
self.user_info.as_ref().map(|u| u.from_cow_source(&self.source))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the host part of the authority URI.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// If the host was provided in brackets (such as for IPv6 addresses), the
|
||||||
|
/// brackets will not be part of the returned string.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Authority;
|
||||||
|
///
|
||||||
|
/// let uri = Authority::parse("domain.com:123").unwrap();
|
||||||
|
/// assert_eq!(uri.host(), "domain.com");
|
||||||
|
///
|
||||||
|
/// let uri = Authority::parse("username:password@host:123").unwrap();
|
||||||
|
/// assert_eq!(uri.host(), "host");
|
||||||
|
///
|
||||||
|
/// let uri = Authority::parse("username:password@[1::2]:123").unwrap();
|
||||||
|
/// assert_eq!(uri.host(), "1::2");
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn host(&self) -> &str {
|
||||||
|
self.host.inner().from_cow_source(&self.source)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the port part of the authority URI, if there is one.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Authority;
|
||||||
|
///
|
||||||
|
/// // With a port.
|
||||||
|
/// let uri = Authority::parse("username:password@host:123").unwrap();
|
||||||
|
/// assert_eq!(uri.port(), Some(123));
|
||||||
|
///
|
||||||
|
/// let uri = Authority::parse("domain.com:8181").unwrap();
|
||||||
|
/// assert_eq!(uri.port(), Some(8181));
|
||||||
|
///
|
||||||
|
/// // Without a port.
|
||||||
|
/// let uri = Authority::parse("username:password@host").unwrap();
|
||||||
|
/// assert_eq!(uri.port(), None);
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn port(&self) -> Option<u16> {
|
||||||
|
self.port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> PartialEq<Authority<'b>> for Authority<'a> {
|
||||||
|
fn eq(&self, other: &Authority<'b>) -> bool {
|
||||||
|
self.user_info() == other.user_info()
|
||||||
|
&& self.host() == other.host()
|
||||||
|
&& self.host.is_bracketed() == other.host.is_bracketed()
|
||||||
|
&& self.port() == other.port()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for Authority<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
if let Some(user_info) = self.user_info() {
|
||||||
|
write!(f, "{}@", user_info)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.host {
|
||||||
|
Host::Bracketed(_) => write!(f, "[{}]", self.host())?,
|
||||||
|
Host::Raw(_) => write!(f, "{}", self.host())?
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(port) = self.port {
|
||||||
|
write!(f, ":{}", port)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Host<T> {
|
||||||
|
#[inline]
|
||||||
|
fn inner(&self) -> &T {
|
||||||
|
match *self {
|
||||||
|
Host::Bracketed(ref inner) | Host::Raw(ref inner) => inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_bracketed(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Host::Bracketed(_) => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn map_inner<F, U>(self, f: F) -> Host<U>
|
||||||
|
where F: FnOnce(T) -> U
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Host::Bracketed(inner) => Host::Bracketed(f(inner)),
|
||||||
|
Host::Raw(inner) => Host::Raw(f(inner))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,17 @@
|
||||||
//! Types for absolute URIs and traits for URI display.
|
//! Types for URIs and traits for rendering URI components.
|
||||||
|
|
||||||
mod uri;
|
mod uri;
|
||||||
mod uri_display;
|
mod uri_display;
|
||||||
mod from_uri_param;
|
mod from_uri_param;
|
||||||
|
mod origin;
|
||||||
|
mod authority;
|
||||||
|
mod absolute;
|
||||||
|
|
||||||
|
pub use parse::uri::Error;
|
||||||
|
|
||||||
pub use self::uri::*;
|
pub use self::uri::*;
|
||||||
|
pub use self::authority::*;
|
||||||
|
pub use self::origin::*;
|
||||||
|
pub use self::absolute::*;
|
||||||
pub use self::uri_display::*;
|
pub use self::uri_display::*;
|
||||||
pub use self::from_uri_param::*;
|
pub use self::from_uri_param::*;
|
||||||
|
|
|
@ -0,0 +1,653 @@
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use ext::IntoOwned;
|
||||||
|
use parse::{Indexed, IndexedStr};
|
||||||
|
use uri::{as_utf8_unchecked, Error};
|
||||||
|
|
||||||
|
use state::Storage;
|
||||||
|
|
||||||
|
/// A URI with an absolute path and optional query: `/path?query`.
|
||||||
|
///
|
||||||
|
/// Origin URIs are the primary type of URI encountered in Rocket applications.
|
||||||
|
/// They are also the _simplest_ type of URIs, made up of only a path and an
|
||||||
|
/// optional query.
|
||||||
|
///
|
||||||
|
/// # Structure
|
||||||
|
///
|
||||||
|
/// The following diagram illustrates the syntactic structure of an origin URI:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// /first_segment/second_segment/third?optional=query
|
||||||
|
/// |---------------------------------| |------------|
|
||||||
|
/// path query
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The URI must begin with a `/`, can be followed by any number of _segments_,
|
||||||
|
/// and an optional `?` query separator and query string.
|
||||||
|
///
|
||||||
|
/// # Normalization
|
||||||
|
///
|
||||||
|
/// Rocket prefers, and will sometimes require, origin URIs to be _normalized_.
|
||||||
|
/// A normalized origin URI is a valid origin URI that contains zero empty
|
||||||
|
/// segments except when there are no segments.
|
||||||
|
///
|
||||||
|
/// As an example, the following URIs are all valid, normalized URIs:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// # use rocket::http::uri::Origin;
|
||||||
|
/// # let valid_uris = [
|
||||||
|
/// "/",
|
||||||
|
/// "/a/b/c",
|
||||||
|
/// "/a/b/c?q",
|
||||||
|
/// "/some%20thing"
|
||||||
|
/// # ];
|
||||||
|
/// # for uri in &valid_uris {
|
||||||
|
/// # assert!(Origin::parse(uri).unwrap().is_normalized());
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// By contrast, the following are valid but _abnormal_ URIs:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// # use rocket::http::uri::Origin;
|
||||||
|
/// # let invalid = [
|
||||||
|
/// "//", // one empty segment
|
||||||
|
/// "/a/b/", // trailing empty segment
|
||||||
|
/// "/a/ab//c//d" // two empty segments
|
||||||
|
/// # ];
|
||||||
|
/// # for uri in &invalid {
|
||||||
|
/// # assert!(!Origin::parse(uri).unwrap().is_normalized());
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The [`Origin::to_normalized()`] method can be used to normalize any
|
||||||
|
/// `Origin`:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// # use rocket::http::uri::Origin;
|
||||||
|
/// # let invalid = [
|
||||||
|
/// // abnormal versions
|
||||||
|
/// "//", "/a/b/", "/a/ab//c//d"
|
||||||
|
/// # ,
|
||||||
|
///
|
||||||
|
/// // normalized versions
|
||||||
|
/// "/", "/a/b", "/a/ab/c/d"
|
||||||
|
/// # ];
|
||||||
|
/// # for i in 0..(invalid.len() / 2) {
|
||||||
|
/// # let abnormal = Origin::parse(invalid[i]).unwrap();
|
||||||
|
/// # let expected = Origin::parse(invalid[i + (invalid.len() / 2)]).unwrap();
|
||||||
|
/// # assert_eq!(abnormal.to_normalized(), expected);
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Origin<'a> {
|
||||||
|
crate source: Option<Cow<'a, str>>,
|
||||||
|
crate path: IndexedStr<'a>,
|
||||||
|
crate query: Option<IndexedStr<'a>>,
|
||||||
|
crate segment_count: Storage<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> PartialEq<Origin<'b>> for Origin<'a> {
|
||||||
|
fn eq(&self, other: &Origin<'b>) -> bool {
|
||||||
|
self.path() == other.path() && self.query() == other.query()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoOwned for Origin<'a> {
|
||||||
|
type Owned = Origin<'static>;
|
||||||
|
|
||||||
|
fn into_owned(self) -> Origin<'static> {
|
||||||
|
Origin {
|
||||||
|
source: self.source.into_owned(),
|
||||||
|
path: self.path.into_owned(),
|
||||||
|
query: self.query.into_owned(),
|
||||||
|
segment_count: self.segment_count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Origin<'a> {
|
||||||
|
#[inline]
|
||||||
|
crate unsafe fn raw(
|
||||||
|
source: Cow<'a, [u8]>,
|
||||||
|
path: Indexed<'a, [u8]>,
|
||||||
|
query: Option<Indexed<'a, [u8]>>
|
||||||
|
) -> Origin<'a> {
|
||||||
|
Origin {
|
||||||
|
source: Some(as_utf8_unchecked(source)),
|
||||||
|
path: path.coerce(),
|
||||||
|
query: query.map(|q| q.coerce()),
|
||||||
|
segment_count: Storage::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used mostly for testing and to construct known good URIs from other parts
|
||||||
|
// of Rocket. This should _really_ not be used outside of Rocket because the
|
||||||
|
// resulting `Origin's` may not be valid origin URIs!
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn new<P, Q>(path: P, query: Option<Q>) -> Origin<'a>
|
||||||
|
where P: Into<Cow<'a, str>>, Q: Into<Cow<'a, str>>
|
||||||
|
{
|
||||||
|
Origin {
|
||||||
|
source: None,
|
||||||
|
path: Indexed::from(path),
|
||||||
|
query: query.map(Indexed::from),
|
||||||
|
segment_count: Storage::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to fabricate URIs in several places. Equivalent to `Origin::new("/",
|
||||||
|
// None)` or `Origin::parse("/").unwrap()`. Should not be used outside of
|
||||||
|
// Rocket, though doing so would be less harmful.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn dummy() -> Origin<'static> {
|
||||||
|
Origin::new::<&'static str, &'static str>("/", None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses the string `string` into an `Origin`. Parsing will never
|
||||||
|
/// allocate. Returns an `Error` if `string` is not a valid origin URI.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// // Parse a valid origin URI.
|
||||||
|
/// let uri = Origin::parse("/a/b/c?query").expect("valid URI");
|
||||||
|
/// assert_eq!(uri.path(), "/a/b/c");
|
||||||
|
/// assert_eq!(uri.query(), Some("query"));
|
||||||
|
///
|
||||||
|
/// // Invalid URIs fail to parse.
|
||||||
|
/// Origin::parse("foo bar").expect_err("invalid URI");
|
||||||
|
/// ```
|
||||||
|
pub fn parse(string: &'a str) -> Result<Origin<'a>, Error<'a>> {
|
||||||
|
::parse::uri::origin_from_str(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses an `Origin` that may contain `<` or `>` characters which are
|
||||||
|
// invalid according to the RFC but used by Rocket's routing URIs Don't use
|
||||||
|
// this outside of Rocket!
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn parse_route(string: &'a str) -> Result<Origin<'a>, Error<'a>> {
|
||||||
|
::parse::uri::route_origin_from_str(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses the string `string` into an `Origin`. Parsing will never
|
||||||
|
/// allocate. This method should be used instead of [`Origin::parse()`] when
|
||||||
|
/// the source URI is already a `String`. Returns an `Error` if `string` is
|
||||||
|
/// not a valid origin URI.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let source = format!("/foo/{}/three", 2);
|
||||||
|
/// let uri = Origin::parse_owned(source).expect("valid URI");
|
||||||
|
/// assert_eq!(uri.path(), "/foo/2/three");
|
||||||
|
/// assert_eq!(uri.query(), None);
|
||||||
|
/// ```
|
||||||
|
pub fn parse_owned(string: String) -> Result<Origin<'static>, Error<'static>> {
|
||||||
|
// We create a copy of a pointer to `string` to escape the borrow
|
||||||
|
// checker. This is so that we can "move out of the borrow" later.
|
||||||
|
//
|
||||||
|
// For this to be correct and safe, we need to ensure that:
|
||||||
|
//
|
||||||
|
// 1. No `&mut` references to `string` are created after this line.
|
||||||
|
// 2. `string` isn't dropped by `copy_of_str` is live.
|
||||||
|
//
|
||||||
|
// These two facts can be easily verified. An `&mut` can be created
|
||||||
|
// because `string` isn't `mut`. Then, `string` is clearly not dropped
|
||||||
|
// since it's passed in to `source`.
|
||||||
|
let copy_of_str = unsafe { &*(string.as_str() as *const str) };
|
||||||
|
let origin = Origin::parse(copy_of_str)?;
|
||||||
|
|
||||||
|
let uri = match origin {
|
||||||
|
Origin { source: Some(_), path, query, segment_count } => Origin {
|
||||||
|
segment_count,
|
||||||
|
path: path.into_owned(),
|
||||||
|
query: query.into_owned(),
|
||||||
|
// At this point, it's impossible for anything to be borrowing
|
||||||
|
// `string` except for `source`, even though Rust doesn't know
|
||||||
|
// it. Because we're replacing `source` here, there can't
|
||||||
|
// possibly be a borrow remaining, it's safe to "move out of the
|
||||||
|
// borrow".
|
||||||
|
source: Some(Cow::Owned(string)),
|
||||||
|
},
|
||||||
|
_ => unreachable!("parser always parses with a source")
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if `self` is normalized. Otherwise, returns `false`.
|
||||||
|
///
|
||||||
|
/// See [Normalization](#normalization) for more information on what it
|
||||||
|
/// means for an origin URI to be normalized.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let normal = Origin::parse("/").unwrap();
|
||||||
|
/// assert!(normal.is_normalized());
|
||||||
|
///
|
||||||
|
/// let normal = Origin::parse("/a/b/c").unwrap();
|
||||||
|
/// assert!(normal.is_normalized());
|
||||||
|
///
|
||||||
|
/// let abnormal = Origin::parse("/a/b/c//d").unwrap();
|
||||||
|
/// assert!(!abnormal.is_normalized());
|
||||||
|
/// ```
|
||||||
|
pub fn is_normalized(&self) -> bool {
|
||||||
|
self.path().starts_with('/') &&
|
||||||
|
!self.path().contains("//") &&
|
||||||
|
!(self.path().len() > 1 && self.path().ends_with('/'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Normalizes `self`.
|
||||||
|
///
|
||||||
|
/// See [Normalization](#normalization) for more information on what it
|
||||||
|
/// means for an origin URI to be normalized.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let abnormal = Origin::parse("/a/b/c//d").unwrap();
|
||||||
|
/// assert!(!abnormal.is_normalized());
|
||||||
|
///
|
||||||
|
/// let normalized = abnormal.to_normalized();
|
||||||
|
/// assert!(normalized.is_normalized());
|
||||||
|
/// assert_eq!(normalized, Origin::parse("/a/b/c/d").unwrap());
|
||||||
|
/// ```
|
||||||
|
pub fn to_normalized(&self) -> Origin {
|
||||||
|
if self.is_normalized() {
|
||||||
|
Origin::new(self.path(), self.query())
|
||||||
|
} else {
|
||||||
|
let mut new_path = String::with_capacity(self.path().len());
|
||||||
|
for segment in self.segments() {
|
||||||
|
use std::fmt::Write;
|
||||||
|
let _ = write!(new_path, "/{}", segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_path.is_empty() {
|
||||||
|
new_path.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
Origin::new(new_path, self.query())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the path part of this URI.
|
||||||
|
///
|
||||||
|
/// ### Examples
|
||||||
|
///
|
||||||
|
/// A URI with only a path:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("/a/b/c").unwrap();
|
||||||
|
/// assert_eq!(uri.path(), "/a/b/c");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// A URI with a query:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("/a/b/c?name=bob").unwrap();
|
||||||
|
/// assert_eq!(uri.path(), "/a/b/c");
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn path(&self) -> &str {
|
||||||
|
self.path.from_cow_source(&self.source)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the query part of this URI without the question mark, if there is
|
||||||
|
/// any.
|
||||||
|
///
|
||||||
|
/// ### Examples
|
||||||
|
///
|
||||||
|
/// A URI with a query part:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("/a/b/c?alphabet=true").unwrap();
|
||||||
|
/// assert_eq!(uri.query(), Some("alphabet=true"));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// A URI without the query part:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("/a/b/c").unwrap();
|
||||||
|
/// assert_eq!(uri.query(), None);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn query(&self) -> Option<&str> {
|
||||||
|
self.query.as_ref().map(|q| q.from_cow_source(&self.source))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the query part of this URI, if there is any.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let mut uri = Origin::parse("/a/b/c?query=some").unwrap();
|
||||||
|
/// assert_eq!(uri.query(), Some("query=some"));
|
||||||
|
///
|
||||||
|
/// uri.clear_query();
|
||||||
|
/// assert_eq!(uri.query(), None);
|
||||||
|
/// ```
|
||||||
|
pub fn clear_query(&mut self) {
|
||||||
|
self.query = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over the segments of the path in this URI. Skips
|
||||||
|
/// empty segments.
|
||||||
|
///
|
||||||
|
/// ### Examples
|
||||||
|
///
|
||||||
|
/// A valid URI with only non-empty segments:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("/a/b/c?a=true").unwrap();
|
||||||
|
/// for (i, segment) in uri.segments().enumerate() {
|
||||||
|
/// match i {
|
||||||
|
/// 0 => assert_eq!(segment, "a"),
|
||||||
|
/// 1 => assert_eq!(segment, "b"),
|
||||||
|
/// 2 => assert_eq!(segment, "c"),
|
||||||
|
/// _ => unreachable!("only three segments")
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// A URI with empty segments:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("///a//b///c////d?query¶m").unwrap();
|
||||||
|
/// for (i, segment) in uri.segments().enumerate() {
|
||||||
|
/// match i {
|
||||||
|
/// 0 => assert_eq!(segment, "a"),
|
||||||
|
/// 1 => assert_eq!(segment, "b"),
|
||||||
|
/// 2 => assert_eq!(segment, "c"),
|
||||||
|
/// 3 => assert_eq!(segment, "d"),
|
||||||
|
/// _ => unreachable!("only four segments")
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn segments(&self) -> Segments {
|
||||||
|
Segments(self.path())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of segments in the URI. Empty segments, which are
|
||||||
|
/// invalid according to RFC#3986, are not counted.
|
||||||
|
///
|
||||||
|
/// The segment count is cached after the first invocation. As a result,
|
||||||
|
/// this function is O(1) after the first invocation, and O(n) before.
|
||||||
|
///
|
||||||
|
/// ### Examples
|
||||||
|
///
|
||||||
|
/// A valid URI with only non-empty segments:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("/a/b/c").unwrap();
|
||||||
|
/// assert_eq!(uri.segment_count(), 3);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// A URI with empty segments:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("/a/b//c/d///e").unwrap();
|
||||||
|
/// assert_eq!(uri.segment_count(), 5);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn segment_count(&self) -> usize {
|
||||||
|
*self.segment_count.get_or_set(|| self.segments().count())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for Origin<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.path())?;
|
||||||
|
if let Some(q) = self.query() {
|
||||||
|
write!(f, "?{}", q)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over the segments of an absolute URI path. Skips empty segments.
|
||||||
|
///
|
||||||
|
/// ### Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("/a/////b/c////////d").unwrap();
|
||||||
|
/// let segments = uri.segments();
|
||||||
|
/// for (i, segment) in segments.enumerate() {
|
||||||
|
/// match i {
|
||||||
|
/// 0 => assert_eq!(segment, "a"),
|
||||||
|
/// 1 => assert_eq!(segment, "b"),
|
||||||
|
/// 2 => assert_eq!(segment, "c"),
|
||||||
|
/// 3 => assert_eq!(segment, "d"),
|
||||||
|
/// _ => panic!("only four segments")
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Segments<'a>(pub &'a str);
|
||||||
|
|
||||||
|
impl<'a> Iterator for Segments<'a> {
|
||||||
|
type Item = &'a str;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
// Find the start of the next segment (first that's not '/').
|
||||||
|
let i = self.0.find(|c| c != '/')?;
|
||||||
|
|
||||||
|
// Get the index of the first character that _is_ a '/' after start.
|
||||||
|
// j = index of first character after i (hence the i +) that's not a '/'
|
||||||
|
let j = self.0[i..].find('/').map_or(self.0.len(), |j| i + j);
|
||||||
|
|
||||||
|
// Save the result, update the iterator, and return!
|
||||||
|
let result = Some(&self.0[i..j]);
|
||||||
|
self.0 = &self.0[j..];
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Potentially take a second parameter with Option<cached count> and
|
||||||
|
// return it here if it's Some. The downside is that a decision has to be
|
||||||
|
// made about -when- to compute and cache that count. A place to do it is in
|
||||||
|
// the segments() method. But this means that the count will always be
|
||||||
|
// computed regardless of whether it's needed. Maybe this is ok. We'll see.
|
||||||
|
// fn count(self) -> usize where Self: Sized {
|
||||||
|
// self.1.unwrap_or_else(self.fold(0, |cnt, _| cnt + 1))
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Origin;
|
||||||
|
|
||||||
|
fn seg_count(path: &str, expected: usize) -> bool {
|
||||||
|
let actual = Origin::parse(path).unwrap().segment_count();
|
||||||
|
if actual != expected {
|
||||||
|
eprintln!("Count mismatch: expected {}, got {}.", expected, actual);
|
||||||
|
eprintln!("{}", if actual != expected { "lifetime" } else { "buf" });
|
||||||
|
eprintln!("Segments (for {}):", path);
|
||||||
|
for (i, segment) in Origin::parse(path).unwrap().segments().enumerate() {
|
||||||
|
eprintln!("{}: {}", i, segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual == expected
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eq_segments(path: &str, expected: &[&str]) -> bool {
|
||||||
|
let uri = match Origin::parse(path) {
|
||||||
|
Ok(uri) => uri,
|
||||||
|
Err(e) => panic!("failed to parse {}: {}", path, e)
|
||||||
|
};
|
||||||
|
|
||||||
|
let actual: Vec<&str> = uri.segments().collect();
|
||||||
|
actual == expected
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn send_and_sync() {
|
||||||
|
fn assert<T: Send + Sync>() {};
|
||||||
|
assert::<Origin>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_segment_count() {
|
||||||
|
assert!(seg_count("/", 0));
|
||||||
|
assert!(seg_count("/a", 1));
|
||||||
|
assert!(seg_count("/a/", 1));
|
||||||
|
assert!(seg_count("/a/", 1));
|
||||||
|
assert!(seg_count("/a/b", 2));
|
||||||
|
assert!(seg_count("/a/b/", 2));
|
||||||
|
assert!(seg_count("/a/b/", 2));
|
||||||
|
assert!(seg_count("/ab/", 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn segment_count() {
|
||||||
|
assert!(seg_count("////", 0));
|
||||||
|
assert!(seg_count("//a//", 1));
|
||||||
|
assert!(seg_count("//abc//", 1));
|
||||||
|
assert!(seg_count("//abc/def/", 2));
|
||||||
|
assert!(seg_count("//////abc///def//////////", 2));
|
||||||
|
assert!(seg_count("/a/b/c/d/e/f/g", 7));
|
||||||
|
assert!(seg_count("/a/b/c/d/e/f/g", 7));
|
||||||
|
assert!(seg_count("/a/b/c/d/e/f/g/", 7));
|
||||||
|
assert!(seg_count("/a/b/cdjflk/d/e/f/g", 7));
|
||||||
|
assert!(seg_count("//aaflja/b/cdjflk/d/e/f/g", 7));
|
||||||
|
assert!(seg_count("/a/b", 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_segments_match() {
|
||||||
|
assert!(eq_segments("/", &[]));
|
||||||
|
assert!(eq_segments("/a", &["a"]));
|
||||||
|
assert!(eq_segments("/a/", &["a"]));
|
||||||
|
assert!(eq_segments("///a/", &["a"]));
|
||||||
|
assert!(eq_segments("///a///////", &["a"]));
|
||||||
|
assert!(eq_segments("/a///////", &["a"]));
|
||||||
|
assert!(eq_segments("//a", &["a"]));
|
||||||
|
assert!(eq_segments("/abc", &["abc"]));
|
||||||
|
assert!(eq_segments("/abc/", &["abc"]));
|
||||||
|
assert!(eq_segments("///abc/", &["abc"]));
|
||||||
|
assert!(eq_segments("///abc///////", &["abc"]));
|
||||||
|
assert!(eq_segments("/abc///////", &["abc"]));
|
||||||
|
assert!(eq_segments("//abc", &["abc"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_segments_match() {
|
||||||
|
assert!(eq_segments("/a/b/c", &["a", "b", "c"]));
|
||||||
|
assert!(eq_segments("/a/b", &["a", "b"]));
|
||||||
|
assert!(eq_segments("/a///b", &["a", "b"]));
|
||||||
|
assert!(eq_segments("/a/b/c/d", &["a", "b", "c", "d"]));
|
||||||
|
assert!(eq_segments("///a///////d////c", &["a", "d", "c"]));
|
||||||
|
assert!(eq_segments("/abc/abc", &["abc", "abc"]));
|
||||||
|
assert!(eq_segments("/abc/abc/", &["abc", "abc"]));
|
||||||
|
assert!(eq_segments("///abc///////a", &["abc", "a"]));
|
||||||
|
assert!(eq_segments("/////abc/b", &["abc", "b"]));
|
||||||
|
assert!(eq_segments("//abc//c////////d", &["abc", "c", "d"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_segments_match_funky_chars() {
|
||||||
|
assert!(eq_segments("/a/b/c!!!", &["a", "b", "c!!!"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn segment_mismatch() {
|
||||||
|
assert!(!eq_segments("/", &["a"]));
|
||||||
|
assert!(!eq_segments("/a", &[]));
|
||||||
|
assert!(!eq_segments("/a/a", &["a"]));
|
||||||
|
assert!(!eq_segments("/a/b", &["b", "a"]));
|
||||||
|
assert!(!eq_segments("/a/a/b", &["a", "b"]));
|
||||||
|
assert!(!eq_segments("///a/", &[]));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_query(uri: &str, query: Option<&str>) {
|
||||||
|
let uri = Origin::parse(uri).unwrap();
|
||||||
|
assert_eq!(uri.query(), query);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn query_does_not_exist() {
|
||||||
|
test_query("/test", None);
|
||||||
|
test_query("/a/b/c/d/e", None);
|
||||||
|
test_query("/////", None);
|
||||||
|
test_query("//a///", None);
|
||||||
|
test_query("/a/b/c", None);
|
||||||
|
test_query("/", None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn query_exists() {
|
||||||
|
test_query("/test?abc", Some("abc"));
|
||||||
|
test_query("/a/b/c?abc", Some("abc"));
|
||||||
|
test_query("/a/b/c/d/e/f/g/?abc", Some("abc"));
|
||||||
|
test_query("/?123", Some("123"));
|
||||||
|
test_query("/?", Some(""));
|
||||||
|
test_query("/?", Some(""));
|
||||||
|
test_query("/?hi", Some("hi"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normalized() {
|
||||||
|
let uri_to_string = |s| Origin::parse(s)
|
||||||
|
.unwrap()
|
||||||
|
.to_normalized()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
assert_eq!(uri_to_string("/"), "/".to_string());
|
||||||
|
assert_eq!(uri_to_string("//"), "/".to_string());
|
||||||
|
assert_eq!(uri_to_string("//////a/"), "/a".to_string());
|
||||||
|
assert_eq!(uri_to_string("//ab"), "/ab".to_string());
|
||||||
|
assert_eq!(uri_to_string("//a"), "/a".to_string());
|
||||||
|
assert_eq!(uri_to_string("/a/b///c"), "/a/b/c".to_string());
|
||||||
|
assert_eq!(uri_to_string("/a///b/c/d///"), "/a/b/c/d".to_string());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,255 +1,154 @@
|
||||||
use std::fmt;
|
use std::fmt::{self, Display};
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::str::Utf8Error;
|
use std::str::Utf8Error;
|
||||||
use std::sync::atomic::{AtomicIsize, Ordering};
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
/// Index (start, end) into a string, to prevent borrowing.
|
use ext::IntoOwned;
|
||||||
type Index = (usize, usize);
|
use parse::Indexed;
|
||||||
|
use uri::{Origin, Authority, Absolute, Error};
|
||||||
|
|
||||||
/// Representation of an empty segment count.
|
/// An `enum` encapsulating any of the possible URI variants.
|
||||||
const EMPTY: isize = -1;
|
///
|
||||||
|
/// # Usage
|
||||||
// TODO: Reconsider deriving PartialEq and Eq to make "//a/b" == "/a/b".
|
///
|
||||||
/// Borrowed string type for absolute URIs.
|
/// In Rocket, this type will rarely be used directly. Instead, you will
|
||||||
#[derive(Debug)]
|
/// typically encounter URIs via the [`Origin`] type. This is because all
|
||||||
pub struct Uri<'a> {
|
/// incoming requests contain origin-type URIs.
|
||||||
uri: Cow<'a, str>,
|
///
|
||||||
path: Index,
|
/// Nevertheless, the `Uri` type is typically enountered as a conversion target.
|
||||||
query: Option<Index>,
|
/// In particular, you will likely see generic bounds of the form: `T:
|
||||||
fragment: Option<Index>,
|
/// TryInto<Uri>` (for instance, in [`Redirect`](rocket::Redirect) methods).
|
||||||
// The cached segment count. `EMPTY` is used to represent no segment count.
|
/// This means that you can provide any type `T` that implements `TryInto<Uri>`,
|
||||||
segment_count: AtomicIsize,
|
/// or, equivalently, any type `U` for which `Uri` implements `TryFrom<U>` or
|
||||||
|
/// `From<U>`. These include `&str` and `String`, [`Origin`], [`Authority`], and
|
||||||
|
/// [`Absolute`].
|
||||||
|
///
|
||||||
|
/// ## Parsing
|
||||||
|
///
|
||||||
|
/// The `Uri` type implements a full, zero-allocation, zero-copy [RFC 7230]
|
||||||
|
/// compliant parser. To parse an `&str` into a `Uri`, use the [`Uri::parse()`]
|
||||||
|
/// method. Alternatively, you may also use the `TryFrom<&str>` and
|
||||||
|
/// `TryFrom<String>` trait implementation. To inspect the parsed type, match on
|
||||||
|
/// the resulting `enum` and use the methods of the internal structure.
|
||||||
|
///
|
||||||
|
/// [RFC 7230]: https://tools.ietf.org/html/rfc7230
|
||||||
|
///
|
||||||
|
/// ## Percent Encoding/Decoding
|
||||||
|
///
|
||||||
|
/// This type also provides the following percent encoding/decoding helper
|
||||||
|
/// methods: [`Uri::percent_encode`], [`Uri::percent_decode`], and
|
||||||
|
/// [`Uri::percent_decode_lossy`].
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Uri<'a> {
|
||||||
|
/// An [`Origin`] URI.
|
||||||
|
Origin(Origin<'a>),
|
||||||
|
/// An [`Authority`] URI.
|
||||||
|
Authority(Authority<'a>),
|
||||||
|
/// An [`Absolute`] URI.
|
||||||
|
Absolute(Absolute<'a>),
|
||||||
|
/// An asterisk: exactly `*`.
|
||||||
|
Asterisk,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Uri<'a> {
|
impl<'a> Uri<'a> {
|
||||||
/// Constructs a new URI from a given string. The URI is assumed to be an
|
#[inline]
|
||||||
/// absolute, well formed URI.
|
crate unsafe fn raw_absolute(
|
||||||
pub fn new<T: Into<Cow<'a, str>>>(uri: T) -> Uri<'a> {
|
source: Cow<'a, [u8]>,
|
||||||
let uri = uri.into();
|
scheme: Indexed<'a, [u8]>,
|
||||||
let qmark = uri.find('?');
|
path: Indexed<'a, [u8]>,
|
||||||
let hmark = uri.find('#');
|
query: Option<Indexed<'a, [u8]>>,
|
||||||
|
) -> Uri<'a> {
|
||||||
let end = uri.len();
|
let origin = Origin::raw(source.clone(), path, query);
|
||||||
let (path, query, fragment) = match (qmark, hmark) {
|
Uri::Absolute(Absolute::raw(source.clone(), scheme, None, Some(origin)))
|
||||||
(Some(i), Some(j)) if i < j => ((0, i), Some((i+1, j)), Some((j+1, end))),
|
|
||||||
(Some(_i), Some(j)) => ((0, j), None, Some((j+1, end))),
|
|
||||||
(Some(i), None) => ((0, i), Some((i+1, end)), None),
|
|
||||||
(None, Some(j)) => ((0, j), None, Some((j+1, end))),
|
|
||||||
(None, None) => ((0, end), None, None),
|
|
||||||
};
|
|
||||||
|
|
||||||
Uri { uri, path, query, fragment, segment_count: AtomicIsize::new(EMPTY) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of segments in the URI. Empty segments, which are
|
/// Parses the string `string` into a `Uri`. Parsing will never allocate.
|
||||||
/// invalid according to RFC#3986, are not counted.
|
/// Returns an `Error` if `string` is not a valid URI.
|
||||||
///
|
///
|
||||||
/// The segment count is cached after the first invocation. As a result,
|
/// # Example
|
||||||
/// this function is O(1) after the first invocation, and O(n) before.
|
|
||||||
///
|
|
||||||
/// ### Examples
|
|
||||||
///
|
|
||||||
/// A valid URI with only non-empty segments:
|
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # extern crate rocket;
|
||||||
/// use rocket::http::uri::Uri;
|
/// use rocket::http::uri::Uri;
|
||||||
///
|
///
|
||||||
/// let uri = Uri::new("/a/b/c");
|
/// // Parse a valid origin URI (note: in practice, use `Origin::parse()`).
|
||||||
/// assert_eq!(uri.segment_count(), 3);
|
/// let uri = Uri::parse("/a/b/c?query").expect("valid URI");
|
||||||
|
/// let origin = uri.origin().expect("origin URI");
|
||||||
|
/// assert_eq!(origin.path(), "/a/b/c");
|
||||||
|
/// assert_eq!(origin.query(), Some("query"));
|
||||||
|
///
|
||||||
|
/// // Invalid URIs fail to parse.
|
||||||
|
/// Uri::parse("foo bar").expect_err("invalid URI");
|
||||||
/// ```
|
/// ```
|
||||||
///
|
pub fn parse(string: &'a str) -> Result<Uri<'a>, Error> {
|
||||||
/// A URI with empty segments:
|
::parse::uri::from_str(string)
|
||||||
///
|
}
|
||||||
/// ```rust
|
|
||||||
/// # extern crate rocket;
|
|
||||||
/// use rocket::http::uri::Uri;
|
|
||||||
///
|
|
||||||
/// let uri = Uri::new("/a/b//c/d///e");
|
|
||||||
/// assert_eq!(uri.segment_count(), 5);
|
|
||||||
/// ```
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn segment_count(&self) -> usize {
|
|
||||||
let count = self.segment_count.load(Ordering::Relaxed);
|
|
||||||
if count == EMPTY {
|
|
||||||
let real_count = self.segments().count();
|
|
||||||
if real_count <= isize::max_value() as usize {
|
|
||||||
self.segment_count.store(real_count as isize, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
real_count
|
/// Returns the internal instance of `Origin` if `self` is a `Uri::Origin`.
|
||||||
} else {
|
/// Otherwise, returns `None`.
|
||||||
count as usize
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Uri;
|
||||||
|
///
|
||||||
|
/// let uri = Uri::parse("/a/b/c?query").expect("valid URI");
|
||||||
|
/// assert!(uri.origin().is_some());
|
||||||
|
///
|
||||||
|
/// let uri = Uri::parse("http://google.com").expect("valid URI");
|
||||||
|
/// assert!(uri.origin().is_none());
|
||||||
|
/// ```
|
||||||
|
pub fn origin(&self) -> Option<&Origin<'a>> {
|
||||||
|
match self {
|
||||||
|
Uri::Origin(ref inner) => Some(inner),
|
||||||
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator over the segments of the path in this URI. Skips
|
/// Returns the internal instance of `Authority` if `self` is a
|
||||||
/// empty segments.
|
/// `Uri::Authority`. Otherwise, returns `None`.
|
||||||
///
|
///
|
||||||
/// ### Examples
|
/// # Example
|
||||||
///
|
|
||||||
/// A valid URI with only non-empty segments:
|
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # extern crate rocket;
|
||||||
/// use rocket::http::uri::Uri;
|
/// use rocket::http::uri::Uri;
|
||||||
///
|
///
|
||||||
/// let uri = Uri::new("/a/b/c?a=true#done");
|
/// let uri = Uri::parse("user:pass@domain.com").expect("valid URI");
|
||||||
/// for (i, segment) in uri.segments().enumerate() {
|
/// assert!(uri.authority().is_some());
|
||||||
/// match i {
|
///
|
||||||
/// 0 => assert_eq!(segment, "a"),
|
/// let uri = Uri::parse("http://google.com").expect("valid URI");
|
||||||
/// 1 => assert_eq!(segment, "b"),
|
/// assert!(uri.authority().is_none());
|
||||||
/// 2 => assert_eq!(segment, "c"),
|
|
||||||
/// _ => panic!("only three segments")
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
pub fn authority(&self) -> Option<&Authority<'a>> {
|
||||||
/// A URI with empty segments:
|
match self {
|
||||||
///
|
Uri::Authority(ref inner) => Some(inner),
|
||||||
/// ```rust
|
_ => None
|
||||||
/// # extern crate rocket;
|
}
|
||||||
/// use rocket::http::uri::Uri;
|
|
||||||
///
|
|
||||||
/// let uri = Uri::new("///a//b///c////d?#");
|
|
||||||
/// for (i, segment) in uri.segments().enumerate() {
|
|
||||||
/// match i {
|
|
||||||
/// 0 => assert_eq!(segment, "a"),
|
|
||||||
/// 1 => assert_eq!(segment, "b"),
|
|
||||||
/// 2 => assert_eq!(segment, "c"),
|
|
||||||
/// 3 => assert_eq!(segment, "d"),
|
|
||||||
/// _ => panic!("only four segments")
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn segments(&self) -> Segments {
|
|
||||||
Segments(self.path())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the path part of this URI.
|
/// Returns the internal instance of `Absolute` if `self` is a
|
||||||
|
/// `Uri::Absolute`. Otherwise, returns `None`.
|
||||||
///
|
///
|
||||||
/// ### Examples
|
/// # Example
|
||||||
///
|
|
||||||
/// A URI with only a path:
|
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # extern crate rocket;
|
||||||
/// use rocket::http::uri::Uri;
|
/// use rocket::http::uri::Uri;
|
||||||
///
|
///
|
||||||
/// let uri = Uri::new("/a/b/c");
|
/// let uri = Uri::parse("http://google.com").expect("valid URI");
|
||||||
/// assert_eq!(uri.path(), "/a/b/c");
|
/// assert!(uri.absolute().is_some());
|
||||||
|
///
|
||||||
|
/// let uri = Uri::parse("/path").expect("valid URI");
|
||||||
|
/// assert!(uri.absolute().is_none());
|
||||||
/// ```
|
/// ```
|
||||||
///
|
pub fn absolute(&self) -> Option<&Absolute<'a>> {
|
||||||
/// A URI with other components:
|
match self {
|
||||||
///
|
Uri::Absolute(ref inner) => Some(inner),
|
||||||
/// ```rust
|
_ => None
|
||||||
/// # extern crate rocket;
|
}
|
||||||
/// use rocket::http::uri::Uri;
|
|
||||||
///
|
|
||||||
/// let uri = Uri::new("/a/b/c?name=bob#done");
|
|
||||||
/// assert_eq!(uri.path(), "/a/b/c");
|
|
||||||
/// ```
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn path(&self) -> &str {
|
|
||||||
let (i, j) = self.path;
|
|
||||||
&self.uri[i..j]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the query part of this URI without the question mark, if there is
|
|
||||||
/// any.
|
|
||||||
///
|
|
||||||
/// ### Examples
|
|
||||||
///
|
|
||||||
/// A URI with a query part:
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # extern crate rocket;
|
|
||||||
/// use rocket::http::uri::Uri;
|
|
||||||
///
|
|
||||||
/// let uri = Uri::new("/a/b/c?alphabet=true");
|
|
||||||
/// assert_eq!(uri.query(), Some("alphabet=true"));
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// A URI without the query part:
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # extern crate rocket;
|
|
||||||
/// use rocket::http::uri::Uri;
|
|
||||||
///
|
|
||||||
/// let uri = Uri::new("/a/b/c");
|
|
||||||
/// assert_eq!(uri.query(), None);
|
|
||||||
/// ```
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn query(&self) -> Option<&str> {
|
|
||||||
self.query.map(|(i, j)| &self.uri[i..j])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the fragment part of this URI without the hash mark, if there is
|
|
||||||
/// any.
|
|
||||||
///
|
|
||||||
/// ### Examples
|
|
||||||
///
|
|
||||||
/// A URI with a fragment part:
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # extern crate rocket;
|
|
||||||
/// use rocket::http::uri::Uri;
|
|
||||||
///
|
|
||||||
/// let uri = Uri::new("/a?alphabet=true#end");
|
|
||||||
/// assert_eq!(uri.fragment(), Some("end"));
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// A URI without the fragment part:
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # extern crate rocket;
|
|
||||||
/// use rocket::http::uri::Uri;
|
|
||||||
///
|
|
||||||
/// let uri = Uri::new("/a?query=true");
|
|
||||||
/// assert_eq!(uri.fragment(), None);
|
|
||||||
/// ```
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn fragment(&self) -> Option<&str> {
|
|
||||||
self.fragment.map(|(i, j)| &self.uri[i..j])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a URL-decoded version of the string. If the percent encoded
|
|
||||||
/// values are not valid UTF-8, an `Err` is returned.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # extern crate rocket;
|
|
||||||
/// use rocket::http::uri::Uri;
|
|
||||||
///
|
|
||||||
/// let uri = Uri::new("/Hello%2C%20world%21");
|
|
||||||
/// let decoded_path = Uri::percent_decode(uri.path().as_bytes()).expect("decoded");
|
|
||||||
/// assert_eq!(decoded_path, "/Hello, world!");
|
|
||||||
/// ```
|
|
||||||
pub fn percent_decode(string: &[u8]) -> Result<Cow<str>, Utf8Error> {
|
|
||||||
let decoder = ::percent_encoding::percent_decode(string);
|
|
||||||
decoder.decode_utf8()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a URL-decoded version of the path. Any invalid UTF-8
|
|
||||||
/// percent-encoded byte sequences will be replaced <20> U+FFFD, the
|
|
||||||
/// replacement character.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # extern crate rocket;
|
|
||||||
/// use rocket::http::uri::Uri;
|
|
||||||
///
|
|
||||||
/// let uri = Uri::new("/Hello%2C%20world%21");
|
|
||||||
/// let decoded_path = Uri::percent_decode_lossy(uri.path().as_bytes());
|
|
||||||
/// assert_eq!(decoded_path, "/Hello, world!");
|
|
||||||
/// ```
|
|
||||||
pub fn percent_decode_lossy(string: &[u8]) -> Cow<str> {
|
|
||||||
let decoder = ::percent_encoding::percent_decode(string);
|
|
||||||
decoder.decode_utf8_lossy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a URL-encoded version of the string. Any characters outside of
|
/// Returns a URL-encoded version of the string. Any characters outside of
|
||||||
|
@ -270,340 +169,104 @@ impl<'a> Uri<'a> {
|
||||||
::percent_encoding::utf8_percent_encode(string, set).into()
|
::percent_encoding::utf8_percent_encode(string, set).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the inner string of this URI.
|
/// Returns a URL-decoded version of the string. If the percent encoded
|
||||||
|
/// values are not valid UTF-8, an `Err` is returned.
|
||||||
///
|
///
|
||||||
/// The returned string is in raw form. It contains empty segments. If you'd
|
/// # Examples
|
||||||
/// like a string without empty segments, use `to_string` instead.
|
|
||||||
///
|
|
||||||
/// ### Example
|
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # extern crate rocket;
|
||||||
/// use rocket::http::uri::Uri;
|
/// use rocket::http::uri::Uri;
|
||||||
///
|
///
|
||||||
/// let uri = Uri::new("/a/b///c/d/e//f?name=Mike#end");
|
/// let decoded = Uri::percent_decode("/Hello%2C%20world%21".as_bytes());
|
||||||
/// assert_eq!(uri.as_str(), "/a/b///c/d/e//f?name=Mike#end");
|
/// assert_eq!(decoded.unwrap(), "/Hello, world!");
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
pub fn percent_decode(string: &[u8]) -> Result<Cow<str>, Utf8Error> {
|
||||||
pub fn as_str(&self) -> &str {
|
let decoder = ::percent_encoding::percent_decode(string);
|
||||||
&self.uri
|
decoder.decode_utf8()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a URL-decoded version of the path. Any invalid UTF-8
|
||||||
|
/// percent-encoded byte sequences will be replaced <20> U+FFFD, the
|
||||||
|
/// replacement character.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Uri;
|
||||||
|
///
|
||||||
|
/// let decoded = Uri::percent_decode_lossy("/Hello%2C%20world%21".as_bytes());
|
||||||
|
/// assert_eq!(decoded, "/Hello, world!");
|
||||||
|
/// ```
|
||||||
|
pub fn percent_decode_lossy(string: &[u8]) -> Cow<str> {
|
||||||
|
let decoder = ::percent_encoding::percent_decode(string);
|
||||||
|
decoder.decode_utf8_lossy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Clone for Uri<'a> {
|
crate unsafe fn as_utf8_unchecked(input: Cow<[u8]>) -> Cow<str> {
|
||||||
#[inline(always)]
|
match input {
|
||||||
fn clone(&self) -> Uri<'a> {
|
Cow::Borrowed(bytes) => Cow::Borrowed(::std::str::from_utf8_unchecked(bytes)),
|
||||||
Uri {
|
Cow::Owned(bytes) => Cow::Owned(String::from_utf8_unchecked(bytes))
|
||||||
uri: self.uri.clone(),
|
}
|
||||||
path: self.path,
|
}
|
||||||
query: self.query,
|
|
||||||
fragment: self.fragment,
|
impl<'a> TryFrom<&'a str> for Uri<'a> {
|
||||||
segment_count: AtomicIsize::new(EMPTY),
|
type Error = Error<'a>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_from(string: &'a str) -> Result<Uri<'a>, Self::Error> {
|
||||||
|
Uri::parse(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Uri<'static> {
|
||||||
|
type Error = Error<'static>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_from(string: String) -> Result<Uri<'static>, Self::Error> {
|
||||||
|
// TODO: Potentially optimize this like `Origin::parse_owned`.
|
||||||
|
Uri::parse(&string)
|
||||||
|
.map(|u| u.into_owned())
|
||||||
|
.map_err(|e| e.into_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoOwned for Uri<'a> {
|
||||||
|
type Owned = Uri<'static>;
|
||||||
|
|
||||||
|
fn into_owned(self) -> Uri<'static> {
|
||||||
|
match self {
|
||||||
|
Uri::Origin(origin) => Uri::Origin(origin.into_owned()),
|
||||||
|
Uri::Authority(authority) => Uri::Authority(authority.into_owned()),
|
||||||
|
Uri::Absolute(absolute) => Uri::Absolute(absolute.into_owned()),
|
||||||
|
Uri::Asterisk => Uri::Asterisk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> PartialEq<Uri<'b>> for Uri<'a> {
|
impl<'a> Display for Uri<'a> {
|
||||||
#[inline]
|
|
||||||
fn eq(&self, other: &Uri<'b>) -> bool {
|
|
||||||
self.path() == other.path() &&
|
|
||||||
self.query() == other.query() &&
|
|
||||||
self.fragment() == other.fragment()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Eq for Uri<'a> {}
|
|
||||||
|
|
||||||
impl<'a> From<&'a str> for Uri<'a> {
|
|
||||||
#[inline(always)]
|
|
||||||
fn from(uri: &'a str) -> Uri<'a> {
|
|
||||||
Uri::new(uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<Cow<'a, str>> for Uri<'a> {
|
|
||||||
#[inline(always)]
|
|
||||||
fn from(uri: Cow<'a, str>) -> Uri<'a> {
|
|
||||||
Uri::new(uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for Uri<'static> {
|
|
||||||
#[inline(always)]
|
|
||||||
fn from(uri: String) -> Uri<'static> {
|
|
||||||
Uri::new(uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> fmt::Display for Uri<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
// If this is the root path, then there are "zero" segments.
|
match *self {
|
||||||
if self.segment_count() == 0 {
|
Uri::Origin(ref origin) => write!(f, "{}", origin),
|
||||||
write!(f, "/")?;
|
Uri::Authority(ref authority) => write!(f, "{}", authority),
|
||||||
} else {
|
Uri::Absolute(ref absolute) => write!(f, "{}", absolute),
|
||||||
for segment in self.segments() {
|
Uri::Asterisk => write!(f, "*")
|
||||||
write!(f, "/{}", segment)?;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_uri_from {
|
||||||
|
($type:ident) => (
|
||||||
|
impl<'a> From<$type<'a>> for Uri<'a> {
|
||||||
|
fn from(other: $type<'a>) -> Uri<'a> {
|
||||||
|
Uri::$type(other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
if let Some(query_str) = self.query() {
|
|
||||||
write!(f, "?{}", query_str)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(fragment_str) = self.fragment() {
|
|
||||||
write!(f, "#{}", fragment_str)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterator over the segments of an absolute URI path. Skips empty segments.
|
impl_uri_from!(Origin);
|
||||||
///
|
impl_uri_from!(Authority);
|
||||||
/// ### Examples
|
impl_uri_from!(Absolute);
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # extern crate rocket;
|
|
||||||
/// use rocket::http::uri::Uri;
|
|
||||||
///
|
|
||||||
/// let uri = Uri::new("/a/////b/c////////d");
|
|
||||||
/// let segments = uri.segments();
|
|
||||||
/// for (i, segment) in segments.enumerate() {
|
|
||||||
/// match i {
|
|
||||||
/// 0 => assert_eq!(segment, "a"),
|
|
||||||
/// 1 => assert_eq!(segment, "b"),
|
|
||||||
/// 2 => assert_eq!(segment, "c"),
|
|
||||||
/// 3 => assert_eq!(segment, "d"),
|
|
||||||
/// _ => panic!("only four segments")
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Segments<'a>(pub &'a str);
|
|
||||||
|
|
||||||
impl<'a> Iterator for Segments<'a> {
|
|
||||||
type Item = &'a str;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
// Find the start of the next segment (first that's not '/').
|
|
||||||
let i = match self.0.find(|c| c != '/') {
|
|
||||||
Some(index) => index,
|
|
||||||
None => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get the index of the first character that _is_ a '/' after start.
|
|
||||||
// j = index of first character after i (hence the i +) that's not a '/'
|
|
||||||
let j = self.0[i..].find('/').map_or(self.0.len(), |j| i + j);
|
|
||||||
|
|
||||||
// Save the result, update the iterator, and return!
|
|
||||||
let result = Some(&self.0[i..j]);
|
|
||||||
self.0 = &self.0[j..];
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Potentially take a second parameter with Option<cached count> and
|
|
||||||
// return it here if it's Some. The downside is that a decision has to be
|
|
||||||
// made about -when- to compute and cache that count. A place to do it is in
|
|
||||||
// the segments() method. But this means that the count will always be
|
|
||||||
// computed regardless of whether it's needed. Maybe this is ok. We'll see.
|
|
||||||
// fn count(self) -> usize where Self: Sized {
|
|
||||||
// self.1.unwrap_or_else(self.fold(0, |cnt, _| cnt + 1))
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Errors which can occur when attempting to interpret a segment string as a
|
|
||||||
/// valid path segment.
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub enum SegmentError {
|
|
||||||
/// The segment contained invalid UTF8 characters when percent decoded.
|
|
||||||
Utf8(Utf8Error),
|
|
||||||
/// The segment started with the wrapped invalid character.
|
|
||||||
BadStart(char),
|
|
||||||
/// The segment contained the wrapped invalid character.
|
|
||||||
BadChar(char),
|
|
||||||
/// The segment ended with the wrapped invalid character.
|
|
||||||
BadEnd(char),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::Uri;
|
|
||||||
|
|
||||||
fn seg_count(path: &str, expected: usize) -> bool {
|
|
||||||
let actual = Uri::new(path).segment_count();
|
|
||||||
if actual != expected {
|
|
||||||
eprintln!("Count mismatch: expected {}, got {}.", expected, actual);
|
|
||||||
eprintln!("{}", if actual != expected { "lifetime" } else { "buf" });
|
|
||||||
eprintln!("Segments (for {}):", path);
|
|
||||||
for (i, segment) in Uri::new(path).segments().enumerate() {
|
|
||||||
eprintln!("{}: {}", i, segment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
actual == expected
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eq_segments(path: &str, expected: &[&str]) -> bool {
|
|
||||||
let uri = Uri::new(path);
|
|
||||||
let actual: Vec<&str> = uri.segments().collect();
|
|
||||||
actual == expected
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn send_and_sync() {
|
|
||||||
fn assert<T: Send + Sync>() {};
|
|
||||||
assert::<Uri>();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn simple_segment_count() {
|
|
||||||
assert!(seg_count("", 0));
|
|
||||||
assert!(seg_count("/", 0));
|
|
||||||
assert!(seg_count("a", 1));
|
|
||||||
assert!(seg_count("/a", 1));
|
|
||||||
assert!(seg_count("a/", 1));
|
|
||||||
assert!(seg_count("/a/", 1));
|
|
||||||
assert!(seg_count("/a/b", 2));
|
|
||||||
assert!(seg_count("/a/b/", 2));
|
|
||||||
assert!(seg_count("a/b/", 2));
|
|
||||||
assert!(seg_count("ab/", 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn segment_count() {
|
|
||||||
assert!(seg_count("////", 0));
|
|
||||||
assert!(seg_count("//a//", 1));
|
|
||||||
assert!(seg_count("//abc//", 1));
|
|
||||||
assert!(seg_count("//abc/def/", 2));
|
|
||||||
assert!(seg_count("//////abc///def//////////", 2));
|
|
||||||
assert!(seg_count("a/b/c/d/e/f/g", 7));
|
|
||||||
assert!(seg_count("/a/b/c/d/e/f/g", 7));
|
|
||||||
assert!(seg_count("/a/b/c/d/e/f/g/", 7));
|
|
||||||
assert!(seg_count("/a/b/cdjflk/d/e/f/g", 7));
|
|
||||||
assert!(seg_count("//aaflja/b/cdjflk/d/e/f/g", 7));
|
|
||||||
assert!(seg_count("/a /b", 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_segments_match() {
|
|
||||||
assert!(eq_segments("", &[]));
|
|
||||||
assert!(eq_segments("a", &["a"]));
|
|
||||||
assert!(eq_segments("/a", &["a"]));
|
|
||||||
assert!(eq_segments("/a/", &["a"]));
|
|
||||||
assert!(eq_segments("a/", &["a"]));
|
|
||||||
assert!(eq_segments("///a/", &["a"]));
|
|
||||||
assert!(eq_segments("///a///////", &["a"]));
|
|
||||||
assert!(eq_segments("a///////", &["a"]));
|
|
||||||
assert!(eq_segments("//a", &["a"]));
|
|
||||||
assert!(eq_segments("", &[]));
|
|
||||||
assert!(eq_segments("abc", &["abc"]));
|
|
||||||
assert!(eq_segments("/a", &["a"]));
|
|
||||||
assert!(eq_segments("/abc/", &["abc"]));
|
|
||||||
assert!(eq_segments("abc/", &["abc"]));
|
|
||||||
assert!(eq_segments("///abc/", &["abc"]));
|
|
||||||
assert!(eq_segments("///abc///////", &["abc"]));
|
|
||||||
assert!(eq_segments("abc///////", &["abc"]));
|
|
||||||
assert!(eq_segments("//abc", &["abc"]));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn multi_segments_match() {
|
|
||||||
assert!(eq_segments("a/b/c", &["a", "b", "c"]));
|
|
||||||
assert!(eq_segments("/a/b", &["a", "b"]));
|
|
||||||
assert!(eq_segments("/a///b", &["a", "b"]));
|
|
||||||
assert!(eq_segments("a/b/c/d", &["a", "b", "c", "d"]));
|
|
||||||
assert!(eq_segments("///a///////d////c", &["a", "d", "c"]));
|
|
||||||
assert!(eq_segments("abc/abc", &["abc", "abc"]));
|
|
||||||
assert!(eq_segments("abc/abc/", &["abc", "abc"]));
|
|
||||||
assert!(eq_segments("///abc///////a", &["abc", "a"]));
|
|
||||||
assert!(eq_segments("/////abc/b", &["abc", "b"]));
|
|
||||||
assert!(eq_segments("//abc//c////////d", &["abc", "c", "d"]));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn multi_segments_match_funky_chars() {
|
|
||||||
assert!(eq_segments("a/b/c!!!", &["a", "b", "c!!!"]));
|
|
||||||
assert!(eq_segments("a /b", &["a ", "b"]));
|
|
||||||
assert!(eq_segments(" a/b", &[" a", "b"]));
|
|
||||||
assert!(eq_segments(" a/b ", &[" a", "b "]));
|
|
||||||
assert!(eq_segments(" a///b ", &[" a", "b "]));
|
|
||||||
assert!(eq_segments(" ab ", &[" ab "]));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn segment_mismatch() {
|
|
||||||
assert!(!eq_segments("", &["a"]));
|
|
||||||
assert!(!eq_segments("a", &[]));
|
|
||||||
assert!(!eq_segments("/a/a", &["a"]));
|
|
||||||
assert!(!eq_segments("/a/b", &["b", "a"]));
|
|
||||||
assert!(!eq_segments("/a/a/b", &["a", "b"]));
|
|
||||||
assert!(!eq_segments("///a/", &[]));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_query(uri: &str, query: Option<&str>) {
|
|
||||||
let uri = Uri::new(uri);
|
|
||||||
assert_eq!(uri.query(), query);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_fragment(uri: &str, fragment: Option<&str>) {
|
|
||||||
let uri = Uri::new(uri);
|
|
||||||
assert_eq!(uri.fragment(), fragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn query_does_not_exist() {
|
|
||||||
test_query("/test", None);
|
|
||||||
test_query("/a/b/c/d/e", None);
|
|
||||||
test_query("/////", None);
|
|
||||||
test_query("//a///", None);
|
|
||||||
test_query("/a/b/c#a?123", None);
|
|
||||||
test_query("/#", None);
|
|
||||||
test_query("/#?", None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn query_exists() {
|
|
||||||
test_query("/test?abc", Some("abc"));
|
|
||||||
test_query("/a/b/c?abc", Some("abc"));
|
|
||||||
test_query("/a/b/c/d/e/f/g/?abc#hijklmnop", Some("abc"));
|
|
||||||
test_query("?123", Some("123"));
|
|
||||||
test_query("?", Some(""));
|
|
||||||
test_query("/?", Some(""));
|
|
||||||
test_query("?#", Some(""));
|
|
||||||
test_query("/?hi", Some("hi"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn fragment_exists() {
|
|
||||||
test_fragment("/test#abc", Some("abc"));
|
|
||||||
test_fragment("/#abc", Some("abc"));
|
|
||||||
test_fragment("/#ab?c", Some("ab?c"));
|
|
||||||
test_fragment("/a/b/c?123#a", Some("a"));
|
|
||||||
test_fragment("/a/b/c#a?123", Some("a?123"));
|
|
||||||
test_fragment("/a/b/c?123#a?b", Some("a?b"));
|
|
||||||
test_fragment("/#a", Some("a"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn fragment_does_not_exist() {
|
|
||||||
test_fragment("/testabc", None);
|
|
||||||
test_fragment("/abc", None);
|
|
||||||
test_fragment("/a/b/c?123", None);
|
|
||||||
test_fragment("/a", None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn to_string() {
|
|
||||||
let uri_to_string = |string| Uri::new(string).to_string();
|
|
||||||
|
|
||||||
assert_eq!(uri_to_string("/"), "/".to_string());
|
|
||||||
assert_eq!(uri_to_string("//"), "/".to_string());
|
|
||||||
assert_eq!(uri_to_string("//////a/"), "/a".to_string());
|
|
||||||
assert_eq!(uri_to_string("//ab"), "/ab".to_string());
|
|
||||||
assert_eq!(uri_to_string("//a"), "/a".to_string());
|
|
||||||
assert_eq!(uri_to_string("/a/b///c"), "/a/b/c".to_string());
|
|
||||||
assert_eq!(uri_to_string("/a///b/c/d///"), "/a/b/c/d".to_string());
|
|
||||||
assert_eq!(uri_to_string("/a/b/c#a?123"), "/a/b/c#a?123".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -74,11 +74,12 @@ use self::priv_encode_set::PATH_ENCODE_SET;
|
||||||
/// The implementation of `UriDisplay` for these types is identical to the
|
/// The implementation of `UriDisplay` for these types is identical to the
|
||||||
/// `Display` implementation.
|
/// `Display` implementation.
|
||||||
///
|
///
|
||||||
/// * **[`&RawStr`](/rocket/http/struct.RawStr.html), String, &str, Cow<str>**
|
/// * **[`&RawStr`](/rocket/http/struct.RawStr.html), `String`, `&str`,
|
||||||
|
/// `Cow<str>`**
|
||||||
///
|
///
|
||||||
/// The string is percent encoded.
|
/// The string is percent encoded.
|
||||||
///
|
///
|
||||||
/// * **&T, &mut T** _where_ **T: UriDisplay**
|
/// * **`&T`, `&mut T`** _where_ **`T: UriDisplay`**
|
||||||
///
|
///
|
||||||
/// Uses the implementation of `UriDisplay` for `T`.
|
/// Uses the implementation of `UriDisplay` for `T`.
|
||||||
///
|
///
|
||||||
|
@ -95,8 +96,6 @@ use self::priv_encode_set::PATH_ENCODE_SET;
|
||||||
/// below, for instance, `Name`'s implementation defers to `String`'s
|
/// below, for instance, `Name`'s implementation defers to `String`'s
|
||||||
/// implementation. To percent-encode a string, use [`Uri::percent_encode()`].
|
/// implementation. To percent-encode a string, use [`Uri::percent_encode()`].
|
||||||
///
|
///
|
||||||
/// [`Uri::percent_encode()`]: https://api.rocket.rs/rocket/http/uri/struct.Uri.html#method.percent_encode
|
|
||||||
///
|
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
/// The following snippet consists of a `Name` type that implements both
|
/// The following snippet consists of a `Name` type that implements both
|
||||||
|
|
|
@ -99,7 +99,7 @@ impl Catcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn handle<'r>(&self, e: Error, r: &'r Request) -> response::Result<'r> {
|
crate fn handle<'r>(&self, e: Error, r: &'r Request) -> response::Result<'r> {
|
||||||
(self.handler)(e, r)
|
(self.handler)(e, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ impl Catcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn is_default(&self) -> bool {
|
crate fn is_default(&self) -> bool {
|
||||||
self.is_default
|
self.is_default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,9 +52,9 @@ pub struct Config {
|
||||||
/// How much information to log.
|
/// How much information to log.
|
||||||
pub log_level: LoggingLevel,
|
pub log_level: LoggingLevel,
|
||||||
/// The secret key.
|
/// The secret key.
|
||||||
pub(crate) secret_key: SecretKey,
|
crate secret_key: SecretKey,
|
||||||
/// TLS configuration.
|
/// TLS configuration.
|
||||||
pub(crate) tls: Option<TlsConfig>,
|
crate tls: Option<TlsConfig>,
|
||||||
/// Streaming read size limits.
|
/// Streaming read size limits.
|
||||||
pub limits: Limits,
|
pub limits: Limits,
|
||||||
/// Extra parameters that aren't part of Rocket's core config.
|
/// Extra parameters that aren't part of Rocket's core config.
|
||||||
|
@ -221,7 +221,7 @@ impl Config {
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if randomness cannot be retrieved from the OS.
|
/// Panics if randomness cannot be retrieved from the OS.
|
||||||
pub(crate) fn default<P>(env: Environment, path: P) -> Result<Config>
|
crate fn default<P>(env: Environment, path: P) -> Result<Config>
|
||||||
where P: AsRef<Path>
|
where P: AsRef<Path>
|
||||||
{
|
{
|
||||||
let config_path = path.as_ref().to_path_buf();
|
let config_path = path.as_ref().to_path_buf();
|
||||||
|
@ -288,7 +288,7 @@ impl Config {
|
||||||
/// Constructs a `BadType` error given the entry `name`, the invalid `val`
|
/// Constructs a `BadType` error given the entry `name`, the invalid `val`
|
||||||
/// at that entry, and the `expect`ed type name.
|
/// at that entry, and the `expect`ed type name.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn bad_type(&self,
|
crate fn bad_type(&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
actual: &'static str,
|
actual: &'static str,
|
||||||
expect: &'static str) -> ConfigError {
|
expect: &'static str) -> ConfigError {
|
||||||
|
@ -312,7 +312,7 @@ impl Config {
|
||||||
/// * **log**: String
|
/// * **log**: String
|
||||||
/// * **secret_key**: String (256-bit base64)
|
/// * **secret_key**: String (256-bit base64)
|
||||||
/// * **tls**: Table (`certs` (path as String), `key` (path as String))
|
/// * **tls**: Table (`certs` (path as String), `key` (path as String))
|
||||||
pub(crate) fn set_raw(&mut self, name: &str, val: &Value) -> Result<()> {
|
crate fn set_raw(&mut self, name: &str, val: &Value) -> Result<()> {
|
||||||
let (id, ok) = (|val| val, |_| Ok(()));
|
let (id, ok) = (|val| val, |_| Ok(()));
|
||||||
config_from_raw!(self, name, val,
|
config_from_raw!(self, name, val,
|
||||||
address => (str, set_address, id),
|
address => (str, set_address, id),
|
||||||
|
@ -663,7 +663,7 @@ impl Config {
|
||||||
|
|
||||||
/// Retrieves the secret key from `self`.
|
/// Retrieves the secret key from `self`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn secret_key(&self) -> &Key {
|
crate fn secret_key(&self) -> &Key {
|
||||||
self.secret_key.inner()
|
self.secret_key.inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,14 @@ pub enum SecretKey {
|
||||||
|
|
||||||
impl SecretKey {
|
impl SecretKey {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn inner(&self) -> &Key {
|
crate fn inner(&self) -> &Key {
|
||||||
match *self {
|
match *self {
|
||||||
SecretKey::Generated(ref key) | SecretKey::Provided(ref key) => key
|
SecretKey::Generated(ref key) | SecretKey::Provided(ref key) => key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn is_generated(&self) -> bool {
|
crate fn is_generated(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
SecretKey::Generated(_) => true,
|
SecretKey::Generated(_) => true,
|
||||||
_ => false
|
_ => false
|
||||||
|
@ -79,7 +79,7 @@ pub struct TlsConfig;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Limits {
|
pub struct Limits {
|
||||||
// We cache this internally but don't share that fact in the API.
|
// We cache this internally but don't share that fact in the API.
|
||||||
pub(crate) forms: u64,
|
crate forms: u64,
|
||||||
extra: Vec<(String, u64)>
|
extra: Vec<(String, u64)>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,13 +40,13 @@ impl Environment {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a string with a comma-separated list of valid environments.
|
/// Returns a string with a comma-separated list of valid environments.
|
||||||
pub(crate) fn valid() -> &'static str {
|
crate fn valid() -> &'static str {
|
||||||
"development, staging, production"
|
"development, staging, production"
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a list of all of the possible environments.
|
/// Returns a list of all of the possible environments.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn all() -> [Environment; 3] {
|
crate fn all() -> [Environment; 3] {
|
||||||
[Development, Staging, Production]
|
[Development, Staging, Production]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -217,7 +217,7 @@ pub use self::environment::Environment;
|
||||||
pub use self::config::Config;
|
pub use self::config::Config;
|
||||||
pub use self::builder::ConfigBuilder;
|
pub use self::builder::ConfigBuilder;
|
||||||
pub use logger::LoggingLevel;
|
pub use logger::LoggingLevel;
|
||||||
pub(crate) use self::toml_ext::LoggedValue;
|
crate use self::toml_ext::LoggedValue;
|
||||||
|
|
||||||
use logger;
|
use logger;
|
||||||
use self::Environment::*;
|
use self::Environment::*;
|
||||||
|
@ -470,7 +470,7 @@ impl RocketConfig {
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// If there is a problem, prints a nice error message and bails.
|
/// If there is a problem, prints a nice error message and bails.
|
||||||
pub(crate) fn init() -> Config {
|
crate fn init() -> Config {
|
||||||
let bail = |e: ConfigError| -> ! {
|
let bail = |e: ConfigError| -> ! {
|
||||||
logger::init(LoggingLevel::Debug);
|
logger::init(LoggingLevel::Debug);
|
||||||
e.pretty_print();
|
e.pretty_print();
|
||||||
|
|
|
@ -81,7 +81,7 @@ pub fn parse_simple_toml_value(mut input: &str) -> StdResult<Value, String> {
|
||||||
|
|
||||||
/// A simple wrapper over a `Value` reference with a custom implementation of
|
/// A simple wrapper over a `Value` reference with a custom implementation of
|
||||||
/// `Display`. This is used to log config values at initialization.
|
/// `Display`. This is used to log config values at initialization.
|
||||||
pub(crate) struct LoggedValue<'a>(pub &'a Value);
|
crate struct LoggedValue<'a>(pub &'a Value);
|
||||||
|
|
||||||
impl<'a> fmt::Display for LoggedValue<'a> {
|
impl<'a> fmt::Display for LoggedValue<'a> {
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -87,7 +87,7 @@ impl Data {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: This is absolutely terrible (downcasting!), thanks to Hyper.
|
// FIXME: This is absolutely terrible (downcasting!), thanks to Hyper.
|
||||||
pub(crate) fn from_hyp(mut body: HyperBodyReader) -> Result<Data, &'static str> {
|
crate fn from_hyp(mut body: HyperBodyReader) -> Result<Data, &'static str> {
|
||||||
// Steal the internal, undecoded data buffer and net stream from Hyper.
|
// Steal the internal, undecoded data buffer and net stream from Hyper.
|
||||||
let (mut hyper_buf, pos, cap) = body.get_mut().take_buf();
|
let (mut hyper_buf, pos, cap) = body.get_mut().take_buf();
|
||||||
// This is only valid because we know that hyper's `cap` represents the
|
// This is only valid because we know that hyper's `cap` represents the
|
||||||
|
@ -235,7 +235,7 @@ impl Data {
|
||||||
// bytes `vec[pos..cap]` are buffered and unread. The remainder of the data
|
// bytes `vec[pos..cap]` are buffered and unread. The remainder of the data
|
||||||
// bytes can be read from `stream`.
|
// bytes can be read from `stream`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn new(mut stream: BodyReader) -> Data {
|
crate fn new(mut stream: BodyReader) -> Data {
|
||||||
trace_!("Date::new({:?})", stream);
|
trace_!("Date::new({:?})", stream);
|
||||||
let mut peek_buf: Vec<u8> = vec![0; PEEK_BYTES];
|
let mut peek_buf: Vec<u8> = vec![0; PEEK_BYTES];
|
||||||
|
|
||||||
|
@ -269,7 +269,7 @@ impl Data {
|
||||||
|
|
||||||
/// This creates a `data` object from a local data source `data`.
|
/// This creates a `data` object from a local data source `data`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn local(data: Vec<u8>) -> Data {
|
crate fn local(data: Vec<u8>) -> Data {
|
||||||
let empty_stream = Cursor::new(vec![]).chain(NetStream::Empty);
|
let empty_stream = Cursor::new(vec![]).chain(NetStream::Empty);
|
||||||
|
|
||||||
Data {
|
Data {
|
||||||
|
|
|
@ -14,7 +14,7 @@ pub type InnerStream = Chain<Cursor<Vec<u8>>, BodyReader>;
|
||||||
/// [Data::open](/rocket/data/struct.Data.html#method.open). The stream contains
|
/// [Data::open](/rocket/data/struct.Data.html#method.open). The stream contains
|
||||||
/// all of the data in the body of the request. It exposes no methods directly.
|
/// all of the data in the body of the request. It exposes no methods directly.
|
||||||
/// Instead, it must be used as an opaque `Read` structure.
|
/// Instead, it must be used as an opaque `Read` structure.
|
||||||
pub struct DataStream(pub(crate) InnerStream);
|
pub struct DataStream(crate InnerStream);
|
||||||
|
|
||||||
// TODO: Have a `BufRead` impl for `DataStream`. At the moment, this isn't
|
// TODO: Have a `BufRead` impl for `DataStream`. At the moment, this isn't
|
||||||
// possible since Hyper's `HttpReader` doesn't implement `BufRead`.
|
// possible since Hyper's `HttpReader` doesn't implement `BufRead`.
|
||||||
|
|
|
@ -98,7 +98,7 @@ pub struct LaunchError {
|
||||||
|
|
||||||
impl LaunchError {
|
impl LaunchError {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn new(kind: LaunchErrorKind) -> LaunchError {
|
crate fn new(kind: LaunchErrorKind) -> LaunchError {
|
||||||
LaunchError { handled: AtomicBool::new(false), kind: kind }
|
LaunchError { handled: AtomicBool::new(false), kind: kind }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ mod fairings;
|
||||||
mod ad_hoc;
|
mod ad_hoc;
|
||||||
mod info_kind;
|
mod info_kind;
|
||||||
|
|
||||||
pub(crate) use self::fairings::Fairings;
|
crate use self::fairings::Fairings;
|
||||||
pub use self::ad_hoc::AdHoc;
|
pub use self::ad_hoc::AdHoc;
|
||||||
pub use self::info_kind::{Info, Kind};
|
pub use self::info_kind::{Info, Kind};
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
#![feature(fnbox)]
|
#![feature(fnbox)]
|
||||||
#![feature(never_type)]
|
#![feature(never_type)]
|
||||||
#![feature(proc_macro_non_items, use_extern_macros)]
|
#![feature(proc_macro_non_items, use_extern_macros)]
|
||||||
|
#![feature(crate_visibility_modifier)]
|
||||||
|
#![feature(try_from)]
|
||||||
|
|
||||||
#![recursion_limit="256"]
|
#![recursion_limit="256"]
|
||||||
|
|
||||||
|
@ -128,7 +130,6 @@ pub mod error;
|
||||||
|
|
||||||
// Reexport of HTTP everything.
|
// Reexport of HTTP everything.
|
||||||
pub mod http {
|
pub mod http {
|
||||||
// FIXME: This unfortunately doesn't work! See rust-lang/rust#51252.
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use rocket_http::*;
|
pub use rocket_http::*;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use {Rocket, Request, Response};
|
|
||||||
use local::LocalRequest;
|
|
||||||
use http::{Method, CookieJar, uri::Uri};
|
|
||||||
use error::LaunchError;
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use Rocket;
|
||||||
|
use local::LocalRequest;
|
||||||
|
use http::{Method, CookieJar};
|
||||||
|
use error::LaunchError;
|
||||||
|
|
||||||
/// A structure to construct requests for local dispatching.
|
/// A structure to construct requests for local dispatching.
|
||||||
///
|
///
|
||||||
|
@ -53,7 +55,7 @@ use std::cell::RefCell;
|
||||||
/// [`post`]: #method.post
|
/// [`post`]: #method.post
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
rocket: Rocket,
|
rocket: Rocket,
|
||||||
cookies: Option<RefCell<CookieJar>>,
|
crate cookies: Option<RefCell<CookieJar>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
|
@ -150,25 +152,6 @@ impl Client {
|
||||||
&self.rocket
|
&self.rocket
|
||||||
}
|
}
|
||||||
|
|
||||||
// If `self` is tracking cookies, updates the internal cookie jar with the
|
|
||||||
// changes reflected by `response`.
|
|
||||||
pub(crate) fn update_cookies(&self, response: &Response) {
|
|
||||||
if let Some(ref jar) = self.cookies {
|
|
||||||
let mut jar = jar.borrow_mut();
|
|
||||||
let current_time = ::time::now();
|
|
||||||
for cookie in response.cookies() {
|
|
||||||
if let Some(expires) = cookie.expires() {
|
|
||||||
if expires <= current_time {
|
|
||||||
jar.force_remove(cookie);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jar.add(cookie.into_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a local `GET` request to the URI `uri`.
|
/// Create a local `GET` request to the URI `uri`.
|
||||||
///
|
///
|
||||||
/// When dispatched, the request will be served by the instance of Rocket
|
/// When dispatched, the request will be served by the instance of Rocket
|
||||||
|
@ -186,7 +169,7 @@ impl Client {
|
||||||
/// let req = client.get("/hello");
|
/// let req = client.get("/hello");
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn get<'c, 'u: 'c, U: Into<Uri<'u>>>(&'c self, uri: U) -> LocalRequest<'c> {
|
pub fn get<'c, 'u: 'c, U: Into<Cow<'u, str>>>(&'c self, uri: U) -> LocalRequest<'c> {
|
||||||
self.req(Method::Get, uri)
|
self.req(Method::Get, uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,7 +190,7 @@ impl Client {
|
||||||
/// let req = client.put("/hello");
|
/// let req = client.put("/hello");
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn put<'c, 'u: 'c, U: Into<Uri<'u>>>(&'c self, uri: U) -> LocalRequest<'c> {
|
pub fn put<'c, 'u: 'c, U: Into<Cow<'u, str>>>(&'c self, uri: U) -> LocalRequest<'c> {
|
||||||
self.req(Method::Put, uri)
|
self.req(Method::Put, uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,7 +215,7 @@ impl Client {
|
||||||
/// .header(ContentType::Form);
|
/// .header(ContentType::Form);
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn post<'c, 'u: 'c, U: Into<Uri<'u>>>(&'c self, uri: U) -> LocalRequest<'c> {
|
pub fn post<'c, 'u: 'c, U: Into<Cow<'u, str>>>(&'c self, uri: U) -> LocalRequest<'c> {
|
||||||
self.req(Method::Post, uri)
|
self.req(Method::Post, uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,7 +237,7 @@ impl Client {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn delete<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c>
|
pub fn delete<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c>
|
||||||
where U: Into<Uri<'u>>
|
where U: Into<Cow<'u, str>>
|
||||||
{
|
{
|
||||||
self.req(Method::Delete, uri)
|
self.req(Method::Delete, uri)
|
||||||
}
|
}
|
||||||
|
@ -277,7 +260,7 @@ impl Client {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn options<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c>
|
pub fn options<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c>
|
||||||
where U: Into<Uri<'u>>
|
where U: Into<Cow<'u, str>>
|
||||||
{
|
{
|
||||||
self.req(Method::Options, uri)
|
self.req(Method::Options, uri)
|
||||||
}
|
}
|
||||||
|
@ -300,7 +283,7 @@ impl Client {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn head<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c>
|
pub fn head<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c>
|
||||||
where U: Into<Uri<'u>>
|
where U: Into<Cow<'u, str>>
|
||||||
{
|
{
|
||||||
self.req(Method::Head, uri)
|
self.req(Method::Head, uri)
|
||||||
}
|
}
|
||||||
|
@ -323,7 +306,7 @@ impl Client {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn patch<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c>
|
pub fn patch<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c>
|
||||||
where U: Into<Uri<'u>>
|
where U: Into<Cow<'u, str>>
|
||||||
{
|
{
|
||||||
self.req(Method::Patch, uri)
|
self.req(Method::Patch, uri)
|
||||||
}
|
}
|
||||||
|
@ -347,16 +330,8 @@ impl Client {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn req<'c, 'u: 'c, U>(&'c self, method: Method, uri: U) -> LocalRequest<'c>
|
pub fn req<'c, 'u: 'c, U>(&'c self, method: Method, uri: U) -> LocalRequest<'c>
|
||||||
where U: Into<Uri<'u>>
|
where U: Into<Cow<'u, str>>
|
||||||
{
|
{
|
||||||
let request = Request::new(&self.rocket, method, uri);
|
LocalRequest::new(self, method, uri.into())
|
||||||
|
|
||||||
if let Some(ref jar) = self.cookies {
|
|
||||||
for cookie in jar.borrow().iter() {
|
|
||||||
request.cookies().add_original(cookie.clone().into_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalRequest::new(&self, request)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,11 @@ use std::fmt;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use {Request, Response, Data};
|
use {Request, Response, Data};
|
||||||
|
use http::{Status, Method, Header, Cookie, uri::Origin, ext::IntoOwned};
|
||||||
use local::Client;
|
use local::Client;
|
||||||
use http::{Header, Cookie};
|
|
||||||
|
|
||||||
/// A structure representing a local request as created by [`Client`].
|
/// A structure representing a local request as created by [`Client`].
|
||||||
///
|
///
|
||||||
|
@ -37,26 +38,24 @@ use http::{Header, Cookie};
|
||||||
///
|
///
|
||||||
/// # Dispatching
|
/// # Dispatching
|
||||||
///
|
///
|
||||||
/// A `LocalRequest` can be dispatched in one of three ways:
|
/// A `LocalRequest` can be dispatched in one of two ways:
|
||||||
///
|
///
|
||||||
/// 1. [`dispatch`]
|
/// 1. [`dispatch`]
|
||||||
///
|
///
|
||||||
/// This method should always be preferred. The `LocalRequest` is consumed
|
/// This method should always be preferred. The `LocalRequest` is consumed
|
||||||
/// and a response is returned.
|
/// and a response is returned.
|
||||||
///
|
///
|
||||||
/// 2. [`cloned_dispatch`]
|
/// 2. [`mut_dispatch`]
|
||||||
///
|
|
||||||
/// This method should be used when one `LocalRequest` will be dispatched
|
|
||||||
/// many times. This method clones the request and dispatches the clone, so
|
|
||||||
/// the request _is not_ consumed and can be reused.
|
|
||||||
///
|
|
||||||
/// 3. [`mut_dispatch`]
|
|
||||||
///
|
///
|
||||||
/// This method should _only_ be used when either it is known that the
|
/// This method should _only_ be used when either it is known that the
|
||||||
/// application will not modify the request, or it is desired to see
|
/// application will not modify the request, or it is desired to see
|
||||||
/// modifications to the request. No cloning occurs, and the request is not
|
/// modifications to the request. No cloning occurs, and the request is not
|
||||||
/// consumed.
|
/// consumed.
|
||||||
///
|
///
|
||||||
|
/// Additionally, note that `LocalRequest` implements `Clone`. As such, if the
|
||||||
|
/// same request needs to be dispatched multiple times, the request can first be
|
||||||
|
/// cloned and then dispatched: `request.clone().dispatch()`.
|
||||||
|
///
|
||||||
/// [`Client`]: /rocket/local/struct.Client.html
|
/// [`Client`]: /rocket/local/struct.Client.html
|
||||||
/// [`header`]: #method.header
|
/// [`header`]: #method.header
|
||||||
/// [`add_header`]: #method.add_header
|
/// [`add_header`]: #method.add_header
|
||||||
|
@ -66,7 +65,6 @@ use http::{Header, Cookie};
|
||||||
/// [`set_body`]: #method.set_body
|
/// [`set_body`]: #method.set_body
|
||||||
/// [`dispatch`]: #method.dispatch
|
/// [`dispatch`]: #method.dispatch
|
||||||
/// [`mut_dispatch`]: #method.mut_dispatch
|
/// [`mut_dispatch`]: #method.mut_dispatch
|
||||||
/// [`cloned_dispatch`]: #method.cloned_dispatch
|
|
||||||
pub struct LocalRequest<'c> {
|
pub struct LocalRequest<'c> {
|
||||||
client: &'c Client,
|
client: &'c Client,
|
||||||
// This pointer exists to access the `Rc<Request>` mutably inside of
|
// This pointer exists to access the `Rc<Request>` mutably inside of
|
||||||
|
@ -97,15 +95,31 @@ pub struct LocalRequest<'c> {
|
||||||
// is converted into its owned counterpart before insertion, ensuring stable
|
// is converted into its owned counterpart before insertion, ensuring stable
|
||||||
// addresses. Together, these properties guarantee the second condition.
|
// addresses. Together, these properties guarantee the second condition.
|
||||||
request: Rc<Request<'c>>,
|
request: Rc<Request<'c>>,
|
||||||
data: Vec<u8>
|
data: Vec<u8>,
|
||||||
|
uri: Cow<'c, str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'c> LocalRequest<'c> {
|
impl<'c> LocalRequest<'c> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn new(client: &'c Client, request: Request<'c>) -> LocalRequest<'c> {
|
crate fn new(
|
||||||
|
client: &'c Client,
|
||||||
|
method: Method,
|
||||||
|
uri: Cow<'c, str>
|
||||||
|
) -> LocalRequest<'c> {
|
||||||
|
// We set a dummy string for now and check the user's URI on dispatch.
|
||||||
|
let request = Request::new(client.rocket(), method, Origin::dummy());
|
||||||
|
|
||||||
|
// Set up any cookies we know about.
|
||||||
|
if let Some(ref jar) = client.cookies {
|
||||||
|
for cookie in jar.borrow().iter() {
|
||||||
|
request.cookies().add_original(cookie.clone().into_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// See the comments on the structure for what's going on here.
|
||||||
let mut request = Rc::new(request);
|
let mut request = Rc::new(request);
|
||||||
let ptr = Rc::get_mut(&mut request).unwrap() as *mut Request;
|
let ptr = Rc::get_mut(&mut request).unwrap() as *mut Request;
|
||||||
LocalRequest { client, ptr, request, data: vec![] }
|
LocalRequest { client, ptr, request, uri, data: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves the inner `Request` as seen by Rocket.
|
/// Retrieves the inner `Request` as seen by Rocket.
|
||||||
|
@ -135,8 +149,8 @@ impl<'c> LocalRequest<'c> {
|
||||||
fn long_lived_request<'a>(&mut self) -> &'a mut Request<'c> {
|
fn long_lived_request<'a>(&mut self) -> &'a mut Request<'c> {
|
||||||
// See the comments in the structure for the argument of correctness.
|
// See the comments in the structure for the argument of correctness.
|
||||||
// Additionally, the caller must ensure that the owned instance of
|
// Additionally, the caller must ensure that the owned instance of
|
||||||
// `Request` itself remains valid as long as the returned reference can
|
// `Rc<Request>` remains valid as long as the returned reference can be
|
||||||
// be accessed.
|
// accessed.
|
||||||
unsafe { &mut *self.ptr }
|
unsafe { &mut *self.ptr }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,34 +347,8 @@ impl<'c> LocalRequest<'c> {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn dispatch(mut self) -> LocalResponse<'c> {
|
pub fn dispatch(mut self) -> LocalResponse<'c> {
|
||||||
let req = self.long_lived_request();
|
let r = self.long_lived_request();
|
||||||
let response = self.client.rocket().dispatch(req, Data::local(self.data));
|
LocalRequest::_dispatch(self.client, r, self.request, &self.uri, self.data)
|
||||||
self.client.update_cookies(&response);
|
|
||||||
LocalResponse { _request: self.request, response }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dispatches the request, returning the response.
|
|
||||||
///
|
|
||||||
/// This method _does not_ consume `self`. Instead, it clones `self` and
|
|
||||||
/// dispatches the clone. As such, `self` can be reused.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use rocket::local::Client;
|
|
||||||
///
|
|
||||||
/// let client = Client::new(rocket::ignite()).unwrap();
|
|
||||||
///
|
|
||||||
/// let req = client.get("/");
|
|
||||||
/// let response_a = req.cloned_dispatch();
|
|
||||||
/// let response_b = req.cloned_dispatch();
|
|
||||||
/// ```
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn cloned_dispatch(&self) -> LocalResponse<'c> {
|
|
||||||
let cloned = (*self.request).clone();
|
|
||||||
let mut req = LocalRequest::new(self.client, cloned);
|
|
||||||
req.data = self.data.clone();
|
|
||||||
req.dispatch()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dispatches the request, returning the response.
|
/// Dispatches the request, returning the response.
|
||||||
|
@ -373,11 +361,9 @@ impl<'c> LocalRequest<'c> {
|
||||||
///
|
///
|
||||||
/// This method should _only_ be used when either it is known that
|
/// This method should _only_ be used when either it is known that
|
||||||
/// the application will not modify the request, or it is desired to see
|
/// the application will not modify the request, or it is desired to see
|
||||||
/// modifications to the request. Prefer to use [`dispatch`] or
|
/// modifications to the request. Prefer to use [`dispatch`] instead.
|
||||||
/// [`cloned_dispatch`] instead
|
|
||||||
///
|
///
|
||||||
/// [`dispatch`]: #method.dispatch
|
/// [`dispatch`]: #method.dispatch
|
||||||
/// [`cloned_dispatch`]: #method.cloned_dispatch
|
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
|
@ -392,11 +378,53 @@ impl<'c> LocalRequest<'c> {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn mut_dispatch(&mut self) -> LocalResponse<'c> {
|
pub fn mut_dispatch(&mut self) -> LocalResponse<'c> {
|
||||||
let data = ::std::mem::replace(&mut self.data, vec![]);
|
|
||||||
let req = self.long_lived_request();
|
let req = self.long_lived_request();
|
||||||
let response = self.client.rocket().dispatch(req, Data::local(data));
|
let data = ::std::mem::replace(&mut self.data, vec![]);
|
||||||
self.client.update_cookies(&response);
|
let rc_req = self.request.clone();
|
||||||
LocalResponse { _request: self.request.clone(), response }
|
LocalRequest::_dispatch(self.client, req, rc_req, &self.uri, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performs the actual dispatch.
|
||||||
|
fn _dispatch(
|
||||||
|
client: &'c Client,
|
||||||
|
request: &'c mut Request<'c>,
|
||||||
|
owned_request: Rc<Request<'c>>,
|
||||||
|
uri: &str,
|
||||||
|
data: Vec<u8>
|
||||||
|
) -> LocalResponse<'c> {
|
||||||
|
// First, validate the URI, returning an error response (generated from
|
||||||
|
// an error catcher) immediately if it's invalid.
|
||||||
|
if let Ok(uri) = Origin::parse(uri) {
|
||||||
|
request.set_uri(uri.into_owned());
|
||||||
|
} else {
|
||||||
|
let res = client.rocket().handle_error(Status::BadRequest, request);
|
||||||
|
return LocalResponse { _request: owned_request, response: res };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actually dispatch the request.
|
||||||
|
let response = client.rocket().dispatch(request, Data::local(data));
|
||||||
|
|
||||||
|
// If the client is tracking cookies, updates the internal cookie jar
|
||||||
|
// with the changes reflected by `response`.
|
||||||
|
if let Some(ref jar) = client.cookies {
|
||||||
|
let mut jar = jar.borrow_mut();
|
||||||
|
let current_time = ::time::now();
|
||||||
|
for cookie in response.cookies() {
|
||||||
|
if let Some(expires) = cookie.expires() {
|
||||||
|
if expires <= current_time {
|
||||||
|
jar.force_remove(cookie);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jar.add(cookie.into_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalResponse {
|
||||||
|
_request: owned_request,
|
||||||
|
response: response
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,7 +470,19 @@ impl<'c> fmt::Debug for LocalResponse<'c> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
impl<'c> Clone for LocalRequest<'c> {
|
||||||
|
fn clone(&self) -> LocalRequest<'c> {
|
||||||
|
LocalRequest {
|
||||||
|
client: self.client,
|
||||||
|
ptr: self.ptr,
|
||||||
|
request: self.request.clone(),
|
||||||
|
data: self.data.clone(),
|
||||||
|
uri: self.uri.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
// Someday...
|
// Someday...
|
||||||
|
|
||||||
|
@ -474,8 +514,10 @@ mod tests {
|
||||||
// is_send::<::local::LocalResponse>();
|
// is_send::<::local::LocalResponse>();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// This checks that a response can't outlive the `Client`.
|
||||||
|
// #[compile_fail]
|
||||||
// fn test() {
|
// fn test() {
|
||||||
// use local::Client;
|
// use {Rocket, local::Client};
|
||||||
|
|
||||||
// let rocket = Rocket::ignite();
|
// let rocket = Rocket::ignite();
|
||||||
// let res = {
|
// let res = {
|
||||||
|
@ -488,8 +530,10 @@ mod tests {
|
||||||
// // let res2 = client.get("/").dispatch();
|
// // let res2 = client.get("/").dispatch();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// This checks that a response can't outlive the `Client`.
|
||||||
|
// #[compile_fail]
|
||||||
// fn test() {
|
// fn test() {
|
||||||
// use local::Client;
|
// use {Rocket, local::Client};
|
||||||
|
|
||||||
// let rocket = Rocket::ignite();
|
// let rocket = Rocket::ignite();
|
||||||
// let res = {
|
// let res = {
|
||||||
|
@ -502,8 +546,11 @@ mod tests {
|
||||||
// // let res2 = client.get("/").dispatch();
|
// // let res2 = client.get("/").dispatch();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// This checks that a response can't outlive the `Client`, in this case, by
|
||||||
|
// moving `client` while it is borrowed.
|
||||||
|
// #[compile_fail]
|
||||||
// fn test() {
|
// fn test() {
|
||||||
// use local::Client;
|
// use {Rocket, local::Client};
|
||||||
|
|
||||||
// let rocket = Rocket::ignite();
|
// let rocket = Rocket::ignite();
|
||||||
// let client = Client::new(rocket).unwrap();
|
// let client = Client::new(rocket).unwrap();
|
||||||
|
@ -511,13 +558,15 @@ mod tests {
|
||||||
// let res = {
|
// let res = {
|
||||||
// let x = client.get("/").dispatch();
|
// let x = client.get("/").dispatch();
|
||||||
// let y = client.get("/").dispatch();
|
// let y = client.get("/").dispatch();
|
||||||
|
// (x, y)
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// let x = client;
|
// let x = client;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// #[compile_fail]
|
||||||
// fn test() {
|
// fn test() {
|
||||||
// use local::Client;
|
// use {Rocket, local::Client};
|
||||||
|
|
||||||
// let rocket1 = Rocket::ignite();
|
// let rocket1 = Rocket::ignite();
|
||||||
// let rocket2 = Rocket::ignite();
|
// let rocket2 = Rocket::ignite();
|
||||||
|
@ -527,7 +576,7 @@ mod tests {
|
||||||
|
|
||||||
// let res = {
|
// let res = {
|
||||||
// let mut res1 = client1.get("/");
|
// let mut res1 = client1.get("/");
|
||||||
// res1.set_client(&client2);
|
// res1.client = &client2;
|
||||||
// res1
|
// res1
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
|
|
@ -142,7 +142,7 @@ impl log::Log for RocketLogger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn try_init(level: LoggingLevel, verbose: bool) -> bool {
|
crate fn try_init(level: LoggingLevel, verbose: bool) -> bool {
|
||||||
if level == LoggingLevel::Off {
|
if level == LoggingLevel::Off {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -192,13 +192,13 @@ fn usize_to_filter(num: usize) -> log::LevelFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn push_max_level(level: LoggingLevel) {
|
crate fn push_max_level(level: LoggingLevel) {
|
||||||
LAST_LOG_FILTER.store(filter_to_usize(log::max_level()), Ordering::Release);
|
LAST_LOG_FILTER.store(filter_to_usize(log::max_level()), Ordering::Release);
|
||||||
PUSHED.store(true, Ordering::Release);
|
PUSHED.store(true, Ordering::Release);
|
||||||
log::set_max_level(level.to_level_filter());
|
log::set_max_level(level.to_level_filter());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn pop_max_level() {
|
crate fn pop_max_level() {
|
||||||
if PUSHED.load(Ordering::Acquire) {
|
if PUSHED.load(Ordering::Acquire) {
|
||||||
log::set_max_level(usize_to_filter(LAST_LOG_FILTER.load(Ordering::Acquire)));
|
log::set_max_level(usize_to_filter(LAST_LOG_FILTER.load(Ordering::Acquire)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,7 +239,7 @@ impl<'f, T: FromForm<'f> + 'f> Form<'f, T> {
|
||||||
// caller via `get()` and constrain everything to that lifetime. This is, in
|
// caller via `get()` and constrain everything to that lifetime. This is, in
|
||||||
// reality a little coarser than necessary, but the user can simply move the
|
// reality a little coarser than necessary, but the user can simply move the
|
||||||
// call to right after the creation of a Form object to get the same effect.
|
// call to right after the creation of a Form object to get the same effect.
|
||||||
pub(crate) fn new(string: String, strict: bool) -> FormResult<Self, T::Error> {
|
crate fn new(string: String, strict: bool) -> FormResult<Self, T::Error> {
|
||||||
let long_lived_string: &'f str = unsafe {
|
let long_lived_string: &'f str = unsafe {
|
||||||
::std::mem::transmute(string.as_str())
|
::std::mem::transmute(string.as_str())
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,8 +6,7 @@ use request::Request;
|
||||||
use outcome::{self, IntoOutcome};
|
use outcome::{self, IntoOutcome};
|
||||||
use outcome::Outcome::*;
|
use outcome::Outcome::*;
|
||||||
|
|
||||||
use http::{Status, ContentType, Accept, Method, Cookies};
|
use http::{Status, ContentType, Accept, Method, Cookies, uri::Origin};
|
||||||
use http::uri::Uri;
|
|
||||||
|
|
||||||
/// Type alias for the `Outcome` of a `FromRequest` conversion.
|
/// Type alias for the `Outcome` of a `FromRequest` conversion.
|
||||||
pub type Outcome<S, E> = outcome::Outcome<S, (Status, E), ()>;
|
pub type Outcome<S, E> = outcome::Outcome<S, (Status, E), ()>;
|
||||||
|
@ -102,10 +101,10 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
||||||
///
|
///
|
||||||
/// _This implementation always returns successfully._
|
/// _This implementation always returns successfully._
|
||||||
///
|
///
|
||||||
/// * **&URI**
|
/// * **&Origin**
|
||||||
///
|
///
|
||||||
/// Extracts the [`Uri`](/rocket/http/uri/struct.Uri.html) from the incoming
|
/// Extracts the [`Origin`](/rocket/http/uri/struct.Origin.html) URI from
|
||||||
/// request.
|
/// the incoming request.
|
||||||
///
|
///
|
||||||
/// _This implementation always returns successfully._
|
/// _This implementation always returns successfully._
|
||||||
///
|
///
|
||||||
|
@ -229,7 +228,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for Method {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for &'a Uri<'a> {
|
impl<'a, 'r> FromRequest<'a, 'r> for &'a Origin<'a> {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
|
fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
|
||||||
|
|
|
@ -11,7 +11,7 @@ mod tests;
|
||||||
|
|
||||||
pub use self::request::Request;
|
pub use self::request::Request;
|
||||||
pub use self::from_request::{FromRequest, Outcome};
|
pub use self::from_request::{FromRequest, Outcome};
|
||||||
pub use self::param::{FromParam, FromSegments};
|
pub use self::param::{FromParam, FromSegments, SegmentError};
|
||||||
pub use self::form::{Form, LenientForm, FromForm, FromFormValue, FormItems};
|
pub use self::form::{Form, LenientForm, FromForm, FromFormValue, FormItems};
|
||||||
pub use self::state::State;
|
pub use self::state::State;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use std::str::FromStr;
|
use std::str::{FromStr, Utf8Error};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use http::uri::{Uri, Segments, SegmentError};
|
use http::{RawStr, uri::{Uri, Segments}};
|
||||||
use http::RawStr;
|
|
||||||
|
|
||||||
/// Trait to convert a dynamic path segment string to a concrete value.
|
/// Trait to convert a dynamic path segment string to a concrete value.
|
||||||
///
|
///
|
||||||
|
@ -309,6 +308,20 @@ impl<'a> FromSegments<'a> for Segments<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Errors which can occur when attempting to interpret a segment string as a
|
||||||
|
/// valid path segment.
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub enum SegmentError {
|
||||||
|
/// The segment contained invalid UTF8 characters when percent decoded.
|
||||||
|
Utf8(Utf8Error),
|
||||||
|
/// The segment started with the wrapped invalid character.
|
||||||
|
BadStart(char),
|
||||||
|
/// The segment contained the wrapped invalid character.
|
||||||
|
BadChar(char),
|
||||||
|
/// The segment ended with the wrapped invalid character.
|
||||||
|
BadEnd(char),
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a `PathBuf` from a `Segments` iterator. The returned `PathBuf` is
|
/// Creates a `PathBuf` from a `Segments` iterator. The returned `PathBuf` is
|
||||||
/// percent-decoded. If a segment is equal to "..", the previous segment (if
|
/// percent-decoded. If a segment is equal to "..", the previous segment (if
|
||||||
/// any) is skipped.
|
/// any) is skipped.
|
||||||
|
|
|
@ -12,7 +12,7 @@ use super::{FromParam, FromSegments, FromRequest, Outcome};
|
||||||
use rocket::Rocket;
|
use rocket::Rocket;
|
||||||
use router::Route;
|
use router::Route;
|
||||||
use config::{Config, Limits};
|
use config::{Config, Limits};
|
||||||
use http::uri::{Uri, Segments};
|
use http::uri::{Origin, Segments};
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use http::{Method, Header, HeaderMap, Cookies, CookieJar};
|
use http::{Method, Header, HeaderMap, Cookies, CookieJar};
|
||||||
use http::{RawStr, ContentType, Accept, MediaType};
|
use http::{RawStr, ContentType, Accept, MediaType};
|
||||||
|
@ -40,25 +40,23 @@ struct RequestState<'r> {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Request<'r> {
|
pub struct Request<'r> {
|
||||||
method: Cell<Method>,
|
method: Cell<Method>,
|
||||||
uri: Uri<'r>,
|
uri: Origin<'r>,
|
||||||
headers: HeaderMap<'r>,
|
headers: HeaderMap<'r>,
|
||||||
remote: Option<SocketAddr>,
|
remote: Option<SocketAddr>,
|
||||||
state: RequestState<'r>,
|
state: RequestState<'r>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r> Request<'r> {
|
impl<'r> Request<'r> {
|
||||||
/// Create a new `Request` with the given `method` and `uri`. The `uri`
|
/// Create a new `Request` with the given `method` and `uri`.
|
||||||
/// parameter can be of any type that implements `Into<Uri>` including
|
|
||||||
/// `&str` and `String`; it must be a valid absolute URI.
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn new<'s: 'r, U: Into<Uri<'s>>>(
|
crate fn new<'s: 'r>(
|
||||||
rocket: &'r Rocket,
|
rocket: &'r Rocket,
|
||||||
method: Method,
|
method: Method,
|
||||||
uri: U
|
uri: Origin<'s>
|
||||||
) -> Request<'r> {
|
) -> Request<'r> {
|
||||||
Request {
|
Request {
|
||||||
method: Cell::new(method),
|
method: Cell::new(method),
|
||||||
uri: uri.into(),
|
uri: uri,
|
||||||
headers: HeaderMap::new(),
|
headers: HeaderMap::new(),
|
||||||
remote: None,
|
remote: None,
|
||||||
state: RequestState {
|
state: RequestState {
|
||||||
|
@ -74,9 +72,11 @@ impl<'r> Request<'r> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only used by doc-tests!
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn example<F: Fn(&mut Request)>(method: Method, uri: &str, f: F) {
|
pub fn example<F: Fn(&mut Request)>(method: Method, uri: &str, f: F) {
|
||||||
let rocket = Rocket::custom(Config::development().unwrap());
|
let rocket = Rocket::custom(Config::development().unwrap());
|
||||||
|
let uri = Origin::parse(uri).expect("invalid URI in example");
|
||||||
let mut request = Request::new(&rocket, method, uri);
|
let mut request = Request::new(&rocket, method, uri);
|
||||||
f(&mut request);
|
f(&mut request);
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,7 @@ impl<'r> Request<'r> {
|
||||||
self._set_method(method);
|
self._set_method(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Borrow the URI from `self`, which is guaranteed to be an absolute URI.
|
/// Borrow the `Origin` URI from `self`.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
|
@ -158,31 +158,34 @@ impl<'r> Request<'r> {
|
||||||
/// # use rocket::Request;
|
/// # use rocket::Request;
|
||||||
/// # use rocket::http::Method;
|
/// # use rocket::http::Method;
|
||||||
/// # Request::example(Method::Get, "/uri", |request| {
|
/// # Request::example(Method::Get, "/uri", |request| {
|
||||||
/// assert_eq!(request.uri().as_str(), "/uri");
|
/// assert_eq!(request.uri().path(), "/uri");
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn uri(&self) -> &Uri {
|
pub fn uri(&self) -> &Origin {
|
||||||
&self.uri
|
&self.uri
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the URI in `self`. The `uri` parameter can be of any type that
|
/// Set the URI in `self` to `uri`.
|
||||||
/// implements `Into<Uri>` including `&str` and `String`; it _must_ be a
|
|
||||||
/// valid, absolute URI.
|
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
/// # use rocket::Request;
|
/// # use rocket::Request;
|
||||||
/// # use rocket::http::Method;
|
/// # use rocket::http::Method;
|
||||||
/// # Request::example(Method::Get, "/uri", |mut request| {
|
/// # Request::example(Method::Get, "/uri", |mut request| {
|
||||||
/// request.set_uri("/hello/Sergio?type=greeting");
|
/// let uri = Origin::parse("/hello/Sergio?type=greeting").unwrap();
|
||||||
/// assert_eq!(request.uri().as_str(), "/hello/Sergio?type=greeting");
|
///
|
||||||
|
/// request.set_uri(uri);
|
||||||
|
/// assert_eq!(request.uri().path(), "/hello/Sergio");
|
||||||
|
/// assert_eq!(request.uri().query(), Some("type=greeting"));
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set_uri<'u: 'r, U: Into<Uri<'u>>>(&mut self, uri: U) {
|
pub fn set_uri<'u: 'r>(&mut self, uri: Origin<'u>) {
|
||||||
self.uri = uri.into();
|
self.uri = uri;
|
||||||
*self.state.params.borrow_mut() = Vec::new();
|
*self.state.params.borrow_mut() = Vec::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -647,36 +650,37 @@ impl<'r> Request<'r> {
|
||||||
/// use may result in out of bounds indexing.
|
/// use may result in out of bounds indexing.
|
||||||
/// TODO: Figure out the mount path from here.
|
/// TODO: Figure out the mount path from here.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn set_route(&self, route: &'r Route) {
|
crate fn set_route(&self, route: &'r Route) {
|
||||||
self.state.route.set(Some(route));
|
self.state.route.set(Some(route));
|
||||||
*self.state.params.borrow_mut() = route.get_param_indexes(self.uri());
|
*self.state.params.borrow_mut() = route.get_param_indexes(self.uri());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the method of `self`, even when `self` is a shared reference.
|
/// Set the method of `self`, even when `self` is a shared reference.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn _set_method(&self, method: Method) {
|
crate fn _set_method(&self, method: Method) {
|
||||||
self.method.set(method);
|
self.method.set(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace all of the cookies in `self` with those in `jar`.
|
/// Replace all of the cookies in `self` with those in `jar`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn set_cookies(&mut self, jar: CookieJar) {
|
crate fn set_cookies(&mut self, jar: CookieJar) {
|
||||||
self.state.cookies = RefCell::new(jar);
|
self.state.cookies = RefCell::new(jar);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the managed state T, if it exists. For internal use only!
|
/// Get the managed state T, if it exists. For internal use only!
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn get_state<T: Send + Sync + 'static>(&self) -> Option<&'r T> {
|
crate fn get_state<T: Send + Sync + 'static>(&self) -> Option<&'r T> {
|
||||||
self.state.managed.try_get()
|
self.state.managed.try_get()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert from Hyper types into a Rocket Request.
|
/// Convert from Hyper types into a Rocket Request.
|
||||||
pub(crate) fn from_hyp(rocket: &'r Rocket,
|
crate fn from_hyp(
|
||||||
h_method: hyper::Method,
|
rocket: &'r Rocket,
|
||||||
h_headers: hyper::header::Headers,
|
h_method: hyper::Method,
|
||||||
h_uri: hyper::RequestUri,
|
h_headers: hyper::header::Headers,
|
||||||
h_addr: SocketAddr,
|
h_uri: hyper::RequestUri,
|
||||||
) -> Result<Request<'r>, String> {
|
h_addr: SocketAddr,
|
||||||
|
) -> Result<Request<'r>, String> {
|
||||||
// Get a copy of the URI for later use.
|
// Get a copy of the URI for later use.
|
||||||
let uri = match h_uri {
|
let uri = match h_uri {
|
||||||
hyper::RequestUri::AbsolutePath(s) => s,
|
hyper::RequestUri::AbsolutePath(s) => s,
|
||||||
|
@ -689,6 +693,9 @@ impl<'r> Request<'r> {
|
||||||
None => return Err(format!("Invalid method: {}", h_method))
|
None => return Err(format!("Invalid method: {}", h_method))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// We need to re-parse the URI since we don't trust Hyper... :(
|
||||||
|
let uri = Origin::parse_owned(uri).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
// Construct the request object.
|
// Construct the request object.
|
||||||
let mut request = Request::new(rocket, method, uri);
|
let mut request = Request::new(rocket, method, uri);
|
||||||
request.set_remote(h_addr);
|
request.set_remote(h_addr);
|
||||||
|
|
|
@ -26,7 +26,7 @@ mod stream;
|
||||||
mod response;
|
mod response;
|
||||||
mod failure;
|
mod failure;
|
||||||
|
|
||||||
pub(crate) mod flash;
|
crate mod flash;
|
||||||
|
|
||||||
pub mod content;
|
pub mod content;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use request::Request;
|
use request::Request;
|
||||||
use response::{Response, Responder};
|
use response::{Response, Responder};
|
||||||
use http::uri::Uri;
|
use http::uri::Uri;
|
||||||
|
@ -7,7 +9,7 @@ use http::Status;
|
||||||
///
|
///
|
||||||
/// This type simplifies returning a redirect response to the client.
|
/// This type simplifies returning a redirect response to the client.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Redirect(Status, Uri<'static>);
|
pub struct Redirect(Status, Option<Uri<'static>>);
|
||||||
|
|
||||||
impl Redirect {
|
impl Redirect {
|
||||||
/// Construct a temporary "see other" (303) redirect response. This is the
|
/// Construct a temporary "see other" (303) redirect response. This is the
|
||||||
|
@ -23,82 +25,82 @@ impl Redirect {
|
||||||
/// # #[allow(unused_variables)]
|
/// # #[allow(unused_variables)]
|
||||||
/// let redirect = Redirect::to("/other_url");
|
/// let redirect = Redirect::to("/other_url");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn to<U: Into<Uri<'static>>>(uri: U) -> Redirect {
|
pub fn to<U: TryInto<Uri<'static>>>(uri: U) -> Redirect {
|
||||||
Redirect(Status::SeeOther, uri.into())
|
Redirect(Status::SeeOther, uri.try_into().ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a "temporary" (307) redirect response. This response instructs
|
/// Construct a "temporary" (307) redirect response. This response instructs
|
||||||
/// the client to reissue the current request to a different URL,
|
/// the client to reissue the current request to a different URL,
|
||||||
/// maintaining the contents of the request identically. This means that,
|
/// maintaining the contents of the request identically. This means that,
|
||||||
/// for example, a `POST` request will be resent, contents included, to the
|
/// for example, a `POST` request will be resent, contents included, to the
|
||||||
/// requested URL.
|
/// requested URL.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::response::Redirect;
|
/// use rocket::response::Redirect;
|
||||||
///
|
///
|
||||||
/// # #[allow(unused_variables)]
|
/// # #[allow(unused_variables)]
|
||||||
/// let redirect = Redirect::temporary("/other_url");
|
/// let redirect = Redirect::temporary("/other_url");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn temporary<U: Into<Uri<'static>>>(uri: U) -> Redirect {
|
pub fn temporary<U: TryInto<Uri<'static>>>(uri: U) -> Redirect {
|
||||||
Redirect(Status::TemporaryRedirect, uri.into())
|
Redirect(Status::TemporaryRedirect, uri.try_into().ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a "permanent" (308) redirect response. This redirect must only
|
/// Construct a "permanent" (308) redirect response. This redirect must only
|
||||||
/// be used for permanent redirects as it is cached by clients. This
|
/// be used for permanent redirects as it is cached by clients. This
|
||||||
/// response instructs the client to reissue requests for the current URL to
|
/// response instructs the client to reissue requests for the current URL to
|
||||||
/// a different URL, now and in the future, maintaining the contents of the
|
/// a different URL, now and in the future, maintaining the contents of the
|
||||||
/// request identically. This means that, for example, a `POST` request will
|
/// request identically. This means that, for example, a `POST` request will
|
||||||
/// be resent, contents included, to the requested URL.
|
/// be resent, contents included, to the requested URL.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::response::Redirect;
|
/// use rocket::response::Redirect;
|
||||||
///
|
///
|
||||||
/// # #[allow(unused_variables)]
|
/// # #[allow(unused_variables)]
|
||||||
/// let redirect = Redirect::permanent("/other_url");
|
/// let redirect = Redirect::permanent("/other_url");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn permanent<U: Into<Uri<'static>>>(uri: U) -> Redirect {
|
pub fn permanent<U: TryInto<Uri<'static>>>(uri: U) -> Redirect {
|
||||||
Redirect(Status::PermanentRedirect, uri.into())
|
Redirect(Status::PermanentRedirect, uri.try_into().ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a temporary "found" (302) redirect response. This response
|
/// Construct a temporary "found" (302) redirect response. This response
|
||||||
/// instructs the client to reissue the current request to a different URL,
|
/// instructs the client to reissue the current request to a different URL,
|
||||||
/// ideally maintaining the contents of the request identically.
|
/// ideally maintaining the contents of the request identically.
|
||||||
/// Unfortunately, different clients may respond differently to this type of
|
/// Unfortunately, different clients may respond differently to this type of
|
||||||
/// redirect, so `303` or `307` redirects, which disambiguate, are
|
/// redirect, so `303` or `307` redirects, which disambiguate, are
|
||||||
/// preferred.
|
/// preferred.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::response::Redirect;
|
/// use rocket::response::Redirect;
|
||||||
///
|
///
|
||||||
/// # #[allow(unused_variables)]
|
/// # #[allow(unused_variables)]
|
||||||
/// let redirect = Redirect::found("/other_url");
|
/// let redirect = Redirect::found("/other_url");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn found<U: Into<Uri<'static>>>(uri: U) -> Redirect {
|
pub fn found<U: TryInto<Uri<'static>>>(uri: U) -> Redirect {
|
||||||
Redirect(Status::Found, uri.into())
|
Redirect(Status::Found, uri.try_into().ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a permanent "moved" (301) redirect response. This response
|
/// Construct a permanent "moved" (301) redirect response. This response
|
||||||
/// should only be used for permanent redirects as it can be cached by
|
/// should only be used for permanent redirects as it can be cached by
|
||||||
/// browsers. Because different clients may respond differently to this type
|
/// browsers. Because different clients may respond differently to this type
|
||||||
/// of redirect, a `308` redirect, which disambiguates, is preferred.
|
/// of redirect, a `308` redirect, which disambiguates, is preferred.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::response::Redirect;
|
/// use rocket::response::Redirect;
|
||||||
///
|
///
|
||||||
/// # #[allow(unused_variables)]
|
/// # #[allow(unused_variables)]
|
||||||
/// let redirect = Redirect::moved("/other_url");
|
/// let redirect = Redirect::moved("/other_url");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn moved<U: Into<Uri<'static>>>(uri: U) -> Redirect {
|
pub fn moved<U: TryInto<Uri<'static>>>(uri: U) -> Redirect {
|
||||||
Redirect(Status::MovedPermanently, uri.into())
|
Redirect(Status::MovedPermanently, uri.try_into().ok())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a response with the appropriate status code and the given URL in
|
/// Constructs a response with the appropriate status code and the given URL in
|
||||||
|
@ -106,9 +108,14 @@ impl Redirect {
|
||||||
/// responder does not fail.
|
/// responder does not fail.
|
||||||
impl Responder<'static> for Redirect {
|
impl Responder<'static> for Redirect {
|
||||||
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
|
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
|
||||||
Response::build()
|
if let Some(uri) = self.1 {
|
||||||
.status(self.0)
|
Response::build()
|
||||||
.raw_header("Location", self.1.to_string())
|
.status(self.0)
|
||||||
.ok()
|
.raw_header("Location", uri.to_string())
|
||||||
|
.ok()
|
||||||
|
} else {
|
||||||
|
error!("Invalid URI used for redirect.");
|
||||||
|
Err(Status::InternalServerError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -976,7 +976,7 @@ impl<'r> Response<'r> {
|
||||||
// Makes the `Read`er in the body empty but leaves the size of the body if
|
// Makes the `Read`er in the body empty but leaves the size of the body if
|
||||||
// it exists. Only meant to be used to handle HEAD requests automatically.
|
// it exists. Only meant to be used to handle HEAD requests automatically.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn strip_body(&mut self) {
|
crate fn strip_body(&mut self) {
|
||||||
if let Some(body) = self.take_body() {
|
if let Some(body) = self.take_body() {
|
||||||
self.body = match body {
|
self.body = match body {
|
||||||
Body::Sized(_, n) => Some(Body::Sized(Box::new(io::empty()), n)),
|
Body::Sized(_, n) => Some(Body::Sized(Box::new(io::empty()), n)),
|
||||||
|
|
|
@ -24,16 +24,16 @@ use fairing::{Fairing, Fairings};
|
||||||
|
|
||||||
use http::{Method, Status, Header};
|
use http::{Method, Status, Header};
|
||||||
use http::hyper::{self, header};
|
use http::hyper::{self, header};
|
||||||
use http::uri::Uri;
|
use http::uri::Origin;
|
||||||
|
|
||||||
/// The main `Rocket` type: used to mount routes and catchers and launch the
|
/// The main `Rocket` type: used to mount routes and catchers and launch the
|
||||||
/// application.
|
/// application.
|
||||||
pub struct Rocket {
|
pub struct Rocket {
|
||||||
pub(crate) config: Config,
|
crate config: Config,
|
||||||
router: Router,
|
router: Router,
|
||||||
default_catchers: HashMap<u16, Catcher>,
|
default_catchers: HashMap<u16, Catcher>,
|
||||||
catchers: HashMap<u16, Catcher>,
|
catchers: HashMap<u16, Catcher>,
|
||||||
pub(crate) state: Container,
|
crate state: Container,
|
||||||
fairings: Fairings,
|
fairings: Fairings,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,11 @@ impl hyper::Handler for Rocket {
|
||||||
Ok(req) => req,
|
Ok(req) => req,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Bad incoming request: {}", e);
|
error!("Bad incoming request: {}", e);
|
||||||
let dummy = Request::new(self, Method::Get, Uri::new("<unknown>"));
|
// TODO: We don't have a request to pass in, so we just
|
||||||
|
// fabricate one. This is weird. We should let the user know
|
||||||
|
// that we failed to parse a request (by invoking some special
|
||||||
|
// handler) instead of doing this.
|
||||||
|
let dummy = Request::new(self, Method::Get, Origin::dummy());
|
||||||
let r = self.handle_error(Status::BadRequest, &dummy);
|
let r = self.handle_error(Status::BadRequest, &dummy);
|
||||||
return self.issue_response(r, res);
|
return self.issue_response(r, res);
|
||||||
}
|
}
|
||||||
|
@ -193,7 +197,7 @@ impl Rocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn dispatch<'s, 'r>(
|
crate fn dispatch<'s, 'r>(
|
||||||
&'s self,
|
&'s self,
|
||||||
request: &'r mut Request<'s>,
|
request: &'r mut Request<'s>,
|
||||||
data: Data
|
data: Data
|
||||||
|
@ -269,7 +273,7 @@ impl Rocket {
|
||||||
// (ensuring `handler` takes an immutable borrow), any caller to `route`
|
// (ensuring `handler` takes an immutable borrow), any caller to `route`
|
||||||
// should be able to supply an `&mut` and retain an `&` after the call.
|
// should be able to supply an `&mut` and retain an `&` after the call.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn route<'s, 'r>(
|
crate fn route<'s, 'r>(
|
||||||
&'s self,
|
&'s self,
|
||||||
request: &'r Request<'s>,
|
request: &'r Request<'s>,
|
||||||
mut data: Data,
|
mut data: Data,
|
||||||
|
@ -302,7 +306,11 @@ impl Rocket {
|
||||||
// catcher is called. If the catcher fails to return a good response, the
|
// catcher is called. If the catcher fails to return a good response, the
|
||||||
// 500 catcher is executed. If there is no registered catcher for `status`,
|
// 500 catcher is executed. If there is no registered catcher for `status`,
|
||||||
// the default catcher is used.
|
// the default catcher is used.
|
||||||
fn handle_error<'r>(&self, status: Status, req: &'r Request) -> Response<'r> {
|
crate fn handle_error<'r>(
|
||||||
|
&self,
|
||||||
|
status: Status,
|
||||||
|
req: &'r Request
|
||||||
|
) -> Response<'r> {
|
||||||
warn_!("Responding with {} catcher.", Paint::red(&status));
|
warn_!("Responding with {} catcher.", Paint::red(&status));
|
||||||
|
|
||||||
// Try to get the active catcher but fallback to user's 500 catcher.
|
// Try to get the active catcher but fallback to user's 500 catcher.
|
||||||
|
@ -434,8 +442,12 @@ impl Rocket {
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// The `base` mount point must be a static path. That is, the mount point
|
/// Panics if the `base` mount point is not a valid static path: a valid
|
||||||
/// must _not_ contain dynamic path parameters: `<param>`.
|
/// origin URI without dynamic parameters.
|
||||||
|
///
|
||||||
|
/// Panics if any route's URI is not a valid origin URI. This kind of panic
|
||||||
|
/// is guaranteed not to occur if the routes were generated using Rocket's
|
||||||
|
/// code generation.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
@ -486,17 +498,36 @@ impl Rocket {
|
||||||
Paint::purple("Mounting"),
|
Paint::purple("Mounting"),
|
||||||
Paint::blue(base));
|
Paint::blue(base));
|
||||||
|
|
||||||
if base.contains('<') || !base.starts_with('/') {
|
if base.contains('<') || base.contains('>') {
|
||||||
error_!("Bad mount point: '{}'.", base);
|
error_!("Invalid mount point: {}", base);
|
||||||
error_!("Mount points must be static, absolute URIs: `/example`");
|
panic!("Mount points cannot contain dynamic parameters");
|
||||||
panic!("Bad mount point.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for mut route in routes {
|
for mut route in routes {
|
||||||
let uri = Uri::new(format!("{}/{}", base, route.uri));
|
let base_uri = Origin::parse(base)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
error_!("Invalid origin URI used as mount point: {}", base);
|
||||||
|
panic!("Error: {}", e);
|
||||||
|
});
|
||||||
|
|
||||||
route.set_base(base);
|
if base_uri.query().is_some() {
|
||||||
route.set_uri(uri.to_string());
|
error_!("Mount point cannot contain a query string: {}", base_uri);
|
||||||
|
panic!("Invalid mount point.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let complete_uri = format!("{}/{}", base_uri, route.uri);
|
||||||
|
let uri = Origin::parse_route(&complete_uri)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
error_!("Invalid route URI: {}", base);
|
||||||
|
panic!("Error: {}", e)
|
||||||
|
});
|
||||||
|
|
||||||
|
if !uri.is_normalized() {
|
||||||
|
warn_!("Abnormal URI '{}' will be automatically normalized.", uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
route.set_base(base_uri);
|
||||||
|
route.set_uri(uri.to_normalized());
|
||||||
|
|
||||||
info_!("{}", route);
|
info_!("{}", route);
|
||||||
self.router.add(route);
|
self.router.add(route);
|
||||||
|
@ -633,7 +664,7 @@ impl Rocket {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn prelaunch_check(&self) -> Option<LaunchError> {
|
crate fn prelaunch_check(&self) -> Option<LaunchError> {
|
||||||
let collisions = self.router.collisions();
|
let collisions = self.router.collisions();
|
||||||
if !collisions.is_empty() {
|
if !collisions.is_empty() {
|
||||||
let owned = collisions.iter().map(|&(a, b)| (a.clone(), b.clone()));
|
let owned = collisions.iter().map(|&(a, b)| (a.clone(), b.clone()));
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use super::Route;
|
use super::Route;
|
||||||
|
|
||||||
use http::uri::Uri;
|
use http::uri::Origin;
|
||||||
use http::MediaType;
|
use http::MediaType;
|
||||||
use request::Request;
|
use request::Request;
|
||||||
|
|
||||||
|
@ -38,8 +38,8 @@ impl<'a> Collider<str> for &'a str {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This _only_ checks the `path` component of the URI.
|
// This _only_ checks the `path` component of the URI.
|
||||||
impl<'a, 'b> Collider<Uri<'b>> for Uri<'a> {
|
impl<'a, 'b> Collider<Origin<'b>> for Origin<'a> {
|
||||||
fn collides_with(&self, other: &Uri<'b>) -> bool {
|
fn collides_with(&self, other: &Origin<'b>) -> bool {
|
||||||
for (seg_a, seg_b) in self.segments().zip(other.segments()) {
|
for (seg_a, seg_b) in self.segments().zip(other.segments()) {
|
||||||
if seg_a.ends_with("..>") || seg_b.ends_with("..>") {
|
if seg_a.ends_with("..>") || seg_b.ends_with("..>") {
|
||||||
return true;
|
return true;
|
||||||
|
@ -123,7 +123,7 @@ mod tests {
|
||||||
use handler::Outcome;
|
use handler::Outcome;
|
||||||
use router::route::Route;
|
use router::route::Route;
|
||||||
use http::{Method, MediaType, ContentType, Accept};
|
use http::{Method, MediaType, ContentType, Accept};
|
||||||
use http::uri::Uri;
|
use http::uri::Origin;
|
||||||
use http::Method::*;
|
use http::Method::*;
|
||||||
|
|
||||||
type SimpleRoute = (Method, &'static str);
|
type SimpleRoute = (Method, &'static str);
|
||||||
|
@ -139,16 +139,18 @@ mod tests {
|
||||||
|
|
||||||
fn unranked_collide(a: &'static str, b: &'static str) -> bool {
|
fn unranked_collide(a: &'static str, b: &'static str) -> bool {
|
||||||
let route_a = Route::ranked(0, Get, a.to_string(), dummy_handler);
|
let route_a = Route::ranked(0, Get, a.to_string(), dummy_handler);
|
||||||
route_a.collides_with(&Route::ranked(0, Get, b.to_string(), dummy_handler))
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn s_s_collide(a: &'static str, b: &'static str) -> bool {
|
fn s_s_collide(a: &'static str, b: &'static str) -> bool {
|
||||||
Uri::new(a).collides_with(&Uri::new(b))
|
Origin::parse_route(a).unwrap()
|
||||||
|
.collides_with(&Origin::parse_route(b).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple_collisions() {
|
fn simple_collisions() {
|
||||||
assert!(unranked_collide("a", "a"));
|
|
||||||
assert!(unranked_collide("/a", "/a"));
|
assert!(unranked_collide("/a", "/a"));
|
||||||
assert!(unranked_collide("/hello", "/hello"));
|
assert!(unranked_collide("/hello", "/hello"));
|
||||||
assert!(unranked_collide("/hello", "/hello/"));
|
assert!(unranked_collide("/hello", "/hello/"));
|
||||||
|
@ -253,6 +255,13 @@ mod tests {
|
||||||
assert!(!m_collide((Get, "/hello"), (Put, "/hello")));
|
assert!(!m_collide((Get, "/hello"), (Put, "/hello")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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")));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_str_non_collisions() {
|
fn test_str_non_collisions() {
|
||||||
assert!(!s_s_collide("/a", "/b"));
|
assert!(!s_s_collide("/a", "/b"));
|
||||||
|
@ -365,7 +374,7 @@ mod tests {
|
||||||
where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>>
|
where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>>
|
||||||
{
|
{
|
||||||
let rocket = Rocket::custom(Config::development().unwrap());
|
let rocket = Rocket::custom(Config::development().unwrap());
|
||||||
let mut req = Request::new(&rocket, m, "/");
|
let mut req = Request::new(&rocket, m, Origin::dummy());
|
||||||
if let Some(mt_str) = mt1.into() {
|
if let Some(mt_str) = mt1.into() {
|
||||||
if m.supports_payload() {
|
if m.supports_payload() {
|
||||||
req.replace_header(mt_str.parse::<ContentType>().unwrap());
|
req.replace_header(mt_str.parse::<ContentType>().unwrap());
|
||||||
|
@ -423,7 +432,7 @@ mod tests {
|
||||||
|
|
||||||
fn req_route_path_collide(a: &'static str, b: &'static str) -> bool {
|
fn req_route_path_collide(a: &'static str, b: &'static str) -> bool {
|
||||||
let rocket = Rocket::custom(Config::development().unwrap());
|
let rocket = Rocket::custom(Config::development().unwrap());
|
||||||
let req = Request::new(&rocket, Get, a.to_string());
|
let req = Request::new(&rocket, Get, Origin::parse(a).expect("valid URI"));
|
||||||
let route = Route::ranked(0, Get, b.to_string(), dummy_handler);
|
let route = Route::ranked(0, Get, b.to_string(), dummy_handler);
|
||||||
route.collides_with(&req)
|
route.collides_with(&req)
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,6 @@ impl Router {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// This is slow. Don't expose this publicly; only for tests.
|
// This is slow. Don't expose this publicly; only for tests.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn has_collisions(&self) -> bool {
|
fn has_collisions(&self) -> bool {
|
||||||
|
@ -78,7 +77,7 @@ mod test {
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use http::Method;
|
use http::Method;
|
||||||
use http::Method::*;
|
use http::Method::*;
|
||||||
use http::uri::Uri;
|
use http::uri::Origin;
|
||||||
use request::Request;
|
use request::Request;
|
||||||
use data::Data;
|
use data::Data;
|
||||||
use handler::Outcome;
|
use handler::Outcome;
|
||||||
|
@ -139,12 +138,28 @@ mod test {
|
||||||
assert!(unranked_route_collisions(&["/<a>/b", "/a/<a..>"]));
|
assert!(unranked_route_collisions(&["/<a>/b", "/a/<a..>"]));
|
||||||
assert!(unranked_route_collisions(&["/a/<b>", "/a/<a..>"]));
|
assert!(unranked_route_collisions(&["/a/<b>", "/a/<a..>"]));
|
||||||
assert!(unranked_route_collisions(&["/a/b/<c>", "/a/<a..>"]));
|
assert!(unranked_route_collisions(&["/a/b/<c>", "/a/<a..>"]));
|
||||||
assert!(unranked_route_collisions(&["<a..>", "/a/<a..>"]));
|
assert!(unranked_route_collisions(&["/<a..>", "/a/<a..>"]));
|
||||||
assert!(unranked_route_collisions(&["/a/<a..>", "/a/<a..>"]));
|
assert!(unranked_route_collisions(&["/a/<a..>", "/a/<a..>"]));
|
||||||
assert!(unranked_route_collisions(&["/a/b/<a..>", "/a/<a..>"]));
|
assert!(unranked_route_collisions(&["/a/b/<a..>", "/a/<a..>"]));
|
||||||
assert!(unranked_route_collisions(&["/a/b/c/d", "/a/<a..>"]));
|
assert!(unranked_route_collisions(&["/a/b/c/d", "/a/<a..>"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_collisions_normalize() {
|
||||||
|
assert!(unranked_route_collisions(&["/hello/", "/hello"]));
|
||||||
|
assert!(unranked_route_collisions(&["//hello/", "/hello"]));
|
||||||
|
assert!(unranked_route_collisions(&["//hello/", "/hello//"]));
|
||||||
|
assert!(unranked_route_collisions(&["/<a>", "/hello//"]));
|
||||||
|
assert!(unranked_route_collisions(&["/<a>", "/hello///"]));
|
||||||
|
assert!(unranked_route_collisions(&["/hello///bob", "/hello/<b>"]));
|
||||||
|
assert!(unranked_route_collisions(&["/<a..>//", "/a//<a..>"]));
|
||||||
|
assert!(unranked_route_collisions(&["/a/<a..>//", "/a/<a..>"]));
|
||||||
|
assert!(unranked_route_collisions(&["/a/<a..>//", "/a/b//c//d/"]));
|
||||||
|
assert!(unranked_route_collisions(&["/a/<a..>/", "/a/bd/e/"]));
|
||||||
|
assert!(unranked_route_collisions(&["/a/<a..>//", "/a/b//c//d/e/"]));
|
||||||
|
assert!(unranked_route_collisions(&["/a//<a..>//", "/a/b//c//d/e/"]));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_no_collisions() {
|
fn test_no_collisions() {
|
||||||
assert!(!unranked_route_collisions(&["/<a>", "/a/<a..>"]));
|
assert!(!unranked_route_collisions(&["/<a>", "/a/<a..>"]));
|
||||||
|
@ -166,7 +181,7 @@ mod test {
|
||||||
|
|
||||||
fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> {
|
fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> {
|
||||||
let rocket = Rocket::custom(Config::development().unwrap());
|
let rocket = Rocket::custom(Config::development().unwrap());
|
||||||
let request = Request::new(&rocket, method, Uri::new(uri));
|
let request = Request::new(&rocket, method, Origin::parse(uri).unwrap());
|
||||||
let matches = router.route(&request);
|
let matches = router.route(&request);
|
||||||
if matches.len() > 0 {
|
if matches.len() > 0 {
|
||||||
Some(matches[0])
|
Some(matches[0])
|
||||||
|
@ -177,7 +192,7 @@ mod test {
|
||||||
|
|
||||||
fn matches<'a>(router: &'a Router, method: Method, uri: &str) -> Vec<&'a Route> {
|
fn matches<'a>(router: &'a Router, method: Method, uri: &str) -> Vec<&'a Route> {
|
||||||
let rocket = Rocket::custom(Config::development().unwrap());
|
let rocket = Rocket::custom(Config::development().unwrap());
|
||||||
let request = Request::new(&rocket, method, Uri::new(uri));
|
let request = Request::new(&rocket, method, Origin::parse(uri).unwrap());
|
||||||
router.route(&request)
|
router.route(&request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,8 +260,8 @@ mod test {
|
||||||
macro_rules! assert_ranked_routes {
|
macro_rules! assert_ranked_routes {
|
||||||
($routes:expr, $to:expr, $want:expr) => ({
|
($routes:expr, $to:expr, $want:expr) => ({
|
||||||
let router = router_with_routes($routes);
|
let router = router_with_routes($routes);
|
||||||
let route_path = route(&router, Get, $to).unwrap().uri.as_str();
|
let route_path = route(&router, Get, $to).unwrap().uri.to_string();
|
||||||
assert_eq!(route_path as &str, $want as &str);
|
assert_eq!(route_path, $want.to_string());
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,21 +290,21 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_no_manual_ranked_collisions() {
|
fn test_no_manual_ranked_collisions() {
|
||||||
assert!(!ranked_collisions(&[(1, "a/<b>"), (2, "a/<b>")]));
|
assert!(!ranked_collisions(&[(1, "/a/<b>"), (2, "/a/<b>")]));
|
||||||
assert!(!ranked_collisions(&[(0, "a/<b>"), (2, "a/<b>")]));
|
assert!(!ranked_collisions(&[(0, "/a/<b>"), (2, "/a/<b>")]));
|
||||||
assert!(!ranked_collisions(&[(5, "a/<b>"), (2, "a/<b>")]));
|
assert!(!ranked_collisions(&[(5, "/a/<b>"), (2, "/a/<b>")]));
|
||||||
assert!(!ranked_collisions(&[(1, "a/<b>"), (1, "b/<b>")]));
|
assert!(!ranked_collisions(&[(1, "/a/<b>"), (1, "/b/<b>")]));
|
||||||
assert!(!ranked_collisions(&[(1, "a/<b..>"), (2, "a/<b..>")]));
|
assert!(!ranked_collisions(&[(1, "/a/<b..>"), (2, "/a/<b..>")]));
|
||||||
assert!(!ranked_collisions(&[(0, "a/<b..>"), (2, "a/<b..>")]));
|
assert!(!ranked_collisions(&[(0, "/a/<b..>"), (2, "/a/<b..>")]));
|
||||||
assert!(!ranked_collisions(&[(5, "a/<b..>"), (2, "a/<b..>")]));
|
assert!(!ranked_collisions(&[(5, "/a/<b..>"), (2, "/a/<b..>")]));
|
||||||
assert!(!ranked_collisions(&[(1, "<a..>"), (2, "<a..>")]));
|
assert!(!ranked_collisions(&[(1, "/<a..>"), (2, "/<a..>")]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ranked_collisions() {
|
fn test_ranked_collisions() {
|
||||||
assert!(ranked_collisions(&[(2, "a/<b..>"), (2, "a/<b..>")]));
|
assert!(ranked_collisions(&[(2, "/a/<b..>"), (2, "/a/<b..>")]));
|
||||||
assert!(ranked_collisions(&[(2, "a/c/<b..>"), (2, "a/<b..>")]));
|
assert!(ranked_collisions(&[(2, "/a/c/<b..>"), (2, "/a/<b..>")]));
|
||||||
assert!(ranked_collisions(&[(2, "<b..>"), (2, "a/<b..>")]));
|
assert!(ranked_collisions(&[(2, "/<b..>"), (2, "/a/<b..>")]));
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! assert_ranked_routing {
|
macro_rules! assert_ranked_routing {
|
||||||
|
@ -300,7 +315,7 @@ mod test {
|
||||||
assert!(routed_to.len() == expected.len());
|
assert!(routed_to.len() == expected.len());
|
||||||
for (got, expected) in routed_to.iter().zip(expected.iter()) {
|
for (got, expected) in routed_to.iter().zip(expected.iter()) {
|
||||||
assert_eq!(got.rank, expected.0);
|
assert_eq!(got.rank, expected.0);
|
||||||
assert_eq!(got.uri.as_str(), expected.1);
|
assert_eq!(got.uri.to_string(), expected.1.to_string());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -308,33 +323,33 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ranked_routing() {
|
fn test_ranked_routing() {
|
||||||
assert_ranked_routing!(
|
assert_ranked_routing!(
|
||||||
to: "a/b",
|
to: "/a/b",
|
||||||
with: [(1, "a/<b>"), (2, "a/<b>")],
|
with: [(1, "/a/<b>"), (2, "/a/<b>")],
|
||||||
expect: (1, "a/<b>"), (2, "a/<b>")
|
expect: (1, "/a/<b>"), (2, "/a/<b>")
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_ranked_routing!(
|
assert_ranked_routing!(
|
||||||
to: "b/b",
|
to: "/b/b",
|
||||||
with: [(1, "a/<b>"), (2, "b/<b>"), (3, "b/b")],
|
with: [(1, "/a/<b>"), (2, "/b/<b>"), (3, "/b/b")],
|
||||||
expect: (2, "b/<b>"), (3, "b/b")
|
expect: (2, "/b/<b>"), (3, "/b/b")
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_ranked_routing!(
|
assert_ranked_routing!(
|
||||||
to: "b/b",
|
to: "/b/b",
|
||||||
with: [(2, "b/<b>"), (1, "a/<b>"), (3, "b/b")],
|
with: [(2, "/b/<b>"), (1, "/a/<b>"), (3, "/b/b")],
|
||||||
expect: (2, "b/<b>"), (3, "b/b")
|
expect: (2, "/b/<b>"), (3, "/b/b")
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_ranked_routing!(
|
assert_ranked_routing!(
|
||||||
to: "b/b",
|
to: "/b/b",
|
||||||
with: [(3, "b/b"), (2, "b/<b>"), (1, "a/<b>")],
|
with: [(3, "/b/b"), (2, "/b/<b>"), (1, "/a/<b>")],
|
||||||
expect: (2, "b/<b>"), (3, "b/b")
|
expect: (2, "/b/<b>"), (3, "/b/b")
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_ranked_routing!(
|
assert_ranked_routing!(
|
||||||
to: "b/b",
|
to: "/b/b",
|
||||||
with: [(1, "a/<b>"), (2, "b/<b>"), (0, "b/b")],
|
with: [(1, "/a/<b>"), (2, "/b/<b>"), (0, "/b/b")],
|
||||||
expect: (0, "b/b"), (2, "b/<b>")
|
expect: (0, "/b/b"), (2, "/b/<b>")
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_ranked_routing!(
|
assert_ranked_routing!(
|
||||||
|
@ -369,7 +384,7 @@ mod test {
|
||||||
let expected = &[$($want),+];
|
let expected = &[$($want),+];
|
||||||
assert!(routed_to.len() == expected.len());
|
assert!(routed_to.len() == expected.len());
|
||||||
for (got, expected) in routed_to.iter().zip(expected.iter()) {
|
for (got, expected) in routed_to.iter().zip(expected.iter()) {
|
||||||
assert_eq!(got.uri.as_str(), expected as &str);
|
assert_eq!(got.uri.to_string(), expected.to_string());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -377,34 +392,35 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_ranked_routing() {
|
fn test_default_ranked_routing() {
|
||||||
assert_default_ranked_routing!(
|
assert_default_ranked_routing!(
|
||||||
to: "a/b?v=1",
|
to: "/a/b?v=1",
|
||||||
with: ["a/<b>", "a/b"],
|
with: ["/a/<b>", "/a/b"],
|
||||||
expect: "a/b", "a/<b>"
|
expect: "/a/b", "/a/<b>"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_default_ranked_routing!(
|
assert_default_ranked_routing!(
|
||||||
to: "a/b?v=1",
|
to: "/a/b?v=1",
|
||||||
with: ["a/<b>", "a/b", "a/b?<v>"],
|
with: ["/a/<b>", "/a/b", "/a/b?<v>"],
|
||||||
expect: "a/b?<v>", "a/b", "a/<b>"
|
expect: "/a/b?<v>", "/a/b", "/a/<b>"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_default_ranked_routing!(
|
assert_default_ranked_routing!(
|
||||||
to: "a/b?v=1",
|
to: "/a/b?v=1",
|
||||||
with: ["a/<b>", "a/b", "a/b?<v>", "a/<b>?<v>"],
|
with: ["/a/<b>", "/a/b", "/a/b?<v>", "/a/<b>?<v>"],
|
||||||
expect: "a/b?<v>", "a/b", "a/<b>?<v>", "a/<b>"
|
expect: "/a/b?<v>", "/a/b", "/a/<b>?<v>", "/a/<b>"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_default_ranked_routing!(
|
assert_default_ranked_routing!(
|
||||||
to: "a/b",
|
to: "/a/b",
|
||||||
with: ["a/<b>", "a/b", "a/b?<v>", "a/<b>?<v>"],
|
with: ["/a/<b>", "/a/b", "/a/b?<v>", "/a/<b>?<v>"],
|
||||||
expect: "a/b", "a/<b>"
|
expect: "/a/b", "/a/<b>"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_params(router: &Router, path: &str, expected: &[&str]) -> bool {
|
fn match_params(router: &Router, path: &str, expected: &[&str]) -> bool {
|
||||||
println!("Testing: {} (expect: {:?})", path, expected);
|
println!("Testing: {} (expect: {:?})", path, expected);
|
||||||
route(router, Get, path).map_or(false, |route| {
|
route(router, Get, path).map_or(false, |route| {
|
||||||
let params = route.get_param_indexes(&Uri::new(path));
|
let uri = Origin::parse_route(path).unwrap();
|
||||||
|
let params = route.get_param_indexes(&uri);
|
||||||
if params.len() != expected.len() {
|
if params.len() != expected.len() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,11 @@ use yansi::Color::*;
|
||||||
use codegen::StaticRouteInfo;
|
use codegen::StaticRouteInfo;
|
||||||
use handler::Handler;
|
use handler::Handler;
|
||||||
use http::{Method, MediaType};
|
use http::{Method, MediaType};
|
||||||
use http::uri::Uri;
|
use http::ext::IntoOwned;
|
||||||
|
use http::uri::Origin;
|
||||||
|
|
||||||
/// A route: a method, its handler, path, rank, and format/media type.
|
/// A route: a method, its handler, path, rank, and format/media type.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Route {
|
pub struct Route {
|
||||||
/// The name of this route, if one was given.
|
/// The name of this route, if one was given.
|
||||||
pub name: Option<&'static str>,
|
pub name: Option<&'static str>,
|
||||||
|
@ -17,10 +19,10 @@ pub struct Route {
|
||||||
/// The function that should be called when the route matches.
|
/// The function that should be called when the route matches.
|
||||||
pub handler: Handler,
|
pub handler: Handler,
|
||||||
/// The base mount point of this `Route`.
|
/// The base mount point of this `Route`.
|
||||||
pub base: Uri<'static>,
|
pub base: Origin<'static>,
|
||||||
/// The uri (in Rocket format) that should be matched against. This uri
|
/// The uri (in Rocket's route format) that should be matched against. This
|
||||||
/// already includes the base mount point.
|
/// URI already includes the base mount point.
|
||||||
pub uri: Uri<'static>,
|
pub uri: Origin<'static>,
|
||||||
/// The rank of this route. Lower ranks have higher priorities.
|
/// The rank of this route. Lower ranks have higher priorities.
|
||||||
pub rank: isize,
|
pub rank: isize,
|
||||||
/// The media type this route matches against, if any.
|
/// The media type this route matches against, if any.
|
||||||
|
@ -28,10 +30,10 @@ pub struct Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn default_rank(uri: &Uri) -> isize {
|
fn default_rank(uri: &Origin) -> isize {
|
||||||
// static path, query = -4; static path, no query = -3
|
// static path, query = -4; static path, no query = -3
|
||||||
// dynamic path, query = -2; dynamic path, no query = -1
|
// dynamic path, query = -2; dynamic path, no query = -1
|
||||||
match (!uri.path().contains('<'), uri.query().is_some()) {
|
match (!uri.path().contains('<'), uri.query().is_some()) {
|
||||||
(true, true) => -4,
|
(true, true) => -4,
|
||||||
(true, false) => -3,
|
(true, false) => -3,
|
||||||
(false, true) => -2,
|
(false, true) => -2,
|
||||||
|
@ -77,19 +79,19 @@ impl Route {
|
||||||
/// // this is a rank -1 route matching requests to `GET /<name>`
|
/// // this is a rank -1 route matching requests to `GET /<name>`
|
||||||
/// let name = Route::new(Method::Get, "/<name>", handler);
|
/// let name = Route::new(Method::Get, "/<name>", handler);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new<S>(m: Method, path: S, handler: Handler) -> Route
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `path` is not a valid origin URI.
|
||||||
|
pub fn new<S>(method: Method, path: S, handler: Handler) -> Route
|
||||||
where S: AsRef<str>
|
where S: AsRef<str>
|
||||||
{
|
{
|
||||||
let uri = Uri::from(path.as_ref().to_string());
|
let path = path.as_ref();
|
||||||
Route {
|
let origin = Origin::parse_route(path)
|
||||||
name: None,
|
.expect("invalid URI used as route path in `Route::new()`");
|
||||||
method: m,
|
|
||||||
handler: handler,
|
let rank = default_rank(&origin);
|
||||||
rank: default_rank(&uri),
|
Route::ranked(rank, method, path, handler)
|
||||||
base: Uri::from("/"),
|
|
||||||
uri: uri,
|
|
||||||
format: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new route with the given rank, method, path, and handler with
|
/// Creates a new route with the given rank, method, path, and handler with
|
||||||
|
@ -109,17 +111,22 @@ impl Route {
|
||||||
/// // this is a rank 1 route matching requests to `GET /`
|
/// // this is a rank 1 route matching requests to `GET /`
|
||||||
/// let index = Route::ranked(1, Method::Get, "/", handler);
|
/// let index = Route::ranked(1, Method::Get, "/", handler);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn ranked<S>(rank: isize, m: Method, uri: S, handler: Handler) -> Route
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `path` is not a valid origin URI.
|
||||||
|
pub fn ranked<S>(rank: isize, method: Method, path: S, handler: Handler) -> Route
|
||||||
where S: AsRef<str>
|
where S: AsRef<str>
|
||||||
{
|
{
|
||||||
|
let uri = Origin::parse_route(path.as_ref())
|
||||||
|
.expect("invalid URI used as route path in `Route::ranked()`")
|
||||||
|
.into_owned();
|
||||||
|
|
||||||
Route {
|
Route {
|
||||||
name: None,
|
name: None,
|
||||||
method: m,
|
|
||||||
handler: handler,
|
|
||||||
base: Uri::from("/"),
|
|
||||||
uri: Uri::from(uri.as_ref().to_string()),
|
|
||||||
rank: rank,
|
|
||||||
format: None,
|
format: None,
|
||||||
|
base: Origin::dummy(),
|
||||||
|
method, handler, rank, uri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,14 +153,14 @@ impl Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the base mount point of the route. Does not update the rank or any
|
/// Sets the base mount point of the route. Does not update the rank or any
|
||||||
/// other parameters.
|
/// other parameters. If `path` contains a query, it is ignored.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::{Request, Route, Data};
|
/// use rocket::{Request, Route, Data};
|
||||||
|
/// use rocket::http::{Method, uri::Origin};
|
||||||
/// use rocket::handler::Outcome;
|
/// use rocket::handler::Outcome;
|
||||||
/// use rocket::http::Method;
|
|
||||||
///
|
///
|
||||||
/// fn handler<'r>(request: &'r Request, _data: Data) -> Outcome<'r> {
|
/// fn handler<'r>(request: &'r Request, _data: Data) -> Outcome<'r> {
|
||||||
/// Outcome::from(request, "Hello, world!")
|
/// Outcome::from(request, "Hello, world!")
|
||||||
|
@ -163,12 +170,13 @@ impl Route {
|
||||||
/// assert_eq!(index.base(), "/");
|
/// assert_eq!(index.base(), "/");
|
||||||
/// assert_eq!(index.base.path(), "/");
|
/// assert_eq!(index.base.path(), "/");
|
||||||
///
|
///
|
||||||
/// index.set_base("/hi");
|
/// index.set_base(Origin::parse("/hi").unwrap());
|
||||||
/// assert_eq!(index.base(), "/hi");
|
/// assert_eq!(index.base(), "/hi");
|
||||||
/// assert_eq!(index.base.path(), "/hi");
|
/// assert_eq!(index.base.path(), "/hi");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set_base<S>(&mut self, path: S) where S: AsRef<str> {
|
pub fn set_base<'a>(&mut self, path: Origin<'a>) {
|
||||||
self.base = Uri::from(path.as_ref().to_string());
|
self.base = path.into_owned();
|
||||||
|
self.base.clear_query();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the path of the route. Does not update the rank or any other
|
/// Sets the path of the route. Does not update the rank or any other
|
||||||
|
@ -178,8 +186,8 @@ impl Route {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::{Request, Route, Data};
|
/// use rocket::{Request, Route, Data};
|
||||||
|
/// use rocket::http::{Method, uri::Origin};
|
||||||
/// use rocket::handler::Outcome;
|
/// use rocket::handler::Outcome;
|
||||||
/// use rocket::http::Method;
|
|
||||||
///
|
///
|
||||||
/// fn handler<'r>(request: &'r Request, _data: Data) -> Outcome<'r> {
|
/// fn handler<'r>(request: &'r Request, _data: Data) -> Outcome<'r> {
|
||||||
/// Outcome::from(request, "Hello, world!")
|
/// Outcome::from(request, "Hello, world!")
|
||||||
|
@ -188,11 +196,11 @@ impl Route {
|
||||||
/// let mut index = Route::ranked(1, Method::Get, "/", handler);
|
/// let mut index = Route::ranked(1, Method::Get, "/", handler);
|
||||||
/// assert_eq!(index.uri.path(), "/");
|
/// assert_eq!(index.uri.path(), "/");
|
||||||
///
|
///
|
||||||
/// index.set_uri("/hello");
|
/// index.set_uri(Origin::parse("/hello").unwrap());
|
||||||
/// assert_eq!(index.uri.path(), "/hello");
|
/// assert_eq!(index.uri.path(), "/hello");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set_uri<S>(&mut self, uri: S) where S: AsRef<str> {
|
pub fn set_uri<'a>(&mut self, uri: Origin<'a>) {
|
||||||
self.uri = Uri::from(uri.as_ref().to_string());
|
self.uri = uri.into_owned();
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Decide whether a component has to be fully variable or not. That
|
// FIXME: Decide whether a component has to be fully variable or not. That
|
||||||
|
@ -200,7 +208,7 @@ impl Route {
|
||||||
// TODO: Don't return a Vec...take in an &mut [&'a str] (no alloc!)
|
// TODO: Don't return a Vec...take in an &mut [&'a str] (no alloc!)
|
||||||
/// Given a URI, returns a vector of slices of that URI corresponding to the
|
/// Given a URI, returns a vector of slices of that URI corresponding to the
|
||||||
/// dynamic segments in this route.
|
/// dynamic segments in this route.
|
||||||
pub(crate) fn get_param_indexes(&self, uri: &Uri) -> Vec<(usize, usize)> {
|
crate fn get_param_indexes(&self, uri: &Origin) -> Vec<(usize, usize)> {
|
||||||
let route_segs = self.uri.segments();
|
let route_segs = self.uri.segments();
|
||||||
let uri_segs = uri.segments();
|
let uri_segs = uri.segments();
|
||||||
let start_addr = uri.path().as_ptr() as usize;
|
let start_addr = uri.path().as_ptr() as usize;
|
||||||
|
@ -221,20 +229,6 @@ impl Route {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Route {
|
|
||||||
fn clone(&self) -> Route {
|
|
||||||
Route {
|
|
||||||
name: self.name,
|
|
||||||
method: self.method,
|
|
||||||
handler: self.handler,
|
|
||||||
rank: self.rank,
|
|
||||||
base: self.base.clone(),
|
|
||||||
uri: self.uri.clone(),
|
|
||||||
format: self.format.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Route {
|
impl fmt::Display for Route {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{} {}", Green.paint(&self.method), Blue.paint(&self.uri))?;
|
write!(f, "{} {}", Green.paint(&self.method), Blue.paint(&self.uri))?;
|
||||||
|
@ -258,7 +252,14 @@ impl fmt::Display for Route {
|
||||||
|
|
||||||
impl fmt::Debug for Route {
|
impl fmt::Debug for Route {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
<Route as fmt::Display>::fmt(self, f)
|
f.debug_struct("Route")
|
||||||
|
.field("name", &self.name)
|
||||||
|
.field("method", &self.method)
|
||||||
|
.field("base", &self.base)
|
||||||
|
.field("uri", &self.uri)
|
||||||
|
.field("rank", &self.rank)
|
||||||
|
.field("format", &self.format)
|
||||||
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
#![feature(plugin, decl_macro)]
|
||||||
|
#![plugin(rocket_codegen)]
|
||||||
|
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
|
use rocket::response::Redirect;
|
||||||
|
|
||||||
|
#[get("/google")]
|
||||||
|
fn google() -> Redirect {
|
||||||
|
Redirect::to("https://www.google.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/rocket")]
|
||||||
|
fn rocket() -> Redirect {
|
||||||
|
Redirect::to("https://rocket.rs:80")
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_absolute_uris_okay {
|
||||||
|
use super::*;
|
||||||
|
use rocket::local::Client;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn redirect_works() {
|
||||||
|
let rocket = rocket::ignite().mount("/", routes![google, rocket]);
|
||||||
|
let client = Client::new(rocket).unwrap();
|
||||||
|
|
||||||
|
let response = client.get("/google").dispatch();
|
||||||
|
let location = response.headers().get_one("Location");
|
||||||
|
assert_eq!(location, Some("https://www.google.com"));
|
||||||
|
|
||||||
|
let response = client.get("/rocket").dispatch();
|
||||||
|
let location = response.headers().get_one("Location");
|
||||||
|
assert_eq!(location, Some("https://rocket.rs:80"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -66,12 +66,12 @@ mod local_request_content_type_tests {
|
||||||
let client = Client::new(rocket()).unwrap();
|
let client = Client::new(rocket()).unwrap();
|
||||||
|
|
||||||
let mut req = client.post("/");
|
let mut req = client.post("/");
|
||||||
assert_eq!(req.cloned_dispatch().body_string(), Some("Absent".to_string()));
|
assert_eq!(req.clone().dispatch().body_string(), Some("Absent".to_string()));
|
||||||
assert_eq!(req.mut_dispatch().body_string(), Some("Absent".to_string()));
|
assert_eq!(req.mut_dispatch().body_string(), Some("Absent".to_string()));
|
||||||
assert_eq!(req.dispatch().body_string(), Some("Absent".to_string()));
|
assert_eq!(req.dispatch().body_string(), Some("Absent".to_string()));
|
||||||
|
|
||||||
let mut req = client.post("/data");
|
let mut req = client.post("/data");
|
||||||
assert_eq!(req.cloned_dispatch().body_string(), Some("Data Absent".to_string()));
|
assert_eq!(req.clone().dispatch().body_string(), Some("Data Absent".to_string()));
|
||||||
assert_eq!(req.mut_dispatch().body_string(), Some("Data Absent".to_string()));
|
assert_eq!(req.mut_dispatch().body_string(), Some("Data Absent".to_string()));
|
||||||
assert_eq!(req.dispatch().body_string(), Some("Data Absent".to_string()));
|
assert_eq!(req.dispatch().body_string(), Some("Data Absent".to_string()));
|
||||||
}
|
}
|
||||||
|
@ -81,12 +81,12 @@ mod local_request_content_type_tests {
|
||||||
let client = Client::new(rocket()).unwrap();
|
let client = Client::new(rocket()).unwrap();
|
||||||
|
|
||||||
let mut req = client.post("/").header(ContentType::JSON);
|
let mut req = client.post("/").header(ContentType::JSON);
|
||||||
assert_eq!(req.cloned_dispatch().body_string(), Some("Present".to_string()));
|
assert_eq!(req.clone().dispatch().body_string(), Some("Present".to_string()));
|
||||||
assert_eq!(req.mut_dispatch().body_string(), Some("Present".to_string()));
|
assert_eq!(req.mut_dispatch().body_string(), Some("Present".to_string()));
|
||||||
assert_eq!(req.dispatch().body_string(), Some("Present".to_string()));
|
assert_eq!(req.dispatch().body_string(), Some("Present".to_string()));
|
||||||
|
|
||||||
let mut req = client.post("/data").header(ContentType::JSON);
|
let mut req = client.post("/data").header(ContentType::JSON);
|
||||||
assert_eq!(req.cloned_dispatch().body_string(), Some("Data Present".to_string()));
|
assert_eq!(req.clone().dispatch().body_string(), Some("Data Present".to_string()));
|
||||||
assert_eq!(req.mut_dispatch().body_string(), Some("Data Present".to_string()));
|
assert_eq!(req.mut_dispatch().body_string(), Some("Data Present".to_string()));
|
||||||
assert_eq!(req.dispatch().body_string(), Some("Data Present".to_string()));
|
assert_eq!(req.dispatch().body_string(), Some("Data Present".to_string()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ fn about() -> Template {
|
||||||
#[catch(404)]
|
#[catch(404)]
|
||||||
fn not_found(req: &Request) -> Template {
|
fn not_found(req: &Request) -> Template {
|
||||||
let mut map = std::collections::HashMap::new();
|
let mut map = std::collections::HashMap::new();
|
||||||
map.insert("path", req.uri().as_str());
|
map.insert("path", req.uri().path());
|
||||||
Template::render("error/404", &map)
|
Template::render("error/404", &map)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ fn name<'a>(req: &'a Request, _: Data) -> Outcome<'a> {
|
||||||
|
|
||||||
fn echo_url(req: &Request, _: Data) -> Outcome<'static> {
|
fn echo_url(req: &Request, _: Data) -> Outcome<'static> {
|
||||||
let param = req.uri()
|
let param = req.uri()
|
||||||
.as_str()
|
.path()
|
||||||
.split_at(6)
|
.split_at(6)
|
||||||
.1;
|
.1;
|
||||||
|
|
||||||
|
|
|
@ -24,9 +24,8 @@ fn test_name() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_echo() {
|
fn test_echo() {
|
||||||
let echo = "echo text";
|
let uri = format!("/echo:echo%20text");
|
||||||
let uri = format!("/echo:echo text");
|
test(&uri, ContentType::Plain, Status::Ok, "echo text".into());
|
||||||
test(&uri, ContentType::Plain, Status::Ok, echo.to_string());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -34,7 +34,7 @@ fn get(name: String) -> Template {
|
||||||
#[catch(404)]
|
#[catch(404)]
|
||||||
fn not_found(req: &Request) -> Template {
|
fn not_found(req: &Request) -> Template {
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert("path", req.uri().as_str());
|
map.insert("path", req.uri().path());
|
||||||
Template::render("error/404", &map)
|
Template::render("error/404", &map)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue