rocket/response/
flash.rs

1use time::Duration;
2use serde::ser::{Serialize, Serializer, SerializeStruct};
3
4use crate::outcome::IntoOutcome;
5use crate::response::{self, Responder};
6use crate::request::{self, Request, FromRequest};
7use crate::http::{Status, Cookie, CookieJar};
8use std::sync::atomic::{AtomicBool, Ordering};
9
10// The name of the actual flash cookie.
11const FLASH_COOKIE_NAME: &str = "_flash";
12
13// Character to use as a delimiter after the cookie's name's length.
14const FLASH_COOKIE_DELIM: char = ':';
15
16/// Sets a "flash" cookie that will be removed when it is accessed. The
17/// analogous request type is [`FlashMessage`].
18///
19/// This type makes it easy to send messages across requests. It is typically
20/// used for "status" messages after redirects. For instance, if a user attempts
21/// to visit a page he/she does not have access to, you may want to redirect the
22/// user to a safe place and show a message indicating what happened on the
23/// redirected page. The message should only persist for a single request. This
24/// can be accomplished with this type.
25///
26/// # Usage
27///
28/// Each `Flash` message consists of a `kind` and `message`. A generic
29/// constructor ([new](#method.new)) can be used to construct a message of any
30/// kind, while the [warning](#method.warning), [success](#method.success), and
31/// [error](#method.error) constructors create messages with the corresponding
32/// kinds.
33///
34/// Messages can be retrieved on the request side via the [`FlashMessage`] type
35/// and the [kind](#method.kind) and [message](#method.message) methods.
36///
37/// # Response
38///
39/// The `Responder` implementation for `Flash` sets the message cookie and then
40/// uses the passed in responder `res` to complete the response. In other words,
41/// it simply sets a cookie and delegates the rest of the response handling to
42/// the wrapped responder.
43///
44/// # Example
45///
46/// The following routes illustrate the use of a `Flash` message on both the
47/// request and response sides.
48///
49/// ```rust
50/// # #[macro_use] extern crate rocket;
51/// use rocket::response::{Flash, Redirect};
52/// use rocket::request::FlashMessage;
53///
54/// #[post("/login/<name>")]
55/// fn login(name: &str) -> Result<&'static str, Flash<Redirect>> {
56///     if name == "special_user" {
57///         Ok("Hello, special user!")
58///     } else {
59///         Err(Flash::error(Redirect::to(uri!(index)), "Invalid username."))
60///     }
61/// }
62///
63/// #[get("/")]
64/// fn index(flash: Option<FlashMessage<'_>>) -> String {
65///     flash.map(|flash| format!("{}: {}", flash.kind(), flash.message()))
66///          .unwrap_or_else(|| "Welcome!".to_string())
67/// }
68/// ```
69///
70/// On the response side (in `login`), a `Flash` error message is set if some
71/// fictional authentication failed, and the user is redirected to `"/"`. On the
72/// request side (in `index`), the handler emits the flash message if there is
73/// one and otherwise emits a standard welcome message. Note that if the user
74/// were to refresh the index page after viewing a flash message, the user would
75/// receive the standard welcome message.
76#[derive(Debug)]
77pub struct Flash<R> {
78    kind: String,
79    message: String,
80    consumed: AtomicBool,
81    inner: R,
82}
83
84/// Type alias to retrieve [`Flash`] messages from a request.
85///
86/// # Flash Cookie
87///
88/// A `FlashMessage` holds the parsed contents of the flash cookie. As long as
89/// there is a flash cookie present (set by the `Flash` `Responder`), a
90/// `FlashMessage` request guard will succeed.
91///
92/// The flash cookie is cleared if either the [`kind()`] or [`message()`] method is
93/// called. If neither method is called, the flash cookie is not cleared.
94///
95/// [`kind()`]: Flash::kind()
96/// [`message()`]: Flash::message()
97pub type FlashMessage<'a> = crate::response::Flash<&'a CookieJar<'a>>;
98
99impl<R> Flash<R> {
100    /// Constructs a new `Flash` message with the given `kind`, `message`, and
101    /// underlying `responder`.
102    ///
103    /// # Examples
104    ///
105    /// Construct a "suggestion" message with contents "Try this out!" that
106    /// redirects to "/".
107    ///
108    /// ```rust
109    /// use rocket::response::{Redirect, Flash};
110    ///
111    /// # #[allow(unused_variables)]
112    /// let message = Flash::new(Redirect::to("/"), "suggestion", "Try this out!");
113    /// ```
114    pub fn new<K: Into<String>, M: Into<String>>(res: R, kind: K, message: M) -> Flash<R> {
115        Flash {
116            kind: kind.into(),
117            message: message.into(),
118            consumed: AtomicBool::default(),
119            inner: res,
120        }
121    }
122
123    /// Constructs a "success" `Flash` message with the given `responder` and
124    /// `message`.
125    ///
126    /// # Examples
127    ///
128    /// Construct a "success" message with contents "It worked!" that redirects
129    /// to "/".
130    ///
131    /// ```rust
132    /// use rocket::response::{Redirect, Flash};
133    ///
134    /// # #[allow(unused_variables)]
135    /// let message = Flash::success(Redirect::to("/"), "It worked!");
136    /// ```
137    pub fn success<S: Into<String>>(responder: R, message: S) -> Flash<R> {
138        Flash::new(responder, "success", message.into())
139    }
140
141    /// Constructs a "warning" `Flash` message with the given `responder` and
142    /// `message`.
143    ///
144    /// # Examples
145    ///
146    /// Construct a "warning" message with contents "Watch out!" that redirects
147    /// to "/".
148    ///
149    /// ```rust
150    /// use rocket::response::{Redirect, Flash};
151    ///
152    /// # #[allow(unused_variables)]
153    /// let message = Flash::warning(Redirect::to("/"), "Watch out!");
154    /// ```
155    pub fn warning<S: Into<String>>(responder: R, message: S) -> Flash<R> {
156        Flash::new(responder, "warning", message.into())
157    }
158
159    /// Constructs an "error" `Flash` message with the given `responder` and
160    /// `message`.
161    ///
162    /// # Examples
163    ///
164    /// Construct an "error" message with contents "Whoops!" that redirects
165    /// to "/".
166    ///
167    /// ```rust
168    /// use rocket::response::{Redirect, Flash};
169    ///
170    /// # #[allow(unused_variables)]
171    /// let message = Flash::error(Redirect::to("/"), "Whoops!");
172    /// ```
173    pub fn error<S: Into<String>>(responder: R, message: S) -> Flash<R> {
174        Flash::new(responder, "error", message.into())
175    }
176
177    fn cookie(&self) -> Cookie<'static> {
178        let content = format!("{}{}{}{}",
179            self.kind.len(), FLASH_COOKIE_DELIM, self.kind, self.message);
180
181        Cookie::build((FLASH_COOKIE_NAME, content))
182            .max_age(Duration::minutes(5))
183            .build()
184    }
185}
186
187/// Sets the message cookie and then uses the wrapped responder to complete the
188/// response. In other words, simply sets a cookie and delegates the rest of the
189/// response handling to the wrapped responder. As a result, the `Outcome` of
190/// the response is the `Outcome` of the wrapped `Responder`.
191impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Flash<R> {
192    fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
193        req.cookies().add(self.cookie());
194        self.inner.respond_to(req)
195    }
196}
197
198impl<'r> FlashMessage<'r> {
199    /// Constructs a new message with the given name and message for the given
200    /// request.
201    fn named<S: Into<String>>(kind: S, message: S, req: &'r Request<'_>) -> Self {
202        Flash {
203            kind: kind.into(),
204            message: message.into(),
205            consumed: AtomicBool::new(false),
206            inner: req.cookies(),
207        }
208    }
209
210    // Clears the request cookie if it hasn't already been cleared.
211    fn clear_cookie_if_needed(&self) {
212        // Remove the cookie if it hasn't already been removed.
213        if !self.consumed.swap(true, Ordering::Relaxed) {
214            self.inner.remove(FLASH_COOKIE_NAME);
215        }
216    }
217
218    /// Returns a tuple of `(kind, message)`, consuming `self`.
219    pub fn into_inner(self) -> (String, String) {
220        self.clear_cookie_if_needed();
221        (self.kind, self.message)
222    }
223
224    /// Returns the `kind` of this message.
225    pub fn kind(&self) -> &str {
226        self.clear_cookie_if_needed();
227        &self.kind
228    }
229
230    /// Returns the `message` contents of this message.
231    pub fn message(&self) -> &str {
232        self.clear_cookie_if_needed();
233        &self.message
234    }
235}
236
237/// Retrieves a flash message from a flash cookie. If there is no flash cookie,
238/// or if the flash cookie is malformed, an empty `Err` is returned.
239///
240/// The suggested use is through an `Option` and the `FlashMessage` type alias
241/// in `request`: `Option<FlashMessage>`.
242#[crate::async_trait]
243impl<'r> FromRequest<'r> for FlashMessage<'r> {
244    type Error = ();
245
246    async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
247        trace_!("Flash: attempting to retrieve message.");
248        req.cookies().get(FLASH_COOKIE_NAME).ok_or(()).and_then(|cookie| {
249            trace_!("Flash: retrieving message: {:?}", cookie);
250
251            // Parse the flash message.
252            let content = cookie.value();
253            let (len_str, kv) = match content.find(FLASH_COOKIE_DELIM) {
254                Some(i) => (&content[..i], &content[(i + 1)..]),
255                None => return Err(()),
256            };
257
258            match len_str.parse::<usize>() {
259                Ok(i) if i <= kv.len() => Ok(Flash::named(&kv[..i], &kv[i..], req)),
260                _ => Err(())
261            }
262        }).or_error(Status::BadRequest)
263    }
264}
265
266impl Serialize for FlashMessage<'_> {
267    fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
268        let mut flash = ser.serialize_struct("Flash", 2)?;
269        flash.serialize_field("kind", self.kind())?;
270        flash.serialize_field("message", self.message())?;
271        flash.end()
272    }
273}