mirror of
https://github.com/YGGverse/ggemini.git
synced 2026-03-31 09:05:45 +00:00
147 lines
4 KiB
Rust
147 lines
4 KiB
Rust
pub mod error;
|
|
pub use error::Error;
|
|
|
|
const REQUIRED: (u8, &str) = (60, "Certificate required");
|
|
const NOT_AUTHORIZED: (u8, &str) = (61, "Certificate not authorized");
|
|
const NOT_VALID: (u8, &str) = (62, "Certificate not valid");
|
|
|
|
/// 6* status code group
|
|
/// https://geminiprotocol.net/docs/protocol-specification.gmi#client-certificates
|
|
pub enum Certificate {
|
|
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-60
|
|
Required {
|
|
header: String,
|
|
message: Option<String>,
|
|
},
|
|
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-61-certificate-not-authorized
|
|
NotAuthorized {
|
|
header: String,
|
|
message: Option<String>,
|
|
},
|
|
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-62-certificate-not-valid
|
|
NotValid {
|
|
header: String,
|
|
message: Option<String>,
|
|
},
|
|
}
|
|
|
|
impl Certificate {
|
|
// Constructors
|
|
|
|
/// Create new `Self` from buffer include header bytes
|
|
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
|
|
use std::str::FromStr;
|
|
let len = buffer.len();
|
|
match std::str::from_utf8(
|
|
&buffer[..if len > super::HEADER_LEN {
|
|
super::HEADER_LEN
|
|
} else {
|
|
len
|
|
}],
|
|
) {
|
|
Ok(header) => Self::from_str(header),
|
|
Err(e) => Err(Error::Utf8Error(e)),
|
|
}
|
|
}
|
|
|
|
// Getters
|
|
|
|
pub fn to_code(&self) -> u8 {
|
|
match self {
|
|
Self::Required { .. } => REQUIRED,
|
|
Self::NotAuthorized { .. } => NOT_AUTHORIZED,
|
|
Self::NotValid { .. } => NOT_VALID,
|
|
}
|
|
.0
|
|
}
|
|
|
|
pub fn header(&self) -> &str {
|
|
match self {
|
|
Self::Required { header, .. }
|
|
| Self::NotAuthorized { header, .. }
|
|
| Self::NotValid { header, .. } => header,
|
|
}
|
|
.as_str()
|
|
}
|
|
|
|
pub fn message(&self) -> Option<&str> {
|
|
match self {
|
|
Self::Required { message, .. }
|
|
| Self::NotAuthorized { message, .. }
|
|
| Self::NotValid { message, .. } => message,
|
|
}
|
|
.as_deref()
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for Certificate {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
write!(
|
|
f,
|
|
"{}",
|
|
match self {
|
|
Self::Required { .. } => REQUIRED,
|
|
Self::NotAuthorized { .. } => NOT_AUTHORIZED,
|
|
Self::NotValid { .. } => NOT_VALID,
|
|
}
|
|
.1
|
|
)
|
|
}
|
|
}
|
|
|
|
impl std::str::FromStr for Certificate {
|
|
type Err = Error;
|
|
fn from_str(header: &str) -> Result<Self, Self::Err> {
|
|
let len = header.len();
|
|
if len > super::HEADER_LEN {
|
|
return Err(Error::HeaderLen(len));
|
|
}
|
|
if let Some(postfix) = header.strip_prefix("60") {
|
|
return Ok(Self::Required {
|
|
header: header.to_string(),
|
|
message: message(postfix),
|
|
});
|
|
}
|
|
if let Some(postfix) = header.strip_prefix("61") {
|
|
return Ok(Self::NotAuthorized {
|
|
header: header.to_string(),
|
|
message: message(postfix),
|
|
});
|
|
}
|
|
if let Some(postfix) = header.strip_prefix("62") {
|
|
return Ok(Self::NotValid {
|
|
header: header.to_string(),
|
|
message: message(postfix),
|
|
});
|
|
}
|
|
Err(Error::Code)
|
|
}
|
|
}
|
|
|
|
// Tools
|
|
|
|
fn message(value: &str) -> Option<String> {
|
|
let value = value.trim();
|
|
if value.is_empty() {
|
|
None
|
|
} else {
|
|
Some(value.to_string())
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_from_str() {
|
|
use std::str::FromStr;
|
|
|
|
let required = Certificate::from_str("60 Message\r\n").unwrap();
|
|
|
|
assert_eq!(required.message(), Some("Message"));
|
|
assert_eq!(required.to_code(), REQUIRED.0);
|
|
assert_eq!(required.to_string(), REQUIRED.1);
|
|
|
|
let required = Certificate::from_str("60\r\n").unwrap();
|
|
|
|
assert_eq!(required.message(), None);
|
|
assert_eq!(required.to_code(), REQUIRED.0);
|
|
assert_eq!(required.to_string(), REQUIRED.1);
|
|
}
|