#![allow(dead_code)]
use crate::errors::{error, nil, New};
use crate::net::textproto::MIMEHeader;
use crate::types::string;
use std::io::{Read, Write};
pub struct Writer<W: Write> {
w: W,
boundary: string,
last_part_open: bool,
wrote_first_boundary: bool,
}
pub fn NewWriter<W: Write>(w: W) -> Writer<W> {
Writer { w, boundary: random_boundary(), last_part_open: false, wrote_first_boundary: false }
}
pub fn NewReader<R: Read>(r: R, boundary: &str) -> Reader {
Reader::NewReader(r, boundary)
}
impl<W: Write> Writer<W> {
pub fn NewWriter(w: W) -> Writer<W> {
Writer { w, boundary: random_boundary(), last_part_open: false, wrote_first_boundary: false }
}
pub fn Boundary(&self) -> string { self.boundary.clone() }
pub fn SetBoundary(&mut self, boundary: &str) -> error {
if !valid_boundary(boundary) {
return New(&format!("mime: invalid boundary character"));
}
self.boundary = boundary.to_string();
nil
}
pub fn FormDataContentType(&self) -> string {
format!("multipart/form-data; boundary={}", self.boundary)
}
pub fn CreatePart(&mut self, header: MIMEHeader) -> (Part<'_, W>, error) {
if self.last_part_open {
}
let sep: &[u8] = if !self.wrote_first_boundary { b"" } else { b"\r\n" };
if let Err(e) = self.w.write_all(sep) {
return (Part::dummy(), New(&e.to_string()));
}
if let Err(e) = write!(self.w, "--{}\r\n", self.boundary) {
return (Part::dummy(), New(&e.to_string()));
}
self.wrote_first_boundary = true;
for k in header.Keys() {
for v in header.Values(&k) {
if let Err(e) = write!(self.w, "{}: {}\r\n", k, v) {
return (Part::dummy(), New(&e.to_string()));
}
}
}
if let Err(e) = self.w.write_all(b"\r\n") {
return (Part::dummy(), New(&e.to_string()));
}
self.last_part_open = true;
(Part::new(&mut self.w), nil)
}
pub fn CreateFormFile(&mut self, fieldname: &str, filename: &str) -> (Part<'_, W>, error) {
let mut h = MIMEHeader::new();
h.Set("Content-Disposition",
&format!("form-data; name=\"{}\"; filename=\"{}\"",
escape_quotes(fieldname), escape_quotes(filename)));
h.Set("Content-Type", "application/octet-stream");
self.CreatePart(h)
}
pub fn CreateFormField(&mut self, fieldname: &str) -> (Part<'_, W>, error) {
let mut h = MIMEHeader::new();
h.Set("Content-Disposition",
&format!("form-data; name=\"{}\"", escape_quotes(fieldname)));
self.CreatePart(h)
}
pub fn WriteField(&mut self, fieldname: &str, value: &str) -> error {
let (mut p, err) = self.CreateFormField(fieldname);
if err != nil { return err; }
let (_, e) = p.Write(value.as_bytes());
e
}
pub fn Close(&mut self) -> error {
if !self.wrote_first_boundary {
if let Err(e) = write!(self.w, "--{}--\r\n", self.boundary) {
return New(&e.to_string());
}
return nil;
}
if let Err(e) = write!(self.w, "\r\n--{}--\r\n", self.boundary) {
return New(&e.to_string());
}
nil
}
}
fn escape_quotes(s: &str) -> string {
s.replace('\\', "\\\\").replace('"', "\\\"")
}
fn valid_boundary(s: &str) -> bool {
if s.is_empty() || s.len() > 70 { return false; }
for b in s.bytes() {
let ok = matches!(b,
b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z'
| b'\''..=b')' | b'+' | b'_' | b',' | b'-' | b'.' | b'/' | b':' | b'=' | b'?');
let is_space = b == b' ';
if !ok && !is_space { return false; }
}
s.as_bytes().last().map(|&c| c != b' ').unwrap_or(false)
}
fn random_boundary() -> string {
use std::sync::atomic::{AtomicU64, Ordering};
static SEQ: AtomicU64 = AtomicU64::new(0);
let n = SEQ.fetch_add(1, Ordering::Relaxed);
let t = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
format!("{:016x}{:016x}goishboundary", t ^ 0x9E3779B97F4A7C15_u64, n)
}
pub struct Part<'a, W: Write> {
w: Option<&'a mut W>,
}
impl<'a, W: Write> Part<'a, W> {
fn new(w: &'a mut W) -> Part<'a, W> { Part { w: Some(w) } }
fn dummy() -> Part<'a, W> { Part { w: None } }
pub fn Write(&mut self, data: &[u8]) -> (i64, error) {
match &mut self.w {
Some(w) => match w.write_all(data) {
Ok(_) => (data.len() as i64, nil),
Err(e) => (0, New(&e.to_string())),
},
None => (0, New("mime: write on closed part")),
}
}
}
pub struct Reader {
buf: Vec<u8>,
pos: usize,
boundary: string,
eof: bool,
first_part: bool,
}
impl Reader {
pub fn NewReader<R: Read>(mut r: R, boundary: &str) -> Reader {
let mut buf = Vec::new();
let _ = r.read_to_end(&mut buf);
Reader { buf, pos: 0, boundary: boundary.to_string(), eof: false, first_part: true }
}
pub fn NextPart(&mut self) -> (ReaderPart, error) {
if self.eof {
return (ReaderPart::default(), New("EOF"));
}
let sep = if self.first_part { format!("--{}", self.boundary) }
else { format!("\r\n--{}", self.boundary) };
let sep_b = sep.as_bytes();
let start = match find(&self.buf[self.pos..], sep_b) {
Some(i) => self.pos + i + sep_b.len(),
None => {
self.eof = true;
return (ReaderPart::default(), New("EOF"));
}
};
self.first_part = false;
if start + 2 > self.buf.len() {
self.eof = true;
return (ReaderPart::default(), New("EOF"));
}
if &self.buf[start..start + 2] == b"--" {
self.eof = true;
return (ReaderPart::default(), New("EOF"));
}
if &self.buf[start..start + 2] != b"\r\n" {
self.eof = true;
return (ReaderPart::default(), New("mime: malformed boundary (no CRLF)"));
}
let mut p = start + 2;
let mut header = MIMEHeader::new();
loop {
let line_end = match find(&self.buf[p..], b"\r\n") {
Some(i) => p + i,
None => return (ReaderPart::default(), New("mime: malformed headers")),
};
if line_end == p { p += 2; break; } let line = &self.buf[p..line_end];
if let Some(colon) = line.iter().position(|&b| b == b':') {
let k = std::str::from_utf8(&line[..colon]).unwrap_or("");
let v = std::str::from_utf8(&line[colon + 1..]).unwrap_or("").trim();
header.Add(k, v);
}
p = line_end + 2;
}
let end_sep = format!("\r\n--{}", self.boundary);
let end = match find(&self.buf[p..], end_sep.as_bytes()) {
Some(i) => p + i,
None => {
self.eof = true;
return (ReaderPart::default(), New("mime: unterminated part"));
}
};
let body = self.buf[p..end].to_vec();
self.pos = end;
(ReaderPart { header, body, read: 0 }, nil)
}
}
fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
if needle.is_empty() { return Some(0); }
if haystack.len() < needle.len() { return None; }
for i in 0..=(haystack.len() - needle.len()) {
if &haystack[i..i + needle.len()] == needle { return Some(i); }
}
None
}
#[derive(Default)]
pub struct ReaderPart {
pub header: MIMEHeader,
body: Vec<u8>,
read: usize,
}
impl ReaderPart {
pub fn FormName(&self) -> string {
let cd = self.header.Get("Content-Disposition");
parse_param(&cd, "name").unwrap_or_default()
}
pub fn FileName(&self) -> string {
let cd = self.header.Get("Content-Disposition");
parse_param(&cd, "filename").unwrap_or_default()
}
pub fn Header(&self) -> &MIMEHeader { &self.header }
pub fn Body(&self) -> &[u8] { &self.body }
}
impl Read for ReaderPart {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let n = (self.body.len() - self.read).min(buf.len());
if n == 0 { return Ok(0); }
buf[..n].copy_from_slice(&self.body[self.read..self.read + n]);
self.read += n;
Ok(n)
}
}
fn parse_param(cd: &str, name: &str) -> Option<string> {
let prefix = format!("{}=", name);
let idx = cd.find(&prefix)?;
let rest = &cd[idx + prefix.len()..];
if let Some(stripped) = rest.strip_prefix('"') {
let end = stripped.find('"')?;
return Some(stripped[..end].to_string());
}
let end = rest.find(|c: char| c == ';' || c == ' ').unwrap_or(rest.len());
Some(rest[..end].to_string())
}