diff --git a/src/client/builder.rs b/src/client/builder.rs index c42c3c1..9e1f144 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -20,12 +20,15 @@ pub struct ClientBuilder { } impl Default for ClientBuilder { + /// Same as [`ClientBuilder::new()`]. fn default() -> Self { Self::new() } } impl ClientBuilder { + /// Instantiate a builder with empty [`rustls::RootCertStore`], + /// no cert verifiers and default TLS versions. pub fn new() -> Self { ClientBuilder { root_certs: rustls::RootCertStore::empty(), @@ -35,6 +38,7 @@ impl ClientBuilder { } } + /// Construct a [`Client`] from the configuration. pub fn build(self) -> Client { let provider = rustls::crypto::CryptoProvider::get_default() .cloned() @@ -82,12 +86,17 @@ impl ClientBuilder { Client::from(tls_config) } + /// Include webpki trust anchors. + /// Not recommended (useless) as most Gemini capsules use self-signed + /// TLS certs and properly configured TOFU policy is enough. pub fn with_webpki_roots(mut self) -> Self { self.root_certs .extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); self } + /// Include custom trust anchors. + /// Not recommended (useless), see note for [`ClientBuilder::with_webpki_roots`]. pub fn with_custom_roots( mut self, iter: impl IntoIterator>, @@ -96,6 +105,10 @@ impl ClientBuilder { self } + /// Include a self-signed cert verifier. + /// If you only need a known_hosts file, consider using + /// [`crate::certs::file_sscv::FileBasedCertVerifier`], + /// it will do all the work for you. pub fn with_selfsigned_cert_verifier( mut self, ss_verifier: impl SelfsignedCertVerifier + 'static, @@ -104,6 +117,10 @@ impl ClientBuilder { self } + /// Include a custom TLS cert verifier implementing rustls' [`ServerCertVerifier`]. + /// Normally need to be used only for [`crate::certs::insecure::AllowAllCertVerifier`]. + /// Note: the webpki verifier and a self-signed cert verifier are not called + /// when a custom verifier is set. pub fn with_custom_verifier( mut self, custom_verifier: impl ServerCertVerifier + 'static, @@ -112,6 +129,8 @@ impl ClientBuilder { self } + /// Limit the supported TLS versions list to the specified ones. + /// Default is [`rustls::DEFAULT_VERSIONS`]. pub fn with_tls_versions( mut self, versions: &'static [&'static SupportedProtocolVersion], diff --git a/src/client/mod.rs b/src/client/mod.rs index 869fc5d..cc5c857 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,3 +1,5 @@ +//! Everything related to Gemini client except cert verification + pub mod builder; use crate::{error::*, response::Response, status::*}; @@ -21,6 +23,8 @@ pub struct Client { } impl Default for Client { + /// Create a Client with webpki_roots trust anchors and no client auth cert. + /// Will be possibly removed in next versions. fn default() -> Self { let roots = rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); @@ -32,6 +36,7 @@ impl Default for Client { } impl From for Client { + /// Create a Client from a Rustls config. #[inline] fn from(config: rustls::ClientConfig) -> Self { Client { @@ -41,18 +46,30 @@ impl From for Client { } impl Client { + /// Create a Client with a customized configuration, + /// see [`ClientBuilder`] methods. pub fn builder() -> ClientBuilder { ClientBuilder::new() } } impl Client { + /// Parse the given URL, do some checks, + /// take host and port (default is 1965) from the URL + /// and perform a Gemini request, returning [`Response`] on success. + /// + /// # Errors + /// Everything is converted into [`LibError`]. + /// - [`InvalidUrl::ParseError`] means that the given URL cannot be parsed. + /// - [`InvalidUrl::SchemeNotGemini`] is returned when the scheme is not `gemini://` + /// (for proxying use [`Client::request_with_host`]). + /// - [`InvalidUrl::UserinfoPresent`] is returned when the URL contains userinfo -- `user:password@` -- + /// which is forbidden by the Gemini specs. + /// - For the rest, see [`Client::request_with_host`]. pub async fn request(&self, url_str: &str) -> Result { let url = Url::parse(url_str).map_err(InvalidUrl::ParseError)?; - // for proxying http(s) through gemini server, - // use Client::request_with_host + // deny non-Gemini requests if url.scheme() != "gemini" { - // deny non-Gemini req return Err(InvalidUrl::SchemeNotGemini.into()); } // userinfo (user:pswd@) is not allowed in Gemini @@ -66,6 +83,21 @@ impl Client { self.request_with_host(url_str, host, port).await } + /// Perform a Gemini request with the specified host, port and URL. + /// Non-`gemini://` URLs is OK if the remote server supports proxying. + /// + /// # Errors + /// Everything is converted into [`LibError`]. + /// - [`InvalidUrl::ConvertError`] is an error while converting + /// host and port into [`std::net::SocketAddr`] or [`pki_types::ServerName`]. + /// - [`std::io::Error`] is returned in nearly all cases: + /// could not open a TCP connection, perform a TLS handshake, + /// write to or read from the TCP stream. + /// Check the inner error if you need to determine what exactly happened + /// - [`LibError::StatusOutOfRange`] means that a server returned an incorrect status code + /// (less than 10 or greater than 69). + /// - [`LibError::MessageNotUtf8`] is returned when metadata (the text after the status code) + /// is not in UTF-8 and cannot be converted to string without errors. pub async fn request_with_host( &self, url_str: &str, diff --git a/src/lib.rs b/src/lib.rs index e6f456e..6171cb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +//! Gemini client and server implementaion in async Rust with Tokio and Rustls. +//! You probably need [`Client::builder()`] + pub mod certs; pub mod client; pub mod error;