mirror of https://github.com/rwf2/Rocket.git
Clear flash cookies only after they're inspected.
Resolves #512. Resolves #466.
This commit is contained in:
parent
eda635aaeb
commit
9629b40202
|
@ -7,10 +7,11 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Rocket Session: Please Login</h1>
|
<h1>Rocket Session: Please Login</h1>
|
||||||
|
|
||||||
|
<p>Please login to continue.</p>
|
||||||
|
|
||||||
{{#if flash}}
|
{{#if flash}}
|
||||||
<p>{{ flash }}</p>
|
<p>Error: {{ flash }}</p>
|
||||||
{{else}}
|
|
||||||
<p>Please login to continue.</p>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<form action="/login" method="post" accept-charset="utf-8">
|
<form action="/login" method="post" accept-charset="utf-8">
|
||||||
|
|
|
@ -15,6 +15,5 @@ pub use self::param::{FromParam, FromSegments};
|
||||||
pub use self::form::{Form, LenientForm, FromForm, FromFormValue, FormItems};
|
pub use self::form::{Form, LenientForm, FromForm, FromFormValue, FormItems};
|
||||||
pub use self::state::State;
|
pub use self::state::State;
|
||||||
|
|
||||||
/// Type alias to retrieve [Flash](/rocket/response/struct.Flash.html) messages
|
#[doc(inline)]
|
||||||
/// from a request.
|
pub use response::flash::FlashMessage;
|
||||||
pub type FlashMessage = ::response::Flash<()>;
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ use outcome::IntoOutcome;
|
||||||
use response::{Response, Responder};
|
use response::{Response, Responder};
|
||||||
use request::{self, Request, FromRequest};
|
use request::{self, Request, FromRequest};
|
||||||
use http::{Status, Cookie};
|
use http::{Status, Cookie};
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
// The name of the actual flash cookie.
|
// The name of the actual flash cookie.
|
||||||
const FLASH_COOKIE_NAME: &'static str = "_flash";
|
const FLASH_COOKIE_NAME: &'static str = "_flash";
|
||||||
|
@ -87,9 +88,26 @@ const FLASH_COOKIE_NAME: &'static str = "_flash";
|
||||||
pub struct Flash<R> {
|
pub struct Flash<R> {
|
||||||
name: String,
|
name: String,
|
||||||
message: String,
|
message: String,
|
||||||
responder: R,
|
consumed: AtomicBool,
|
||||||
|
inner: R,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Type alias to retrieve [`Flash`](/rocket/response/struct.Flash.html)
|
||||||
|
/// messages from a request.
|
||||||
|
///
|
||||||
|
/// # Flash Cookie
|
||||||
|
///
|
||||||
|
/// A `FlashMessage` holds the parsed contents of the flash cookie. As long as
|
||||||
|
/// there is a flash cookie present (set by the `Flash` `Responder`), a
|
||||||
|
/// `FlashMessage` reuqest guard will succeed.
|
||||||
|
///
|
||||||
|
/// The flash cookie is cleared if either the [`name()`] or [`msg()`] method is
|
||||||
|
/// called. If neither method is called, the flash cookie is not cleared.
|
||||||
|
///
|
||||||
|
/// [`name()`]: /rocket/response.struct.Flash.html#method.name
|
||||||
|
/// [`msg()`]: /rocket/response.struct.Flash.html#method.msg
|
||||||
|
pub type FlashMessage<'a, 'r> = ::response::Flash<&'a Request<'r>>;
|
||||||
|
|
||||||
impl<'r, R: Responder<'r>> Flash<R> {
|
impl<'r, R: Responder<'r>> Flash<R> {
|
||||||
/// Constructs a new `Flash` message with the given `name`, `msg`, and
|
/// Constructs a new `Flash` message with the given `name`, `msg`, and
|
||||||
/// underlying `responder`.
|
/// underlying `responder`.
|
||||||
|
@ -109,7 +127,8 @@ impl<'r, R: Responder<'r>> Flash<R> {
|
||||||
Flash {
|
Flash {
|
||||||
name: name.as_ref().to_string(),
|
name: name.as_ref().to_string(),
|
||||||
message: msg.as_ref().to_string(),
|
message: msg.as_ref().to_string(),
|
||||||
responder: res,
|
consumed: AtomicBool::default(),
|
||||||
|
inner: res,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,45 +202,56 @@ impl<'r, R: Responder<'r>> Flash<R> {
|
||||||
impl<'r, R: Responder<'r>> Responder<'r> for Flash<R> {
|
impl<'r, R: Responder<'r>> Responder<'r> for Flash<R> {
|
||||||
fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
|
fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
|
||||||
trace_!("Flash: setting message: {}:{}", self.name, self.message);
|
trace_!("Flash: setting message: {}:{}", self.name, self.message);
|
||||||
let cookie = self.cookie();
|
req.cookies().add(self.cookie());
|
||||||
Response::build_from(self.responder.respond_to(req)?)
|
self.inner.respond_to(req)
|
||||||
.header_adjoin(&cookie)
|
|
||||||
.ok()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Flash<()> {
|
impl<'a, 'r> Flash<&'a Request<'r>> {
|
||||||
/// Constructs a new message with the given name and message.
|
/// Constructs a new message with the given name and message for the given
|
||||||
fn named(name: &str, msg: &str) -> Flash<()> {
|
/// request.
|
||||||
|
fn named(name: &str, msg: &str, request: &'a Request<'r>) -> Flash<&'a Request<'r>> {
|
||||||
Flash {
|
Flash {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
message: msg.to_string(),
|
message: msg.to_string(),
|
||||||
responder: (),
|
consumed: AtomicBool::new(false),
|
||||||
|
inner: request,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clears the request cookie if it hasn't already been cleared.
|
||||||
|
fn clear_cookie_if_needed(&self) {
|
||||||
|
// Remove the cookie if it hasn't already been removed.
|
||||||
|
if !self.consumed.swap(true, Ordering::Relaxed) {
|
||||||
|
let cookie = Cookie::build(FLASH_COOKIE_NAME, "").path("/").finish();
|
||||||
|
self.inner.cookies().remove(cookie);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `name` of this message.
|
/// Returns the `name` of this message.
|
||||||
pub fn name(&self) -> &str {
|
pub fn name(&self) -> &str {
|
||||||
self.name.as_str()
|
self.clear_cookie_if_needed();
|
||||||
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `msg` contents of this message.
|
/// Returns the `msg` contents of this message.
|
||||||
pub fn msg(&self) -> &str {
|
pub fn msg(&self) -> &str {
|
||||||
self.message.as_str()
|
self.clear_cookie_if_needed();
|
||||||
|
&self.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves a flash message from a flash cookie and deletes the flash cookie.
|
/// Retrieves a flash message from a flash cookie. If there is no flash cookie,
|
||||||
/// If there is no flash cookie, an empty `Err` is returned.
|
/// or if the flash cookie is malformed, an empty `Err` is returned.
|
||||||
///
|
///
|
||||||
/// The suggested use is through an `Option` and the `FlashMessage` type alias
|
/// The suggested use is through an `Option` and the `FlashMessage` type alias
|
||||||
/// in `request`: `Option<FlashMessage>`.
|
/// in `request`: `Option<FlashMessage>`.
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for Flash<()> {
|
impl<'a, 'r> FromRequest<'a, 'r> for Flash<&'a Request<'r>> {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||||
trace_!("Flash: attemping to retrieve message.");
|
trace_!("Flash: attemping to retrieve message.");
|
||||||
let r = request.cookies().get(FLASH_COOKIE_NAME).ok_or(()).and_then(|cookie| {
|
request.cookies().get(FLASH_COOKIE_NAME).ok_or(()).and_then(|cookie| {
|
||||||
trace_!("Flash: retrieving message: {:?}", cookie);
|
trace_!("Flash: retrieving message: {:?}", cookie);
|
||||||
|
|
||||||
// Parse the flash message.
|
// Parse the flash message.
|
||||||
|
@ -233,15 +263,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for Flash<()> {
|
||||||
|
|
||||||
let name_len: usize = len_str.parse().map_err(|_| ())?;
|
let name_len: usize = len_str.parse().map_err(|_| ())?;
|
||||||
let (name, msg) = (&rest[..name_len], &rest[name_len..]);
|
let (name, msg) = (&rest[..name_len], &rest[name_len..]);
|
||||||
Ok(Flash::named(name, msg))
|
Ok(Flash::named(name, msg, request))
|
||||||
});
|
}).into_outcome(Status::BadRequest)
|
||||||
|
|
||||||
// If we found a flash cookie, delete it from the jar.
|
|
||||||
if r.is_ok() {
|
|
||||||
let cookie = Cookie::build(FLASH_COOKIE_NAME, "").path("/").finish();
|
|
||||||
request.cookies().remove(cookie);
|
|
||||||
}
|
|
||||||
|
|
||||||
r.into_outcome(Status::BadRequest)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,12 +21,13 @@
|
||||||
|
|
||||||
mod responder;
|
mod responder;
|
||||||
mod redirect;
|
mod redirect;
|
||||||
mod flash;
|
|
||||||
mod named_file;
|
mod named_file;
|
||||||
mod stream;
|
mod stream;
|
||||||
mod response;
|
mod response;
|
||||||
mod failure;
|
mod failure;
|
||||||
|
|
||||||
|
pub(crate) mod flash;
|
||||||
|
|
||||||
pub mod content;
|
pub mod content;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
#![feature(plugin, decl_macro, custom_derive)]
|
||||||
|
#![plugin(rocket_codegen)]
|
||||||
|
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
|
use rocket::request::FlashMessage;
|
||||||
|
use rocket::response::Flash;
|
||||||
|
|
||||||
|
const FLASH_MESSAGE: &str = "Hey! I'm a flash message. :)";
|
||||||
|
|
||||||
|
#[post("/")]
|
||||||
|
fn set() -> Flash<&'static str> {
|
||||||
|
Flash::success("This is the page.", FLASH_MESSAGE)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/unused")]
|
||||||
|
fn unused(flash: Option<FlashMessage>) -> Option<()> {
|
||||||
|
flash.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/use")]
|
||||||
|
fn used(flash: Option<FlashMessage>) -> Option<String> {
|
||||||
|
flash.map(|flash| flash.msg().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
mod flash_lazy_remove_tests {
|
||||||
|
use rocket::local::Client;
|
||||||
|
use rocket::http::Status;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
use super::*;
|
||||||
|
let r = rocket::ignite().mount("/", routes![set, unused, used]);
|
||||||
|
let client = Client::new(r).unwrap();
|
||||||
|
|
||||||
|
// Ensure the cookie's not there at first.
|
||||||
|
let response = client.get("/unused").dispatch();
|
||||||
|
assert_eq!(response.status(), Status::NotFound);
|
||||||
|
|
||||||
|
// Set the flash cookie.
|
||||||
|
client.post("/").dispatch();
|
||||||
|
|
||||||
|
// Try once.
|
||||||
|
let response = client.get("/unused").dispatch();
|
||||||
|
assert_eq!(response.status(), Status::Ok);
|
||||||
|
|
||||||
|
// Try again; should still be there.
|
||||||
|
let response = client.get("/unused").dispatch();
|
||||||
|
assert_eq!(response.status(), Status::Ok);
|
||||||
|
|
||||||
|
// Now use it.
|
||||||
|
let mut response = client.get("/use").dispatch();
|
||||||
|
assert_eq!(response.body_string(), Some(FLASH_MESSAGE.into()));
|
||||||
|
|
||||||
|
// Now it should be gone.
|
||||||
|
let response = client.get("/unused").dispatch();
|
||||||
|
assert_eq!(response.status(), Status::NotFound);
|
||||||
|
|
||||||
|
// Still gone.
|
||||||
|
let response = client.get("/use").dispatch();
|
||||||
|
assert_eq!(response.status(), Status::NotFound);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue