diff --git a/src/handling.rs b/src/handling.rs index 13128ac..f1e2639 100644 --- a/src/handling.rs +++ b/src/handling.rs @@ -30,9 +30,15 @@ use crate::{Document, types::{Body, Response, Request}}; /// Each implementation has bespoke docs that describe how the type is used, and what /// response is produced. pub enum Handler { + + /// A handler that responds to a request by delegating to an [`Fn`] FnHandler(HandlerInner), + + /// A handler that always serves an identical response, for any and all request StaticHandler(Response), + #[cfg(feature = "serve_dir")] + /// A handler that serves a directory, including a directory listing FilesHandler(PathBuf), } diff --git a/src/lib.rs b/src/lib.rs index e12ab6c..c347670 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![warn(missing_docs)] //! Kochab is an ergonomic and intuitive library for quickly building highly functional //! and advanced Gemini applications on either SCGI or raw Gemini. //! @@ -228,7 +229,7 @@ use tokio_rustls::TlsAcceptor; #[cfg(feature = "gemini_srv")] use rustls::Session; -pub mod types; +mod types; pub mod util; pub mod routing; pub mod handling; @@ -247,7 +248,10 @@ pub use cert::CertGenMode; pub use uriparse as uri; pub use types::*; +/// The maximun length of a Request URI pub const REQUEST_URI_MAX_LEN: usize = 1024; + +/// The default port for the gemini protocol pub const GEMINI_PORT: u16 = 1965; use handling::Handler; diff --git a/src/routing.rs b/src/routing.rs index 69ff34e..7b12870 100644 --- a/src/routing.rs +++ b/src/routing.rs @@ -206,6 +206,11 @@ impl Default for RoutingNode { } #[derive(Debug, Clone, Copy)] +/// An error returned when attempting to register a route that already exists +/// +/// Routes will not be overridden if this error is returned. Routes are never overwritten +/// +/// See [`RoutingNode::add_route_by_path()`] pub struct ConflictingRouteError(); impl std::error::Error for ConflictingRouteError { } diff --git a/src/types/body.rs b/src/types/body.rs index d9aeca3..ef418a0 100644 --- a/src/types/body.rs +++ b/src/types/body.rs @@ -8,8 +8,19 @@ use std::borrow::Borrow; use crate::types::Document; +/// The body of a response +/// +/// The content of a successful response to be sent back to the user. This can be either +/// some bytes which will be sent directly to the user, or a reader which will be read at +/// some point before sending to the user. pub enum Body { + /// In-memory bytes that may be sent back to the user Bytes(Vec), + + /// A reader which will be streamed to the user + /// + /// If a reader blocks for too long, it MAY be killed before finishing, which results + /// in the user receiving a malformed response or timing out. Reader(Box), } diff --git a/src/types/request.rs b/src/types/request.rs index 7739b1a..fb9367a 100644 --- a/src/types/request.rs +++ b/src/types/request.rs @@ -19,6 +19,20 @@ use ring::digest; use crate::user_management::{UserManager, User}; #[derive(Clone)] +/// A request from a Gemini client to the app +/// +/// When originally sent out by a client, a request is literally just a URL, and honestly, +/// if you want to use it as just a URL, that'll work fine! +/// +/// That said, kochab and any proxies the request might hit add a little bit more +/// information that you can use, like +/// * [What TLS certificate (if any) did the client use](Self::certificate) +/// * [What part of the path is relevant (ie, everything after the route)](Self::trailing_segments) +/// * [Is the user registered with the user database?](Self::user) +/// +/// The only way to get your hands on one of these bad boys is when you register an [`Fn`] +/// based handler to a [`Server`](crate::Server), and a user makes a request to the +/// endpoint. pub struct Request { uri: URIReference<'static>, input: Option, @@ -33,7 +47,34 @@ pub struct Request { } impl Request { - pub fn new( + /// Construct a new request + /// + /// When in `gemini_srv` mode, this is done using a URL. If you do construct a + /// request this way, by default it will not have a certificate attached, so make + /// sure you add in a certificate with [`Request::set_cert()`]. + /// + /// By contrast, in `scgi_srv` mode, the certificate fingerprint is grabbed out of the + /// request parameters, so you don't need to do anything. The headers passed should + /// be the header sent by the SCGI client. + /// + /// When in SCGI mode, the following headers are expected: + /// + /// * `PATH_INFO`: The part of the path following the route the app is bound to + /// * `QUERY_STRING`: The part of the request following ?, url encoded. Will produce + /// an error if it contains invalid UTF-8. No error if missing + /// * `TLS_CLIENT_HASH`: Optional. The base64 or hex encoded SHA256 sum of the DER + /// certificate of the requester. + /// * `SCRIPT_PATH` or `SCRIPT_NAME`: The base path the app is mounted on + /// + /// # Errors + /// + /// Produces an error if: + /// * The SCGI server didn't include the mandatory `PATH_INFO` header + /// * The provided URI reference is invalid, including if the SCGI server sent an + /// invalid `PATH_INFO` + /// * The `TLS_CLIENT_HASH` sent by the SCGI server isn't sha256, or is encoded with + /// something other than base64 or hexadecimal + pub (crate) fn new( #[cfg(feature = "gemini_srv")] mut uri: URIReference<'static>, #[cfg(feature = "scgi_srv")] @@ -112,6 +153,19 @@ impl Request { }) } + /// The URI reference requested by the user + /// + /// Although they are not exactly the same thing, it is generally preferred to use the + /// [`Request::trailing_segments()`] method if possible. + /// + /// Returns the URIReference requested by the user. **If running in SCGI mode, this + /// will contain only the parts of the URIReference that were relevant to the app.** + /// This means you will get `/path`, not `/app/path`. + /// + /// When running in `scgi_srv` mode, this is guaranteed to be a relative reference. + /// When running in `gemini_srv` mode, clients are obliged by the spec to send a full + /// URI, but if a client fails to respect this, kochab will still accept and pass on + /// the relative reference. pub const fn uri(&self) -> &URIReference { &self.uri } @@ -123,11 +177,6 @@ impl Request { /// received to `/api/v1/endpoint`, then this value would be `["v1", "endpoint"]`. /// This should not be confused with [`path_segments()`](Self::path_segments()), which /// contains *all* of the segments, not just those trailing the route. - /// - /// If the trailing segments have not been set, this method will panic, but this - /// should only be possible if you are constructing the Request yourself. Requests - /// to handlers registered through [`add_route()`](crate::Server::add_route()) will - /// always have trailing segments set. pub fn trailing_segments(&self) -> &Vec { self.trailing_segments.as_ref().unwrap() } @@ -167,6 +216,10 @@ impl Request { /// the request otherwise. Bear in mind that **not all SCGI clients send the same /// headers**, and these are *never* available when operating in `gemini_srv` mode. /// + /// By using this method, you are almost certainly reducing the number of proxy + /// servers your app supports, and you are strongly encouraged to find a different + /// method. + /// /// Some examples of headers mollybrown sets are: /// - `REMOTE_ADDR` (The user's IP address and port) /// - `TLS_CLIENT_SUBJECT_CN` (The CommonName on the user's certificate, when present) @@ -187,7 +240,8 @@ impl Request { }); } - pub fn set_trailing(&mut self, segments: Vec) { + /// Sets the segments returned by [`Request::trailing_segments()`] + pub (crate) fn set_trailing(&mut self, segments: Vec) { self.trailing_segments = Some(segments); } diff --git a/src/types/response.rs b/src/types/response.rs index ca0f372..dadfa79 100644 --- a/src/types/response.rs +++ b/src/types/response.rs @@ -2,9 +2,48 @@ use std::borrow::Borrow; use crate::types::{Body, Document}; +/// A response to a client's [`Request`] +/// +/// Requests in Gemini are pretty simple. They consist of three parts: +/// +/// * A two status code, similar to the status codes in HTML. You don't need to know +/// anything about these, since this part of the response will be filled in for you +/// depending on the associated function you use to create the Response +/// * A meta, a <1024 byte string whose meaning depends on the status +/// * A body, but only for successful requests +/// +/// Responses will be identical in both `scgi_srv` mode and `gemini_srv` mode. +/// +/// [`Request`]: crate::Request pub struct Response { + /// The status code of the request. A value between 10 and 62 + /// + /// Each block of 10 status codes (e.g. 10-19) has a specific meaning or category, + /// defined in depth in the gemini documentation. Generally: + /// + /// * 1X is input + /// * 20 is success + /// * 3X is redirect + /// * >= 40 is an error pub status: u8, + + /// The meta associated with this request + /// + /// Because the meaning of the meta field depends on the status, please consult the + /// status code before interpreting this value. The function signature of the method + /// used to create the response should also provide more detail about what the field + /// is. In general, the meaning of the meta for a status code is + /// + /// * If the status code is 20, the meta is the mime type of the body + /// * If the status code is 3X, the meta is a URL to redirect to + /// * If the status code is 44, the meta is a time in seconds until ratelimiting ends + /// * If the status code is anything els, the meta is a message or prompt for the user pub meta: String, + + /// The body of this request + /// + /// This never needs to be present, and **cannot** be present if the status code != + /// 20. pub body: Option, } diff --git a/src/util.rs b/src/util.rs index d3166dd..79d6cfa 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,8 @@ +//! Utilities for serving a file or directory +//! +//! ⚠️ Docs still under construction & API not yet stable ⚠️ +#![allow(missing_docs)] + #[cfg(feature="serve_dir")] use std::path::{Path, PathBuf}; use anyhow::*;