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}