#![allow(dead_code)]
use crate::errors::{error, nil, New};
use crate::types::string;
use std::collections::HashMap;
use std::io::{BufRead, BufReader, Read, Write};
#[derive(Debug, Clone, Default)]
pub struct MIMEHeader {
entries: Vec<(string, Vec<string>)>,
}
impl MIMEHeader {
pub fn new() -> MIMEHeader { MIMEHeader::default() }
pub fn Add(&mut self, key: &str, value: &str) {
let k = CanonicalMIMEHeaderKey(key);
for (ek, ev) in self.entries.iter_mut() {
if *ek == k {
ev.push(value.into());
return;
}
}
self.entries.push((k, vec![value.into()]));
}
pub fn Set(&mut self, key: &str, value: &str) {
let k = CanonicalMIMEHeaderKey(key);
for (ek, ev) in self.entries.iter_mut() {
if *ek == k {
*ev = vec![value.into()];
return;
}
}
self.entries.push((k, vec![value.into()]));
}
pub fn Get(&self, key: &str) -> string {
let k = CanonicalMIMEHeaderKey(key);
for (ek, ev) in self.entries.iter() {
if *ek == k {
return ev.first().cloned().unwrap_or_default();
}
}
"".into()
}
pub fn Values(&self, key: &str) -> Vec<string> {
let k = CanonicalMIMEHeaderKey(key);
for (ek, ev) in self.entries.iter() {
if *ek == k { return ev.clone(); }
}
Vec::new()
}
pub fn Del(&mut self, key: &str) {
let k = CanonicalMIMEHeaderKey(key);
self.entries.retain(|(ek, _)| *ek != k);
}
pub fn Len(&self) -> i64 { self.entries.len() as i64 }
pub fn Keys(&self) -> Vec<string> {
self.entries.iter().map(|(k, _)| k.clone()).collect()
}
}
pub fn CanonicalMIMEHeaderKey(key: &str) -> string {
if !is_valid_header_key(key) {
return key.into();
}
let mut out = std::string::String::with_capacity(key.len());
let mut upper = true;
for c in key.chars() {
if upper { out.push(c.to_ascii_uppercase()); }
else { out.push(c.to_ascii_lowercase()); }
upper = c == '-';
}
out.into()
}
fn is_valid_header_key(key: &str) -> bool {
if key.is_empty() { return false; }
for b in key.bytes() {
let ok = matches!(b,
b'!' | b'#' | b'$' | b'%' | b'&' | b'\'' | b'*' | b'+'
| b'-' | b'.' | b'^' | b'_' | b'`' | b'|' | b'~'
| b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z');
if !ok { return false; }
}
true
}
pub struct Reader<R: Read> {
r: BufReader<R>,
dot_state: Option<DotState>,
}
enum DotState {
Start,
Mid,
EndCR,
Done,
}
pub fn NewReader<R: Read>(r: R) -> Reader<R> {
Reader { r: BufReader::new(r), dot_state: None }
}
pub fn NewWriter<W: Write>(w: W) -> Writer<W> {
Writer { w }
}
impl<R: Read> Reader<R> {
pub fn NewReader(r: R) -> Reader<R> {
Reader { r: BufReader::new(r), dot_state: None }
}
pub fn ReadLine(&mut self) -> (string, error) {
let mut buf = std::string::String::new();
match self.r.read_line(&mut buf) {
Ok(0) => ("".into(), New("EOF")),
Ok(_) => {
if buf.ends_with('\n') { buf.pop(); }
if buf.ends_with('\r') { buf.pop(); }
(buf.into(), nil)
}
Err(e) => ("".into(), New(&e.to_string())),
}
}
pub fn ReadContinuedLine(&mut self) -> (string, error) {
let (first, err) = self.ReadLine();
if err != nil { return (first, err); }
if first.is_empty() { return (first, nil); }
let mut out: std::string::String = first.as_str().into();
loop {
let buf = match self.r.fill_buf() {
Ok(b) if b.is_empty() => break,
Ok(b) => b,
Err(e) => return (out.into(), New(&e.to_string())),
};
if !(buf[0] == b' ' || buf[0] == b'\t') { break; }
let (cont, err) = self.ReadLine();
if err != nil { return (out.into(), err); }
while out.ends_with(' ') || out.ends_with('\t') { out.pop(); }
out.push(' ');
out.push_str(cont.trim_start_matches(|c: char| c == ' ' || c == '\t'));
}
(out.into(), nil)
}
pub fn ReadMIMEHeader(&mut self) -> (MIMEHeader, error) {
let mut h = MIMEHeader::new();
loop {
let (line, err) = self.ReadContinuedLine();
if err != nil && line.is_empty() {
if h.Len() == 0 { return (h, err); }
return (h, err);
}
if line.is_empty() { return (h, nil); }
let colon = match line.find(':') {
Some(i) => i,
None => return (h, New(&format!("malformed MIME header line: {}", line))),
};
let key: string = line[..colon].into();
if key.is_empty() {
return (h, New(&format!("malformed MIME header line: {}", line)));
}
for b in key.bytes() {
if b == b'\r' || b == b'\n' {
return (h, New(&format!("malformed MIME header line: {}", line)));
}
}
let value: string = line[colon + 1..].trim_matches(|c: char| c == ' ' || c == '\t').into();
h.Add(&key, &value);
}
}
pub fn ReadCodeLine(&mut self, expect: i64) -> (i64, string, error) {
let (line, err) = self.ReadLine();
if err != nil { return (0, "".into(), err); }
if line.len() < 4 {
return (0, line.clone(), New(&format!("short response: {}", line)));
}
let code: i64 = match line[..3].parse() {
Ok(c) => c,
Err(_) => return (0, line.clone(), New(&format!("invalid response code: {}", line))),
};
let sep = line.as_bytes()[3];
if sep != b' ' && sep != b'-' {
return (code, line[4..].into(),
New(&format!("invalid response separator: {}", line)));
}
let msg: string = line[4..].into();
if expect != 0 {
let ok = if expect >= 100 {
code == expect
} else if expect >= 10 {
code / 10 == expect
} else {
code / 100 == expect
};
if !ok {
return (code, msg.clone(), New(&format!("{} {}", code, msg)));
}
}
(code, msg, nil)
}
pub fn ReadDotLines(&mut self) -> (Vec<string>, error) {
let mut lines = Vec::new();
loop {
let (line, err) = self.ReadLine();
if err != nil { return (lines, err); }
if line == "." { return (lines, nil); }
let unstuffed = if let Some(r) = line.strip_prefix('.') { r.into() } else { line };
lines.push(unstuffed);
}
}
pub fn ReadDotBytes(&mut self) -> (Vec<u8>, error) {
let (lines, err) = self.ReadDotLines();
if err != nil { return (Vec::new(), err); }
let mut out = Vec::new();
for l in lines {
out.extend_from_slice(l.as_bytes());
out.push(b'\n');
}
(out, nil)
}
}
pub struct Writer<W: Write> {
w: W,
}
impl<W: Write> Writer<W> {
pub fn NewWriter(w: W) -> Writer<W> { Writer { w } }
pub fn PrintfLine(&mut self, format: &str, args: &[&dyn std::fmt::Display]) -> error {
let mut s: std::string::String = format.into();
for a in args {
let needle = "%s";
if let Some(i) = s.find(needle) {
s.replace_range(i..i + needle.len(), &a.to_string());
}
}
s.push_str("\r\n");
match self.w.write_all(s.as_bytes()) {
Ok(_) => nil,
Err(e) => New(&e.to_string()),
}
}
pub fn DotWriter(&mut self) -> DotWriter<'_, W> {
DotWriter { w: &mut self.w, at_line_start: true, closed: false }
}
}
pub struct DotWriter<'a, W: Write> {
w: &'a mut W,
at_line_start: bool,
closed: bool,
}
impl<'a, W: Write> DotWriter<'a, W> {
pub fn Write(&mut self, data: &[u8]) -> (i64, error) {
let mut n = 0;
for &b in data {
if self.at_line_start && b == b'.' {
if let Err(e) = self.w.write_all(b".") {
return (n, New(&e.to_string()));
}
}
if let Err(e) = self.w.write_all(&[b]) {
return (n, New(&e.to_string()));
}
n += 1;
self.at_line_start = b == b'\n';
}
(n, nil)
}
pub fn Close(&mut self) -> error {
if self.closed { return nil; }
self.closed = true;
let tail: &[u8] = if self.at_line_start { b".\r\n" } else { b"\r\n.\r\n" };
match self.w.write_all(tail) {
Ok(_) => nil,
Err(e) => New(&e.to_string()),
}
}
}
impl<'a, W: Write> Drop for DotWriter<'a, W> {
fn drop(&mut self) { let _ = self.Close(); }
}
pub fn TrimString(s: &str) -> string {
s.trim_matches(|c: char| c == ' ' || c == '\t').into()
}
pub fn TrimBytes(b: &[u8]) -> Vec<u8> {
let mut start = 0;
let mut end = b.len();
while start < end && (b[start] == b' ' || b[start] == b'\t') { start += 1; }
while end > start && (b[end - 1] == b' ' || b[end - 1] == b'\t') { end -= 1; }
b[start..end].to_vec()
}
#[allow(dead_code)]
fn _unused(_: HashMap<string, Vec<string>>) {}