aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFuwn <[email protected]>2022-03-26 00:46:03 +0000
committerFuwn <[email protected]>2022-03-26 03:12:32 +0000
commitfdfc6c4123ecdb8d8e173a4661a0bcf7d7220a33 (patch)
tree447c5f99a973d9e69e5b3b490acad8c37e07b0c7 /src
parentdocs(cargo): add meta (diff)
downloadwindmark-fdfc6c4123ecdb8d8e173a4661a0bcf7d7220a33.tar.xz
windmark-fdfc6c4123ecdb8d8e173a4661a0bcf7d7220a33.zip
feat: working proof
Forgot to commit this last night!
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs391
-rw-r--r--src/response.rs27
-rw-r--r--src/status.rs39
3 files changed, 451 insertions, 6 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 1b4a90c..de5693d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,8 +1,387 @@
-#[cfg(test)]
-mod tests {
- #[test]
- fn it_works() {
- let result = 2 + 2;
- assert_eq!(result, 4);
+// This file is part of Windmark <https://github.com/gemrest/windmark>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#![deny(
+ warnings,
+ nonstandard_style,
+ unused,
+ future_incompatible,
+ rust_2018_idioms,
+ unsafe_code
+)]
+#![deny(clippy::all, clippy::nursery, clippy::pedantic)]
+#![recursion_limit = "128"]
+
+pub mod response;
+pub mod status;
+
+#[cfg(feature = "logger")]
+#[macro_use]
+extern crate log;
+
+use std::{collections::HashMap, net::TcpStream, sync::Arc};
+
+use openssl::ssl::{self, SslAcceptor, SslMethod};
+use url::Url;
+
+#[derive(Clone)]
+pub struct Router {
+ routes: HashMap<String, fn(&TcpStream) -> String>,
+ error_handler: fn(&TcpStream) -> String,
+ private_key_file_name: String,
+ certificate_chain_file_name: String,
+ header: fn(&TcpStream) -> String,
+ footer: fn(&TcpStream) -> String,
+ ssl_acceptor: Arc<SslAcceptor>,
+ default_logger: bool,
+ pre_route_callback: fn(&TcpStream),
+ post_route_callback: fn(&TcpStream),
+}
+impl Router {
+ /// Create a new `Router`
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// let _router = windmark::Router::new();
+ /// ```
+ ///
+ /// # Panics
+ ///
+ /// if a default `SslAcceptor` could not be built.
+ #[must_use]
+ pub fn new() -> Self { Self::default() }
+
+ /// Set the filename of the private key file.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// windmark::Router::new().set_private_key_file("windmark_private.pem");
+ /// ```
+ pub fn set_private_key_file(
+ &mut self,
+ private_key_file_name: &str,
+ ) -> &mut Self {
+ self.private_key_file_name = private_key_file_name.to_string();
+
+ self
+ }
+
+ /// Set the filename of the certificate chain file.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// windmark::Router::new().set_certificate_chain_file("windmark_pair.pem");
+ /// ```
+ pub fn set_certificate_chain_file(
+ &mut self,
+ certificate_chain_file_name: &str,
+ ) -> &mut Self {
+ self.certificate_chain_file_name = certificate_chain_file_name.to_string();
+
+ self
+ }
+
+ /// Map routes to URL paths
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// windmark::Router::new()
+ /// .get("/", |_| "This is the index page!".into())
+ /// .get("/test", |_| "This is a test page!".into());
+ /// ```
+ pub fn get(
+ &mut self,
+ route: &str,
+ handler: fn(&TcpStream) -> String,
+ ) -> &mut Self {
+ self.routes.insert(route.to_string(), handler);
+
+ self
+ }
+
+ /// Create an error handler which will be displayed on any error.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// windmark::Router::new()
+ /// .set_error_handler(|_| "You have encountered an error!".into());
+ /// ```
+ pub fn set_error_handler(
+ &mut self,
+ handler: fn(&TcpStream) -> String,
+ ) -> &mut Self {
+ self.error_handler = handler;
+
+ self
+ }
+
+ /// Set a header for the `Router` which should be displayed on every route.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// windmark::Router::new().set_header(|_| {
+ /// "This will be displayed on every route! (at the top)".into()
+ /// });
+ /// ```
+ pub fn set_header(&mut self, handler: fn(&TcpStream) -> String) -> &mut Self {
+ self.header = handler;
+
+ self
+ }
+
+ /// Set a footer for the `Router` which should be displayed on every route.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// windmark::Router::new().set_footer(|_| {
+ /// "This will be displayed on every route! (at the bottom)".into()
+ /// });
+ /// ```
+ pub fn set_footer(&mut self, handler: fn(&TcpStream) -> String) -> &mut Self {
+ self.footer = handler;
+
+ self
+ }
+
+ /// Run the `Router` and wait for requests
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// windmark::Router::new().run();
+ /// ```
+ ///
+ /// # Panics
+ ///
+ /// if the client could not be accepted.
+ ///
+ /// # Errors
+ ///
+ /// if the `TcpListener` could not be bound.
+ pub fn run(&mut self) -> std::io::Result<()> {
+ self.create_acceptor();
+
+ #[cfg(feature = "logger")]
+ if self.default_logger {
+ pretty_env_logger::init();
+ }
+
+ let acceptor = self.ssl_acceptor.clone();
+ let listener = std::net::TcpListener::bind("0.0.0.0:1965")?;
+
+ #[cfg(feature = "logger")]
+ info!("windmark is listening for connections");
+
+ for stream in listener.incoming() {
+ match stream {
+ Ok(stream) => {
+ let acceptor = acceptor.clone();
+ let self_clone = self.clone();
+
+ std::thread::spawn(move || {
+ let mut stream = acceptor.accept(stream).unwrap();
+
+ self_clone.handle(&mut stream);
+ });
+ }
+ Err(e) => eprintln!("tcp error: {:?}", e),
+ }
+ }
+
+ Ok(())
+ }
+
+ fn handle(&self, stream: &mut ssl::SslStream<std::net::TcpStream>) {
+ let mut buffer = [0u8; 1024];
+ let mut url = Url::parse("gemini://fuwn.me/").unwrap();
+
+ while let Ok(size) = stream.ssl_read(&mut buffer) {
+ let content = String::from_utf8(buffer[0..size].to_vec()).unwrap();
+
+ url = url::Url::parse(&content.replace("\r\n", "")).unwrap();
+
+ if content.contains("\r\n") {
+ break;
+ }
+ }
+
+ (self.pre_route_callback)(stream.get_ref());
+
+ stream
+ .ssl_write(
+ format!(
+ "20 text/gemini; charset=utf-8\r\n{}{}{}",
+ {
+ let header = (self.header)(stream.get_ref());
+
+ if header.is_empty() {
+ "".to_string()
+ } else {
+ format!("{}\n", header)
+ }
+ },
+ self.routes.get(url.path()).unwrap_or(&self.error_handler)(
+ stream.get_ref()
+ ),
+ {
+ let footer = (self.footer)(stream.get_ref());
+
+ if footer.is_empty() {
+ "".to_string()
+ } else {
+ format!("\n{}", footer)
+ }
+ },
+ )
+ .as_bytes(),
+ )
+ .unwrap();
+
+ (self.post_route_callback)(stream.get_ref());
+
+ stream.shutdown().unwrap();
+ }
+
+ fn create_acceptor(&mut self) {
+ let mut builder =
+ SslAcceptor::mozilla_intermediate(ssl::SslMethod::tls()).unwrap();
+
+ builder
+ .set_private_key_file(&self.private_key_file_name, ssl::SslFiletype::PEM)
+ .unwrap();
+ builder
+ .set_certificate_chain_file(&self.certificate_chain_file_name)
+ .unwrap();
+ builder.check_private_key().unwrap();
+
+ self.ssl_acceptor = Arc::new(builder.build());
+ }
+
+ /// Use a self-made `SslAcceptor`
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use openssl::ssl;
+ ///
+ /// windmark::Router::new().set_ssl_acceptor({
+ /// let mut builder =
+ /// ssl::SslAcceptor::mozilla_intermediate(ssl::SslMethod::tls()).unwrap();
+ ///
+ /// builder
+ /// .set_private_key_file("windmark_private.pem", ssl::SslFiletype::PEM)
+ /// .unwrap();
+ /// builder
+ /// .set_certificate_chain_file("windmark_pair.pem")
+ /// .unwrap();
+ /// builder.check_private_key().unwrap();
+ ///
+ /// builder.build()
+ /// });
+ /// ```
+ pub fn set_ssl_acceptor(&mut self, ssl_acceptor: SslAcceptor) -> &mut Self {
+ self.ssl_acceptor = Arc::new(ssl_acceptor);
+
+ self
+ }
+
+ /// Enabled the default logger (the
+ /// [`pretty_env_logger`](https://crates.io/crates/pretty_env_logger) and
+ /// [`log`](https://crates.io/crates/log) crates).
+ pub fn enable_default_logger(&mut self, enable: bool) -> &mut Self {
+ self.default_logger = enable;
+ std::env::set_var("RUST_LOG", "trace");
+
+ self
+ }
+
+ /// Set a callback to run before a client response is delivered
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use log::info;
+ ///
+ /// windmark::Router::new().set_pre_route_callback(|stream| {
+ /// info!(
+ /// "accepted connection from {}",
+ /// stream.peer_addr().unwrap().ip(),
+ /// )
+ /// });
+ /// ```
+ pub fn set_pre_route_callback(
+ &mut self,
+ callback: fn(&TcpStream),
+ ) -> &mut Self {
+ self.pre_route_callback = callback;
+
+ self
+ }
+
+ /// Set a callback to run after a client response is delivered
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use log::info;
+ ///
+ /// windmark::Router::new().set_post_route_callback(|stream| {
+ /// info!(
+ /// "closed connection from {}",
+ /// stream.peer_addr().unwrap().ip(),
+ /// )
+ /// });
+ /// ```
+ pub fn set_post_route_callback(
+ &mut self,
+ callback: fn(&TcpStream),
+ ) -> &mut Self {
+ self.post_route_callback = callback;
+
+ self
+ }
+}
+impl Default for Router {
+ fn default() -> Self {
+ Self {
+ routes: HashMap::default(),
+ error_handler: |_| {
+ "This capsule has not implemented an error handler...".to_string()
+ },
+ private_key_file_name: "".to_string(),
+ certificate_chain_file_name: "".to_string(),
+ header: |_| "".to_string(),
+ footer: |_| "".to_string(),
+ ssl_acceptor: Arc::new(
+ SslAcceptor::mozilla_intermediate(SslMethod::tls())
+ .unwrap()
+ .build(),
+ ),
+ default_logger: false,
+ pre_route_callback: |_| {},
+ post_route_callback: |_| {},
}
+ }
}
diff --git a/src/response.rs b/src/response.rs
new file mode 100644
index 0000000..ccec4bb
--- /dev/null
+++ b/src/response.rs
@@ -0,0 +1,27 @@
+// This file is part of Windmark <https://github.com/gemrest/windmark>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+pub struct Header {
+ status: crate::status::Code,
+ meta: String,
+}
+impl ToString for Header {
+ fn to_string(&self) -> String {
+ format!("{} {}\r\n", self.status as u8, self.meta)
+ }
+}
diff --git a/src/status.rs b/src/status.rs
new file mode 100644
index 0000000..0ba4c5b
--- /dev/null
+++ b/src/status.rs
@@ -0,0 +1,39 @@
+// This file is part of Windmark <https://github.com/gemrest/windmark>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#[derive(Copy, Clone)]
+pub enum Code {
+ Input = 10,
+ SensitiveInput = 11,
+ Success = 20,
+ TemporaryRedirect = 30,
+ PermanentRedirect = 31,
+ TemporaryFailure = 40,
+ ServerUnavailable = 41,
+ CGIError = 42,
+ ProxyError = 43,
+ SlowDown = 44,
+ PermanentFailure = 50,
+ NotFound = 51,
+ Gone = 52,
+ ProxyRefused = 53,
+ BadRequest = 59,
+ ClientCertificateRequired = 60,
+ CertificateNotAuthorised = 61,
+ CertificateNotValid = 62,
+}