From 56c6a96f6a94ab844a0f571c303d741c7ec668d3 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Sat, 28 Jul 2018 18:26:15 -0700 Subject: [PATCH] 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` 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. --- contrib/lib/src/lib.rs | 1 + contrib/lib/src/templates/engine.rs | 6 +- core/codegen/src/decorators/route.rs | 4 +- core/codegen/src/lib.rs | 29 +- core/codegen/src/macros/uri.rs | 139 ++-- core/codegen/src/parser/route.rs | 13 +- core/codegen/src/parser/uri.rs | 55 +- core/codegen/src/parser/uri_macro.rs | 16 +- .../compile-fail/absolute-mount-paths.rs | 6 +- core/codegen/tests/segments.rs | 2 +- core/codegen/tests/typed-uris.rs | 4 +- core/codegen_next/src/ext.rs | 2 +- core/http/Cargo.toml | 1 + core/http/src/accept.rs | 2 +- core/http/src/ext.rs | 36 + core/http/src/lib.rs | 18 +- core/http/src/parse/accept.rs | 4 +- core/http/src/parse/indexed.rs | 23 +- core/http/src/parse/media_type.rs | 9 +- core/http/src/parse/mod.rs | 10 +- core/http/src/parse/uri/error.rs | 92 +++ core/http/src/parse/uri/mod.rs | 43 ++ core/http/src/parse/uri/parser.rs | 190 +++++ core/http/src/parse/uri/spec.txt | 95 +++ core/http/src/parse/uri/tables.rs | 92 +++ core/http/src/parse/uri/tests.rs | 228 ++++++ core/http/src/uri/absolute.rs | 175 +++++ core/http/src/uri/authority.rs | 227 ++++++ core/http/src/uri/mod.rs | 10 +- core/http/src/uri/origin.rs | 653 ++++++++++++++++ core/http/src/uri/uri.rs | 729 +++++------------- core/http/src/uri/uri_display.rs | 7 +- core/lib/src/catcher.rs | 4 +- core/lib/src/config/config.rs | 12 +- core/lib/src/config/custom_values.rs | 6 +- core/lib/src/config/environment.rs | 4 +- core/lib/src/config/mod.rs | 4 +- core/lib/src/config/toml_ext.rs | 2 +- core/lib/src/data/data.rs | 6 +- core/lib/src/data/data_stream.rs | 2 +- core/lib/src/error.rs | 2 +- core/lib/src/fairing/mod.rs | 2 +- core/lib/src/lib.rs | 3 +- core/lib/src/local/client.rs | 57 +- core/lib/src/local/request.rs | 161 ++-- core/lib/src/logger.rs | 6 +- core/lib/src/request/form/form.rs | 2 +- core/lib/src/request/from_request.rs | 11 +- core/lib/src/request/mod.rs | 2 +- core/lib/src/request/param.rs | 19 +- core/lib/src/request/request.rs | 63 +- core/lib/src/response/mod.rs | 2 +- core/lib/src/response/redirect.rs | 159 ++-- core/lib/src/response/response.rs | 2 +- core/lib/src/rocket.rs | 65 +- core/lib/src/router/collider.rs | 27 +- core/lib/src/router/mod.rs | 112 +-- core/lib/src/router/route.rs | 99 +-- .../lib/tests/absolute-uris-okay-issue-443.rs | 35 + .../local-request-content-type-issue-505.rs | 8 +- examples/handlebars_templates/src/main.rs | 2 +- examples/manual_routes/src/main.rs | 2 +- examples/manual_routes/src/tests.rs | 5 +- examples/tera_templates/src/main.rs | 2 +- 64 files changed, 2765 insertions(+), 1044 deletions(-) create mode 100644 core/http/src/parse/uri/error.rs create mode 100644 core/http/src/parse/uri/mod.rs create mode 100644 core/http/src/parse/uri/parser.rs create mode 100644 core/http/src/parse/uri/spec.txt create mode 100644 core/http/src/parse/uri/tables.rs create mode 100644 core/http/src/parse/uri/tests.rs create mode 100644 core/http/src/uri/absolute.rs create mode 100644 core/http/src/uri/authority.rs create mode 100644 core/http/src/uri/origin.rs create mode 100644 core/lib/tests/absolute-uris-okay-issue-443.rs diff --git a/contrib/lib/src/lib.rs b/contrib/lib/src/lib.rs index b47a5131..3dbc2d41 100644 --- a/contrib/lib/src/lib.rs +++ b/contrib/lib/src/lib.rs @@ -1,4 +1,5 @@ #![feature(use_extern_macros)] +#![feature(crate_visibility_modifier)] // TODO: Version URLs. #![doc(html_root_url = "https://api.rocket.rs")] diff --git a/contrib/lib/src/templates/engine.rs b/contrib/lib/src/templates/engine.rs index 7fa83b78..a0fc61d0 100644 --- a/contrib/lib/src/templates/engine.rs +++ b/contrib/lib/src/templates/engine.rs @@ -62,12 +62,12 @@ pub struct 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 = "handlebars_templates")] Handlebars::EXT, ]; - pub(crate) fn init(templates: &HashMap) -> Option { + crate fn init(templates: &HashMap) -> Option { fn inner(templates: &HashMap) -> Option { let named_templates = templates.iter() .filter(|&(_, i)| i.extension == E::EXT) @@ -91,7 +91,7 @@ impl Engines { }) } - pub(crate) fn render( + crate fn render( &self, name: &str, info: &TemplateInfo, diff --git a/core/codegen/src/decorators/route.rs b/core/codegen/src/decorators/route.rs index 860a0e2d..7f23255d 100644 --- a/core/codegen/src/decorators/route.rs +++ b/core/codegen/src/decorators/route.rs @@ -260,9 +260,9 @@ impl RouteParams { ).expect("consistent uri macro item") } - fn explode(&self, ecx: &ExtCtxt) -> (LocalInternedString, &str, Path, P, P) { + fn explode(&self, ecx: &ExtCtxt) -> (LocalInternedString, String, Path, P, P) { 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 format = self.format.as_ref().map(|kv| kv.value().clone()); let media_type = option_as_expr(ecx, &media_type_to_expr(ecx, format)); diff --git a/core/codegen/src/lib.rs b/core/codegen/src/lib.rs index 3f0eb471..5e632615 100644 --- a/core/codegen/src/lib.rs +++ b/core/codegen/src/lib.rs @@ -88,8 +88,9 @@ //! other: String //! } //! -//! Each field's type is required to implement [`FromFormValue`]. The derive -//! accepts one field attribute: `form`, with the following syntax: +//! Each field's type is required to implement [`FromFormValue`]. +//! +//! The derive accepts one field attribute: `form`, with the following syntax: //! //!
 //! form := 'field' '=' '"' IDENT '"'
@@ -113,8 +114,8 @@
 //! implementation succeeds only when all of the field parses succeed.
 //!
 //! 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
-//! instead of the structure's field name when parsing a form.
+//! field name is expected. In this case, the `field` name in the attribute is
+//! used instead of the structure's actual field name when parsing a form.
 //!
 //! [`FromForm`]: /rocket/request/trait.FromForm.html
 //! [`FromFormValue`]: /rocket/request/trait.FromFormValue.html
@@ -138,7 +139,9 @@
 //! ### Typed URIs: `uri!`
 //!
 //! 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:
 //!
@@ -152,10 +155,10 @@
 //! A URI can be created as follows:
 //!
 //! ```rust,ignore
-//! // with unnamed parameters
+//! // with unnamed parameters, in route path declaration order
 //! 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: age = 28, name = "Mike");
 //!
@@ -183,11 +186,14 @@
 //!
 //! #### Semantics
 //!
-//! The `uri!` macro returns a `Uri` structure with the URI of the supplied
-//! route with the given values. A `uri!` invocation only succeeds if the type
-//! of every value in the invocation matches the type declared for the parameter
-//! in the given route.
+//! The `uri!` macro returns an [`Origin`](rocket::uri::Origin) structure with
+//! the URI of the supplied route interpolated with the given values. Note that
+//! `Origin` implements `Into` (and by extension, `TryInto`), so it
+//! 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
 //! each value. If a `FromUriParam` implementation exists for a type `T`,
 //! 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
 //! ```
 
-
 extern crate syntax;
 extern crate syntax_ext;
 extern crate syntax_pos;
diff --git a/core/codegen/src/macros/uri.rs b/core/codegen/src/macros/uri.rs
index 9702bcb2..06d8eed6 100644
--- a/core/codegen/src/macros/uri.rs
+++ b/core/codegen/src/macros/uri.rs
@@ -3,7 +3,7 @@ use std::fmt::Display;
 use syntax::codemap::Span;
 use syntax::ext::base::{DummyResult, ExtCtxt, MacEager, MacResult};
 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::parse::PResult;
 use syntax::ext::build::AstBuilder;
@@ -11,9 +11,14 @@ use syntax::ptr::P;
 
 use URI_INFO_MACRO_PREFIX;
 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 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(
     ecx: &mut ExtCtxt,
     sp: Span,
@@ -89,49 +94,35 @@ fn extract_exprs<'a>(
     }
 }
 
-#[allow(unused_imports)]
-pub fn uri_internal(
-    ecx: &mut ExtCtxt,
-    sp: Span,
-    tt: &[TokenTree],
-) -> Box {
-    // 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));
+// Validates the mount path and the URI and returns a single Origin URI with
+// both paths concatinated. Validation should always succeed since this macro
+// can only be called if the route attribute succeed, which implies that the
+// route URI was valid.
+fn extract_origin<'a>(
+    ecx: &ExtCtxt<'a>,
+    internal: &InternalUriParams,
+) -> PResult<'a, Origin<'static>> {
+    let base_uri = match internal.uri_params.mount_point {
+        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.
-    let mut argument_stmts = vec![];
-    let mut format_assign_tokens = vec![];
-    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),
-        ));
+    Origin::parse_route(&format!("{}/{}", base_uri, internal.uri.node))
+        .map(|o| o.to_normalized().into_owned())
+        .map_err(|_| ecx.struct_span_err(internal.uri.span, "invalid route URI"))
+}
 
-        // generating: format string arg for `mount`
-        let mut tokens = quote_tokens!(ecx, mount = mount,);
-        tokens.iter_mut().for_each(|tree| tree.set_span(mount_point.span));
-        format_assign_tokens.push(tokens);
-
-        // Ensure the `format!` string contains the `{mount}` parameter.
-        fmt_string = "{mount}".to_string() + &fmt_string;
-    }
-
-    // Now the user's parameters.
+fn explode(ecx: &ExtCtxt, route_str: &str, items: I) -> P
+    where I: Iterator, P)>
+{
+    // Generate the statements to typecheck each parameter.
     // Building <$T as ::rocket::http::uri::FromUriParam<_>>::from_uri_param($e).
-    for (i, &(mut ident, ref ty)) in internal.fn_args.iter().enumerate() {
-        let (span, mut expr) = (exprs[i].span, exprs[i].clone());
-
-        // Format argument names cannot begin with `_`, but a function parameter
-        // might, so we prefix each parameter with the letters `fmt`.
-        ident.name = Symbol::intern(&format!("fmt{}", ident.name));
+    let mut let_bindings = vec![];
+    let mut fmt_exprs = vec![];
+    for (mut ident, ty, expr) in items {
+        let (span, mut expr) = (expr.span, expr.clone());
         ident.span = span;
 
         // path for call: >::from_uri_param
@@ -150,7 +141,7 @@ pub fn uri_internal(
             if !inner.is_location() {
                 let tmp_ident = ident.append("_tmp");
                 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);
             }
         }
@@ -160,19 +151,63 @@ pub fn uri_internal(
         let call = ecx.expr_call(span, path_expr, vec![expr]);
         let stmt = ecx.stmt_let(span, false, ident, call);
         debug!("Emitting URI typecheck statement: {:?}", stmt);
-        argument_stmts.push(stmt);
+        let_bindings.push(stmt);
 
-        // generating: arg assignment tokens for format string
-        let uri_display = quote_path!(ecx, ::rocket::http::uri::UriDisplay);
-        let mut tokens = quote_tokens!(ecx, $ident = &$ident as &$uri_display,);
+        // generating: arg tokens for format string
+        let mut tokens = quote_tokens!(ecx, &$ident as &::rocket::http::uri::UriDisplay,);
         tokens.iter_mut().for_each(|tree| tree.set_span(span));
-        format_assign_tokens.push(tokens);
+        fmt_exprs.push(tokens);
     }
 
-    let expr = quote_expr!(ecx, {
-        $argument_stmts
-        ::rocket::http::uri::Uri::from(format!($fmt_string, $format_assign_tokens))
-    });
+    // Convert all of the '<...>' into '{}'.
+    let mut inside = false;
+    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 {
+    // 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);
     MacEager::expr(expr)
diff --git a/core/codegen/src/parser/route.rs b/core/codegen/src/parser/route.rs
index 4b7d2119..c286d571 100644
--- a/core/codegen/src/parser/route.rs
+++ b/core/codegen/src/parser/route.rs
@@ -10,7 +10,7 @@ use super::Function;
 use super::keyvalue::KVSpanned;
 use super::uri::validate_uri;
 use rocket_http::{Method, MediaType};
-use rocket_http::uri::Uri;
+use rocket_http::uri::Origin;
 
 /// This structure represents the parsed `route` attribute.
 ///
@@ -22,7 +22,7 @@ use rocket_http::uri::Uri;
 pub struct RouteParams {
     pub annotated_fn: Function,
     pub method: Spanned,
-    pub uri: Spanned>,
+    pub uri: Spanned>,
     pub data_param: Option>,
     pub query_param: Option>,
     pub format: Option>,
@@ -185,9 +185,10 @@ fn parse_method(ecx: &ExtCtxt, meta_item: &NestedMetaItem) -> Spanned {
     dummy_spanned(Method::Get)
 }
 
-fn parse_path(ecx: &ExtCtxt,
-              meta_item: &NestedMetaItem)
-              -> (Spanned>, Option>) {
+fn parse_path(
+    ecx: &ExtCtxt,
+    meta_item: &NestedMetaItem
+) -> (Spanned>, Option>) {
     let sp = meta_item.span();
     if let Some((name, lit)) = meta_item.name_value() {
         if name != "path" {
@@ -207,7 +208,7 @@ fn parse_path(ecx: &ExtCtxt,
             .emit();
     }
 
-    (dummy_spanned(Uri::new("")), None)
+    (dummy_spanned(Origin::dummy()), None)
 }
 
 fn parse_opt(ecx: &ExtCtxt, kv: &KVSpanned, f: F) -> Option>
diff --git a/core/codegen/src/parser/uri.rs b/core/codegen/src/parser/uri.rs
index c3a963e2..5441e005 100644
--- a/core/codegen/src/parser/uri.rs
+++ b/core/codegen/src/parser/uri.rs
@@ -2,7 +2,8 @@ use syntax::ast::*;
 use syntax::codemap::{Span, Spanned, dummy_spanned};
 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 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
 // compile-time for empty segments. At the moment, this disallows trailing
 // slashes as well, since then the last segment is empty.
-fn valid_path(ecx: &ExtCtxt, uri: &Uri, sp: Span) -> bool {
-    let cleaned = uri.to_string();
-    if !uri.as_str().starts_with('/') {
-        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() {
+fn valid_path(ecx: &ExtCtxt, uri: &Origin, sp: Span) -> bool {
+    if !uri.is_normalized() {
+        let normalized = uri.to_normalized();
         ecx.struct_span_err(sp, "paths cannot contain empty segments")
-            .note(&format!("expected {:?}, found {:?}", cleaned, uri.as_str()))
-            .emit()
-    } else {
-        return true;
+            .note(&format!("expected '{}', found '{}'", normalized, uri))
+            .emit();
     }
 
-    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 segments_span = None;
     for segment in uri.segments() {
@@ -97,18 +92,32 @@ fn valid_segments(ecx: &ExtCtxt, uri: &Uri, sp: Span) -> bool {
     validated
 }
 
-pub fn validate_uri(ecx: &ExtCtxt,
-                    string: &str,
-                    sp: Span)
-                    -> (Spanned>, Option>) {
-    let uri = Uri::from(string.to_string());
+pub fn validate_uri(
+    ecx: &ExtCtxt,
+    string: &str,
+    sp: Span,
+) -> (Spanned>, Option>) {
     let query_param = string.find('?')
         .map(|i| span(&string[(i + 1)..], sp.trim_left(i + 1)))
         .and_then(|spanned_q_param| param_to_ident(ecx, spanned_q_param));
 
-    if valid_segments(ecx, &uri, sp) && valid_path(ecx, &uri, sp) {
-        (span(uri, sp), query_param)
-    } else {
-        (dummy_spanned(Uri::new("")), query_param)
+    let dummy = (dummy_spanned(Origin::dummy()), query_param);
+    match Origin::parse_route(string) {
+        Ok(uri) => {
+            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/")
+                .emit();
+
+            dummy
+        }
     }
 }
diff --git a/core/codegen/src/parser/uri_macro.rs b/core/codegen/src/parser/uri_macro.rs
index 2214f1ee..e0ac9caf 100644
--- a/core/codegen/src/parser/uri_macro.rs
+++ b/core/codegen/src/parser/uri_macro.rs
@@ -24,6 +24,12 @@ pub enum Args {
     Named(Vec<(Spanned, P)>),
 }
 
+// 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)]
 pub struct UriParams {
     pub mount_point: Option>,
@@ -31,6 +37,9 @@ pub struct UriParams {
     pub arguments: Option>,
 }
 
+// `fn_args` are the URI arguments (excluding guards) from the original route's
+// handler in the order they were declared in the URI (`/`).
+// `uri` is the full URI used in the origin route's attribute
 #[derive(Debug)]
 pub struct InternalUriParams {
     pub uri: Spanned,
@@ -252,11 +261,4 @@ impl InternalUriParams {
             }
         }
     }
-
-    pub fn uri_fmt_string(&self) -> String {
-        self.uri.node
-            .replace('<', "{fmt")
-            .replace("..>", "}")
-            .replace('>', "}")
-    }
 }
diff --git a/core/codegen/tests/compile-fail/absolute-mount-paths.rs b/core/codegen/tests/compile-fail/absolute-mount-paths.rs
index 026a0573..c179abd8 100644
--- a/core/codegen/tests/compile-fail/absolute-mount-paths.rs
+++ b/core/codegen/tests/compile-fail/absolute-mount-paths.rs
@@ -1,13 +1,13 @@
 #![feature(plugin, decl_macro)]
 #![plugin(rocket_codegen)]
 
-#[get("a")] //~ ERROR absolute
+#[get("a")] //~ ERROR invalid
 fn get() -> &'static str { "hi" }
 
-#[get("")] //~ ERROR absolute
+#[get("")] //~ ERROR invalid
 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 main() {  }
diff --git a/core/codegen/tests/segments.rs b/core/codegen/tests/segments.rs
index 93036545..1b14dd8e 100644
--- a/core/codegen/tests/segments.rs
+++ b/core/codegen/tests/segments.rs
@@ -4,7 +4,7 @@
 extern crate rocket;
 
 use std::path::PathBuf;
-use rocket::http::uri::SegmentError;
+use rocket::request::SegmentError;
 
 #[post("//")]
 fn get(a: String, b: PathBuf) -> String {
diff --git a/core/codegen/tests/typed-uris.rs b/core/codegen/tests/typed-uris.rs
index 3237f32d..9a3af6c7 100644
--- a/core/codegen/tests/typed-uris.rs
+++ b/core/codegen/tests/typed-uris.rs
@@ -8,7 +8,7 @@ use std::fmt;
 use std::path::PathBuf;
 
 use rocket::http::{RawStr, Cookies};
-use rocket::http::uri::{Uri, UriDisplay, FromUriParam};
+use rocket::http::uri::{Origin, UriDisplay, FromUriParam};
 use rocket::request::Form;
 
 #[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 { "" }
 
 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]
diff --git a/core/codegen_next/src/ext.rs b/core/codegen_next/src/ext.rs
index 2ae605a7..832df7b2 100644
--- a/core/codegen_next/src/ext.rs
+++ b/core/codegen_next/src/ext.rs
@@ -21,7 +21,7 @@ impl MemberExt for Member {
     }
 }
 
-pub(crate) trait FieldsExt {
+pub trait FieldsExt {
     fn len(&self) -> usize;
     fn is_empty(&self) -> bool;
     fn named(&self) -> Option<&FieldsNamed>;
diff --git a/core/http/Cargo.toml b/core/http/Cargo.toml
index 3124f0cc..a494e591 100644
--- a/core/http/Cargo.toml
+++ b/core/http/Cargo.toml
@@ -24,6 +24,7 @@ hyper = { version = "0.10.13", default-features = false }
 time = "0.1"
 indexmap = "1.0"
 rustls = { version = "0.13", optional = true }
+state = "0.4"
 
 [dependencies.cookie]
 git = "https://github.com/alexcrichton/cookie-rs"
diff --git a/core/http/src/accept.rs b/core/http/src/accept.rs
index f0f72edf..b2a284a1 100644
--- a/core/http/src/accept.rs
+++ b/core/http/src/accept.rs
@@ -163,7 +163,7 @@ impl PartialEq for AcceptParams {
 /// let response = Response::build().header(Accept::JSON).finalize();
 /// ```
 #[derive(Debug, Clone, PartialEq)]
-pub struct Accept(pub(crate) AcceptParams);
+pub struct Accept(crate AcceptParams);
 
 macro_rules! accept_constructor {
     ($($name:ident ($check:ident): $str:expr, $t:expr,
diff --git a/core/http/src/ext.rs b/core/http/src/ext.rs
index 94c7dadb..0ea7a435 100644
--- a/core/http/src/ext.rs
+++ b/core/http/src/ext.rs
@@ -1,9 +1,15 @@
+//! Extension traits implemented by several HTTP types.
+
 use smallvec::{Array, SmallVec};
 
 // TODO: It would be nice if we could somehow have one trait that could give us
 // either SmallVec or Vec.
+/// Trait implemented by types that can be converted into a collection.
 pub trait IntoCollection {
+    /// Converts `self` into a collection.
     fn into_collection>(self) -> SmallVec;
+
+    #[doc(hidden)]
     fn mapped U, A: Array>(self, f: F) -> SmallVec;
 }
 
@@ -62,3 +68,33 @@ impl_for_slice!(; 7);
 impl_for_slice!(; 8);
 impl_for_slice!(; 9);
 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 IntoOwned for Option {
+    type Owned = Option;
+
+    #[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())
+    }
+}
diff --git a/core/http/src/lib.rs b/core/http/src/lib.rs
index 5133160c..f719c4cd 100644
--- a/core/http/src/lib.rs
+++ b/core/http/src/lib.rs
@@ -1,6 +1,8 @@
 #![feature(specialization)]
 #![feature(proc_macro_non_items, use_extern_macros)]
 #![feature(const_fn)]
+#![feature(try_from)]
+#![feature(crate_visibility_modifier)]
 #![recursion_limit="256"]
 
 //! Types that map to concepts in HTTP.
@@ -19,10 +21,13 @@ extern crate percent_encoding;
 extern crate cookie;
 extern crate time;
 extern crate indexmap;
+extern crate state;
 
 pub mod hyper;
 pub mod uri;
+pub mod ext;
 
+#[doc(hidden)]
 #[cfg(feature = "tls")]
 pub mod tls;
 
@@ -38,16 +43,20 @@ mod status;
 mod header;
 mod accept;
 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.
 // 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::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::content_type::ContentType;
 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::cookies::{Cookie, SameSite, Cookies};
-
-#[doc(hidden)]
-pub use self::cookies::{Key, CookieJar};
diff --git a/core/http/src/parse/accept.rs b/core/http/src/parse/accept.rs
index 3136fb56..2b42ed57 100644
--- a/core/http/src/parse/accept.rs
+++ b/core/http/src/parse/accept.rs
@@ -2,10 +2,12 @@ use pear::parser;
 use pear::parsers::*;
 
 use {Accept, QMediaType};
-use parse::{Input, Result};
 use parse::checkers::is_whitespace;
 use parse::media_type::media_type;
 
+type Input<'a> = ::parse::IndexedInput<'a, str>;
+type Result<'a, T> = ::pear::Result>;
+
 #[parser]
 fn weighted_media_type<'a>(input: &mut Input<'a>) -> Result<'a, QMediaType> {
     let media_type = media_type()?;
diff --git a/core/http/src/parse/indexed.rs b/core/http/src/parse/indexed.rs
index 2cd4ea89..fe0a1659 100644
--- a/core/http/src/parse/indexed.rs
+++ b/core/http/src/parse/indexed.rs
@@ -6,7 +6,11 @@ use std::fmt::{self, Debug};
 
 use pear::{Input, Length};
 
+use ext::IntoOwned;
+
 pub type IndexedString = Indexed<'static, str>;
+pub type IndexedStr<'a> = Indexed<'a, str>;
+pub type IndexedBytes<'a> = Indexed<'a, [u8]>;
 
 pub trait AsPtr {
     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")
         }
     }
-}
 
-impl<'a, T: ?Sized + ToOwned + 'a> Indexed<'a, T> {
     #[inline(always)]
     pub fn coerce_lifetime<'b>(self) -> Indexed<'b, T> {
         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;
 
 impl<'a, T: ?Sized + ToOwned + 'a> Add for Indexed<'a, T> {
@@ -303,12 +316,12 @@ pub struct Context {
 impl ::std::fmt::Display for Context {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         const LIMIT: usize = 7;
-        write!(f, "{}", self.offset)?;
+        write!(f, "[{}:]", self.offset)?;
 
         if self.string.len() > LIMIT {
-            write!(f, " ({}..)", &self.string[..LIMIT])
+            write!(f, " {}..", &self.string[..LIMIT])
         } else if !self.string.is_empty() {
-            write!(f, " ({})", &self.string)
+            write!(f, " {}", &self.string)
         } else {
             Ok(())
         }
diff --git a/core/http/src/parse/media_type.rs b/core/http/src/parse/media_type.rs
index 688305f3..ea4c75bd 100644
--- a/core/http/src/parse/media_type.rs
+++ b/core/http/src/parse/media_type.rs
@@ -5,10 +5,13 @@ use pear::parsers::*;
 
 use {MediaType, Source};
 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>;
 
 #[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('"')?;
 
     let mut is_escaped = false;
@@ -23,7 +26,7 @@ fn quoted_string<'a>(input: &mut Input<'a>) -> Result<'a, Slice<'a>> {
 }
 
 #[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 value = switch! {
         peek('"') => quoted_string()?,
diff --git a/core/http/src/parse/mod.rs b/core/http/src/parse/mod.rs
index a160f529..90b6e0d5 100644
--- a/core/http/src/parse/mod.rs
+++ b/core/http/src/parse/mod.rs
@@ -1,12 +1,12 @@
 mod media_type;
 mod accept;
-mod indexed;
 mod checkers;
+mod indexed;
 
-pub use self::indexed::*;
 pub use self::media_type::*;
 pub use self::accept::*;
 
-pub type Input<'a> = IndexedInput<'a, str>;
-pub type Slice<'a> = Indexed<'a, str>;
-pub type Result<'a, T> = ::pear::Result>;
+pub mod uri;
+
+// Exposed for codegen.
+#[doc(hidden)] pub use self::indexed::*;
diff --git a/core/http/src/parse/uri/error.rs b/core/http/src/parse/uri/error.rs
new file mode 100644
index 00000000..1b1381da
--- /dev/null
+++ b/core/http/src/parse/uri/error.rs
@@ -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, Cow<'a, str>, String>,
+    context: Option
+}
+
+#[derive(Debug)]
+enum Or {
+    A(L),
+    B(R)
+}
+
+impl<'a> Error<'a> {
+    crate fn from(src: &'a str, pear_error: ParseErr>) -> 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 {
+    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");
+    }
+}
diff --git a/core/http/src/parse/uri/mod.rs b/core/http/src/parse/uri/mod.rs
new file mode 100644
index 00000000..bc29ef82
--- /dev/null
+++ b/core/http/src/parse/uri/mod.rs
@@ -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 {
+    parse!(uri: &mut RawInput::from(string.as_bytes()))
+        .map_err(|e| Error::from(string, e))
+}
+
+#[inline]
+pub fn origin_from_str(string: &str) -> Result {
+    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 {
+    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 {
+    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 {
+    parse!(absolute_only: &mut RawInput::from(string.as_bytes()))
+        .map_err(|e| Error::from(string, e))
+}
diff --git a/core/http/src/parse/uri/parser.rs b/core/http/src/parse/uri/parser.rs
new file mode 100644
index 00000000..a55b86df
--- /dev/null
+++ b/core/http/src/parse/uri/parser.rs
@@ -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>;
+
+#[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>
+) -> 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)?)
+        }
+    }
+}
diff --git a/core/http/src/parse/uri/spec.txt b/core/http/src/parse/uri/spec.txt
new file mode 100644
index 00000000..a72624ab
--- /dev/null
+++ b/core/http/src/parse/uri/spec.txt
@@ -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
+
+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
diff --git a/core/http/src/parse/uri/tables.rs b/core/http/src/parse/uri/tables.rs
new file mode 100644
index 00000000..7258e9e4
--- /dev/null
+++ b/core/http/src/parse/uri/tables.rs
@@ -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[..]);
+    }
+}
diff --git a/core/http/src/parse/uri/tests.rs b/core/http/src/parse/uri/tests.rs
new file mode 100644
index 00000000..86bd580a
--- /dev/null
+++ b/core/http/src/parse/uri/tests.rs
@@ -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]"
+    }
+}
diff --git a/core/http/src/uri/absolute.rs b/core/http/src/uri/absolute.rs
new file mode 100644
index 00000000..2d7a1c94
--- /dev/null
+++ b/core/http/src/uri/absolute.rs
@@ -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>,
+    scheme: IndexedStr<'a>,
+    authority: Option>,
+    origin: Option>,
+}
+
+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>,
+        origin: Option>,
+    ) -> 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>,
+        origin: Option>
+    ) -> 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, 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> 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(())
+    }
+}
+
diff --git a/core/http/src/uri/authority.rs b/core/http/src/uri/authority.rs
new file mode 100644
index 00000000..b4628153
--- /dev/null
+++ b/core/http/src/uri/authority.rs
@@ -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>,
+    user_info: Option>,
+    host: Host>,
+    port: Option,
+}
+
+#[derive(Debug)]
+crate enum Host {
+    Bracketed(T),
+    Raw(T)
+}
+
+impl IntoOwned for Host {
+    type Owned = Host;
+
+    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>,
+        host: Host>,
+        port: Option
+    ) -> 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
+    ) -> 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, 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 {
+        self.port
+    }
+}
+
+impl<'a, 'b> PartialEq> 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 Host {
+    #[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(self, f: F) -> Host
+        where F: FnOnce(T) -> U
+    {
+        match self {
+            Host::Bracketed(inner) => Host::Bracketed(f(inner)),
+            Host::Raw(inner) => Host::Raw(f(inner))
+        }
+    }
+}
diff --git a/core/http/src/uri/mod.rs b/core/http/src/uri/mod.rs
index 37cf799f..8275feb3 100644
--- a/core/http/src/uri/mod.rs
+++ b/core/http/src/uri/mod.rs
@@ -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_display;
 mod from_uri_param;
+mod origin;
+mod authority;
+mod absolute;
+
+pub use parse::uri::Error;
 
 pub use self::uri::*;
+pub use self::authority::*;
+pub use self::origin::*;
+pub use self::absolute::*;
 pub use self::uri_display::*;
 pub use self::from_uri_param::*;
diff --git a/core/http/src/uri/origin.rs b/core/http/src/uri/origin.rs
new file mode 100644
index 00000000..b4e3794a
--- /dev/null
+++ b/core/http/src/uri/origin.rs
@@ -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>,
+    crate path: IndexedStr<'a>,
+    crate query: Option>,
+    crate segment_count: Storage,
+}
+
+impl<'a, 'b> PartialEq> 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>
+    ) -> 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(path: P, query: Option) -> Origin<'a>
+        where P: Into>, Q: Into>
+    {
+        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, 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, 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, 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 {
+        // 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 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() {};
+        assert::();
+    }
+
+    #[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());
+    }
+}
diff --git a/core/http/src/uri/uri.rs b/core/http/src/uri/uri.rs
index ad4b9b00..25d1d50d 100644
--- a/core/http/src/uri/uri.rs
+++ b/core/http/src/uri/uri.rs
@@ -1,255 +1,154 @@
-use std::fmt;
+use std::fmt::{self, Display};
 use std::convert::From;
 use std::borrow::Cow;
 use std::str::Utf8Error;
-use std::sync::atomic::{AtomicIsize, Ordering};
+use std::convert::TryFrom;
 
-/// Index (start, end) into a string, to prevent borrowing.
-type Index = (usize, usize);
+use ext::IntoOwned;
+use parse::Indexed;
+use uri::{Origin, Authority, Absolute, Error};
 
-/// Representation of an empty segment count.
-const EMPTY: isize = -1;
-
-// TODO: Reconsider deriving PartialEq and Eq to make "//a/b" == "/a/b".
-/// Borrowed string type for absolute URIs.
-#[derive(Debug)]
-pub struct Uri<'a> {
-    uri: Cow<'a, str>,
-    path: Index,
-    query: Option,
-    fragment: Option,
-    // The cached segment count. `EMPTY` is used to represent no segment count.
-    segment_count: AtomicIsize,
+/// An `enum` encapsulating any of the possible URI variants.
+///
+/// # Usage
+///
+/// In Rocket, this type will rarely be used directly. Instead, you will
+/// typically encounter URIs via the [`Origin`] type. This is because all
+/// incoming requests contain origin-type URIs.
+///
+/// Nevertheless, the `Uri` type is typically enountered as a conversion target.
+/// In particular, you will likely see generic bounds of the form: `T:
+/// TryInto` (for instance, in [`Redirect`](rocket::Redirect) methods).
+/// This means that you can provide any type `T` that implements `TryInto`,
+/// or, equivalently, any type `U` for which `Uri` implements `TryFrom` or
+/// `From`. 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` 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> {
-    /// Constructs a new URI from a given string. The URI is assumed to be an
-    /// absolute, well formed URI.
-    pub fn new>>(uri: T) -> Uri<'a> {
-        let uri = uri.into();
-        let qmark = uri.find('?');
-        let hmark = uri.find('#');
-
-        let end = uri.len();
-        let (path, query, fragment) = match (qmark, hmark) {
-            (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) }
+    #[inline]
+    crate unsafe fn raw_absolute(
+        source: Cow<'a, [u8]>,
+        scheme: Indexed<'a, [u8]>,
+        path: Indexed<'a, [u8]>,
+        query: Option>,
+    ) -> Uri<'a> {
+        let origin = Origin::raw(source.clone(), path, query);
+        Uri::Absolute(Absolute::raw(source.clone(), scheme, None, Some(origin)))
     }
 
-    /// Returns the number of segments in the URI. Empty segments, which are
-    /// invalid according to RFC#3986, are not counted.
+    /// Parses the string `string` into a `Uri`. Parsing will never allocate.
+    /// Returns an `Error` if `string` is not a valid URI.
     ///
-    /// 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:
+    /// # Example
     ///
     /// ```rust
     /// # extern crate rocket;
     /// use rocket::http::uri::Uri;
     ///
-    /// let uri = Uri::new("/a/b/c");
-    /// assert_eq!(uri.segment_count(), 3);
+    /// // Parse a valid origin URI (note: in practice, use `Origin::parse()`).
+    /// 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");
     /// ```
-    ///
-    /// A URI with empty segments:
-    ///
-    /// ```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);
-            }
+    pub fn parse(string: &'a str) -> Result, Error> {
+        ::parse::uri::from_str(string)
+    }
 
-            real_count
-        } else {
-            count as usize
+    /// Returns the internal instance of `Origin` if `self` is a `Uri::Origin`.
+    /// Otherwise, returns `None`.
+    ///
+    /// # 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
-    /// empty segments.
+    /// Returns the internal instance of `Authority` if `self` is a
+    /// `Uri::Authority`. Otherwise, returns `None`.
     ///
-    /// ### Examples
-    ///
-    /// A valid URI with only non-empty segments:
+    /// # Example
     ///
     /// ```rust
     /// # extern crate rocket;
     /// use rocket::http::uri::Uri;
     ///
-    /// let uri = Uri::new("/a/b/c?a=true#done");
-    /// for (i, segment) in uri.segments().enumerate() {
-    ///     match i {
-    ///         0 => assert_eq!(segment, "a"),
-    ///         1 => assert_eq!(segment, "b"),
-    ///         2 => assert_eq!(segment, "c"),
-    ///         _ => panic!("only three segments")
-    ///     }
-    /// }
+    /// let uri = Uri::parse("user:pass@domain.com").expect("valid URI");
+    /// assert!(uri.authority().is_some());
+    ///
+    /// let uri = Uri::parse("http://google.com").expect("valid URI");
+    /// assert!(uri.authority().is_none());
     /// ```
-    ///
-    /// A URI with empty segments:
-    ///
-    /// ```rust
-    /// # 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())
+    pub fn authority(&self) -> Option<&Authority<'a>> {
+        match self {
+            Uri::Authority(ref inner) => Some(inner),
+            _ => None
+        }
     }
 
-    /// Returns the path part of this URI.
+    /// Returns the internal instance of `Absolute` if `self` is a
+    /// `Uri::Absolute`. Otherwise, returns `None`.
     ///
-    /// ### Examples
-    ///
-    /// A URI with only a path:
+    /// # Example
     ///
     /// ```rust
     /// # extern crate rocket;
     /// use rocket::http::uri::Uri;
     ///
-    /// let uri = Uri::new("/a/b/c");
-    /// assert_eq!(uri.path(), "/a/b/c");
+    /// let uri = Uri::parse("http://google.com").expect("valid URI");
+    /// assert!(uri.absolute().is_some());
+    ///
+    /// let uri = Uri::parse("/path").expect("valid URI");
+    /// assert!(uri.absolute().is_none());
     /// ```
-    ///
-    /// A URI with other components:
-    ///
-    /// ```rust
-    /// # 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, 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 οΏ½ 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 {
-        let decoder = ::percent_encoding::percent_decode(string);
-        decoder.decode_utf8_lossy()
+    pub fn absolute(&self) -> Option<&Absolute<'a>> {
+        match self {
+            Uri::Absolute(ref inner) => Some(inner),
+            _ => None
+        }
     }
 
     /// 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()
     }
 
-    /// 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
-    /// like a string without empty segments, use `to_string` instead.
-    ///
-    /// ### Example
+    /// # Examples
     ///
     /// ```rust
     /// # extern crate rocket;
     /// use rocket::http::uri::Uri;
     ///
-    /// let uri = Uri::new("/a/b///c/d/e//f?name=Mike#end");
-    /// assert_eq!(uri.as_str(), "/a/b///c/d/e//f?name=Mike#end");
+    /// let decoded = Uri::percent_decode("/Hello%2C%20world%21".as_bytes());
+    /// assert_eq!(decoded.unwrap(), "/Hello, world!");
     /// ```
-    #[inline(always)]
-    pub fn as_str(&self) -> &str {
-        &self.uri
+    pub fn percent_decode(string: &[u8]) -> Result, 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 οΏ½ 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 {
+        let decoder = ::percent_encoding::percent_decode(string);
+        decoder.decode_utf8_lossy()
     }
 }
 
-impl<'a> Clone for Uri<'a> {
-    #[inline(always)]
-    fn clone(&self) -> Uri<'a> {
-        Uri {
-            uri: self.uri.clone(),
-            path: self.path,
-            query: self.query,
-            fragment: self.fragment,
-            segment_count: AtomicIsize::new(EMPTY),
+crate unsafe fn as_utf8_unchecked(input: Cow<[u8]>) -> Cow {
+    match input {
+        Cow::Borrowed(bytes) => Cow::Borrowed(::std::str::from_utf8_unchecked(bytes)),
+        Cow::Owned(bytes) => Cow::Owned(String::from_utf8_unchecked(bytes))
+    }
+}
+
+impl<'a> TryFrom<&'a str> for Uri<'a> {
+    type Error = Error<'a>;
+
+    #[inline]
+    fn try_from(string: &'a str) -> Result, Self::Error> {
+        Uri::parse(string)
+    }
+}
+
+impl TryFrom for Uri<'static> {
+    type Error = Error<'static>;
+
+    #[inline]
+    fn try_from(string: String) -> Result, 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> 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> for Uri<'a> {
-    #[inline(always)]
-    fn from(uri: Cow<'a, str>) -> Uri<'a> {
-        Uri::new(uri)
-    }
-}
-
-impl From for Uri<'static> {
-    #[inline(always)]
-    fn from(uri: String) -> Uri<'static> {
-        Uri::new(uri)
-    }
-}
-
-impl<'a> fmt::Display for Uri<'a> {
+impl<'a> Display for Uri<'a> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        // If this is the root path, then there are "zero" segments.
-        if self.segment_count() == 0 {
-            write!(f, "/")?;
-        } else {
-            for segment in self.segments() {
-                write!(f, "/{}", segment)?;
+        match *self {
+            Uri::Origin(ref origin) => write!(f, "{}", origin),
+            Uri::Authority(ref authority) => write!(f, "{}", authority),
+            Uri::Absolute(ref absolute) => write!(f, "{}", absolute),
+            Uri::Asterisk => write!(f, "*")
+        }
+    }
+}
+
+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.
-///
-/// ### Examples
-///
-/// ```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 {
-        // 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 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() {};
-        assert::();
-    }
-
-    #[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());
-    }
-}
+impl_uri_from!(Origin);
+impl_uri_from!(Authority);
+impl_uri_from!(Absolute);
diff --git a/core/http/src/uri/uri_display.rs b/core/http/src/uri/uri_display.rs
index 62d81a06..a80f15a9 100644
--- a/core/http/src/uri/uri_display.rs
+++ b/core/http/src/uri/uri_display.rs
@@ -74,11 +74,12 @@ use self::priv_encode_set::PATH_ENCODE_SET;
 ///     The implementation of `UriDisplay` for these types is identical to the
 ///     `Display` implementation.
 ///
-///   * **[`&RawStr`](/rocket/http/struct.RawStr.html), String, &str, Cow**
+///   * **[`&RawStr`](/rocket/http/struct.RawStr.html), `String`, `&str`,
+///     `Cow`**
 ///
 ///     The string is percent encoded.
 ///
-///   * **&T, &mut T** _where_ **T: UriDisplay**
+///   * **`&T`, `&mut T`** _where_ **`T: UriDisplay`**
 ///
 ///     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
 /// 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
 ///
 /// The following snippet consists of a `Name` type that implements both
diff --git a/core/lib/src/catcher.rs b/core/lib/src/catcher.rs
index f4741917..9f6188ea 100644
--- a/core/lib/src/catcher.rs
+++ b/core/lib/src/catcher.rs
@@ -99,7 +99,7 @@ impl Catcher {
     }
 
     #[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)
     }
 
@@ -109,7 +109,7 @@ impl Catcher {
     }
 
     #[inline(always)]
-    pub(crate) fn is_default(&self) -> bool {
+    crate fn is_default(&self) -> bool {
         self.is_default
     }
 }
diff --git a/core/lib/src/config/config.rs b/core/lib/src/config/config.rs
index 26f9d470..731551f7 100644
--- a/core/lib/src/config/config.rs
+++ b/core/lib/src/config/config.rs
@@ -52,9 +52,9 @@ pub struct Config {
     /// How much information to log.
     pub log_level: LoggingLevel,
     /// The secret key.
-    pub(crate) secret_key: SecretKey,
+    crate secret_key: SecretKey,
     /// TLS configuration.
-    pub(crate) tls: Option,
+    crate tls: Option,
     /// Streaming read size limits.
     pub limits: Limits,
     /// Extra parameters that aren't part of Rocket's core config.
@@ -221,7 +221,7 @@ impl Config {
     /// # Panics
     ///
     /// Panics if randomness cannot be retrieved from the OS.
-    pub(crate) fn default

(env: Environment, path: P) -> Result + crate fn default

(env: Environment, path: P) -> Result where P: AsRef { 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` /// at that entry, and the `expect`ed type name. #[inline(always)] - pub(crate) fn bad_type(&self, + crate fn bad_type(&self, name: &str, actual: &'static str, expect: &'static str) -> ConfigError { @@ -312,7 +312,7 @@ impl Config { /// * **log**: String /// * **secret_key**: String (256-bit base64) /// * **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(())); config_from_raw!(self, name, val, address => (str, set_address, id), @@ -663,7 +663,7 @@ impl Config { /// Retrieves the secret key from `self`. #[inline] - pub(crate) fn secret_key(&self) -> &Key { + crate fn secret_key(&self) -> &Key { self.secret_key.inner() } diff --git a/core/lib/src/config/custom_values.rs b/core/lib/src/config/custom_values.rs index a198c34c..e1ded0ea 100644 --- a/core/lib/src/config/custom_values.rs +++ b/core/lib/src/config/custom_values.rs @@ -14,14 +14,14 @@ pub enum SecretKey { impl SecretKey { #[inline] - pub(crate) fn inner(&self) -> &Key { + crate fn inner(&self) -> &Key { match *self { SecretKey::Generated(ref key) | SecretKey::Provided(ref key) => key } } #[inline] - pub(crate) fn is_generated(&self) -> bool { + crate fn is_generated(&self) -> bool { match *self { SecretKey::Generated(_) => true, _ => false @@ -79,7 +79,7 @@ pub struct TlsConfig; #[derive(Debug, Clone)] pub struct Limits { // We cache this internally but don't share that fact in the API. - pub(crate) forms: u64, + crate forms: u64, extra: Vec<(String, u64)> } diff --git a/core/lib/src/config/environment.rs b/core/lib/src/config/environment.rs index af226551..30eddf6c 100644 --- a/core/lib/src/config/environment.rs +++ b/core/lib/src/config/environment.rs @@ -40,13 +40,13 @@ impl Environment { } /// 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" } /// Returns a list of all of the possible environments. #[inline] - pub(crate) fn all() -> [Environment; 3] { + crate fn all() -> [Environment; 3] { [Development, Staging, Production] } diff --git a/core/lib/src/config/mod.rs b/core/lib/src/config/mod.rs index e5a91a62..c16dacbc 100644 --- a/core/lib/src/config/mod.rs +++ b/core/lib/src/config/mod.rs @@ -217,7 +217,7 @@ pub use self::environment::Environment; pub use self::config::Config; pub use self::builder::ConfigBuilder; pub use logger::LoggingLevel; -pub(crate) use self::toml_ext::LoggedValue; +crate use self::toml_ext::LoggedValue; use logger; use self::Environment::*; @@ -470,7 +470,7 @@ impl RocketConfig { /// # Panics /// /// 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| -> ! { logger::init(LoggingLevel::Debug); e.pretty_print(); diff --git a/core/lib/src/config/toml_ext.rs b/core/lib/src/config/toml_ext.rs index d5e4a1bf..bf5a6199 100644 --- a/core/lib/src/config/toml_ext.rs +++ b/core/lib/src/config/toml_ext.rs @@ -81,7 +81,7 @@ pub fn parse_simple_toml_value(mut input: &str) -> StdResult { /// A simple wrapper over a `Value` reference with a custom implementation of /// `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> { #[inline] diff --git a/core/lib/src/data/data.rs b/core/lib/src/data/data.rs index a0aedb84..a2c3a3ba 100644 --- a/core/lib/src/data/data.rs +++ b/core/lib/src/data/data.rs @@ -87,7 +87,7 @@ impl Data { } // FIXME: This is absolutely terrible (downcasting!), thanks to Hyper. - pub(crate) fn from_hyp(mut body: HyperBodyReader) -> Result { + crate fn from_hyp(mut body: HyperBodyReader) -> Result { // Steal the internal, undecoded data buffer and net stream from Hyper. let (mut hyper_buf, pos, cap) = body.get_mut().take_buf(); // 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 can be read from `stream`. #[inline(always)] - pub(crate) fn new(mut stream: BodyReader) -> Data { + crate fn new(mut stream: BodyReader) -> Data { trace_!("Date::new({:?})", stream); let mut peek_buf: Vec = vec![0; PEEK_BYTES]; @@ -269,7 +269,7 @@ impl Data { /// This creates a `data` object from a local data source `data`. #[inline] - pub(crate) fn local(data: Vec) -> Data { + crate fn local(data: Vec) -> Data { let empty_stream = Cursor::new(vec![]).chain(NetStream::Empty); Data { diff --git a/core/lib/src/data/data_stream.rs b/core/lib/src/data/data_stream.rs index fac1509d..367358a2 100644 --- a/core/lib/src/data/data_stream.rs +++ b/core/lib/src/data/data_stream.rs @@ -14,7 +14,7 @@ pub type InnerStream = Chain>, BodyReader>; /// [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. /// 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 // possible since Hyper's `HttpReader` doesn't implement `BufRead`. diff --git a/core/lib/src/error.rs b/core/lib/src/error.rs index 29136ca5..79b45bad 100644 --- a/core/lib/src/error.rs +++ b/core/lib/src/error.rs @@ -98,7 +98,7 @@ pub struct LaunchError { impl LaunchError { #[inline(always)] - pub(crate) fn new(kind: LaunchErrorKind) -> LaunchError { + crate fn new(kind: LaunchErrorKind) -> LaunchError { LaunchError { handled: AtomicBool::new(false), kind: kind } } diff --git a/core/lib/src/fairing/mod.rs b/core/lib/src/fairing/mod.rs index 56c4b5bb..e564b6eb 100644 --- a/core/lib/src/fairing/mod.rs +++ b/core/lib/src/fairing/mod.rs @@ -55,7 +55,7 @@ mod fairings; mod ad_hoc; mod info_kind; -pub(crate) use self::fairings::Fairings; +crate use self::fairings::Fairings; pub use self::ad_hoc::AdHoc; pub use self::info_kind::{Info, Kind}; diff --git a/core/lib/src/lib.rs b/core/lib/src/lib.rs index 623407fc..ceaaaef1 100644 --- a/core/lib/src/lib.rs +++ b/core/lib/src/lib.rs @@ -5,6 +5,8 @@ #![feature(fnbox)] #![feature(never_type)] #![feature(proc_macro_non_items, use_extern_macros)] +#![feature(crate_visibility_modifier)] +#![feature(try_from)] #![recursion_limit="256"] @@ -128,7 +130,6 @@ pub mod error; // Reexport of HTTP everything. pub mod http { - // FIXME: This unfortunately doesn't work! See rust-lang/rust#51252. #[doc(inline)] pub use rocket_http::*; } diff --git a/core/lib/src/local/client.rs b/core/lib/src/local/client.rs index beea3e4f..1076d944 100644 --- a/core/lib/src/local/client.rs +++ b/core/lib/src/local/client.rs @@ -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::borrow::Cow; + +use Rocket; +use local::LocalRequest; +use http::{Method, CookieJar}; +use error::LaunchError; /// A structure to construct requests for local dispatching. /// @@ -53,7 +55,7 @@ use std::cell::RefCell; /// [`post`]: #method.post pub struct Client { rocket: Rocket, - cookies: Option>, + crate cookies: Option>, } impl Client { @@ -150,25 +152,6 @@ impl Client { &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`. /// /// When dispatched, the request will be served by the instance of Rocket @@ -186,7 +169,7 @@ impl Client { /// let req = client.get("/hello"); /// ``` #[inline(always)] - pub fn get<'c, 'u: 'c, U: Into>>(&'c self, uri: U) -> LocalRequest<'c> { + pub fn get<'c, 'u: 'c, U: Into>>(&'c self, uri: U) -> LocalRequest<'c> { self.req(Method::Get, uri) } @@ -207,7 +190,7 @@ impl Client { /// let req = client.put("/hello"); /// ``` #[inline(always)] - pub fn put<'c, 'u: 'c, U: Into>>(&'c self, uri: U) -> LocalRequest<'c> { + pub fn put<'c, 'u: 'c, U: Into>>(&'c self, uri: U) -> LocalRequest<'c> { self.req(Method::Put, uri) } @@ -232,7 +215,7 @@ impl Client { /// .header(ContentType::Form); /// ``` #[inline(always)] - pub fn post<'c, 'u: 'c, U: Into>>(&'c self, uri: U) -> LocalRequest<'c> { + pub fn post<'c, 'u: 'c, U: Into>>(&'c self, uri: U) -> LocalRequest<'c> { self.req(Method::Post, uri) } @@ -254,7 +237,7 @@ impl Client { /// ``` #[inline(always)] pub fn delete<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c> - where U: Into> + where U: Into> { self.req(Method::Delete, uri) } @@ -277,7 +260,7 @@ impl Client { /// ``` #[inline(always)] pub fn options<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c> - where U: Into> + where U: Into> { self.req(Method::Options, uri) } @@ -300,7 +283,7 @@ impl Client { /// ``` #[inline(always)] pub fn head<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c> - where U: Into> + where U: Into> { self.req(Method::Head, uri) } @@ -323,7 +306,7 @@ impl Client { /// ``` #[inline(always)] pub fn patch<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c> - where U: Into> + where U: Into> { self.req(Method::Patch, uri) } @@ -347,16 +330,8 @@ impl Client { /// ``` #[inline(always)] pub fn req<'c, 'u: 'c, U>(&'c self, method: Method, uri: U) -> LocalRequest<'c> - where U: Into> + where U: Into> { - let request = Request::new(&self.rocket, method, uri); - - 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) + LocalRequest::new(self, method, uri.into()) } } diff --git a/core/lib/src/local/request.rs b/core/lib/src/local/request.rs index d1233116..1250c8e9 100644 --- a/core/lib/src/local/request.rs +++ b/core/lib/src/local/request.rs @@ -2,10 +2,11 @@ use std::fmt; use std::rc::Rc; use std::net::SocketAddr; use std::ops::{Deref, DerefMut}; +use std::borrow::Cow; use {Request, Response, Data}; +use http::{Status, Method, Header, Cookie, uri::Origin, ext::IntoOwned}; use local::Client; -use http::{Header, Cookie}; /// A structure representing a local request as created by [`Client`]. /// @@ -37,26 +38,24 @@ use http::{Header, Cookie}; /// /// # Dispatching /// -/// A `LocalRequest` can be dispatched in one of three ways: +/// A `LocalRequest` can be dispatched in one of two ways: /// /// 1. [`dispatch`] /// /// This method should always be preferred. The `LocalRequest` is consumed /// and a response is returned. /// -/// 2. [`cloned_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`] +/// 2. [`mut_dispatch`] /// /// 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 /// modifications to the request. No cloning occurs, and the request is not /// 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 /// [`header`]: #method.header /// [`add_header`]: #method.add_header @@ -66,7 +65,6 @@ use http::{Header, Cookie}; /// [`set_body`]: #method.set_body /// [`dispatch`]: #method.dispatch /// [`mut_dispatch`]: #method.mut_dispatch -/// [`cloned_dispatch`]: #method.cloned_dispatch pub struct LocalRequest<'c> { client: &'c Client, // This pointer exists to access the `Rc` mutably inside of @@ -97,15 +95,31 @@ pub struct LocalRequest<'c> { // is converted into its owned counterpart before insertion, ensuring stable // addresses. Together, these properties guarantee the second condition. request: Rc>, - data: Vec + data: Vec, + uri: Cow<'c, str>, } impl<'c> LocalRequest<'c> { #[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 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. @@ -135,8 +149,8 @@ impl<'c> LocalRequest<'c> { fn long_lived_request<'a>(&mut self) -> &'a mut Request<'c> { // See the comments in the structure for the argument of correctness. // Additionally, the caller must ensure that the owned instance of - // `Request` itself remains valid as long as the returned reference can - // be accessed. + // `Rc` remains valid as long as the returned reference can be + // accessed. unsafe { &mut *self.ptr } } @@ -333,34 +347,8 @@ impl<'c> LocalRequest<'c> { /// ``` #[inline(always)] pub fn dispatch(mut self) -> LocalResponse<'c> { - let req = self.long_lived_request(); - let response = self.client.rocket().dispatch(req, Data::local(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() + let r = self.long_lived_request(); + LocalRequest::_dispatch(self.client, r, self.request, &self.uri, self.data) } /// 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 /// the application will not modify the request, or it is desired to see - /// modifications to the request. Prefer to use [`dispatch`] or - /// [`cloned_dispatch`] instead + /// modifications to the request. Prefer to use [`dispatch`] instead. /// /// [`dispatch`]: #method.dispatch - /// [`cloned_dispatch`]: #method.cloned_dispatch /// /// # Example /// @@ -392,11 +378,53 @@ impl<'c> LocalRequest<'c> { /// ``` #[inline(always)] pub fn mut_dispatch(&mut self) -> LocalResponse<'c> { - let data = ::std::mem::replace(&mut self.data, vec![]); let req = self.long_lived_request(); - let response = self.client.rocket().dispatch(req, Data::local(data)); - self.client.update_cookies(&response); - LocalResponse { _request: self.request.clone(), response } + let data = ::std::mem::replace(&mut self.data, vec![]); + let rc_req = self.request.clone(); + 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>, + uri: &str, + data: Vec + ) -> 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 { // Someday... @@ -474,8 +514,10 @@ mod tests { // is_send::<::local::LocalResponse>(); // } + // This checks that a response can't outlive the `Client`. + // #[compile_fail] // fn test() { - // use local::Client; + // use {Rocket, local::Client}; // let rocket = Rocket::ignite(); // let res = { @@ -488,8 +530,10 @@ mod tests { // // let res2 = client.get("/").dispatch(); // } + // This checks that a response can't outlive the `Client`. + // #[compile_fail] // fn test() { - // use local::Client; + // use {Rocket, local::Client}; // let rocket = Rocket::ignite(); // let res = { @@ -502,8 +546,11 @@ mod tests { // // 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() { - // use local::Client; + // use {Rocket, local::Client}; // let rocket = Rocket::ignite(); // let client = Client::new(rocket).unwrap(); @@ -511,13 +558,15 @@ mod tests { // let res = { // let x = client.get("/").dispatch(); // let y = client.get("/").dispatch(); + // (x, y) // }; // let x = client; // } + // #[compile_fail] // fn test() { - // use local::Client; + // use {Rocket, local::Client}; // let rocket1 = Rocket::ignite(); // let rocket2 = Rocket::ignite(); @@ -527,7 +576,7 @@ mod tests { // let res = { // let mut res1 = client1.get("/"); - // res1.set_client(&client2); + // res1.client = &client2; // res1 // }; diff --git a/core/lib/src/logger.rs b/core/lib/src/logger.rs index b297d941..a875f272 100644 --- a/core/lib/src/logger.rs +++ b/core/lib/src/logger.rs @@ -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 { 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); PUSHED.store(true, Ordering::Release); log::set_max_level(level.to_level_filter()); } -pub(crate) fn pop_max_level() { +crate fn pop_max_level() { if PUSHED.load(Ordering::Acquire) { log::set_max_level(usize_to_filter(LAST_LOG_FILTER.load(Ordering::Acquire))); } diff --git a/core/lib/src/request/form/form.rs b/core/lib/src/request/form/form.rs index 6cfd336f..4d1857be 100644 --- a/core/lib/src/request/form/form.rs +++ b/core/lib/src/request/form/form.rs @@ -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 // 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. - pub(crate) fn new(string: String, strict: bool) -> FormResult { + crate fn new(string: String, strict: bool) -> FormResult { let long_lived_string: &'f str = unsafe { ::std::mem::transmute(string.as_str()) }; diff --git a/core/lib/src/request/from_request.rs b/core/lib/src/request/from_request.rs index 2d3aa8ad..f7d4e236 100644 --- a/core/lib/src/request/from_request.rs +++ b/core/lib/src/request/from_request.rs @@ -6,8 +6,7 @@ use request::Request; use outcome::{self, IntoOutcome}; use outcome::Outcome::*; -use http::{Status, ContentType, Accept, Method, Cookies}; -use http::uri::Uri; +use http::{Status, ContentType, Accept, Method, Cookies, uri::Origin}; /// Type alias for the `Outcome` of a `FromRequest` conversion. pub type Outcome = outcome::Outcome; @@ -102,10 +101,10 @@ impl IntoOutcome for Result { /// /// _This implementation always returns successfully._ /// -/// * **&URI** +/// * **&Origin** /// -/// Extracts the [`Uri`](/rocket/http/uri/struct.Uri.html) from the incoming -/// request. +/// Extracts the [`Origin`](/rocket/http/uri/struct.Origin.html) URI from +/// the incoming request. /// /// _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 = (); fn from_request(request: &'a Request<'r>) -> Outcome { diff --git a/core/lib/src/request/mod.rs b/core/lib/src/request/mod.rs index b4cc2be5..ae4390c5 100644 --- a/core/lib/src/request/mod.rs +++ b/core/lib/src/request/mod.rs @@ -11,7 +11,7 @@ mod tests; pub use self::request::Request; 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::state::State; diff --git a/core/lib/src/request/param.rs b/core/lib/src/request/param.rs index 5f15cb7f..f1ff0684 100644 --- a/core/lib/src/request/param.rs +++ b/core/lib/src/request/param.rs @@ -1,10 +1,9 @@ -use std::str::FromStr; +use std::str::{FromStr, Utf8Error}; use std::path::PathBuf; use std::fmt::Debug; use std::borrow::Cow; -use http::uri::{Uri, Segments, SegmentError}; -use http::RawStr; +use http::{RawStr, uri::{Uri, Segments}}; /// 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 /// percent-decoded. If a segment is equal to "..", the previous segment (if /// any) is skipped. diff --git a/core/lib/src/request/request.rs b/core/lib/src/request/request.rs index d93fb044..b87f6267 100644 --- a/core/lib/src/request/request.rs +++ b/core/lib/src/request/request.rs @@ -12,7 +12,7 @@ use super::{FromParam, FromSegments, FromRequest, Outcome}; use rocket::Rocket; use router::Route; use config::{Config, Limits}; -use http::uri::{Uri, Segments}; +use http::uri::{Origin, Segments}; use error::Error; use http::{Method, Header, HeaderMap, Cookies, CookieJar}; use http::{RawStr, ContentType, Accept, MediaType}; @@ -40,25 +40,23 @@ struct RequestState<'r> { #[derive(Clone)] pub struct Request<'r> { method: Cell, - uri: Uri<'r>, + uri: Origin<'r>, headers: HeaderMap<'r>, remote: Option, state: RequestState<'r>, } impl<'r> Request<'r> { - /// Create a new `Request` with the given `method` and `uri`. The `uri` - /// parameter can be of any type that implements `Into` including - /// `&str` and `String`; it must be a valid absolute URI. + /// Create a new `Request` with the given `method` and `uri`. #[inline(always)] - pub(crate) fn new<'s: 'r, U: Into>>( + crate fn new<'s: 'r>( rocket: &'r Rocket, method: Method, - uri: U + uri: Origin<'s> ) -> Request<'r> { Request { method: Cell::new(method), - uri: uri.into(), + uri: uri, headers: HeaderMap::new(), remote: None, state: RequestState { @@ -74,9 +72,11 @@ impl<'r> Request<'r> { } } + // Only used by doc-tests! #[doc(hidden)] pub fn example(method: Method, uri: &str, f: F) { 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); f(&mut request); } @@ -150,7 +150,7 @@ impl<'r> Request<'r> { self._set_method(method); } - /// Borrow the URI from `self`, which is guaranteed to be an absolute URI. + /// Borrow the `Origin` URI from `self`. /// /// # Example /// @@ -158,31 +158,34 @@ impl<'r> Request<'r> { /// # use rocket::Request; /// # use rocket::http::Method; /// # Request::example(Method::Get, "/uri", |request| { - /// assert_eq!(request.uri().as_str(), "/uri"); + /// assert_eq!(request.uri().path(), "/uri"); /// # }); /// ``` #[inline(always)] - pub fn uri(&self) -> &Uri { + pub fn uri(&self) -> &Origin { &self.uri } - /// Set the URI in `self`. The `uri` parameter can be of any type that - /// implements `Into` including `&str` and `String`; it _must_ be a - /// valid, absolute URI. + /// Set the URI in `self` to `uri`. /// /// # Example /// /// ```rust + /// use rocket::http::uri::Origin; + /// /// # use rocket::Request; /// # use rocket::http::Method; /// # Request::example(Method::Get, "/uri", |mut request| { - /// request.set_uri("/hello/Sergio?type=greeting"); - /// assert_eq!(request.uri().as_str(), "/hello/Sergio?type=greeting"); + /// let uri = Origin::parse("/hello/Sergio?type=greeting").unwrap(); + /// + /// request.set_uri(uri); + /// assert_eq!(request.uri().path(), "/hello/Sergio"); + /// assert_eq!(request.uri().query(), Some("type=greeting")); /// # }); /// ``` #[inline(always)] - pub fn set_uri<'u: 'r, U: Into>>(&mut self, uri: U) { - self.uri = uri.into(); + pub fn set_uri<'u: 'r>(&mut self, uri: Origin<'u>) { + self.uri = uri; *self.state.params.borrow_mut() = Vec::new(); } @@ -647,36 +650,37 @@ impl<'r> Request<'r> { /// use may result in out of bounds indexing. /// TODO: Figure out the mount path from here. #[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.params.borrow_mut() = route.get_param_indexes(self.uri()); } /// Set the method of `self`, even when `self` is a shared reference. #[inline(always)] - pub(crate) fn _set_method(&self, method: Method) { + crate fn _set_method(&self, method: Method) { self.method.set(method); } /// Replace all of the cookies in `self` with those in `jar`. #[inline] - pub(crate) fn set_cookies(&mut self, jar: CookieJar) { + crate fn set_cookies(&mut self, jar: CookieJar) { self.state.cookies = RefCell::new(jar); } /// Get the managed state T, if it exists. For internal use only! #[inline(always)] - pub(crate) fn get_state(&self) -> Option<&'r T> { + crate fn get_state(&self) -> Option<&'r T> { self.state.managed.try_get() } /// Convert from Hyper types into a Rocket Request. - pub(crate) fn from_hyp(rocket: &'r Rocket, - h_method: hyper::Method, - h_headers: hyper::header::Headers, - h_uri: hyper::RequestUri, - h_addr: SocketAddr, - ) -> Result, String> { + crate fn from_hyp( + rocket: &'r Rocket, + h_method: hyper::Method, + h_headers: hyper::header::Headers, + h_uri: hyper::RequestUri, + h_addr: SocketAddr, + ) -> Result, String> { // Get a copy of the URI for later use. let uri = match h_uri { hyper::RequestUri::AbsolutePath(s) => s, @@ -689,6 +693,9 @@ impl<'r> Request<'r> { 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. let mut request = Request::new(rocket, method, uri); request.set_remote(h_addr); diff --git a/core/lib/src/response/mod.rs b/core/lib/src/response/mod.rs index 638d5a4d..9230a4b6 100644 --- a/core/lib/src/response/mod.rs +++ b/core/lib/src/response/mod.rs @@ -26,7 +26,7 @@ mod stream; mod response; mod failure; -pub(crate) mod flash; +crate mod flash; pub mod content; pub mod status; diff --git a/core/lib/src/response/redirect.rs b/core/lib/src/response/redirect.rs index ef891780..a7ca730e 100644 --- a/core/lib/src/response/redirect.rs +++ b/core/lib/src/response/redirect.rs @@ -1,3 +1,5 @@ +use std::convert::TryInto; + use request::Request; use response::{Response, Responder}; use http::uri::Uri; @@ -7,7 +9,7 @@ use http::Status; /// /// This type simplifies returning a redirect response to the client. #[derive(Debug)] -pub struct Redirect(Status, Uri<'static>); +pub struct Redirect(Status, Option>); impl Redirect { /// Construct a temporary "see other" (303) redirect response. This is the @@ -23,82 +25,82 @@ impl Redirect { /// # #[allow(unused_variables)] /// let redirect = Redirect::to("/other_url"); /// ``` - pub fn to>>(uri: U) -> Redirect { - Redirect(Status::SeeOther, uri.into()) + pub fn to>>(uri: U) -> Redirect { + Redirect(Status::SeeOther, uri.try_into().ok()) } - /// Construct a "temporary" (307) redirect response. This response instructs - /// the client to reissue the current request to a different URL, - /// maintaining the contents of the request identically. This means that, - /// for example, a `POST` request will be resent, contents included, to the - /// requested URL. - /// - /// # Examples - /// - /// ```rust - /// use rocket::response::Redirect; - /// - /// # #[allow(unused_variables)] - /// let redirect = Redirect::temporary("/other_url"); - /// ``` - pub fn temporary>>(uri: U) -> Redirect { - Redirect(Status::TemporaryRedirect, uri.into()) - } + /// Construct a "temporary" (307) redirect response. This response instructs + /// the client to reissue the current request to a different URL, + /// maintaining the contents of the request identically. This means that, + /// for example, a `POST` request will be resent, contents included, to the + /// requested URL. + /// + /// # Examples + /// + /// ```rust + /// use rocket::response::Redirect; + /// + /// # #[allow(unused_variables)] + /// let redirect = Redirect::temporary("/other_url"); + /// ``` + pub fn temporary>>(uri: U) -> Redirect { + Redirect(Status::TemporaryRedirect, uri.try_into().ok()) + } - /// Construct a "permanent" (308) redirect response. This redirect must only - /// be used for permanent redirects as it is cached by clients. This - /// 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 - /// request identically. This means that, for example, a `POST` request will - /// be resent, contents included, to the requested URL. - /// - /// # Examples - /// - /// ```rust - /// use rocket::response::Redirect; - /// - /// # #[allow(unused_variables)] - /// let redirect = Redirect::permanent("/other_url"); - /// ``` - pub fn permanent>>(uri: U) -> Redirect { - Redirect(Status::PermanentRedirect, uri.into()) - } + /// Construct a "permanent" (308) redirect response. This redirect must only + /// be used for permanent redirects as it is cached by clients. This + /// 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 + /// request identically. This means that, for example, a `POST` request will + /// be resent, contents included, to the requested URL. + /// + /// # Examples + /// + /// ```rust + /// use rocket::response::Redirect; + /// + /// # #[allow(unused_variables)] + /// let redirect = Redirect::permanent("/other_url"); + /// ``` + pub fn permanent>>(uri: U) -> Redirect { + Redirect(Status::PermanentRedirect, uri.try_into().ok()) + } - /// Construct a temporary "found" (302) redirect response. This response - /// instructs the client to reissue the current request to a different URL, - /// ideally maintaining the contents of the request identically. - /// Unfortunately, different clients may respond differently to this type of - /// redirect, so `303` or `307` redirects, which disambiguate, are - /// preferred. - /// - /// # Examples - /// - /// ```rust - /// use rocket::response::Redirect; - /// - /// # #[allow(unused_variables)] - /// let redirect = Redirect::found("/other_url"); - /// ``` - pub fn found>>(uri: U) -> Redirect { - Redirect(Status::Found, uri.into()) - } + /// Construct a temporary "found" (302) redirect response. This response + /// instructs the client to reissue the current request to a different URL, + /// ideally maintaining the contents of the request identically. + /// Unfortunately, different clients may respond differently to this type of + /// redirect, so `303` or `307` redirects, which disambiguate, are + /// preferred. + /// + /// # Examples + /// + /// ```rust + /// use rocket::response::Redirect; + /// + /// # #[allow(unused_variables)] + /// let redirect = Redirect::found("/other_url"); + /// ``` + pub fn found>>(uri: U) -> Redirect { + Redirect(Status::Found, uri.try_into().ok()) + } - /// Construct a permanent "moved" (301) redirect response. This response - /// should only be used for permanent redirects as it can be cached by - /// browsers. Because different clients may respond differently to this type - /// of redirect, a `308` redirect, which disambiguates, is preferred. - /// - /// # Examples - /// - /// ```rust - /// use rocket::response::Redirect; - /// - /// # #[allow(unused_variables)] - /// let redirect = Redirect::moved("/other_url"); - /// ``` - pub fn moved>>(uri: U) -> Redirect { - Redirect(Status::MovedPermanently, uri.into()) - } + /// Construct a permanent "moved" (301) redirect response. This response + /// should only be used for permanent redirects as it can be cached by + /// browsers. Because different clients may respond differently to this type + /// of redirect, a `308` redirect, which disambiguates, is preferred. + /// + /// # Examples + /// + /// ```rust + /// use rocket::response::Redirect; + /// + /// # #[allow(unused_variables)] + /// let redirect = Redirect::moved("/other_url"); + /// ``` + pub fn moved>>(uri: U) -> Redirect { + Redirect(Status::MovedPermanently, uri.try_into().ok()) + } } /// Constructs a response with the appropriate status code and the given URL in @@ -106,9 +108,14 @@ impl Redirect { /// responder does not fail. impl Responder<'static> for Redirect { fn respond_to(self, _: &Request) -> Result, Status> { - Response::build() - .status(self.0) - .raw_header("Location", self.1.to_string()) - .ok() + if let Some(uri) = self.1 { + Response::build() + .status(self.0) + .raw_header("Location", uri.to_string()) + .ok() + } else { + error!("Invalid URI used for redirect."); + Err(Status::InternalServerError) + } } } diff --git a/core/lib/src/response/response.rs b/core/lib/src/response/response.rs index 5be37e81..e36446ce 100644 --- a/core/lib/src/response/response.rs +++ b/core/lib/src/response/response.rs @@ -976,7 +976,7 @@ impl<'r> Response<'r> { // 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. #[inline(always)] - pub(crate) fn strip_body(&mut self) { + crate fn strip_body(&mut self) { if let Some(body) = self.take_body() { self.body = match body { Body::Sized(_, n) => Some(Body::Sized(Box::new(io::empty()), n)), diff --git a/core/lib/src/rocket.rs b/core/lib/src/rocket.rs index e1e0d6e5..341900fc 100644 --- a/core/lib/src/rocket.rs +++ b/core/lib/src/rocket.rs @@ -24,16 +24,16 @@ use fairing::{Fairing, Fairings}; use http::{Method, Status, 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 /// application. pub struct Rocket { - pub(crate) config: Config, + crate config: Config, router: Router, default_catchers: HashMap, catchers: HashMap, - pub(crate) state: Container, + crate state: Container, fairings: Fairings, } @@ -58,7 +58,11 @@ impl hyper::Handler for Rocket { Ok(req) => req, Err(e) => { error!("Bad incoming request: {}", e); - let dummy = Request::new(self, Method::Get, Uri::new("")); + // 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); return self.issue_response(r, res); } @@ -193,7 +197,7 @@ impl Rocket { } #[inline] - pub(crate) fn dispatch<'s, 'r>( + crate fn dispatch<'s, 'r>( &'s self, request: &'r mut Request<'s>, data: Data @@ -269,7 +273,7 @@ impl Rocket { // (ensuring `handler` takes an immutable borrow), any caller to `route` // should be able to supply an `&mut` and retain an `&` after the call. #[inline] - pub(crate) fn route<'s, 'r>( + crate fn route<'s, 'r>( &'s self, request: &'r Request<'s>, mut data: Data, @@ -302,7 +306,11 @@ impl Rocket { // 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`, // 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)); // Try to get the active catcher but fallback to user's 500 catcher. @@ -434,8 +442,12 @@ impl Rocket { /// /// # Panics /// - /// The `base` mount point must be a static path. That is, the mount point - /// must _not_ contain dynamic path parameters: ``. + /// Panics if the `base` mount point is not a valid static path: a valid + /// 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 /// @@ -486,17 +498,36 @@ impl Rocket { Paint::purple("Mounting"), Paint::blue(base)); - if base.contains('<') || !base.starts_with('/') { - error_!("Bad mount point: '{}'.", base); - error_!("Mount points must be static, absolute URIs: `/example`"); - panic!("Bad mount point.") + if base.contains('<') || base.contains('>') { + error_!("Invalid mount point: {}", base); + panic!("Mount points cannot contain dynamic parameters"); } 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); - route.set_uri(uri.to_string()); + if base_uri.query().is_some() { + 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); self.router.add(route); @@ -633,7 +664,7 @@ impl Rocket { self } - pub(crate) fn prelaunch_check(&self) -> Option { + crate fn prelaunch_check(&self) -> Option { let collisions = self.router.collisions(); if !collisions.is_empty() { let owned = collisions.iter().map(|&(a, b)| (a.clone(), b.clone())); diff --git a/core/lib/src/router/collider.rs b/core/lib/src/router/collider.rs index ee8f3f05..1430df34 100644 --- a/core/lib/src/router/collider.rs +++ b/core/lib/src/router/collider.rs @@ -1,6 +1,6 @@ use super::Route; -use http::uri::Uri; +use http::uri::Origin; use http::MediaType; use request::Request; @@ -38,8 +38,8 @@ impl<'a> Collider for &'a str { } // This _only_ checks the `path` component of the URI. -impl<'a, 'b> Collider> for Uri<'a> { - fn collides_with(&self, other: &Uri<'b>) -> bool { +impl<'a, 'b> Collider> for Origin<'a> { + fn collides_with(&self, other: &Origin<'b>) -> bool { for (seg_a, seg_b) in self.segments().zip(other.segments()) { if seg_a.ends_with("..>") || seg_b.ends_with("..>") { return true; @@ -123,7 +123,7 @@ mod tests { use handler::Outcome; use router::route::Route; use http::{Method, MediaType, ContentType, Accept}; - use http::uri::Uri; + use http::uri::Origin; use http::Method::*; type SimpleRoute = (Method, &'static str); @@ -139,16 +139,18 @@ mod tests { fn unranked_collide(a: &'static str, b: &'static str) -> bool { 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 { - Uri::new(a).collides_with(&Uri::new(b)) + Origin::parse_route(a).unwrap() + .collides_with(&Origin::parse_route(b).unwrap()) } #[test] fn simple_collisions() { - assert!(unranked_collide("a", "a")); assert!(unranked_collide("/a", "/a")); assert!(unranked_collide("/hello", "/hello")); assert!(unranked_collide("/hello", "/hello/")); @@ -253,6 +255,13 @@ mod tests { assert!(!m_collide((Get, "/hello"), (Put, "/hello"))); } + #[test] + fn query_dependent_non_collisions() { + assert!(!m_collide((Get, "/"), (Get, "/?a"))); + assert!(!m_collide((Get, "/"), (Get, "/?"))); + assert!(!m_collide((Get, "/a/"), (Get, "/a/?d"))); + } + #[test] fn test_str_non_collisions() { assert!(!s_s_collide("/a", "/b")); @@ -365,7 +374,7 @@ mod tests { where S1: Into>, S2: Into> { 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 m.supports_payload() { req.replace_header(mt_str.parse::().unwrap()); @@ -423,7 +432,7 @@ mod tests { fn req_route_path_collide(a: &'static str, b: &'static str) -> bool { 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); route.collides_with(&req) } diff --git a/core/lib/src/router/mod.rs b/core/lib/src/router/mod.rs index dbf57b6d..a5db855f 100644 --- a/core/lib/src/router/mod.rs +++ b/core/lib/src/router/mod.rs @@ -57,7 +57,6 @@ impl Router { result } - // This is slow. Don't expose this publicly; only for tests. #[cfg(test)] fn has_collisions(&self) -> bool { @@ -78,7 +77,7 @@ mod test { use config::Config; use http::Method; use http::Method::*; - use http::uri::Uri; + use http::uri::Origin; use request::Request; use data::Data; use handler::Outcome; @@ -139,12 +138,28 @@ mod test { assert!(unranked_route_collisions(&["//b", "/a/"])); assert!(unranked_route_collisions(&["/a/", "/a/"])); assert!(unranked_route_collisions(&["/a/b/", "/a/"])); - assert!(unranked_route_collisions(&["", "/a/"])); + assert!(unranked_route_collisions(&["/", "/a/"])); assert!(unranked_route_collisions(&["/a/", "/a/"])); assert!(unranked_route_collisions(&["/a/b/", "/a/"])); assert!(unranked_route_collisions(&["/a/b/c/d", "/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(&["/", "/hello//"])); + assert!(unranked_route_collisions(&["/", "/hello///"])); + assert!(unranked_route_collisions(&["/hello///bob", "/hello/"])); + assert!(unranked_route_collisions(&["///", "/a//"])); + assert!(unranked_route_collisions(&["/a///", "/a/"])); + assert!(unranked_route_collisions(&["/a///", "/a/b//c//d/"])); + assert!(unranked_route_collisions(&["/a//", "/a/bd/e/"])); + assert!(unranked_route_collisions(&["/a///", "/a/b//c//d/e/"])); + assert!(unranked_route_collisions(&["/a////", "/a/b//c//d/e/"])); + } + #[test] fn test_no_collisions() { assert!(!unranked_route_collisions(&["/", "/a/"])); @@ -166,7 +181,7 @@ mod test { fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> { 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); if matches.len() > 0 { Some(matches[0]) @@ -177,7 +192,7 @@ mod test { fn matches<'a>(router: &'a Router, method: Method, uri: &str) -> Vec<&'a Route> { 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) } @@ -245,8 +260,8 @@ mod test { macro_rules! assert_ranked_routes { ($routes:expr, $to:expr, $want:expr) => ({ let router = router_with_routes($routes); - let route_path = route(&router, Get, $to).unwrap().uri.as_str(); - assert_eq!(route_path as &str, $want as &str); + let route_path = route(&router, Get, $to).unwrap().uri.to_string(); + assert_eq!(route_path, $want.to_string()); }) } @@ -275,21 +290,21 @@ mod test { #[test] fn test_no_manual_ranked_collisions() { - assert!(!ranked_collisions(&[(1, "a/"), (2, "a/")])); - assert!(!ranked_collisions(&[(0, "a/"), (2, "a/")])); - assert!(!ranked_collisions(&[(5, "a/"), (2, "a/")])); - assert!(!ranked_collisions(&[(1, "a/"), (1, "b/")])); - assert!(!ranked_collisions(&[(1, "a/"), (2, "a/")])); - assert!(!ranked_collisions(&[(0, "a/"), (2, "a/")])); - assert!(!ranked_collisions(&[(5, "a/"), (2, "a/")])); - assert!(!ranked_collisions(&[(1, ""), (2, "")])); + assert!(!ranked_collisions(&[(1, "/a/"), (2, "/a/")])); + assert!(!ranked_collisions(&[(0, "/a/"), (2, "/a/")])); + assert!(!ranked_collisions(&[(5, "/a/"), (2, "/a/")])); + assert!(!ranked_collisions(&[(1, "/a/"), (1, "/b/")])); + assert!(!ranked_collisions(&[(1, "/a/"), (2, "/a/")])); + assert!(!ranked_collisions(&[(0, "/a/"), (2, "/a/")])); + assert!(!ranked_collisions(&[(5, "/a/"), (2, "/a/")])); + assert!(!ranked_collisions(&[(1, "/"), (2, "/")])); } #[test] fn test_ranked_collisions() { - assert!(ranked_collisions(&[(2, "a/"), (2, "a/")])); - assert!(ranked_collisions(&[(2, "a/c/"), (2, "a/")])); - assert!(ranked_collisions(&[(2, ""), (2, "a/")])); + assert!(ranked_collisions(&[(2, "/a/"), (2, "/a/")])); + assert!(ranked_collisions(&[(2, "/a/c/"), (2, "/a/")])); + assert!(ranked_collisions(&[(2, "/"), (2, "/a/")])); } macro_rules! assert_ranked_routing { @@ -300,7 +315,7 @@ mod test { assert!(routed_to.len() == expected.len()); for (got, expected) in routed_to.iter().zip(expected.iter()) { 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] fn test_ranked_routing() { assert_ranked_routing!( - to: "a/b", - with: [(1, "a/"), (2, "a/")], - expect: (1, "a/"), (2, "a/") + to: "/a/b", + with: [(1, "/a/"), (2, "/a/")], + expect: (1, "/a/"), (2, "/a/") ); assert_ranked_routing!( - to: "b/b", - with: [(1, "a/"), (2, "b/"), (3, "b/b")], - expect: (2, "b/"), (3, "b/b") + to: "/b/b", + with: [(1, "/a/"), (2, "/b/"), (3, "/b/b")], + expect: (2, "/b/"), (3, "/b/b") ); assert_ranked_routing!( - to: "b/b", - with: [(2, "b/"), (1, "a/"), (3, "b/b")], - expect: (2, "b/"), (3, "b/b") + to: "/b/b", + with: [(2, "/b/"), (1, "/a/"), (3, "/b/b")], + expect: (2, "/b/"), (3, "/b/b") ); assert_ranked_routing!( - to: "b/b", - with: [(3, "b/b"), (2, "b/"), (1, "a/")], - expect: (2, "b/"), (3, "b/b") + to: "/b/b", + with: [(3, "/b/b"), (2, "/b/"), (1, "/a/")], + expect: (2, "/b/"), (3, "/b/b") ); assert_ranked_routing!( - to: "b/b", - with: [(1, "a/"), (2, "b/"), (0, "b/b")], - expect: (0, "b/b"), (2, "b/") + to: "/b/b", + with: [(1, "/a/"), (2, "/b/"), (0, "/b/b")], + expect: (0, "/b/b"), (2, "/b/") ); assert_ranked_routing!( @@ -369,7 +384,7 @@ mod test { let expected = &[$($want),+]; assert!(routed_to.len() == expected.len()); 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] fn test_default_ranked_routing() { assert_default_ranked_routing!( - to: "a/b?v=1", - with: ["a/", "a/b"], - expect: "a/b", "a/" + to: "/a/b?v=1", + with: ["/a/", "/a/b"], + expect: "/a/b", "/a/" ); assert_default_ranked_routing!( - to: "a/b?v=1", - with: ["a/", "a/b", "a/b?"], - expect: "a/b?", "a/b", "a/" + to: "/a/b?v=1", + with: ["/a/", "/a/b", "/a/b?"], + expect: "/a/b?", "/a/b", "/a/" ); assert_default_ranked_routing!( - to: "a/b?v=1", - with: ["a/", "a/b", "a/b?", "a/?"], - expect: "a/b?", "a/b", "a/?", "a/" + to: "/a/b?v=1", + with: ["/a/", "/a/b", "/a/b?", "/a/?"], + expect: "/a/b?", "/a/b", "/a/?", "/a/" ); assert_default_ranked_routing!( - to: "a/b", - with: ["a/", "a/b", "a/b?", "a/?"], - expect: "a/b", "a/" + to: "/a/b", + with: ["/a/", "/a/b", "/a/b?", "/a/?"], + expect: "/a/b", "/a/" ); } fn match_params(router: &Router, path: &str, expected: &[&str]) -> bool { println!("Testing: {} (expect: {:?})", path, expected); 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() { return false; } diff --git a/core/lib/src/router/route.rs b/core/lib/src/router/route.rs index 808baec5..65cb8615 100644 --- a/core/lib/src/router/route.rs +++ b/core/lib/src/router/route.rs @@ -6,9 +6,11 @@ use yansi::Color::*; use codegen::StaticRouteInfo; use handler::Handler; 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. +#[derive(Clone)] pub struct Route { /// The name of this route, if one was given. pub name: Option<&'static str>, @@ -17,10 +19,10 @@ pub struct Route { /// The function that should be called when the route matches. pub handler: Handler, /// The base mount point of this `Route`. - pub base: Uri<'static>, - /// The uri (in Rocket format) that should be matched against. This uri - /// already includes the base mount point. - pub uri: Uri<'static>, + pub base: Origin<'static>, + /// The uri (in Rocket's route format) that should be matched against. This + /// URI already includes the base mount point. + pub uri: Origin<'static>, /// The rank of this route. Lower ranks have higher priorities. pub rank: isize, /// The media type this route matches against, if any. @@ -28,10 +30,10 @@ pub struct Route { } #[inline(always)] -fn default_rank(uri: &Uri) -> isize { +fn default_rank(uri: &Origin) -> isize { // static path, query = -4; static path, no query = -3 // 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, false) => -3, (false, true) => -2, @@ -77,19 +79,19 @@ impl Route { /// // this is a rank -1 route matching requests to `GET /` /// let name = Route::new(Method::Get, "/", handler); /// ``` - pub fn new(m: Method, path: S, handler: Handler) -> Route + /// + /// # Panics + /// + /// Panics if `path` is not a valid origin URI. + pub fn new(method: Method, path: S, handler: Handler) -> Route where S: AsRef { - let uri = Uri::from(path.as_ref().to_string()); - Route { - name: None, - method: m, - handler: handler, - rank: default_rank(&uri), - base: Uri::from("/"), - uri: uri, - format: None, - } + let path = path.as_ref(); + let origin = Origin::parse_route(path) + .expect("invalid URI used as route path in `Route::new()`"); + + let rank = default_rank(&origin); + Route::ranked(rank, method, path, handler) } /// 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 /` /// let index = Route::ranked(1, Method::Get, "/", handler); /// ``` - pub fn ranked(rank: isize, m: Method, uri: S, handler: Handler) -> Route + /// + /// # Panics + /// + /// Panics if `path` is not a valid origin URI. + pub fn ranked(rank: isize, method: Method, path: S, handler: Handler) -> Route where S: AsRef { + let uri = Origin::parse_route(path.as_ref()) + .expect("invalid URI used as route path in `Route::ranked()`") + .into_owned(); + Route { name: None, - method: m, - handler: handler, - base: Uri::from("/"), - uri: Uri::from(uri.as_ref().to_string()), - rank: rank, 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 - /// other parameters. + /// other parameters. If `path` contains a query, it is ignored. /// /// # Example /// /// ```rust /// use rocket::{Request, Route, Data}; + /// use rocket::http::{Method, uri::Origin}; /// use rocket::handler::Outcome; - /// use rocket::http::Method; /// /// fn handler<'r>(request: &'r Request, _data: Data) -> Outcome<'r> { /// Outcome::from(request, "Hello, world!") @@ -163,12 +170,13 @@ impl Route { /// assert_eq!(index.base(), "/"); /// 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.path(), "/hi"); /// ``` - pub fn set_base(&mut self, path: S) where S: AsRef { - self.base = Uri::from(path.as_ref().to_string()); + pub fn set_base<'a>(&mut self, path: Origin<'a>) { + self.base = path.into_owned(); + self.base.clear_query(); } /// Sets the path of the route. Does not update the rank or any other @@ -178,8 +186,8 @@ impl Route { /// /// ```rust /// use rocket::{Request, Route, Data}; + /// use rocket::http::{Method, uri::Origin}; /// use rocket::handler::Outcome; - /// use rocket::http::Method; /// /// fn handler<'r>(request: &'r Request, _data: Data) -> Outcome<'r> { /// Outcome::from(request, "Hello, world!") @@ -188,11 +196,11 @@ impl Route { /// let mut index = Route::ranked(1, Method::Get, "/", handler); /// assert_eq!(index.uri.path(), "/"); /// - /// index.set_uri("/hello"); + /// index.set_uri(Origin::parse("/hello").unwrap()); /// assert_eq!(index.uri.path(), "/hello"); /// ``` - pub fn set_uri(&mut self, uri: S) where S: AsRef { - self.uri = Uri::from(uri.as_ref().to_string()); + pub fn set_uri<'a>(&mut self, uri: Origin<'a>) { + self.uri = uri.into_owned(); } // 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!) /// Given a URI, returns a vector of slices of that URI corresponding to the /// 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 uri_segs = uri.segments(); 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 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} {}", Green.paint(&self.method), Blue.paint(&self.uri))?; @@ -258,7 +252,14 @@ impl fmt::Display for Route { impl fmt::Debug for Route { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ::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() } } diff --git a/core/lib/tests/absolute-uris-okay-issue-443.rs b/core/lib/tests/absolute-uris-okay-issue-443.rs new file mode 100644 index 00000000..dfcf1259 --- /dev/null +++ b/core/lib/tests/absolute-uris-okay-issue-443.rs @@ -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")); + } +} diff --git a/core/lib/tests/local-request-content-type-issue-505.rs b/core/lib/tests/local-request-content-type-issue-505.rs index eeba0d6d..a43503b2 100644 --- a/core/lib/tests/local-request-content-type-issue-505.rs +++ b/core/lib/tests/local-request-content-type-issue-505.rs @@ -66,12 +66,12 @@ mod local_request_content_type_tests { let client = Client::new(rocket()).unwrap(); 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.dispatch().body_string(), Some("Absent".to_string())); 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.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 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.dispatch().body_string(), Some("Present".to_string())); 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.dispatch().body_string(), Some("Data Present".to_string())); } diff --git a/examples/handlebars_templates/src/main.rs b/examples/handlebars_templates/src/main.rs index 49938af6..dffcfdec 100644 --- a/examples/handlebars_templates/src/main.rs +++ b/examples/handlebars_templates/src/main.rs @@ -50,7 +50,7 @@ fn about() -> Template { #[catch(404)] fn not_found(req: &Request) -> Template { 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) } diff --git a/examples/manual_routes/src/main.rs b/examples/manual_routes/src/main.rs index cfc8b9de..e21cf489 100644 --- a/examples/manual_routes/src/main.rs +++ b/examples/manual_routes/src/main.rs @@ -28,7 +28,7 @@ fn name<'a>(req: &'a Request, _: Data) -> Outcome<'a> { fn echo_url(req: &Request, _: Data) -> Outcome<'static> { let param = req.uri() - .as_str() + .path() .split_at(6) .1; diff --git a/examples/manual_routes/src/tests.rs b/examples/manual_routes/src/tests.rs index 98fde69c..8a742e55 100644 --- a/examples/manual_routes/src/tests.rs +++ b/examples/manual_routes/src/tests.rs @@ -24,9 +24,8 @@ fn test_name() { #[test] fn test_echo() { - let echo = "echo text"; - let uri = format!("/echo:echo text"); - test(&uri, ContentType::Plain, Status::Ok, echo.to_string()); + let uri = format!("/echo:echo%20text"); + test(&uri, ContentType::Plain, Status::Ok, "echo text".into()); } #[test] diff --git a/examples/tera_templates/src/main.rs b/examples/tera_templates/src/main.rs index d48cf0a1..0713b515 100644 --- a/examples/tera_templates/src/main.rs +++ b/examples/tera_templates/src/main.rs @@ -34,7 +34,7 @@ fn get(name: String) -> Template { #[catch(404)] fn not_found(req: &Request) -> Template { let mut map = HashMap::new(); - map.insert("path", req.uri().as_str()); + map.insert("path", req.uri().path()); Template::render("error/404", &map) }