mirror of
https://github.com/YGGverse/ggemini.git
synced 2026-03-31 09:05:45 +00:00
reorganize certificate structs format: make constructors lazy, parse members on get
This commit is contained in:
parent
1b96270598
commit
232531a0bc
8 changed files with 345 additions and 91 deletions
|
|
@ -1,19 +1,24 @@
|
|||
pub mod error;
|
||||
pub use error::Error;
|
||||
pub mod not_authorized;
|
||||
pub mod not_valid;
|
||||
pub mod required;
|
||||
|
||||
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");
|
||||
pub use error::Error;
|
||||
pub use not_authorized::NotAuthorized;
|
||||
pub use not_valid::NotValid;
|
||||
pub use required::Required;
|
||||
|
||||
const CODE: u8 = b'6';
|
||||
|
||||
/// 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 { message: Option<String> },
|
||||
Required(Required),
|
||||
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-61-certificate-not-authorized
|
||||
NotAuthorized { message: Option<String> },
|
||||
NotAuthorized(NotAuthorized),
|
||||
/// https://geminiprotocol.net/docs/protocol-specification.gmi#status-62-certificate-not-valid
|
||||
NotValid { message: Option<String> },
|
||||
NotValid(NotValid),
|
||||
}
|
||||
|
||||
impl Certificate {
|
||||
|
|
@ -21,95 +26,72 @@ impl Certificate {
|
|||
|
||||
/// Create new `Self` from buffer include header bytes
|
||||
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
|
||||
use std::str::FromStr;
|
||||
match std::str::from_utf8(buffer) {
|
||||
Ok(header) => Self::from_str(header),
|
||||
Err(e) => Err(Error::Utf8Error(e)),
|
||||
match buffer.first() {
|
||||
Some(b) => match *b {
|
||||
CODE => match buffer.get(1) {
|
||||
Some(b) => match *b {
|
||||
b'0' => Ok(Self::Required(
|
||||
Required::from_utf8(buffer).map_err(Error::Required)?,
|
||||
)),
|
||||
b'1' => Ok(Self::NotAuthorized(
|
||||
NotAuthorized::from_utf8(buffer).map_err(Error::NotAuthorized)?,
|
||||
)),
|
||||
b'2' => Ok(Self::NotValid(
|
||||
NotValid::from_utf8(buffer).map_err(Error::NotValid)?,
|
||||
)),
|
||||
b => Err(Error::SecondByte(b)),
|
||||
},
|
||||
None => Err(Error::UndefinedSecondByte),
|
||||
},
|
||||
b => Err(Error::FirstByte(b)),
|
||||
},
|
||||
None => Err(Error::UndefinedFirstByte),
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
pub fn to_code(&self) -> u8 {
|
||||
match self {
|
||||
Self::Required { .. } => REQUIRED,
|
||||
Self::NotAuthorized { .. } => NOT_AUTHORIZED,
|
||||
Self::NotValid { .. } => NOT_VALID,
|
||||
}
|
||||
.0
|
||||
}
|
||||
|
||||
pub fn message(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::Required { message } => message,
|
||||
Self::NotAuthorized { message } => message,
|
||||
Self::NotValid { message } => message,
|
||||
Self::Required(required) => required.message(),
|
||||
Self::NotAuthorized(not_authorized) => not_authorized.message(),
|
||||
Self::NotValid(not_valid) => not_valid.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
|
||||
)
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Self::Required(required) => required.as_str(),
|
||||
Self::NotAuthorized(not_authorized) => not_authorized.as_str(),
|
||||
Self::NotValid(not_valid) => not_valid.as_str(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Certificate {
|
||||
type Err = Error;
|
||||
fn from_str(header: &str) -> Result<Self, Self::Err> {
|
||||
if let Some(postfix) = header.strip_prefix("60") {
|
||||
return Ok(Self::Required {
|
||||
message: message(postfix),
|
||||
});
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
match self {
|
||||
Self::Required(required) => required.as_bytes(),
|
||||
Self::NotAuthorized(not_authorized) => not_authorized.as_bytes(),
|
||||
Self::NotValid(not_valid) => not_valid.as_bytes(),
|
||||
}
|
||||
if let Some(postfix) = header.strip_prefix("61") {
|
||||
return Ok(Self::NotAuthorized {
|
||||
message: message(postfix),
|
||||
});
|
||||
}
|
||||
if let Some(postfix) = header.strip_prefix("62") {
|
||||
return Ok(Self::NotValid {
|
||||
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);
|
||||
fn test() {
|
||||
fn t(source: &str, message: Option<&str>) {
|
||||
let b = source.as_bytes();
|
||||
let c = Certificate::from_utf8(b).unwrap();
|
||||
assert_eq!(c.message(), message);
|
||||
assert_eq!(c.as_str(), source);
|
||||
assert_eq!(c.as_bytes(), b);
|
||||
}
|
||||
// 60
|
||||
t("60 Required\r\n", Some("Required"));
|
||||
t("60\r\n", None);
|
||||
// 61
|
||||
t("61 Not Authorized\r\n", Some("Not Authorized"));
|
||||
t("61\r\n", None);
|
||||
// 62
|
||||
t("61 Not Valid\r\n", Some("Not Valid"));
|
||||
t("61\r\n", None);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,39 @@
|
|||
use std::{
|
||||
fmt::{Display, Formatter, Result},
|
||||
str::Utf8Error,
|
||||
};
|
||||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Code,
|
||||
Utf8Error(Utf8Error),
|
||||
FirstByte(u8),
|
||||
NotAuthorized(super::not_authorized::Error),
|
||||
NotValid(super::not_valid::Error),
|
||||
Required(super::required::Error),
|
||||
SecondByte(u8),
|
||||
UndefinedFirstByte,
|
||||
UndefinedSecondByte,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Code => {
|
||||
write!(f, "Status code error")
|
||||
Self::FirstByte(b) => {
|
||||
write!(f, "Unexpected first byte: {b}")
|
||||
}
|
||||
Self::Utf8Error(e) => {
|
||||
write!(f, "UTF-8 error: {e}")
|
||||
Self::NotAuthorized(e) => {
|
||||
write!(f, "NotAuthorized status parse error: {e}")
|
||||
}
|
||||
Self::NotValid(e) => {
|
||||
write!(f, "NotValid status parse error: {e}")
|
||||
}
|
||||
Self::Required(e) => {
|
||||
write!(f, "Required status parse error: {e}")
|
||||
}
|
||||
Self::SecondByte(b) => {
|
||||
write!(f, "Unexpected second byte: {b}")
|
||||
}
|
||||
Self::UndefinedFirstByte => {
|
||||
write!(f, "Undefined first byte")
|
||||
}
|
||||
Self::UndefinedSecondByte => {
|
||||
write!(f, "Undefined second byte")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
61
src/client/connection/response/certificate/not_authorized.rs
Normal file
61
src/client/connection/response/certificate/not_authorized.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
pub mod error;
|
||||
pub use error::Error;
|
||||
|
||||
const CODE: &[u8] = b"61";
|
||||
|
||||
/// Hold header `String` for [61](https://geminiprotocol.net/docs/protocol-specification.gmi#status-61) status code
|
||||
/// * this response type does not contain body data
|
||||
/// * the header member is closed to require valid construction
|
||||
pub struct NotAuthorized(String);
|
||||
|
||||
impl NotAuthorized {
|
||||
// Constructors
|
||||
|
||||
/// Parse `Self` from buffer contains header bytes
|
||||
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
|
||||
if !buffer.starts_with(CODE) {
|
||||
return Err(Error::Code);
|
||||
}
|
||||
Ok(Self(
|
||||
std::str::from_utf8(
|
||||
crate::client::connection::response::header_bytes(buffer).map_err(Error::Header)?,
|
||||
)
|
||||
.map_err(Error::Utf8Error)?
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
/// Get optional message for `Self`
|
||||
/// * return `None` if the message is empty
|
||||
pub fn message(&self) -> Option<&str> {
|
||||
self.0.get(2..).map(|s| s.trim()).filter(|x| !x.is_empty())
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.0.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
// ok
|
||||
let not_authorized = NotAuthorized::from_utf8("61 Not Authorized\r\n".as_bytes()).unwrap();
|
||||
assert_eq!(not_authorized.message(), Some("Not Authorized"));
|
||||
assert_eq!(not_authorized.as_str(), "61 Not Authorized\r\n");
|
||||
|
||||
let not_authorized = NotAuthorized::from_utf8("61\r\n".as_bytes()).unwrap();
|
||||
assert_eq!(not_authorized.message(), None);
|
||||
assert_eq!(not_authorized.as_str(), "61\r\n");
|
||||
|
||||
// err
|
||||
assert!(NotAuthorized::from_utf8("62 Fail\r\n".as_bytes()).is_err());
|
||||
assert!(NotAuthorized::from_utf8("62 Fail\r\n".as_bytes()).is_err());
|
||||
assert!(NotAuthorized::from_utf8("Fail\r\n".as_bytes()).is_err());
|
||||
assert!(NotAuthorized::from_utf8("Fail".as_bytes()).is_err());
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Code,
|
||||
Header(crate::client::connection::response::HeaderBytesError),
|
||||
Utf8Error(std::str::Utf8Error),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Code => {
|
||||
write!(f, "Unexpected status code")
|
||||
}
|
||||
Self::Header(e) => {
|
||||
write!(f, "Header error: {e}")
|
||||
}
|
||||
Self::Utf8Error(e) => {
|
||||
write!(f, "UTF-8 decode error: {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/client/connection/response/certificate/not_valid.rs
Normal file
61
src/client/connection/response/certificate/not_valid.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
pub mod error;
|
||||
pub use error::Error;
|
||||
|
||||
const CODE: &[u8] = b"62";
|
||||
|
||||
/// Hold header `String` for [62](https://geminiprotocol.net/docs/protocol-specification.gmi#status-62) status code
|
||||
/// * this response type does not contain body data
|
||||
/// * the header member is closed to require valid construction
|
||||
pub struct NotValid(String);
|
||||
|
||||
impl NotValid {
|
||||
// Constructors
|
||||
|
||||
/// Parse `Self` from buffer contains header bytes
|
||||
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
|
||||
if !buffer.starts_with(CODE) {
|
||||
return Err(Error::Code);
|
||||
}
|
||||
Ok(Self(
|
||||
std::str::from_utf8(
|
||||
crate::client::connection::response::header_bytes(buffer).map_err(Error::Header)?,
|
||||
)
|
||||
.map_err(Error::Utf8Error)?
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
/// Get optional message for `Self`
|
||||
/// * return `None` if the message is empty
|
||||
pub fn message(&self) -> Option<&str> {
|
||||
self.0.get(2..).map(|s| s.trim()).filter(|x| !x.is_empty())
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.0.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
// ok
|
||||
let not_valid = NotValid::from_utf8("62 Not Valid\r\n".as_bytes()).unwrap();
|
||||
assert_eq!(not_valid.message(), Some("Not Valid"));
|
||||
assert_eq!(not_valid.as_str(), "62 Not Valid\r\n");
|
||||
|
||||
let not_valid = NotValid::from_utf8("62\r\n".as_bytes()).unwrap();
|
||||
assert_eq!(not_valid.message(), None);
|
||||
assert_eq!(not_valid.as_str(), "62\r\n");
|
||||
|
||||
// err
|
||||
// @TODO assert!(NotValid::from_utf8("62Fail\r\n".as_bytes()).is_err());
|
||||
assert!(NotValid::from_utf8("63 Fail\r\n".as_bytes()).is_err());
|
||||
assert!(NotValid::from_utf8("Fail\r\n".as_bytes()).is_err());
|
||||
assert!(NotValid::from_utf8("Fail".as_bytes()).is_err());
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Code,
|
||||
Header(crate::client::connection::response::HeaderBytesError),
|
||||
Utf8Error(std::str::Utf8Error),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Code => {
|
||||
write!(f, "Unexpected status code")
|
||||
}
|
||||
Self::Header(e) => {
|
||||
write!(f, "Header error: {e}")
|
||||
}
|
||||
Self::Utf8Error(e) => {
|
||||
write!(f, "UTF-8 decode error: {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/client/connection/response/certificate/required.rs
Normal file
61
src/client/connection/response/certificate/required.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
pub mod error;
|
||||
pub use error::Error;
|
||||
|
||||
const CODE: &[u8] = b"60";
|
||||
|
||||
/// Hold header `String` for [60](https://geminiprotocol.net/docs/protocol-specification.gmi#status-60) status code
|
||||
/// * this response type does not contain body data
|
||||
/// * the header member is closed to require valid construction
|
||||
pub struct Required(String);
|
||||
|
||||
impl Required {
|
||||
// Constructors
|
||||
|
||||
/// Parse `Self` from buffer contains header bytes
|
||||
pub fn from_utf8(buffer: &[u8]) -> Result<Self, Error> {
|
||||
if !buffer.starts_with(CODE) {
|
||||
return Err(Error::Code);
|
||||
}
|
||||
Ok(Self(
|
||||
std::str::from_utf8(
|
||||
crate::client::connection::response::header_bytes(buffer).map_err(Error::Header)?,
|
||||
)
|
||||
.map_err(Error::Utf8Error)?
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
/// Get optional message for `Self`
|
||||
/// * return `None` if the message is empty
|
||||
pub fn message(&self) -> Option<&str> {
|
||||
self.0.get(2..).map(|s| s.trim()).filter(|x| !x.is_empty())
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.0.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
// ok
|
||||
let required = Required::from_utf8("60 Required\r\n".as_bytes()).unwrap();
|
||||
assert_eq!(required.message(), Some("Required"));
|
||||
assert_eq!(required.as_str(), "60 Required\r\n");
|
||||
|
||||
let required = Required::from_utf8("60\r\n".as_bytes()).unwrap();
|
||||
assert_eq!(required.message(), None);
|
||||
assert_eq!(required.as_str(), "60\r\n");
|
||||
|
||||
// err
|
||||
assert!(Required::from_utf8("62 Fail\r\n".as_bytes()).is_err());
|
||||
assert!(Required::from_utf8("62 Fail\r\n".as_bytes()).is_err());
|
||||
assert!(Required::from_utf8("Fail\r\n".as_bytes()).is_err());
|
||||
assert!(Required::from_utf8("Fail".as_bytes()).is_err());
|
||||
}
|
||||
24
src/client/connection/response/certificate/required/error.rs
Normal file
24
src/client/connection/response/certificate/required/error.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Code,
|
||||
Header(crate::client::connection::response::HeaderBytesError),
|
||||
Utf8Error(std::str::Utf8Error),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Self::Code => {
|
||||
write!(f, "Unexpected status code")
|
||||
}
|
||||
Self::Header(e) => {
|
||||
write!(f, "Header error: {e}")
|
||||
}
|
||||
Self::Utf8Error(e) => {
|
||||
write!(f, "UTF-8 decode error: {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue