use crate::status::{Category, Status};
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct Header {
pub status: Status,
meta: String,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum MetaKind {
Prompt,
MimeType,
RedirectTarget,
Message,
}
impl Header {
pub const MAX_META_LEN: usize = 1024;
pub const DEFAULT_MIME_TYPE: &'static str = "text/gemini; charset=utf-8";
pub fn meta(&self) -> &str {
self.meta.as_str()
}
pub fn meta_kind(&self) -> MetaKind {
match self.status.category() {
Category::Input => MetaKind::Prompt,
Category::Success => MetaKind::MimeType,
Category::Redirect => MetaKind::RedirectTarget,
Category::TemporaryFailure
| Category::PermanentFailure
| Category::ClientCertificateRequired => MetaKind::Message,
}
}
pub fn mime_type(&self) -> Option<&str> {
match self.meta_kind() {
MetaKind::MimeType => Some(self.meta()),
_ => None,
}
}
pub fn new(status: Status, meta: String) -> Option<Self> {
if meta.len() < Self::MAX_META_LEN {
let meta = match status {
Status::SUCCESS if meta.trim().is_empty() => Self::DEFAULT_MIME_TYPE.to_string(),
_ => meta,
};
Some(Header { status, meta })
} else {
None
}
}
pub fn new_unchecked(status: Status, meta: String) -> Self {
Header { status, meta }
}
pub fn success(mime_type: String) -> Option<Self> {
Self::new(Status::SUCCESS, mime_type)
}
pub fn gemtext() -> Self {
let status = Status::SUCCESS;
let meta = Self::DEFAULT_MIME_TYPE.to_string();
Header { status, meta }
}
}
#[cfg(feature = "parsers")]
pub mod parse {
use nom::{
bytes::streaming::{tag, take_until},
combinator::{map_opt, map_res},
error::context,
sequence::{terminated, tuple},
IResult,
};
use super::*;
use crate::status::parse::status;
pub fn header(input: &[u8]) -> IResult<&[u8], Header> {
let meta = map_res(take_until("\r\n"), |bs| {
let v = Vec::from(bs);
String::from_utf8(v)
});
context(
"response header",
map_opt(
tuple((terminated(status, tag(" ")), terminated(meta, tag("\r\n")))),
|t| Header::new(t.0, t.1),
),
)(input)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_success() {
let bytes = b"20 text/gemini; charset=utf-8\r\n";
assert_eq!(header(bytes).unwrap().1, Header::gemtext())
}
#[test]
fn test_mimetype() {
let bytes = b"20 text/json\r\n";
assert_eq!(
header(bytes).unwrap().1,
Header::success("text/json".to_string()).unwrap()
)
}
#[test]
fn test_error() {
let bytes = b"59 grr! bark! meow!\r\n";
assert_eq!(
header(bytes).unwrap().1,
Header::new(Status::BAD_REQUEST, "grr! bark! meow!".into()).unwrap()
)
}
}
}