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}