From 3fd065757f02ebf1d055912e6809fac15c8bf058 Mon Sep 17 00:00:00 2001
From: Martin Fischer <martin@push-f.com>
Date: Mon, 25 Jan 2021 22:38:06 +0100
Subject: reintroduce CsrfToken type

Raw HTML is potentially dangerous so it's better to provide an
encapsulating type instead of relying on naked strings.

bump version to 0.3.3
---
 src/request.rs | 36 +++++++++++++++++++++++++++---------
 1 file changed, 27 insertions(+), 9 deletions(-)

(limited to 'src')

diff --git a/src/request.rs b/src/request.rs
index 5b5679d..7734d03 100644
--- a/src/request.rs
+++ b/src/request.rs
@@ -23,11 +23,9 @@ pub trait SputnikParts {
     /// Enforces a specific Content-Type.
     fn enforce_content_type(&self, mime: Mime) -> Result<(), WrongContentTypeError>;
 
-    /// Retrievs the CSRF token from a cookie or generates
-    /// a new token and stores it as a cookie if it doesn't exist.
-    /// Returns a hidden HTML input to be embedded in forms that are received
-    /// with [`crate::request::SputnikBody::into_form_csrf`].
-    fn csrf_html_input(&mut self, builder: &mut Builder) -> String;
+    /// Returns a CSRF token, either extracted from the `csrf` cookie or newly
+    /// generated if the cookie wasn't sent (in which case the cookie is set).
+    fn csrf_token(&mut self, builder: &mut Builder) -> CsrfToken;
 }
 
 impl SputnikParts for hyper::http::request::Parts {
@@ -68,16 +66,36 @@ impl SputnikParts for hyper::http::request::Parts {
         Err(WrongContentTypeError{expected: mime, received: self.headers.get(header::CONTENT_TYPE).as_ref().and_then(|h| h.to_str().ok().map(|s| s.to_owned()))})
     }
 
-    fn csrf_html_input(&mut self, builder: &mut Builder) -> String {
+    fn csrf_token(&mut self, builder: &mut Builder) -> CsrfToken {
         let token = csrf_token_from_cookies(self).unwrap_or_else(|| {
-            let token: String = rand::thread_rng().sample_iter(Alphanumeric).take(16).collect();
+            let token: String = rand::thread_rng().sample_iter(
+                Alphanumeric
+                // must be HTML-safe because we embed it in CsrfToken::html_input
+            ).take(16).collect();
             let mut c = Cookie::new(CSRF_COOKIE_NAME, token.clone());
             c.set_secure(Some(true));
             c.set_max_age(Some(Duration::hours(1)));
             builder.set_cookie(c);
             token
         });
-        format!("<input name=csrf type=hidden value=\"{}\">", token)
+        CsrfToken(token)
+    }
+}
+
+/// A CSRF token retrievable with [`SputnikParts::csrf_token`].
+pub struct CsrfToken(String);
+
+impl std::fmt::Display for CsrfToken {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl CsrfToken {
+    /// Returns a hidden HTML input to be embedded in forms that are received
+    /// with [`crate::request::SputnikBody::into_form_csrf`].
+    pub fn html_input(&self) -> String {
+        format!("<input name=csrf type=hidden value=\"{}\">", self)
     }
 }
 
@@ -94,7 +112,7 @@ pub trait SputnikBody {
     /// Parses a `application/x-www-form-urlencoded` request body into a given struct.
     /// Protects from CSRF by checking that the request body contains the same token retrieved from the cookies.
     ///
-    /// The HTML form must embed a hidden input generated with [`crate::request::SputnikParts::csrf_html_input`].
+    /// The HTML form must embed a hidden input generated with [`CsrfToken::html_input`].
     async fn into_form_csrf<T: DeserializeOwned>(self, req: &mut Parts) -> Result<T, CsrfProtectedFormError>;
 
     /// Attempts to deserialize the request body as JSON.
-- 
cgit v1.2.3