mirror of
https://github.com/YGGverse/titanite.git
synced 2026-03-31 17:15:33 +00:00
add implement data member, use global Header trait
This commit is contained in:
parent
385f9aefc4
commit
91077d3420
4 changed files with 40 additions and 45 deletions
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "titanite"
|
||||
version = "0.1.2"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
|
|
|
|||
|
|
@ -13,16 +13,16 @@ pub use success::Success;
|
|||
use anyhow::{bail, Result};
|
||||
|
||||
/// [Gemini](https://geminiprotocol.net/docs/protocol-specification.gmi) source
|
||||
pub enum Response {
|
||||
pub enum Response<'a> {
|
||||
Certificate(Certificate),
|
||||
Failure(Failure),
|
||||
Input(Input),
|
||||
Redirect(Redirect),
|
||||
Success(Success),
|
||||
Success(Success<'a>),
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub fn from_bytes(buffer: &[u8]) -> Result<Self> {
|
||||
impl<'a> Response<'a> {
|
||||
pub fn from_bytes(buffer: &'a [u8]) -> Result<Self> {
|
||||
match buffer.first() {
|
||||
Some(byte) => Ok(match byte {
|
||||
b'1' => Self::Input(Input::from_bytes(buffer)?),
|
||||
|
|
@ -78,12 +78,15 @@ fn test() {
|
|||
}
|
||||
// 20
|
||||
{
|
||||
let source = format!("20 text/gemini\r\n");
|
||||
let source = format!("20 text/gemini\r\ndata");
|
||||
let target = Response::from_bytes(source.as_bytes()).unwrap();
|
||||
|
||||
match target {
|
||||
Response::Success(ref this) => match this {
|
||||
Success::Default(this) => assert_eq!(this.mime, "text/gemini".to_string()),
|
||||
Success::Default(this) => {
|
||||
assert_eq!(this.mime, "text/gemini".to_string());
|
||||
assert_eq!(this.data, "data".as_bytes());
|
||||
}
|
||||
},
|
||||
_ => panic!(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ pub use default::Default;
|
|||
use anyhow::{bail, Result};
|
||||
|
||||
/// [Success](https://geminiprotocol.net/docs/protocol-specification.gmi#success)
|
||||
pub enum Success {
|
||||
Default(Default),
|
||||
pub enum Success<'a> {
|
||||
Default(Default<'a>),
|
||||
}
|
||||
|
||||
impl Success {
|
||||
pub fn from_bytes(buffer: &[u8]) -> Result<Self> {
|
||||
impl<'a> Success<'a> {
|
||||
pub fn from_bytes(buffer: &'a [u8]) -> Result<Self> {
|
||||
if buffer.first().is_none_or(|b| *b != b'2') {
|
||||
bail!("Unexpected first byte")
|
||||
}
|
||||
|
|
@ -30,12 +30,13 @@ impl Success {
|
|||
|
||||
#[test]
|
||||
fn test() {
|
||||
let request = format!("20 text/gemini\r\n");
|
||||
let request = format!("20 text/gemini\r\ndata");
|
||||
let source = Success::from_bytes(request.as_bytes()).unwrap();
|
||||
|
||||
match source {
|
||||
Success::Default(ref this) => {
|
||||
assert_eq!(this.mime, "text/gemini".to_string())
|
||||
assert_eq!(this.mime, "text/gemini".to_string());
|
||||
assert_eq!(this.data, "data".as_bytes());
|
||||
}
|
||||
}
|
||||
assert_eq!(source.into_bytes(), request.as_bytes());
|
||||
|
|
|
|||
|
|
@ -3,63 +3,54 @@ use anyhow::{bail, Result};
|
|||
pub const CODE: &[u8] = b"20";
|
||||
|
||||
/// [Success](https://geminiprotocol.net/docs/protocol-specification.gmi#success)
|
||||
pub struct Default {
|
||||
pub struct Default<'a> {
|
||||
pub mime: String,
|
||||
pub data: &'a [u8],
|
||||
}
|
||||
|
||||
impl Default {
|
||||
impl<'a> Default<'a> {
|
||||
/// Build `Self` from UTF-8 header bytes
|
||||
/// * expected buffer includes leading status code, message, CRLF
|
||||
pub fn from_bytes(buffer: &[u8]) -> Result<Self> {
|
||||
// calculate length once
|
||||
let len = buffer.len();
|
||||
// validate headers for this response type
|
||||
if !(3..=1024).contains(&len) {
|
||||
bail!("Unexpected header length")
|
||||
}
|
||||
if buffer
|
||||
.get(..2)
|
||||
pub fn from_bytes(buffer: &'a [u8]) -> Result<Self> {
|
||||
use crate::Header;
|
||||
use regex::Regex;
|
||||
use std::str::from_utf8;
|
||||
let h = buffer.header_bytes()?;
|
||||
if h.get(..2)
|
||||
.is_none_or(|c| c[0] != CODE[0] || c[1] != CODE[1])
|
||||
{
|
||||
bail!("Invalid status code")
|
||||
}
|
||||
// collect data bytes
|
||||
let mut m = Vec::with_capacity(len);
|
||||
for b in buffer[3..].iter() {
|
||||
if *b == b'\r' {
|
||||
continue;
|
||||
}
|
||||
if *b == b'\n' {
|
||||
break;
|
||||
}
|
||||
m.push(*b)
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
mime: if m.is_empty() {
|
||||
bail!("Content type required")
|
||||
} else {
|
||||
String::from_utf8(m)?
|
||||
mime: match Regex::new(r"^([^\/]+\/[^\s;]+)")?.captures(from_utf8(&h[3..])?) {
|
||||
Some(c) => match c.get(1) {
|
||||
Some(m) => m.as_str().to_string(),
|
||||
None => bail!("Content type required"),
|
||||
},
|
||||
None => bail!("Could not parse content type"),
|
||||
},
|
||||
data: &buffer[h.len() + 2..],
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert `Self` into UTF-8 bytes presentation
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(self.mime.len() + 5);
|
||||
let mut bytes = Vec::with_capacity(3 + self.mime.len() + 2 + self.data.len());
|
||||
bytes.extend(CODE);
|
||||
bytes.push(b' ');
|
||||
bytes.extend(self.mime.into_bytes());
|
||||
bytes.extend([b'\r', b'\n']);
|
||||
bytes.extend(self.data);
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let request = format!("20 text/gemini\r\n");
|
||||
let source = Default::from_bytes(request.as_bytes()).unwrap();
|
||||
let source = format!("20 text/gemini\r\ndata");
|
||||
let target = Default::from_bytes(source.as_bytes()).unwrap();
|
||||
|
||||
assert_eq!(source.mime, "text/gemini".to_string());
|
||||
assert_eq!(source.into_bytes(), request.as_bytes());
|
||||
assert_eq!(target.mime, "text/gemini".to_string());
|
||||
assert_eq!(target.data, "data".as_bytes());
|
||||
assert_eq!(target.into_bytes(), source.as_bytes());
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue