mirror of
https://github.com/YGGverse/titanite.git
synced 2026-03-31 09:05:31 +00:00
70 lines
2.1 KiB
Rust
70 lines
2.1 KiB
Rust
use anyhow::{bail, Result};
|
|
|
|
pub const CODE: &[u8] = b"61";
|
|
|
|
/// [Certificate authorization](https://geminiprotocol.net/docs/protocol-specification.gmi#status-61-certificate-not-authorized)
|
|
pub struct NotAuthorized {
|
|
pub message: Option<String>,
|
|
}
|
|
|
|
impl NotAuthorized {
|
|
/// 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)
|
|
.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 {
|
|
message: String::from_utf8(m).map(|m| if m.is_empty() { None } else { Some(m) })?,
|
|
})
|
|
}
|
|
|
|
/// Convert `Self` into UTF-8 bytes presentation
|
|
pub fn into_bytes(self) -> Vec<u8> {
|
|
match self.message {
|
|
Some(message) => {
|
|
let mut bytes = Vec::with_capacity(message.len() + 5);
|
|
bytes.extend(CODE);
|
|
bytes.push(b' ');
|
|
bytes.extend(message.into_bytes());
|
|
bytes.extend([b'\r', b'\n']);
|
|
bytes
|
|
}
|
|
None => {
|
|
let mut bytes = Vec::with_capacity(4);
|
|
bytes.extend(CODE);
|
|
bytes.extend([b'\r', b'\n']);
|
|
bytes
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test() {
|
|
let request = format!("61 message\r\n");
|
|
let source = NotAuthorized::from_bytes(request.as_bytes()).unwrap();
|
|
|
|
assert_eq!(source.message, Some("message".to_string()));
|
|
assert_eq!(source.into_bytes(), request.as_bytes());
|
|
}
|