Skip to content

Commit

Permalink
Overhaul URI types, parsers, 'uri!' macro.
Browse files Browse the repository at this point in the history
This commit entirely rewrites Rocket's URI parsing routines and
overhauls the 'uri!' macro resolving all known issues and removing any
potential limitations for compile-time URI creation. This commit:

  * Introduces a new 'Reference' URI variant for URI-references.
  * Modifies 'Redirect' to accept 'TryFrom<Reference>'.
  * Introduces a new 'Asterisk' URI variant for parity.
  * Allows creation of any URI type from a string literal via 'uri!'.
  * Enables dynamic/static prefixing/suffixing of route URIs in 'uri!'.
  * Unifies 'Segments' and 'QuerySegments' into one generic 'Segments'.
  * Consolidates URI formatting types/traits into a 'uri::fmt' module.
  * Makes APIs more symmetric across URI types.

It also includes the following less-relevant changes:

  * Implements 'FromParam' for a single-segment 'PathBuf'.
  * Adds 'FileName::is_safe()'.
  * No longer reparses upstream request URIs.

Resolves #842.
Resolves #853.
Resolves #998.
  • Loading branch information
SergioBenitez committed May 20, 2021
1 parent 15b1cf5 commit fa3e033
Show file tree
Hide file tree
Showing 78 changed files with 4,708 additions and 2,490 deletions.
10 changes: 5 additions & 5 deletions contrib/lib/src/helmet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,17 @@
//! `SpaceHelmet`:
//!
//! ```rust
//! # extern crate rocket;
//! # #[macro_use] extern crate rocket;
//! # extern crate rocket_contrib;
//! use rocket::http::uri::Uri;
//! use rocket_contrib::helmet::{SpaceHelmet, Frame, XssFilter, Hsts, NoSniff};
//!
//! let site_uri = Uri::parse("https://mysite.example.com").unwrap();
//! let report_uri = Uri::parse("https://report.example.com").unwrap();
//! let site_uri = uri!("https://mysite.example.com");
//! let report_uri = uri!("https://report.example.com");
//! let helmet = SpaceHelmet::default()
//! .enable(Hsts::default())
//! .enable(Frame::AllowFrom(site_uri))
//! .enable(XssFilter::EnableReport(report_uri))
//! .enable(Frame::AllowFrom(site_uri.into()))
//! .enable(XssFilter::EnableReport(report_uri.into()))
//! .disable::<NoSniff>();
//! ```
//!
Expand Down
4 changes: 3 additions & 1 deletion contrib/lib/src/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,10 +357,12 @@ impl Into<Vec<Route>> for StaticFiles {
#[rocket::async_trait]
impl Handler for StaticFiles {
async fn handle<'r>(&self, req: &'r Request<'_>, data: Data) -> Outcome<'r> {
use rocket::http::uri::fmt::Path;

// Get the segments as a `PathBuf`, allowing dotfiles requested.
let options = self.options;
let allow_dotfiles = options.contains(Options::DotFiles);
let path = req.segments::<Segments<'_>>(0..).ok()
let path = req.segments::<Segments<'_, Path>>(0..).ok()
.and_then(|segments| segments.to_path_buf(allow_dotfiles).ok())
.map(|path| self.root.join(path));

Expand Down
22 changes: 10 additions & 12 deletions contrib/lib/tests/helmet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ extern crate rocket;

#[cfg(feature = "helmet")]
mod helmet_tests {
use rocket::http::{Status, uri::Uri};
use rocket::http::Status;
use rocket::local::blocking::{Client, LocalResponse};

use rocket_contrib::helmet::*;
Expand Down Expand Up @@ -108,24 +108,22 @@ mod helmet_tests {

#[test]
fn uri_test() {
let allow_uri = Uri::parse("https://www.google.com").unwrap();
let report_uri = Uri::parse("https://www.google.com").unwrap();
let enforce_uri = Uri::parse("https://www.google.com").unwrap();
let allow_uri = uri!("https://rocket.rs");
let report_uri = uri!("https://rocket.rs");
let enforce_uri = uri!("https://rocket.rs");

let helmet = SpaceHelmet::default()
.enable(Frame::AllowFrom(allow_uri))
.enable(XssFilter::EnableReport(report_uri))
.enable(ExpectCt::ReportAndEnforce(Duration::seconds(30), enforce_uri));
.enable(Frame::AllowFrom(allow_uri.into()))
.enable(XssFilter::EnableReport(report_uri.into()))
.enable(ExpectCt::ReportAndEnforce(Duration::seconds(30), enforce_uri.into()));

dispatch!(helmet, |response: LocalResponse<'_>| {
assert_header!(response, "X-Frame-Options",
"ALLOW-FROM https://www.google.com");
assert_header!(response, "X-Frame-Options", "ALLOW-FROM https://rocket.rs");

assert_header!(response, "X-XSS-Protection",
"1; report=https://www.google.com");
assert_header!(response, "X-XSS-Protection", "1; report=https://rocket.rs");

assert_header!(response, "Expect-CT",
"max-age=30, enforce, report-uri=\"https://www.google.com\"");
"max-age=30, enforce, report-uri=\"https://rocket.rs\"");
});
}

Expand Down
4 changes: 2 additions & 2 deletions core/codegen/src/attribute/param/guard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ impl FromMeta for Dynamic {
let string = StringLit::from_meta(meta)?;
let span = string.subspan(1..string.len() + 1);

// We don't allow `_`. We abuse `uri::Query` to enforce this.
Ok(Dynamic::parse::<uri::Query>(&string, span)?)
// We don't allow `_`. We abuse `fmt::Query` to enforce this.
Ok(Dynamic::parse::<fmt::Query>(&string, span)?)
}
}

Expand Down
12 changes: 6 additions & 6 deletions core/codegen/src/attribute/param/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use unicode_xid::UnicodeXID;
use devise::{Diagnostic, ext::SpanDiagnosticExt};

use crate::name::Name;
use crate::http::uri::{self, UriPart};
use crate::proc_macro_ext::StringLit;
use crate::proc_macro2::Span;
use crate::attribute::param::{Parameter, Dynamic};
use crate::http::uri::fmt::{Part, Kind, Path};

#[derive(Debug)]
pub struct Error<'a> {
Expand All @@ -27,7 +27,7 @@ pub enum ErrorKind {
}

impl Dynamic {
pub fn parse<P: UriPart>(
pub fn parse<P: Part>(
segment: &str,
span: Span,
) -> Result<Self, Error<'_>> {
Expand All @@ -40,7 +40,7 @@ impl Dynamic {
}

impl Parameter {
pub fn parse<P: UriPart>(
pub fn parse<P: Part>(
segment: &str,
source_span: Span,
) -> Result<Self, Error<'_>> {
Expand All @@ -62,7 +62,7 @@ impl Parameter {
}

let dynamic = Dynamic { name: Name::new(name, span), trailing, index: 0 };
if dynamic.is_wild() && P::KIND != uri::Kind::Path {
if dynamic.is_wild() && P::KIND != Kind::Path {
return Err(Error::new(name, span, ErrorKind::Ignored));
} else if dynamic.is_wild() {
return Ok(Parameter::Ignored(dynamic));
Expand All @@ -84,7 +84,7 @@ impl Parameter {
Ok(Parameter::Static(Name::new(segment, source_span)))
}

pub fn parse_many<P: uri::UriPart>(
pub fn parse_many<P: Part>(
source: &str,
source_span: Span,
) -> impl Iterator<Item = Result<Self, Error<'_>>> {
Expand Down Expand Up @@ -182,7 +182,7 @@ impl devise::FromMeta for Dynamic {
fn from_meta(meta: &devise::MetaItem) -> devise::Result<Self> {
let string = StringLit::from_meta(meta)?;
let span = string.subspan(1..string.len() + 1);
let param = Dynamic::parse::<uri::Path>(&string, span)?;
let param = Dynamic::parse::<Path>(&string, span)?;

if param.is_wild() {
return Err(Error::new(&string, span, ErrorKind::Ignored).into());
Expand Down
6 changes: 3 additions & 3 deletions core/codegen/src/attribute/route/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::syn_ext::FnArgExt;
use crate::name::Name;
use crate::proc_macro2::Span;
use crate::http::ext::IntoOwned;
use crate::http::uri::{self, Origin};
use crate::http::uri::{Origin, fmt};

/// This structure represents the parsed `route` attribute and associated items.
#[derive(Debug)]
Expand Down Expand Up @@ -159,14 +159,14 @@ impl Route {

// Parse and collect the path parameters.
let (source, span) = (attr.uri.path(), attr.uri.path_span);
let path_params = Parameter::parse_many::<uri::Path>(source.as_str(), span)
let path_params = Parameter::parse_many::<fmt::Path>(source.as_str(), span)
.map(|p| Route::upgrade_param(p?, &arguments))
.filter_map(|p| p.map_err(|e| diags.push(e.into())).ok())
.collect::<Vec<_>>();

// Parse and collect the query parameters.
let query_params = match (attr.uri.query(), attr.uri.query_span) {
(Some(r), Some(span)) => Parameter::parse_many::<uri::Query>(r.as_str(), span)
(Some(q), Some(span)) => Parameter::parse_many::<fmt::Query>(q.as_str(), span)
.map(|p| Route::upgrade_param(p?, &arguments))
.filter_map(|p| p.map_err(|e| diags.push(e.into())).ok())
.collect::<Vec<_>>(),
Expand Down
8 changes: 4 additions & 4 deletions core/codegen/src/bang/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,19 @@ pub fn catchers_macro(input: proc_macro::TokenStream) -> TokenStream {
pub fn uri_macro(input: proc_macro::TokenStream) -> TokenStream {
uri::_uri_macro(input.into())
.unwrap_or_else(|diag| diag.emit_as_expr_tokens_or(quote! {
rocket::http::uri::Origin::dummy()
rocket::http::uri::Origin::ROOT
}))
}

pub fn uri_internal_macro(input: proc_macro::TokenStream) -> TokenStream {
// FIXME: Ideally we would generate an `Origin::dummy()` so that we don't
// TODO: Ideally we would generate a perfect `Origin::ROOT` so that we don't
// assist in propoagate further errors. Alas, we can't set the span to the
// invocation of `uri!` without access to `span.parent()`, and
// `Span::call_site()` here points to the `#[route]`, immediate caller,
// generate a rather confusing error message when there's a type-mismatch.
// generating a rather confusing error message when there's a type-mismatch.
uri::_uri_internal_macro(input.into())
.unwrap_or_else(|diag| diag.emit_as_expr_tokens_or(quote! {
rocket::http::uri::Origin::dummy()
rocket::http::uri::Origin::ROOT
}))
}

Expand Down
Loading

0 comments on commit fa3e033

Please sign in to comment.