use std::convert::AsRef; use time::Duration; use crate::outcome::IntoOutcome; use crate::response::{Responder, ResultFuture}; use crate::request::{self, Request, FromRequest}; use crate::http::{Status, Cookie}; use std::sync::atomic::{AtomicBool, Ordering}; // The name of the actual flash cookie. const FLASH_COOKIE_NAME: &str = "_flash"; // Character to use as a delimiter after the cookie's name's length. const FLASH_COOKIE_DELIM: char = ':'; /// Sets a "flash" cookie that will be removed when it is accessed. The /// analogous request type is [`FlashMessage`]. /// /// 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`] type /// and the [name](#method.name) and [msg](#method.msg) methods. /// /// # Response /// /// 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 delegates the rest of the response handling to /// the wrapped responder. /// /// # Example /// /// The following complete Rocket application illustrates the use of a `Flash` /// message on both the request and response sides. /// /// ```rust /// # #![feature(proc_macro_hygiene)] /// # #[macro_use] extern crate rocket; /// use rocket::response::{Flash, Redirect}; /// use rocket::request::FlashMessage; /// use rocket::http::RawStr; /// /// #[post("/login/")] /// fn login(name: &RawStr) -> Result<&'static str, Flash> { /// if name == "special_user" { /// Ok("Hello, special user!") /// } else { /// Err(Flash::error(Redirect::to("/"), "Invalid username.")) /// } /// } /// /// #[get("/")] /// fn index(flash: Option) -> 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. /// rocket::ignite().mount("/", routes![login, index]).launch(); /// # } /// } /// ``` /// /// 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)] pub struct Flash { name: String, message: String, consumed: AtomicBool, inner: R, } /// Type alias to retrieve [`Flash`] 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` request 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()`]: Flash::name() /// [`msg()`]: Flash::msg() pub type FlashMessage<'a, 'r> = crate::response::Flash<&'a Request<'r>>; impl<'r, R: Responder<'r>> Flash { /// 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}; /// /// # #[allow(unused_variables)] /// let msg = Flash::new(Redirect::to("/"), "suggestion", "Try this out!"); /// ``` pub fn new, M: AsRef>(res: R, name: N, msg: M) -> Flash { Flash { name: name.as_ref().to_string(), message: msg.as_ref().to_string(), consumed: AtomicBool::default(), inner: res, } } /// 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}; /// /// # #[allow(unused_variables)] /// let msg = Flash::success(Redirect::to("/"), "It worked!"); /// ``` pub fn success>(responder: R, msg: S) -> Flash { Flash::new(responder, "success", msg) } /// 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}; /// /// # #[allow(unused_variables)] /// let msg = Flash::warning(Redirect::to("/"), "Watch out!"); /// ``` pub fn warning>(responder: R, msg: S) -> Flash { 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}; /// /// # #[allow(unused_variables)] /// let msg = Flash::error(Redirect::to("/"), "Whoops!"); /// ``` pub fn error>(responder: R, msg: S) -> Flash { Flash::new(responder, "error", msg) } fn cookie(&self) -> Cookie<'static> { let content = format!("{}{}{}{}", self.name.len(), FLASH_COOKIE_DELIM, self.name, self.message); Cookie::build(FLASH_COOKIE_NAME, content) .max_age(Duration::minutes(5)) .path("/") .finish() } } /// Sets the message cookie and then uses the wrapped responder to complete the /// response. In other words, simply sets a cookie and delegates 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`. impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Flash { fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> { trace_!("Flash: setting message: {}:{}", self.name, self.message); req.cookies().add(self.cookie()); self.inner.respond_to(req) } } impl<'a, 'r> Flash<&'a Request<'r>> { /// Constructs a new message with the given name and message for the given /// request. fn named(name: &str, msg: &str, req: &'a Request<'r>) -> Flash<&'a Request<'r>> { Flash { name: name.to_string(), message: msg.to_string(), consumed: AtomicBool::new(false), inner: req, } } // 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. pub fn name(&self) -> &str { self.clear_cookie_if_needed(); &self.name } /// Returns the `msg` contents of this message. pub fn msg(&self) -> &str { self.clear_cookie_if_needed(); &self.message } } /// Retrieves a flash message from a flash cookie. If there is no flash cookie, /// or if the flash cookie is malformed, an empty `Err` is returned. /// /// The suggested use is through an `Option` and the `FlashMessage` type alias /// in `request`: `Option`. impl<'a, 'r> FromRequest<'a, 'r> for Flash<&'a Request<'r>> { type Error = (); fn from_request(req: &'a Request<'r>) -> request::Outcome { trace_!("Flash: attempting to retrieve message."); req.cookies().get(FLASH_COOKIE_NAME).ok_or(()).and_then(|cookie| { trace_!("Flash: retrieving message: {:?}", cookie); // Parse the flash message. let content = cookie.value(); let (len_str, kv) = match content.find(FLASH_COOKIE_DELIM) { Some(i) => (&content[..i], &content[(i + 1)..]), None => return Err(()), }; match len_str.parse::() { Ok(i) if i <= kv.len() => Ok(Flash::named(&kv[..i], &kv[i..], req)), _ => Err(()) } }).into_outcome(Status::BadRequest) } }