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, } impl Response { /// Create a response with a given status and meta pub fn new(status: u8, meta: impl ToString) -> Self { Self { status, meta: meta.to_string(), body: None, } } /// Create a INPUT (10) response with a given prompt /// /// Use [`Response::sensitive_input()`] for collecting any sensitive input, as input /// collected by this request may be logged. pub fn input(prompt: impl ToString) -> Self { Self::new(10, prompt) } /// Create a SENSITIVE INPUT (11) response with a given prompt /// /// See also [`Response::input()`] for unsensitive inputs pub fn sensitive_input(prompt: impl ToString) -> Self { Self::new(11, prompt) } /// Create a SUCCESS (20) response with a given body and MIME pub fn success(mime: impl ToString, body: impl Into) -> Self { Self { status: 20, meta: mime.to_string(), body: Some(body.into()), } } /// Create a SUCCESS (20) response with a `text/gemini` MIME pub fn success_gemini(body: impl Into) -> Self { Self::success("text/gemini", body) } /// Create a SUCCESS (20) response with a `text/plain` MIME pub fn success_plain(body: impl Into) -> Self { Self::success("text/plain", body) } /// Create a REDIRECT - TEMPORARY (30) response with a destination pub fn redirect_temporary(dest: impl ToString) -> Self { Self::new(30, dest) } /// Create a REDIRECT - PERMANENT (31) response with a destination pub fn redirect_permanent(dest: impl ToString) -> Self { Self::new(31, dest) } /// Create a TEMPORARY FAILURE (40) response with a human readable error pub fn temporary_failure(reason: impl ToString) -> Self { Self::new(40, reason) } /// Create a SERVER UNAVAILABLE (41) response with a human readable error /// /// Used to denote that the server is temporarily unavailable, for example due to /// heavy load, or maintenance pub fn server_unavailable(reason: impl ToString) -> Self { Self::new(41, reason) } /// Create a CGI ERROR (42) response with a human readable error pub fn cgi_error(reason: impl ToString) -> Self { Self::new(42, reason) } /// Create a PROXY ERROR (43) response with a human readable error pub fn proxy_error(reason: impl ToString) -> Self { Self::new(43, reason) } /// Create a SLOW DOWN (44) response with a wait time in seconds /// /// Used to denote that the user should wait a certain number of seconds before /// sending another request, often for ratelimiting purposes pub fn slow_down(wait: u64) -> Self { Self::new(44, wait) } /// Create a PERMANENT FAILURE (50) response with a human readable error pub fn permanent_failure(reason: impl ToString) -> Self { Self::new(50, reason) } /// Create a NOT FOUND (51) response with no further information /// /// Essentially a 404 pub fn not_found() -> Self { Self::new(51, String::new()) } /// Create a GONE (52) response with a human readable error /// /// For when a resource used to be here, but never will be again pub fn gone(reason: impl ToString) -> Self { Self::new(52, reason) } /// Create a PROXY REQUEST REFUSED (53) response with a human readable error /// /// The server does not serve content on this domain pub fn proxy_request_refused(reason: impl ToString) -> Self { Self::new(53, reason) } /// Create a BAD REQUEST (59) response with a human readable error pub fn bad_request(reason: impl ToString) -> Self { Self::new(59, reason) } /// Create a CLIENT CERTIFICATE REQUIRED (60) response with a human readable error pub fn client_certificate_required(reason: impl ToString) -> Self { Self::new(60, reason) } /// Create a CERTIFICATE NOT AUTHORIZED (61) response with a human readable error pub fn certificate_not_authorized(reason: impl ToString) -> Self { Self::new(61, reason) } /// Create a CERTIFICATE NOT VALID (62) response with a human readable error pub fn certificate_not_valid(reason: impl ToString) -> Self { Self::new(62, reason) } /// True if the response is a SUCCESS (10) response pub const fn is_success(&self) -> bool { self.status == 10 } #[cfg_attr(feature="gemini_srv",allow(unused_variables))] /// Rewrite any links in this response based on the path identified by a request /// /// Currently, this rewrites any links in: /// * SUCCESS (10) requests with a `text/gemini` MIME /// * REDIRECT (3X) requests /// /// For all other responses, and for any responses without links, this method has no /// effect. /// /// If this response contains a reader-based body, this **MAY** load the reader's /// contents into memory if the mime is "text/gemini". If an IO error occurs during /// this process, this error will be raised /// /// If the request does not contain enough information to rewrite a link (in other /// words, if [`Request::rewrite_path()`] returns [`None`]), then false is returned. /// In all other cases, this method returns true. /// /// Panics if a "text/gemini" response is not UTF-8 formatted /// /// For an overview of methods for rewriting links, see /// [`Server::set_autorewrite()`].\ /// For more information about how rewritten paths are calculated, see /// [`Request::rewrite_path()`]. /// /// [`Server::set_autorewrite()`]: crate::Server::set_autorewrite() /// [`Request::rewrite_path()`]: crate::Server::set_autorewrite() pub async fn rewrite_all(&mut self, based_on: &crate::Request) -> std::io::Result { #[cfg(feature = "scgi_srv")] match self.status { 20 if self.meta == "text/gemini" => { if let Some(body) = self.body.as_mut() { body.rewrite_all(based_on).await } else { Ok(false) } }, 30 | 31 => { if let Some(path) = based_on.rewrite_path(&self.meta) { self.meta = path; Ok(true) } else { Ok(false) } }, _ => Ok(true), } #[cfg(feature = "gemini_srv")] Ok(true) } } impl AsRef> for Response { fn as_ref(&self) -> &Option { &self.body } } impl AsMut> for Response { fn as_mut(&mut self) -> &mut Option { &mut self.body } } impl> From for Response { fn from(doc: D) -> Self { Self::success_gemini(doc) } }