2016-09-12 01:57:04 +00:00
|
|
|
use std::convert::AsRef;
|
2016-10-04 00:09:13 +00:00
|
|
|
|
2017-03-07 09:19:06 +00:00
|
|
|
use time::Duration;
|
2017-01-27 07:08:15 +00:00
|
|
|
|
2016-10-25 09:17:49 +00:00
|
|
|
use outcome::IntoOutcome;
|
2016-12-15 08:47:31 +00:00
|
|
|
use response::{Response, Responder};
|
2016-10-25 09:17:49 +00:00
|
|
|
use request::{self, Request, FromRequest};
|
2017-01-27 07:08:15 +00:00
|
|
|
use http::{Status, Cookie};
|
2016-09-12 01:57:04 +00:00
|
|
|
|
2016-11-03 14:09:01 +00:00
|
|
|
// The name of the actual flash cookie.
|
2016-09-22 11:12:07 +00:00
|
|
|
const FLASH_COOKIE_NAME: &'static str = "_flash";
|
|
|
|
|
2016-11-03 14:09:01 +00:00
|
|
|
/// Sets a "flash" cookie that will be removed when it is accessed. The
|
|
|
|
/// anologous request type is
|
2016-11-02 17:48:43 +00:00
|
|
|
/// [FlashMessage](/rocket/request/type.FlashMessage.html).
|
|
|
|
///
|
|
|
|
/// This type makes it easy to send messages across requests. It is typically
|
|
|
|
/// used for "status" messages after redirects. For instance, if a user attempts
|
|
|
|
/// to visit a page he/she does not have access to, you may want to redirect the
|
|
|
|
/// user to a safe place and show a message indicating what happened on the
|
|
|
|
/// redirected page. The message should only persist for a single request. This
|
|
|
|
/// can be accomplished with this type.
|
|
|
|
///
|
|
|
|
/// # Usage
|
|
|
|
///
|
|
|
|
/// Each `Flash` message consists of a `name` and some `msg` contents. A generic
|
|
|
|
/// constructor ([new](#method.new)) can be used to construct a message with any
|
|
|
|
/// name, while the [warning](#method.warning), [success](#method.success), and
|
|
|
|
/// [error](#method.error) constructors create messages with the corresponding
|
|
|
|
/// names.
|
|
|
|
///
|
|
|
|
/// Messages can be retrieved on the request side via the
|
|
|
|
/// [FlashMessage](/rocket/request/type.FlashMessage.html) type and the
|
|
|
|
/// [name](#method.name) and [msg](#method.msg) methods.
|
|
|
|
///
|
2016-11-03 14:09:01 +00:00
|
|
|
/// # Response
|
2016-11-02 17:48:43 +00:00
|
|
|
///
|
|
|
|
/// The `Responder` implementation for `Flash` sets the message cookie and then
|
|
|
|
/// uses the passed in responder `res` to complete the response. In other words,
|
|
|
|
/// it simply sets a cookie and delagates the rest of the response handling to
|
|
|
|
/// the wrapped responder.
|
2016-11-03 14:09:01 +00:00
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// The following complete Rocket application illustrates the use of a `Flash`
|
|
|
|
/// message on both the request and response sides.
|
|
|
|
///
|
|
|
|
/// ```rust
|
2017-08-29 03:14:59 +00:00
|
|
|
/// # #![feature(plugin, decl_macro)]
|
2016-11-03 14:09:01 +00:00
|
|
|
/// # #![plugin(rocket_codegen)]
|
|
|
|
/// #
|
|
|
|
/// # extern crate rocket;
|
|
|
|
/// #
|
|
|
|
/// use rocket::response::{Flash, Redirect};
|
|
|
|
/// use rocket::request::FlashMessage;
|
2017-03-31 07:18:58 +00:00
|
|
|
/// use rocket::http::RawStr;
|
2016-11-03 14:09:01 +00:00
|
|
|
///
|
|
|
|
/// #[post("/login/<name>")]
|
2017-03-31 07:18:58 +00:00
|
|
|
/// fn login(name: &RawStr) -> Result<&'static str, Flash<Redirect>> {
|
2016-11-03 14:09:01 +00:00
|
|
|
/// if name == "special_user" {
|
|
|
|
/// Ok("Hello, special user!")
|
|
|
|
/// } else {
|
|
|
|
/// Err(Flash::error(Redirect::to("/"), "Invalid username."))
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// #[get("/")]
|
|
|
|
/// fn index(flash: Option<FlashMessage>) -> String {
|
|
|
|
/// flash.map(|msg| format!("{}: {}", msg.name(), msg.msg()))
|
|
|
|
/// .unwrap_or_else(|| "Welcome!".to_string())
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// fn main() {
|
|
|
|
/// # if false { // We don't actually want to launch the server in an example.
|
2017-03-16 05:10:09 +00:00
|
|
|
/// rocket::ignite().mount("/", routes![login, index]).launch();
|
2016-11-03 14:09:01 +00:00
|
|
|
/// # }
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// On the response side (in `login`), a `Flash` error message is set if some
|
|
|
|
/// fictional authentication failed, and the user is redirected to `"/"`. On the
|
|
|
|
/// request side (in `index`), the handler emits the flash message if there is
|
|
|
|
/// one and otherwise emits a standard welcome message. Note that if the user
|
|
|
|
/// were to refresh the index page after viewing a flash message, the user would
|
|
|
|
/// receive the standard welcome message.
|
|
|
|
#[derive(Debug)]
|
2016-09-12 01:57:04 +00:00
|
|
|
pub struct Flash<R> {
|
|
|
|
name: String,
|
|
|
|
message: String,
|
2016-09-30 22:20:11 +00:00
|
|
|
responder: R,
|
2016-09-12 01:57:04 +00:00
|
|
|
}
|
|
|
|
|
2016-12-15 08:47:31 +00:00
|
|
|
impl<'r, R: Responder<'r>> Flash<R> {
|
2016-11-02 17:48:43 +00:00
|
|
|
/// Constructs a new `Flash` message with the given `name`, `msg`, and
|
|
|
|
/// underlying `responder`.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// Construct a "suggestion" message with contents "Try this out!" that
|
|
|
|
/// redirects to "/".
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// use rocket::response::{Redirect, Flash};
|
|
|
|
///
|
2017-02-02 10:16:21 +00:00
|
|
|
/// # #[allow(unused_variables)]
|
2016-11-02 17:48:43 +00:00
|
|
|
/// let msg = Flash::new(Redirect::to("/"), "suggestion", "Try this out!");
|
|
|
|
/// ```
|
2016-09-12 01:57:04 +00:00
|
|
|
pub fn new<N: AsRef<str>, M: AsRef<str>>(res: R, name: N, msg: M) -> Flash<R> {
|
|
|
|
Flash {
|
|
|
|
name: name.as_ref().to_string(),
|
|
|
|
message: msg.as_ref().to_string(),
|
|
|
|
responder: res,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-02 17:48:43 +00:00
|
|
|
/// Constructs a "success" `Flash` message with the given `responder` and
|
|
|
|
/// `msg`.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// Construct a "success" message with contents "It worked!" that redirects
|
|
|
|
/// to "/".
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// use rocket::response::{Redirect, Flash};
|
|
|
|
///
|
2017-02-02 10:16:21 +00:00
|
|
|
/// # #[allow(unused_variables)]
|
2016-11-02 17:48:43 +00:00
|
|
|
/// let msg = Flash::success(Redirect::to("/"), "It worked!");
|
|
|
|
/// ```
|
2016-09-12 01:57:04 +00:00
|
|
|
pub fn success<S: AsRef<str>>(responder: R, msg: S) -> Flash<R> {
|
|
|
|
Flash::new(responder, "success", msg)
|
|
|
|
}
|
|
|
|
|
2016-11-02 17:48:43 +00:00
|
|
|
/// Constructs a "warning" `Flash` message with the given `responder` and
|
|
|
|
/// `msg`.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// Construct a "warning" message with contents "Watch out!" that redirects
|
|
|
|
/// to "/".
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// use rocket::response::{Redirect, Flash};
|
|
|
|
///
|
2017-02-02 10:16:21 +00:00
|
|
|
/// # #[allow(unused_variables)]
|
2016-11-02 17:48:43 +00:00
|
|
|
/// let msg = Flash::warning(Redirect::to("/"), "Watch out!");
|
|
|
|
/// ```
|
|
|
|
pub fn warning<S: AsRef<str>>(responder: R, msg: S) -> Flash<R> {
|
|
|
|
Flash::new(responder, "warning", msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Constructs an "error" `Flash` message with the given `responder` and
|
|
|
|
/// `msg`.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// Construct an "error" message with contents "Whoops!" that redirects
|
|
|
|
/// to "/".
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// use rocket::response::{Redirect, Flash};
|
|
|
|
///
|
2017-02-02 10:16:21 +00:00
|
|
|
/// # #[allow(unused_variables)]
|
2016-11-02 17:48:43 +00:00
|
|
|
/// let msg = Flash::error(Redirect::to("/"), "Whoops!");
|
|
|
|
/// ```
|
2016-09-12 01:57:04 +00:00
|
|
|
pub fn error<S: AsRef<str>>(responder: R, msg: S) -> Flash<R> {
|
|
|
|
Flash::new(responder, "error", msg)
|
|
|
|
}
|
|
|
|
|
2017-01-27 07:08:15 +00:00
|
|
|
fn cookie(&self) -> Cookie<'static> {
|
2016-09-12 01:57:04 +00:00
|
|
|
let content = format!("{}{}{}", self.name.len(), self.name, self.message);
|
2017-01-27 07:08:15 +00:00
|
|
|
Cookie::build(FLASH_COOKIE_NAME, content)
|
|
|
|
.max_age(Duration::minutes(5))
|
|
|
|
.path("/")
|
|
|
|
.finish()
|
2016-09-12 01:57:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-03 14:09:01 +00:00
|
|
|
/// Sets the message cookie and then uses the wrapped responder to complete the
|
|
|
|
/// response. In other words, simply sets a cookie and delagates the rest of the
|
|
|
|
/// response handling to the wrapped responder. As a result, the `Outcome` of
|
|
|
|
/// the response is the `Outcome` of the wrapped `Responder`.
|
2016-12-15 08:47:31 +00:00
|
|
|
impl<'r, R: Responder<'r>> Responder<'r> for Flash<R> {
|
2017-05-19 10:29:08 +00:00
|
|
|
fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
|
2016-09-12 01:57:04 +00:00
|
|
|
trace_!("Flash: setting message: {}:{}", self.name, self.message);
|
2017-01-27 07:08:15 +00:00
|
|
|
let cookie = self.cookie();
|
2017-05-19 10:29:08 +00:00
|
|
|
Response::build_from(self.responder.respond_to(req)?)
|
2017-03-07 09:19:06 +00:00
|
|
|
.header_adjoin(&cookie)
|
2016-12-15 08:47:31 +00:00
|
|
|
.ok()
|
2016-09-12 01:57:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Flash<()> {
|
2016-11-03 14:09:01 +00:00
|
|
|
/// Constructs a new message with the given name and message.
|
2016-09-12 01:57:04 +00:00
|
|
|
fn named(name: &str, msg: &str) -> Flash<()> {
|
|
|
|
Flash {
|
|
|
|
name: name.to_string(),
|
|
|
|
message: msg.to_string(),
|
|
|
|
responder: (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-02 17:48:43 +00:00
|
|
|
/// Returns the `name` of this message.
|
2016-09-12 01:57:04 +00:00
|
|
|
pub fn name(&self) -> &str {
|
|
|
|
self.name.as_str()
|
|
|
|
}
|
|
|
|
|
2016-11-02 17:48:43 +00:00
|
|
|
/// Returns the `msg` contents of this message.
|
2016-09-12 01:57:04 +00:00
|
|
|
pub fn msg(&self) -> &str {
|
|
|
|
self.message.as_str()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-03 14:09:01 +00:00
|
|
|
/// Retrieves a flash message from a flash cookie and deletes the flash cookie.
|
|
|
|
/// If there is no flash cookie, an empty `Err` is returned.
|
|
|
|
///
|
|
|
|
/// The suggested use is through an `Option` and the `FlashMessage` type alias
|
|
|
|
/// in `request`: `Option<FlashMessage>`.
|
2016-12-16 11:07:23 +00:00
|
|
|
impl<'a, 'r> FromRequest<'a, 'r> for Flash<()> {
|
2016-09-12 01:57:04 +00:00
|
|
|
type Error = ();
|
|
|
|
|
2016-12-16 11:07:23 +00:00
|
|
|
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
2016-09-12 01:57:04 +00:00
|
|
|
trace_!("Flash: attemping to retrieve message.");
|
2017-03-07 09:19:06 +00:00
|
|
|
let r = request.cookies().get(FLASH_COOKIE_NAME).ok_or(()).and_then(|cookie| {
|
2016-09-12 01:57:04 +00:00
|
|
|
trace_!("Flash: retrieving message: {:?}", cookie);
|
|
|
|
|
2017-02-09 07:53:29 +00:00
|
|
|
// Parse the flash message.
|
2017-01-27 07:08:15 +00:00
|
|
|
let content = cookie.value();
|
2016-09-12 01:57:04 +00:00
|
|
|
let (len_str, rest) = match content.find(|c: char| !c.is_digit(10)) {
|
|
|
|
Some(i) => (&content[..i], &content[i..]),
|
2016-09-30 22:20:11 +00:00
|
|
|
None => (content, ""),
|
2016-09-12 01:57:04 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let name_len: usize = len_str.parse().map_err(|_| ())?;
|
|
|
|
let (name, msg) = (&rest[..name_len], &rest[name_len..]);
|
|
|
|
Ok(Flash::named(name, msg))
|
2016-10-14 01:39:23 +00:00
|
|
|
});
|
|
|
|
|
2017-03-08 11:28:12 +00:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2017-04-19 04:52:02 +00:00
|
|
|
r.into_outcome(Status::BadRequest)
|
2016-09-12 01:57:04 +00:00
|
|
|
}
|
|
|
|
}
|