rocket_community/response/
flash.rs

1use serde::ser::{Serialize, SerializeStruct, Serializer};
2use time::Duration;
3
4use crate::http::{Cookie, CookieJar, Status};
5use crate::outcome::IntoOutcome;
6use crate::request::{self, FromRequest, Request};
7use crate::response::{self, Responder};
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_community as 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    /// # extern crate rocket_community as rocket;
110    /// use rocket::response::{Redirect, Flash};
111    ///
112    /// # #[allow(unused_variables)]
113    /// let message = Flash::new(Redirect::to("/"), "suggestion", "Try this out!");
114    /// ```
115    pub fn new<K: Into<String>, M: Into<String>>(res: R, kind: K, message: M) -> Flash<R> {
116        Flash {
117            kind: kind.into(),
118            message: message.into(),
119            consumed: AtomicBool::default(),
120            inner: res,
121        }
122    }
123
124    /// Constructs a "success" `Flash` message with the given `responder` and
125    /// `message`.
126    ///
127    /// # Examples
128    ///
129    /// Construct a "success" message with contents "It worked!" that redirects
130    /// to "/".
131    ///
132    /// ```rust
133    /// # extern crate rocket_community as rocket;
134    /// use rocket::response::{Redirect, Flash};
135    ///
136    /// # #[allow(unused_variables)]
137    /// let message = Flash::success(Redirect::to("/"), "It worked!");
138    /// ```
139    pub fn success<S: Into<String>>(responder: R, message: S) -> Flash<R> {
140        Flash::new(responder, "success", message.into())
141    }
142
143    /// Constructs a "warning" `Flash` message with the given `responder` and
144    /// `message`.
145    ///
146    /// # Examples
147    ///
148    /// Construct a "warning" message with contents "Watch out!" that redirects
149    /// to "/".
150    ///
151    /// ```rust
152    /// # extern crate rocket_community as rocket;
153    /// use rocket::response::{Redirect, Flash};
154    ///
155    /// # #[allow(unused_variables)]
156    /// let message = Flash::warning(Redirect::to("/"), "Watch out!");
157    /// ```
158    pub fn warning<S: Into<String>>(responder: R, message: S) -> Flash<R> {
159        Flash::new(responder, "warning", message.into())
160    }
161
162    /// Constructs an "error" `Flash` message with the given `responder` and
163    /// `message`.
164    ///
165    /// # Examples
166    ///
167    /// Construct an "error" message with contents "Whoops!" that redirects
168    /// to "/".
169    ///
170    /// ```rust
171    /// # extern crate rocket_community as rocket;
172    /// use rocket::response::{Redirect, Flash};
173    ///
174    /// # #[allow(unused_variables)]
175    /// let message = Flash::error(Redirect::to("/"), "Whoops!");
176    /// ```
177    pub fn error<S: Into<String>>(responder: R, message: S) -> Flash<R> {
178        Flash::new(responder, "error", message.into())
179    }
180
181    fn cookie(&self) -> Cookie<'static> {
182        let content = format!(
183            "{}{}{}{}",
184            self.kind.len(),
185            FLASH_COOKIE_DELIM,
186            self.kind,
187            self.message
188        );
189
190        Cookie::build((FLASH_COOKIE_NAME, content))
191            .max_age(Duration::minutes(5))
192            .build()
193    }
194}
195
196/// Sets the message cookie and then uses the wrapped responder to complete the
197/// response. In other words, simply sets a cookie and delegates the rest of the
198/// response handling to the wrapped responder. As a result, the `Outcome` of
199/// the response is the `Outcome` of the wrapped `Responder`.
200impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Flash<R> {
201    fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
202        req.cookies().add(self.cookie());
203        self.inner.respond_to(req)
204    }
205}
206
207impl<'r> FlashMessage<'r> {
208    /// Constructs a new message with the given name and message for the given
209    /// request.
210    fn named<S: Into<String>>(kind: S, message: S, req: &'r Request<'_>) -> Self {
211        Flash {
212            kind: kind.into(),
213            message: message.into(),
214            consumed: AtomicBool::new(false),
215            inner: req.cookies(),
216        }
217    }
218
219    // Clears the request cookie if it hasn't already been cleared.
220    fn clear_cookie_if_needed(&self) {
221        // Remove the cookie if it hasn't already been removed.
222        if !self.consumed.swap(true, Ordering::Relaxed) {
223            self.inner.remove(FLASH_COOKIE_NAME);
224        }
225    }
226
227    /// Returns a tuple of `(kind, message)`, consuming `self`.
228    pub fn into_inner(self) -> (String, String) {
229        self.clear_cookie_if_needed();
230        (self.kind, self.message)
231    }
232
233    /// Returns the `kind` of this message.
234    pub fn kind(&self) -> &str {
235        self.clear_cookie_if_needed();
236        &self.kind
237    }
238
239    /// Returns the `message` contents of this message.
240    pub fn message(&self) -> &str {
241        self.clear_cookie_if_needed();
242        &self.message
243    }
244}
245
246/// Retrieves a flash message from a flash cookie. If there is no flash cookie,
247/// or if the flash cookie is malformed, an empty `Err` is returned.
248///
249/// The suggested use is through an `Option` and the `FlashMessage` type alias
250/// in `request`: `Option<FlashMessage>`.
251#[crate::async_trait]
252impl<'r> FromRequest<'r> for FlashMessage<'r> {
253    type Error = ();
254
255    async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
256        req.cookies()
257            .get(FLASH_COOKIE_NAME)
258            .ok_or(())
259            .and_then(|cookie| {
260                // Parse the flash message.
261                let content = cookie.value();
262                let (len_str, kv) = match content.find(FLASH_COOKIE_DELIM) {
263                    Some(i) => (&content[..i], &content[(i + 1)..]),
264                    None => return Err(()),
265                };
266
267                match len_str.parse::<usize>() {
268                    Ok(i) if i <= kv.len() => Ok(Flash::named(&kv[..i], &kv[i..], req)),
269                    _ => Err(()),
270                }
271            })
272            .or_error(Status::BadRequest)
273    }
274}
275
276impl Serialize for FlashMessage<'_> {
277    fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
278        let mut flash = ser.serialize_struct("Flash", 2)?;
279        flash.serialize_field("kind", self.kind())?;
280        flash.serialize_field("message", self.message())?;
281        flash.end()
282    }
283}