1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
//! Actix web flash is an unofficial crate to provide flash messages in servers using Actix web.
//!
//! Flash messages are typically used to display errors on websites that are rendered server side.
//!
//! A user might post a login form with a password. The server notices the password is incorrect.
//! It has to respond with an error. A common approach is to redirect the client to the same form.
//! The error is displayed by being rendered into the html markup on the server side.
//!
//! The data is relayed to the next request via a cookie. This means its not suitable for large data!
//! Currently `actix-web-flash` does not implement any cryptographic checks of the cookie's
//! validity. Treat it as untrusted input!
//!
//! You can find example code below and in the
//! [repository](https://github.com/hatzel/actix-web-flash/tree/master/examples).
//!
//! ## Trivial Example
//! ```no_run
//! use actix_web::{http, server, App, HttpRequest, HttpResponse, Responder};
//! use actix_web_flash::{FlashMessage, FlashResponse, FlashMiddleware};
//!
//! fn show_flash(flash: FlashMessage<String>) -> impl Responder {
//!     flash.into_inner()
//! }
//!
//! fn set_flash(_req: &HttpRequest) -> FlashResponse<HttpResponse, String> {
//!     FlashResponse::new(
//!         Some("This is the message".to_owned()),
//!         HttpResponse::SeeOther()
//!             .header(http::header::LOCATION, "/show_flash")
//!             .finish(),
//!     )
//! }
//!
//! fn main() {
//!     server::new(|| {
//!         App::new()
//!             .middleware(FlashMiddleware::default())
//!             .route("/show_flash", http::Method::GET, show_flash)
//!             .resource("/set_flash", |r| r.f(set_flash))
//!     }).bind("127.0.0.1:8080")
//!         .unwrap()
//!         .run();
//! }
//! ```
//! The example above will redirect the user from `/set_flash` to `/show_flash` and pass along
//! a string, rendering it right away.
//!
//! ## Arbitrary Types
//! Arbitrary types can be used as flash messages.
//!
//! ```
//! use actix_web::Responder;
//! use actix_web_flash::FlashMessage;
//! use serde_derive::{Serialize, Deserialize};
//!
//! #[derive(Deserialize, Serialize, Debug)]
//! struct MyData {
//!     msg: String,
//!     color: (u8, u8, u8),
//! }
//!
//! fn show_flash(flash: FlashMessage<MyData>) -> impl Responder {
//!     format!("Message {:?}", flash.into_inner())
//! }
//! ```
//!
//! ## Optional Messages
//! It is possible to take an `Option<FlashMessage<T>>` thereby allowing for your route to also be
//! called without a flash message having been returned in the previous request.
//!
//! ```
//! use actix_web::Responder;
//! use actix_web_flash::FlashMessage;
//! use serde_derive::Deserialize;
//!
//!
//! fn show_flash(flash: Option<FlashMessage<String>>) -> impl Responder {
//!     match flash {
//!         Some(msg) => format!("There was some error: {}", msg.into_inner()),
//!         None => "All is good!".to_owned()
//!     }
//! }
//! ```
//!
//! ## Limitations and Pitfalls
//!
//! Only a single message is supported. It can however be of any type (that implements `Deserialize`), meaning a `Vec<YourType>`
//! is possible.
//!
//! The cookie will not be cleared unless the [middleware](actix_web_flash::FlashMiddleware) is registered.
//! Meaning an error message will persist unless replaced with a newer one.
use actix_web::{Error, FromRequest, HttpRequest, HttpResponse, Responder};
use actix_web::http::Cookie;
use actix_web::error::ErrorBadRequest;
use actix_web::middleware::{Middleware, Response};
use serde::Serialize;
use serde::de::DeserializeOwned;
use serde_derive;
use serde_json;
use time;
use futures::future::Future;

#[cfg(test)]
mod tests;

pub(crate) const FLASH_COOKIE_NAME: &str = "_flash";

/// Represents a flash message and implements `actix::FromRequest`
///
/// It is used to retrieve the currently set flash message.
#[derive(Debug)]
pub struct FlashMessage<T>(T)
where
    T: DeserializeOwned;

impl<S, T> FromRequest<S> for FlashMessage<T>
where
    T: DeserializeOwned,
{
    type Config = ();
    type Result = Result<FlashMessage<T>, Error>;

    fn from_request(req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {
        if let Some(cookie) = req.cookie(FLASH_COOKIE_NAME) {
            let inner = serde_json::from_str(cookie.value())
                .map_err(|_| ErrorBadRequest("Invalid flash cookie"))?;
            Ok(FlashMessage(inner))
        } else {
            Err(ErrorBadRequest("No flash cookie"))
        }
    }
}

impl<M> FlashMessage<M>
where
    M: Serialize + DeserializeOwned,
{
    pub fn new(inner: M) -> Self {
        FlashMessage(inner)
    }

    pub fn into_inner(self) -> M {
        self.0
    }
}

/// Actix response type that sets a flash message
pub struct FlashResponse<R, M>
where
    R: Responder,
    M: Serialize + DeserializeOwned,
{
    delegate_to: R,
    message: Option<FlashMessage<M>>,
}

impl<R, M> Responder for FlashResponse<R, M>
where
    R: Responder,
    M: Serialize + DeserializeOwned,
{
    type Item = HttpResponse;
    type Error = Error;

    fn respond_to<S: 'static>(self, req: &HttpRequest<S>) -> Result<Self::Item, Self::Error> {
        let response = self.delegate_to
            .respond_to(req)
            .map(|v| v.into())
            .map_err(|v| v.into())?;
        if let Some(msg) = self.message {
            let data = serde_json::to_string(&msg.into_inner())?;
            let flash_cookie = Cookie::new(FLASH_COOKIE_NAME, data);
            let response_future = response.then(|res| -> Result<_, Error> {
                res.and_then(|mut req| {
                    req.add_cookie(&flash_cookie)
                        .map_err(|e| e.into())
                        .map(|_| req)
                })
            });
            response_future.wait()
        } else {
            response.wait()
        }
    }
}

impl<R, M> FlashResponse<R, M>
where
    R: Responder,
    M: Serialize + DeserializeOwned,
{
    ///
    /// Constructs a new `FlashResponse` with a desired message and response.
    ///
    /// The message is saved in a cookie and can be extracted on the next request.
    /// In a typical use-case for the response would be a redirect to a page that will display the
    /// message.
    ///
    /// ```
    /// # use actix_web_flash::{FlashMessage, FlashResponse};
    /// #
    /// FlashResponse::new(
    ///     Some("Some error occurred".to_owned()),
    ///     actix_web::HttpResponse::Ok()
    ///         .header(actix_web::http::header::LOCATION, "/render_error")
    ///         .finish(),
    /// );
    /// ```
    pub fn new(message: Option<M>, response: R) -> Self {
        Self {
            delegate_to: response,
            message: message.map(|m| FlashMessage(m)),
        }
    }
}

#[derive(Debug, Default)]
/// Takes care of deleting the flash cookies after their use.
///
/// Without this middle ware any flash message is be passed into all handlers requesting it, until
/// the cookie is overwritten by a new message.
/// ```no_run
/// # use actix_web_flash::{FlashMiddleware};
/// # use actix_web::{App, server};
/// server::new(|| {
///     App::new()
///         .middleware(FlashMiddleware::default())
/// }).bind("127.0.0.1:8080")
///     .unwrap()
///     .run();
/// ```
pub struct FlashMiddleware();

impl<S> Middleware<S> for FlashMiddleware {
    fn response(
        &self,
        req: &HttpRequest<S>,
        mut resp: HttpResponse,
    ) -> Result<Response, actix_web::Error> {
        let received_flash = req.cookie(FLASH_COOKIE_NAME);
        if received_flash.is_some()
            && resp.cookies()
                .find(|ref c| c.name() == FLASH_COOKIE_NAME)
                .is_none()
        {
            // Delete cookie by setting an expiry date in the past
            let mut expired = Cookie::new(FLASH_COOKIE_NAME, "");
            let time = time::strptime("1970-1-1", "%Y-%m-%d").unwrap();
            expired.set_expires(time);
            resp.add_cookie(&expired)?;
        }
        Ok(Response::Done(resp))
    }
}