about summary refs log tree commit diff
path: root/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs270
1 files changed, 166 insertions, 104 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 2b4d1cc..2585227 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,87 +1,172 @@
-use tide::{Request, Response};
-
-/// Database abstraction layer for Kittybox, allowing the CMS to work with any kind of database.
-pub mod database;
+//use tide::{Request, Response};
+use warp::Filter;
+/*pub mod database;
 mod frontend;
 mod indieauth;
-mod metrics;
-mod micropub;
+mod micropub;*/
+pub mod metrics;
+/// Database abstraction layer for Kittybox, allowing the CMS to work with any kind of database.
+pub mod database;
+pub mod micropub;
+//pub mod indieauth;
 
-use crate::indieauth::IndieAuthMiddleware;
-use crate::micropub::CORSMiddleware;
+/*use crate::indieauth::IndieAuthMiddleware;
+use crate::micropub::CORSMiddleware;*/
 
-#[derive(Clone)]
-pub struct ApplicationState<StorageBackend>
-where
-    StorageBackend: database::Storage + Send + Sync + 'static,
-{
-    token_endpoint: surf::Url,
-    authorization_endpoint: surf::Url,
-    media_endpoint: Option<String>,
-    internal_token: Option<String>,
-    cookie_secret: String,
-    http_client: surf::Client,
-    storage: StorageBackend,
+pub mod rejections {
+    #[derive(Debug)]
+    pub struct UnacceptableContentType;
+    impl warp::reject::Reject for UnacceptableContentType {}
+
+    #[derive(Debug)]
+    pub struct HostHeaderUnset;
+    impl warp::reject::Reject for HostHeaderUnset {}
 }
 
-type App<Storage> = tide::Server<ApplicationState<Storage>>;
-
-static MICROPUB_CLIENT: &[u8] = include_bytes!("./index.html");
-
-fn equip_app<Storage>(mut app: App<Storage>) -> App<Storage>
-where
-    Storage: database::Storage + Send + Sync + Clone,
-{
-    app.at("/micropub")
-        .with(CORSMiddleware {})
-        .with(IndieAuthMiddleware::new())
-        .get(micropub::get_handler)
-        .post(micropub::post_handler);
-    // The Micropub client. It'll start small, but could grow into something full-featured
-    app.at("/micropub/client").get(|_: Request<_>| async move {
-        Ok(Response::builder(200)
-            .body(MICROPUB_CLIENT)
-            .content_type("text/html")
-            .build())
-    });
-    app.at("/")
-        .with(CORSMiddleware {})
-        .with(frontend::ErrorHandlerMiddleware {})
-        .get(frontend::mainpage)
-        .post(frontend::onboarding_receiver);
-    app.at("/login")
-        .with(frontend::ErrorHandlerMiddleware {})
-        .get(frontend::login::form)
-        .post(frontend::login::handler);
-    app.at("/login/callback")
-        .with(frontend::ErrorHandlerMiddleware {})
-        .get(frontend::login::callback);
-    app.at("/static/*path")
-        .with(frontend::ErrorHandlerMiddleware {})
-        .get(frontend::handle_static);
-    app.at("/*path")
-        .with(frontend::ErrorHandlerMiddleware {})
-        .get(frontend::render_post);
-    app.at("/coffee")
-        .with(frontend::ErrorHandlerMiddleware {})
-        .get(frontend::coffee);
-    // TODO make sure the health check actually checks the backend or something
-    // otherwise it'll get false-negatives for application faults like resource
-    // exhaustion
-    app.at("/health").get(|_| async { Ok("OK") });
-    app.at("/metrics").get(metrics::gather);
-
-    app.with(metrics::InstrumentationMiddleware {});
-    app.with(
-        tide::sessions::SessionMiddleware::new(
-            tide::sessions::CookieStore::new(),
-            app.state().cookie_secret.as_bytes(),
-        )
-        .with_cookie_name("kittybox_session")
-        .without_save_unchanged(),
-    );
-    app
+pub static MICROPUB_CLIENT: &[u8] = include_bytes!("./index.html");
+
+pub mod util {
+    use warp::{Filter, host::Authority};
+    use super::rejections;
+
+    pub fn require_host() -> impl Filter<Extract = (Authority,), Error = warp::Rejection> + Copy {
+        warp::host::optional()
+            .and_then(|authority: Option<Authority>| async move {
+                authority.ok_or_else(|| warp::reject::custom(rejections::HostHeaderUnset))
+            })
+    }
+
+    pub fn template<R>(
+        template: R
+    ) -> impl warp::Reply
+    where
+        R: markup::Render + std::fmt::Display
+    {
+        warp::reply::html(template.to_string())
+    }
+
+    pub fn parse_accept() -> impl Filter<Extract = (http_types::Mime,), Error = warp::Rejection> + Copy {
+        warp::header::value("Accept").and_then(|accept: warp::http::HeaderValue| async move {
+            let mut accept: http_types::content::Accept = {
+                // This is unneccesarily complicated because I want to reuse some http-types parsing
+                // and http-types has constructor for Headers private so I need to construct
+                // a mock Request to reason about headers... this is so dumb wtf
+                let bytes: &[u8] = accept.as_bytes();
+                let value = http_types::headers::HeaderValue::from_bytes(bytes.to_vec()).unwrap();
+                let values: http_types::headers::HeaderValues = vec![value].into();
+                let mut request = http_types::Request::new(http_types::Method::Get, "http://example.com/");
+                request.append_header("Accept".parse::<http_types::headers::HeaderName>().unwrap(), &values);
+                http_types::content::Accept::from_headers(&request).unwrap().unwrap()
+            };
+
+            // This code is INCREDIBLY dumb, honestly...
+            // why did I even try to use it?
+            // TODO vendor this stuff in so I can customize it
+            match accept.negotiate(&[
+                "text/html; encoding=\"utf-8\"".into(),
+                "application/json; encoding=\"utf-8\"".into(),
+                "text/html".into(),
+                "application/json".into(),
+
+            ]) {
+                Ok(mime) => {
+                    Ok(http_types::Mime::from(mime.value().as_str()))
+                },
+                Err(err) => {
+                    log::error!("Content-Type negotiation error: {:?}, accepting: {:?}", err, accept);
+                    Err(warp::reject::custom(rejections::UnacceptableContentType))
+                }
+            }
+        })
+    }
+
+    mod tests {
+        #[tokio::test]
+        async fn test_require_host_with_host() {
+            use super::require_host;
+
+            let filter = require_host();
+
+            let res = warp::test::request()
+                .path("/")
+                .header("Host", "localhost:8080")
+                .filter(&filter)
+                .await
+                .unwrap();
+
+            assert_eq!(res, "localhost:8080");
+            
+        }
+
+        #[tokio::test]
+        async fn test_require_host_no_host() {
+            use super::require_host;
+
+            let filter = require_host();
+
+            let res = warp::test::request()
+                .path("/")
+                .filter(&filter)
+                .await;
+
+            assert!(res.is_err());
+        }
+    }
 }
+// fn equip_app<Storage>(mut app: App<Storage>) -> App<Storage>
+// where
+//     Storage: database::Storage + Send + Sync + Clone,
+// {
+//     app.at("/micropub")
+//         .with(CORSMiddleware {})
+//         .with(IndieAuthMiddleware::new())
+//         .get(micropub::get_handler)
+//         .post(micropub::post_handler);
+//     // The Micropub client. It'll start small, but could grow into something full-featured
+//     app.at("/micropub/client").get(|_: Request<_>| async move {
+//         Ok(Response::builder(200)
+//             .body(MICROPUB_CLIENT)
+//             .content_type("text/html")
+//             .build())
+//     });
+//     app.at("/")
+//         .with(CORSMiddleware {})
+//         .with(frontend::ErrorHandlerMiddleware {})
+//         .get(frontend::mainpage)
+//         .post(frontend::onboarding_receiver);
+//     app.at("/login")
+//         .with(frontend::ErrorHandlerMiddleware {})
+//         .get(frontend::login::form)
+//         .post(frontend::login::handler);
+//     app.at("/login/callback")
+//         .with(frontend::ErrorHandlerMiddleware {})
+//         .get(frontend::login::callback);
+//     app.at("/static/*path")
+//         .with(frontend::ErrorHandlerMiddleware {})
+//         .get(frontend::handle_static);
+//     app.at("/*path")
+//         .with(frontend::ErrorHandlerMiddleware {})
+//         .get(frontend::render_post);
+//     app.at("/coffee")
+//         .with(frontend::ErrorHandlerMiddleware {})
+//         .get(frontend::coffee);
+//     // TODO make sure the health check actually checks the backend or something
+//     // otherwise it'll get false-negatives for application faults like resource
+//     // exhaustion
+//     app.at("/health").get(|_| async { Ok("OK") });
+//     app.at("/metrics").get(metrics::gather);
+
+//     app.with(metrics::InstrumentationMiddleware {});
+//     app.with(
+//         tide::sessions::SessionMiddleware::new(
+//             tide::sessions::CookieStore::new(),
+//             app.state().cookie_secret.as_bytes(),
+//         )
+//         .with_cookie_name("kittybox_session")
+//         .without_save_unchanged(),
+//     );
+//     app
+// }
 
 /*#[cfg(feature="redis")]
 pub async fn get_app_with_redis(
@@ -103,30 +188,7 @@ pub async fn get_app_with_redis(
     equip_app(app)
 }*/
 
-pub async fn get_app_with_file(
-    token_endpoint: surf::Url,
-    authorization_endpoint: surf::Url,
-    backend_uri: String,
-    media_endpoint: Option<String>,
-    cookie_secret: String,
-    internal_token: Option<String>,
-) -> App<database::FileStorage> {
-    let folder = backend_uri.strip_prefix("file://").unwrap();
-    let path = std::path::PathBuf::from(folder);
-    let app = tide::with_state(ApplicationState {
-        token_endpoint,
-        media_endpoint,
-        authorization_endpoint,
-        internal_token,
-        cookie_secret,
-        storage: database::FileStorage::new(path).await.unwrap(),
-        http_client: surf::Client::new(),
-    });
-
-    equip_app(app)
-}
-
-#[cfg(test)]
+/*#[cfg(test)]
 pub async fn get_app_with_test_file(
     token_endpoint: surf::Url,
 ) -> (
@@ -151,7 +213,7 @@ pub async fn get_app_with_test_file(
     (tempdir, backend, equip_app(app))
 }
 
-/*#[cfg(all(redis, test))]
+#[cfg(all(redis, test))]
 pub async fn get_app_with_test_redis(
     token_endpoint: surf::Url,
 ) -> (
@@ -176,7 +238,7 @@ pub async fn get_app_with_test_redis(
     (redis_instance, backend, equip_app(app))
 }*/
 
-#[cfg(test)]
+/*#[cfg(test)]
 #[allow(unused_variables)]
 mod tests {
     use super::*;
@@ -459,4 +521,4 @@ mod tests {
         assert_eq!(new_feed["children"][0].as_str().unwrap(), uid);
         assert_eq!(new_feed["children"][1].as_str().unwrap(), first_uid);
     }
-}
+}*/