Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing HTTP 405 if HTTP method doesn't match with required by the route #1276

Closed
wants to merge 13 commits into from
2 changes: 1 addition & 1 deletion core/codegen/tests/route-format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ fn test_formats() {
assert_eq!(response.body_string().unwrap(), "plain");

let response = client.put("/").header(ContentType::HTML).dispatch();
assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.status(), Status::MethodNotAllowed);
}

// Test custom formats.
Expand Down
169 changes: 97 additions & 72 deletions core/lib/src/rocket.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
use std::collections::HashMap;
use std::str::from_utf8;
use std::cmp::min;
use std::collections::HashMap;
use std::io::{self, Write};
use std::time::Duration;
use std::mem;
use std::str::from_utf8;
use std::time::Duration;

use yansi::Paint;
use state::Container;
use yansi::Paint;

#[cfg(feature = "tls")] use crate::http::tls::TlsServer;
#[cfg(feature = "tls")]
use crate::http::tls::TlsServer;

use crate::{logger, handler};
use crate::ext::ReadExt;
use crate::catcher::{self, Catcher};
use crate::config::{self, Config, LoggedValue};
use crate::request::{Request, FormItems};
use crate::data::Data;
use crate::response::{Body, Response};
use crate::router::{Router, Route};
use crate::catcher::{self, Catcher};
use crate::outcome::Outcome;
use crate::error::{LaunchError, LaunchErrorKind};
use crate::ext::ReadExt;
use crate::fairing::{Fairing, Fairings};
use crate::outcome::Outcome;
use crate::request::{FormItems, Request};
use crate::response::{Body, Response};
use crate::router::{Route, Router};
use crate::{handler, logger};

use crate::http::{Method, Status, Header};
use crate::http::hyper::{self, header};
use crate::http::uri::Origin;
use crate::http::{Header, Method, Status};

/// The main `Rocket` type: used to mount routes and catchers and launch the
/// application.
Expand All @@ -44,11 +45,7 @@ impl hyper::Handler for Rocket {
// `dispatch` function, which knows nothing about Hyper. Because responding
// depends on the `HyperResponse` type, this function does the actual
// response processing.
fn handle<'h, 'k>(
&self,
hyp_req: hyper::Request<'h, 'k>,
res: hyper::FreshResponse<'h>,
) {
fn handle<'h, 'k>(&self, hyp_req: hyper::Request<'h, 'k>, res: hyper::FreshResponse<'h>) {
// Get all of the information from Hyper.
let (h_addr, h_method, h_headers, h_uri, _, h_body) = hyp_req.deconstruct();

Expand Down Expand Up @@ -93,15 +90,15 @@ impl hyper::Handler for Rocket {
// closure would be different depending on whether TLS was enabled or not.
#[cfg(not(feature = "tls"))]
macro_rules! serve {
($rocket:expr, $addr:expr, |$server:ident, $proto:ident| $continue:expr) => ({
($rocket:expr, $addr:expr, |$server:ident, $proto:ident| $continue:expr) => {{
let ($proto, $server) = ("http://", hyper::Server::http($addr));
$continue
})
}};
}

#[cfg(feature = "tls")]
macro_rules! serve {
($rocket:expr, $addr:expr, |$server:ident, $proto:ident| $continue:expr) => ({
($rocket:expr, $addr:expr, |$server:ident, $proto:ident| $continue:expr) => {{
if let Some(tls) = $rocket.config.tls.clone() {
let tls = TlsServer::new(tls.certs, tls.key);
let ($proto, $server) = ("https://", hyper::Server::https($addr, tls));
Expand All @@ -110,7 +107,7 @@ macro_rules! serve {
let ($proto, $server) = ("http://", hyper::Server::http($addr));
$continue
}
})
}};
}

impl Rocket {
Expand Down Expand Up @@ -200,7 +197,7 @@ impl Rocket {
pub(crate) fn dispatch<'s, 'r>(
&'s self,
request: &'r mut Request<'s>,
data: Data
data: Data,
) -> Response<'r> {
info!("{}:", request);

Expand Down Expand Up @@ -234,11 +231,7 @@ impl Rocket {
}

/// Route the request and process the outcome to eventually get a response.
fn route_and_process<'s, 'r>(
&'s self,
request: &'r Request<'s>,
data: Data
) -> Response<'r> {
fn route_and_process<'s, 'r>(&'s self, request: &'r Request<'s>, data: Data) -> Response<'r> {
let mut response = match self.route(request, data) {
Outcome::Success(response) => response,
Outcome::Forward(data) => {
Expand All @@ -256,6 +249,7 @@ impl Rocket {
self.handle_error(Status::NotFound, request)
}
}

Outcome::Failure(status) => self.handle_error(status, request),
};

Expand Down Expand Up @@ -287,21 +281,41 @@ impl Rocket {
) -> handler::Outcome<'r> {
// Go through the list of matching routes until we fail or succeed.
let matches = self.router.route(request);
for route in matches {
// Retrieve and set the requests parameters.
info_!("Matched: {}", route);
request.set_route(route);

// Dispatch the request to the handler.
let outcome = route.handler.handle(request, data);

// Check if the request processing completed or if the request needs
// to be forwarded. If it does, continue the loop to try again.
info_!("{} {}", Paint::default("Outcome:").bold(), outcome);
match outcome {
o@Outcome::Success(_) | o@Outcome::Failure(_) => return o,
Outcome::Forward(unused_data) => data = unused_data,
};
let mut counter: usize = 0;
for route in &matches {
counter += 1;

// Must pass HEAD requests foward
if (&request.method() != &Method::Head) && (&route.method != &request.method()) {
// Must make sure it consumed all list before fail
if &counter == &matches.len() {
error_!("No matching routes for {}.", request);
info_!(
"{} {}",
Paint::yellow("A similar route exists:").bold(),
route
);
return Outcome::Failure(Status::MethodNotAllowed);
} else {
continue;
}
} else {
// Retrieve and set the requests parameters.
info_!("Matched: {}", route);

request.set_route(route);

// Dispatch the request to the handler.
let outcome = route.handler.handle(request, data);

// Check if the request processing completed or if the request needs
// to be forwarded. If it does, continue the loop to try again.
info_!("{} {}", Paint::default("Outcome:").bold(), outcome);
match outcome {
o @ Outcome::Success(_) | o @ Outcome::Failure(_) => return o,
Outcome::Forward(unused_data) => data = unused_data,
};
}
}

error_!("No matching routes for {}.", request);
Expand All @@ -314,11 +328,7 @@ impl Rocket {
// catcher for `status`, the catcher is called. If the catcher fails to
// return a good response, the 500 catcher is executed. If there is no
// registered catcher for `status`, the default catcher is used.
pub(crate) fn handle_error<'r>(
&self,
status: Status,
req: &'r Request<'_>
) -> Response<'r> {
pub(crate) fn handle_error<'r>(&self, status: Status, req: &'r Request<'_>) -> Response<'r> {
warn_!("Responding with {} catcher.", Paint::red(&status));

// For now, we reset the delta state to prevent any modifications from
Expand Down Expand Up @@ -403,7 +413,11 @@ impl Rocket {
logger::push_max_level(logger::LoggingLevel::Normal);
}

launch_info!("{}Configured for {}.", Paint::masked("🔧 "), config.environment);
launch_info!(
"{}Configured for {}.",
Paint::masked("🔧 "),
config.environment
);
launch_info_!("address: {}", Paint::default(&config.address).bold());
launch_info_!("port: {}", Paint::default(&config.port).bold());
launch_info_!("log: {}", Paint::default(config.log_level).bold());
Expand Down Expand Up @@ -431,9 +445,12 @@ impl Rocket {
}

for (name, value) in config.extras() {
launch_info_!("{} {}: {}",
Paint::yellow("[extra]"), name,
Paint::default(LoggedValue(value)).bold());
launch_info_!(
"{} {}: {}",
Paint::yellow("[extra]"),
name,
Paint::default(LoggedValue(value)).bold()
);
}

Rocket {
Expand Down Expand Up @@ -502,17 +519,18 @@ impl Rocket {
/// ```
#[inline]
pub fn mount<R: Into<Vec<Route>>>(mut self, base: &str, routes: R) -> Self {
info!("{}{} {}{}",
Paint::masked("🛰 "),
Paint::magenta("Mounting"),
Paint::blue(base),
Paint::magenta(":"));

let base_uri = Origin::parse(base)
.unwrap_or_else(|e| {
error_!("Invalid origin URI '{}' used as mount point.", base);
panic!("Error: {}", e);
});
info!(
"{}{} {}{}",
Paint::masked("🛰 "),
Paint::magenta("Mounting"),
Paint::blue(base),
Paint::magenta(":")
);

let base_uri = Origin::parse(base).unwrap_or_else(|e| {
error_!("Invalid origin URI '{}' used as mount point.", base);
panic!("Error: {}", e);
});

if base_uri.query().is_some() {
error_!("Mount point '{}' contains query string.", base);
Expand Down Expand Up @@ -660,11 +678,13 @@ impl Rocket {
pub(crate) fn prelaunch_check(mut self) -> Result<Rocket, LaunchError> {
self.router = match self.router.collisions() {
Ok(router) => router,
Err(e) => return Err(LaunchError::new(LaunchErrorKind::Collision(e)))
Err(e) => return Err(LaunchError::new(LaunchErrorKind::Collision(e))),
};

if let Some(failures) = self.fairings.failures() {
return Err(LaunchError::new(LaunchErrorKind::FailedFairings(failures.to_vec())))
return Err(LaunchError::new(LaunchErrorKind::FailedFairings(
failures.to_vec(),
)));
}

Ok(self)
Expand All @@ -691,7 +711,7 @@ impl Rocket {
pub fn launch(mut self) -> LaunchError {
self = match self.prelaunch_check() {
Ok(rocket) => rocket,
Err(launch_error) => return launch_error
Err(launch_error) => return launch_error,
};

self.fairings.pretty_print_counts();
Expand All @@ -710,7 +730,10 @@ impl Rocket {
}

// Set the keep-alive.
let timeout = self.config.keep_alive.map(|s| Duration::from_secs(s as u64));
let timeout = self
.config
.keep_alive
.map(|s| Duration::from_secs(s as u64));
server.keep_alive(timeout);

// Freeze managed state for synchronization-free accesses later.
Expand All @@ -720,11 +743,13 @@ impl Rocket {
self.fairings.handle_launch(&self);

let full_addr = format!("{}:{}", self.config.address, self.config.port);
launch_info!("{}{} {}{}",
Paint::masked("🚀 "),
Paint::default("Rocket has launched from").bold(),
Paint::default(proto).bold().underline(),
Paint::default(&full_addr).bold().underline());
launch_info!(
"{}{} {}{}",
Paint::masked("🚀 "),
Paint::default("Rocket has launched from").bold(),
Paint::default(proto).bold().underline(),
Paint::default(&full_addr).bold().underline()
);

// Restore the log level back to what it originally was.
logger::pop_max_level();
Expand Down
Loading