BOFUYB6IISDQYT3G5MKVDNWB2WWGHMNYDKTITJBVS5RED6XJLB4QC
// inspiration taken from
// https://github.com/hyperium/http/blob/master/src/status.rs
//! Gemini Status Codes
//!
//! This module contains types to represent Gemini response statuses, construct
//! them from numbers or byte-strings, as well as to process them by `Category`
//! to allow for "simple but complete" clients and servers as mentioned in the
//! Gemini spec.
//!
//! # Examples
//!
//! ```
//! use gemini::{Category, Status};
//!
//! assert_eq!(Status::from_u8(20).unwrap(), Status::SUCCESS);
//! assert_eq!(Status::NOT_FOUND.code_number(), 51);
//! assert_eq!(Status::TEMPORARY_REDIRECT.category(), Category::Redirect)
//! ```
//!
//! inspiration taken from
//! https://github.com/hyperium/http/blob/master/src/status.rs
//!
//! note: currently constant and `Code` doc comments are taken verbatim from
//! [the Gemini spec](https://gemini.circumlunar.space/docs/specification.html).
($( $cat:ident: [$( $name:ident: $code:ident ),+] ),*) => {
$($(
pub const $name: Self = Self::new(Category::$cat, Code::$code);
)+)*
($(
$(#[$outer:meta])*
$cat:ident: [$(
$(#[$inner:meta])*
$name:ident: $code:ident($code_num:expr, $msg:expr)
),+]
),*) => {
impl Status {
$($(
$(#[$inner])*
pub const $name: Self = Self::new(Category::$cat, Code::$code);
)+)*
/// Attempt to turn valid `u8` codes into `Status` objects.
///
/// Any codes not present in the
/// [Gemini spec](https://gemini.circumlunar.space/docs/specification.html)
/// are considered errors.
pub fn from_u8(code: u8) -> Result<Self, InvalidStatusCode> {
Ok(match code {
$($($code_num => Self::$name,)*)+
_ => return Err(InvalidStatusCode::new()),
})
}
/// Get a short text based description for a `Status` object.
/// Strings will be returned in ALL CAPS.
pub fn description(&self) -> &'static str {
match self {
$($(&Self::$name => $msg,)*)+
_ => unreachable!("Invalid status"),
}
}
}
/// A high level category derived from the leading digit of a `Status`
/// code.
///
/// Simple clients and servers can examine only the `Category` of a
/// `Status` and still follow the spec.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum Category {
$(
$(#[$outer])*
$cat,
)*
}
/// The precise two digit numeric code associated with a `Status`.
///
/// Status codes not present in
/// [the spec](https://gemini.circumlunar.space/docs/specification.html)
/// are considered errors.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum Code {
$($(
$(#[$inner])*
$code = $code_num,
)*)+
}
status_consts!(
/// Status codes which indicate that the client should ask the user for more input.
Input: [
/// The requested resource accepts a line of textual user input. The <META> line is a prompt which should be displayed to the user. The same resource should then be requested again with the user's input included as a query component. Queries are included in requests as per the usual generic URL definition in RFC3986, i.e. separated from the path by a ?. Reserved characters used in the user's input must be "percent-encoded" as per RFC3986, and space characters should also be percent-encoded.
INPUT: Input(10, "INPUT"),
/// As per status code 10, but for use with sensitive input such as passwords. Clients should present the prompt as per status code 10, but the user's input should not be echoed to the screen to prevent it being read by "shoulder surfers".
SENSITIVE_INPUT: SensitiveInput(11, "SENSITIVE INPUT")
],
/// Status codes that indicate a successful request/response cycle.
Success: [
/// The request was handled successfully and a response body will follow the response header. The <META> line is a MIME media type which applies to the response body.
SUCCESS: Success(20, "SUCCESS")
],
/// Status codes that indicate that a resource has moved.
Redirect: [
/// The server is redirecting the client to a new location for the requested resource. There is no response body. <META> is a new URL for the requested resource. The URL may be absolute or relative. The redirect should be considered temporary, i.e. clients should continue to request the resource at the original address and should not performance convenience actions like automatically updating bookmarks. There is no response body.
TEMPORARY_REDIRECT: TemporaryRedirect(30, "REDIRECT - TEMPORARY"),
/// The requested resource should be consistently requested from the new URL provided in future. Tools like search engine indexers or content aggregators should update their configurations to avoid requesting the old URL, and end-user clients may automatically update bookmarks, etc. Note that clients which only pay attention to the initial digit of status codes will treat this as a temporary redirect. They will still end up at the right place, they just won't be able to make use of the knowledge that this redirect is permanent, so they'll pay a small performance penalty by having to follow the redirect each time.
PERMANENT_REDIRECT: PermanentRedirect(31, "REDIRECT - PERMANENT")
],
/// Status codes that indicate a failure which should eventually be resolved.
TemporaryFailure: [
/// The request has failed. There is no response body. The nature of the failure is temporary, i.e. an identical request MAY succeed in the future. The contents of <META> may provide additional information on the failure, and should be displayed to human users.
TEMPORARY_FAILURE: TemporaryFailure(40, "TEMPORARY FAILURE"),
/// The server is unavailable due to overload or maintenance. (cf HTTP 503)
SERVER_UNAVAILABLE: ServerUnavailable(41, "SERVER UNAVAILABLE"),
/// A CGI process, or similar system for generating dynamic content, died unexpectedly or timed out.
CGI_ERROR: CGIError(42, "CGI ERROR"),
/// A proxy request failed because the server was unable to successfully complete a transaction with the remote host. (cf HTTP 502, 504)
PROXY_ERROR: ProxyError(43, "PROXY ERROR"),
/// Rate limiting is in effect. <META> is an integer number of seconds which the client must wait before another request is made to this server. (cf HTTP 429)
SLOW_DOWN: SlowDown(44, "SLOW DOWN")
],
/// Status codes that indicate a failure which will likely continue.
PermanentFailure: [
/// The request has failed. There is no response body. The nature of the failure is permanent, i.e. identical future requests will reliably fail for the same reason. The contents of <META> may provide additional information on the failure, and should be displayed to human users. Automatic clients such as aggregators or indexing crawlers should not repeat this request.
PERMANENT_FAILURE: PermanentFailure(50, "PERMANENT FAILURE"),
/// The requested resource could not be found but may be available in the future. (cf HTTP 404) (struggling to remember this important status code? Easy: you can't find things hidden at Area 51!)
NOT_FOUND: NotFound(51, "NOT FOUND"),
/// The resource requested is no longer available and will not be available again. Search engines and similar tools should remove this resource from their indices. Content aggregators should stop requesting the resource and convey to their human users that the subscribed resource is gone. (cf HTTP 410)
GONE: Gone(52, "GONE"),
/// The request was for a resource at a domain not served by the server and the server does not accept proxy requests.
PROXY_REQUEST_REFUSED: ProxyRequestRefused(53, "PROXY REQUEST REFUSED"),
/// The server was unable to parse the client's request, presumably due to a malformed request. (cf HTTP 400)
BAD_REQUEST: BadRequest(59, "BAD REQUEST")
],
/// Status codes that indicate the client must ask the user to provide a valid certificate.
ClientCertificateRequired: [
/// The requested resource requires a client certificate to access. If the request was made without a certificate, it should be repeated with one. If the request was made with a certificate, the server did not accept it and the request should be repeated with a different certificate. The contents of <META> (and/or the specific 6x code) may provide additional information on certificate requirements or the reason a certificate was rejected.
CLIENT_CERTIFICATE_REQUIRED: ClientCertificateRequired(
60,
"CLIENT CERTIFICATE REQUIRED"
),
/// The supplied client certificate is not authorised for accessing the particular requested resource. The problem is not with the certificate itself, which may be authorised for other resources.
CLIENT_CERTIFICATE_NOT_AUTHORISED: ClientCertificateNotAuthorised(
61,
"CLIENT CERTIFICATE NOT AUTHORISED"
),
/// The supplied client certificate was not accepted because it is not valid. This indicates a problem with the certificate in and of itself, with no consideration of the particular requested resource. The most likely cause is that the certificate's validity start date is in the future or its expiry date has passed, but this code may also indicate an invalid signature, or a violation of a X509 standard requirements. The <META> should provide more information about the exact error.
CERTIFICATE_NOT_VALID: CertificateNotValid(62, "CERTIFICATE NOT VALID")
]
);
}
status_consts!(
Input: [
INPUT: Input,
SENSITIVE_INPUT: SensitiveInput
],
Success: [SUCCESS: Success],
Redirect: [
TEMPORARY_REDIRECT: TemporaryRedirect,
PERMANENT_REDIRECT: PermanentRedirect
],
TemporaryFailure: [
TEMPORARY_FAILURE: TemporaryFailure,
SERVER_UNAVAILABLE: ServerUnavailable,
CGI_ERROR: CGIError,
PROXY_ERROR: ProxyError,
SLOW_DOWN: SlowDown
],
PermanentFailure: [
PERMANENT_FAILURE: PermanentFailure,
NOT_FOUND: NotFound,
GONE: Gone,
PROXY_REQUEST_REFUSED: ProxyRequestRefused,
BAD_REQUEST: BadRequest
],
ClientCertificateRequired: [
CLIENT_CERTIFICATE_REQUIRED: ClientCertificateRequired,
CLIENT_CERTIFICATE_NOT_AUTHORISED: ClientCertificateNotAuthorised,
CERTIFICATE_NOT_VALID: CertificateNotValid
]
);
pub fn from_u8(code: u8) -> Result<Self, InvalidStatusCode> {
Ok(match code {
10 => Self::INPUT,
11 => Self::SENSITIVE_INPUT,
20 => Self::SUCCESS,
30 => Self::TEMPORARY_REDIRECT,
31 => Self::PERMANENT_REDIRECT,
40 => Self::TEMPORARY_FAILURE,
41 => Self::SERVER_UNAVAILABLE,
42 => Self::CGI_ERROR,
43 => Self::PROXY_ERROR,
44 => Self::SLOW_DOWN,
50 => Self::PERMANENT_FAILURE,
51 => Self::NOT_FOUND,
52 => Self::GONE,
53 => Self::PROXY_REQUEST_REFUSED,
59 => Self::BAD_REQUEST,
60 => Self::CLIENT_CERTIFICATE_REQUIRED,
61 => Self::CLIENT_CERTIFICATE_NOT_AUTHORISED,
62 => Self::CERTIFICATE_NOT_VALID,
_ => return Err(InvalidStatusCode::new()),
})
[b1, b2] => {
let d1 = b1.wrapping_sub(b'0');
let d2 = b2.wrapping_sub(b'0');
if d1 == 0 || d1 > 9 || d2 > 9 {
Err(InvalidStatusCode::new())
} else {
let code = (d1 * 10) + d2;
Self::from_u8(code)
}
[b1, b2] if &b'0' <= b1 && b1 <= &b'9' && &b'0' <= b2 && b2 <= &b'9' => {
let d1 = b1 - b'0';
let d2 = b2 - b'0';
Self::from_u8(d1 * 10 + d2)
pub fn description(&self) -> &'static str {
match self {
&Self::INPUT => "INPUT",
&Self::SENSITIVE_INPUT => "SENSITIVE INPUT",
&Self::SUCCESS => "SUCCESS",
&Self::TEMPORARY_REDIRECT => "REDIRECT - TEMPORARY",
&Self::PERMANENT_REDIRECT => "REDIRECT - PERMANENT",
&Self::TEMPORARY_FAILURE => "TEMPORARY FAILURE",
&Self::SERVER_UNAVAILABLE => "SERVER UNAVAILABLE",
&Self::CGI_ERROR => "CGI ERROR",
&Self::PROXY_ERROR => "PROXY ERROR",
&Self::SLOW_DOWN => "SLOW DOWN",
&Self::PERMANENT_FAILURE => "PERMANENT FAILURE",
&Self::NOT_FOUND => "NOT FOUND",
&Self::GONE => "GONE",
&Self::PROXY_REQUEST_REFUSED => "PROXY REQUEST REFUSED",
&Self::BAD_REQUEST => "BAD REQUEST",
&Self::CLIENT_CERTIFICATE_REQUIRED => "CLIENT CERTIFICATE REQUIRED",
&Self::CLIENT_CERTIFICATE_NOT_AUTHORISED => "CLIENT CERTIFICATE NOT AUTHORISED",
&Self::CERTIFICATE_NOT_VALID => "CERTIFICATE NOT VALID",
_ => unreachable!("Invalid status"),
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum Category {
Input,
Success,
Redirect,
TemporaryFailure,
PermanentFailure,
ClientCertificateRequired,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
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,
ProxyRequestRefused = 53,
BadRequest = 59,
ClientCertificateRequired = 60,
ClientCertificateNotAuthorised = 61,
CertificateNotValid = 62,
}
use crate::{header::Header, status::Status};
//! Gemini Responses
//!
//! Gemini reponses consist of a `Header` and an optional response body
//! consisting of a stream of bytes.
use std::str;
use crate::{
header::Header,
status::{Category, Status},
};
}
/// Construct a new response.
///
/// This function maintains the invariant that only successful responses
/// may include response bodies.
pub fn new(header: Header, body: Option<Vec<u8>>) -> Option<Self> {
match (header.status.category(), &body) {
(Category::Success, Some(_)) | (_, None) => Some(Response { header, body }),
_ => None,
}
pub fn with_body(self, body: Vec<u8>) -> Self {
Self::new(self.header, Some(body))
/// Construct a new response without maintaining invariants.
///
/// This constructor may produce a `Response' which is invalid with respect
/// to the Gemini spec. Users of this function should be careful to check
/// that `header` has `Category::Success`.
pub fn new_unchecked(header: Header, body: Option<Vec<u8>>) -> Self {
Response { header, body }
}
/// Return the response body as a utf-8 string, if present.
pub fn body_text(&self) -> Option<&str> {
let bytes = self.body.as_ref()?;
str::from_utf8(bytes).ok()
#[derive(Debug)]
/// Error to indicate a failure in constructing a `Request`.
///
/// Errors will be returned in the case of invalid URLs with respect to the
/// `url` crate, or URLs with a length greater than 1024 bytes.
#[derive(Debug, Copy, Clone)]
const MAX_URL_LEN: usize = 1024;
const DEFAULT_PORT: u16 = 1965;
const GEMINI_SCHEME: &'static str = "gemini";
/// Maximum length, in bytes, of a request URL.
pub const MAX_URL_LEN: usize = 1024;
/// Default port for the Gemini protocol.
pub const DEFAULT_PORT: u16 = 1965;
/// The Gemini scheme, which can be prepended to a URI to attempt to see if
/// it is a valid Gemini URL.
pub const GEMINI_SCHEME: &'static str = "gemini";
pub use header::{Header, MetaKind};
pub use request::{InvalidRequest, Request, Url};
pub use response::Response;
pub use status::{Category, Code, InvalidStatusCode, Status};
//! Gemini Response Headers
//!
//! This module contains types to represent Gemini response headers, construct
//! them from their component parts, and for examining the type and content of
//! the Gemini <META> field, which represents, among other things, the MIME type
//! of successful Gemini responses. See `MetaKind` for more.
//!
//! # Examples
//!
//! ```
//! use gemini::{Header, MetaKind, Status};
//!
//! assert_eq!(Header::new(Status::INPUT, "Hello!".to_string()).unwrap().meta_kind(), MetaKind::Message);
//! assert_eq!(Header::new(Status::SUCCESS, "".to_string()).unwrap().mime_type().unwrap(), "text/gemini; charset=utf-8");
//! ```
//!
//! some item comments are taken verbatim from
//! [the Gemini spec](https://gemini.circumlunar.space/docs/specification.html)
const MAX_META_LEN: usize = 1024;
const DEFAULT_MIME_TYPE: &'static str = "text/gemini; charset=utf-8";
/// Maximum length, in bytes, of the <META> field.
pub const MAX_META_LEN: usize = 1024;
/// Default MIME type for successful Gemini responses.
pub const DEFAULT_MIME_TYPE: &'static str = "text/gemini; charset=utf-8";
* feature-gated parsers using `nom`
* a `gemtext` module for representing `text/gemini` content
* a `gemtext` module for representing `text/gemini` content
* parsers using `nom`
* streaming response bodies
* no_std support