pub struct Meta {
pub size: usize,
pub url: Url,
pub mime: Option<String>,
pub token: Option<String>,
pub options: Option<IndexMap<String, String>>,
}
impl Meta {
pub fn from_bytes(buffer: &[u8]) -> Result<Self> {
use crate::tool::Header;
use regex::Regex;
let header = from_utf8(buffer.header_bytes()?)?;
Ok(Self {
size: match Regex::new(r"size=(\d+)")?.captures(header) {
Some(c) => match c.get(1) {
Some(v) => match v.as_str().parse::<usize>() {
Ok(s) => s,
Err(e) => bail!(e),
},
None => bail!("Size required"),
},
None => bail!("Size required"),
},
mime: match Regex::new(r"mime=([^\/]+\/[^\s;\?]+)")?.captures(header) {
Some(c) => c.get(1).map(|v| v.as_str().to_string()),
None => None,
},
token: match Regex::new(r"token=([^\s;\?]+)")?.captures(header) {
Some(c) => c.get(1).map(|v| v.as_str().to_string()),
None => None,
},
options: match Regex::new(r"\?(.*)$")?.captures(header) {
Some(c) => match c.get(1) {
Some(v) => {
let mut options = IndexMap::new();
for option in v.as_str().split("&") {
let kv: Vec<&str> = option.split('=').collect();
if kv.len() == 2 {
if let Some(v) =
options.insert(kv[0].to_string(), kv[1].to_string())
{
bail!("Option key duplicate with value: {v}")
}
} else {
bail!("Invalid options format")
}
}
Some(options)
}
None => None,
},
None => None,
},
url: match Regex::new(r"^([^;\?]+)")?.captures(header) {
Some(c) => match c.get(1) {
Some(v) => Url::parse(v.as_str())?,
None => bail!("URL required"),
},
None => bail!("URL required"),
},
})
}
pub fn into_bytes(self) -> Vec<u8> {
self.to_bytes()
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut meta = format!("{};size={}", self.url, self.size);
if let Some(ref mime) = self.mime {
meta.push_str(&format!(";mime={mime}"));
}
if let Some(ref token) = self.token {
meta.push_str(&format!(";token={token}"));
}
if let Some(ref options) = self.options {
meta.push('?');
let mut items = Vec::new();
for (k, v) in options {
items.push(format!("{k}={v}"));
}
meta.push_str(&items.join("&"));
}
meta.push('\r');
meta.push('\n');
meta.into_bytes()
}
}
#[test]
fn test() {
const BYTES: &[u8] =
"titan://geminiprotocol.net/raw/path;size=4;mime=text/plain;token=token?key1=value1&key2=value2\r\nDATA"
.as_bytes();
let meta = Meta::from_bytes(BYTES).unwrap().into_bytes();
assert_eq!(meta, BYTES[..BYTES.len() - 4]); }
use anyhow::{bail, Result};
use indexmap::IndexMap;
use std::str::from_utf8;
use url::Url;