use base64::Engine;
use bytes::BytesMut;
pub(crate) fn extract_line(buf: &mut BytesMut) -> Option<Vec<u8>> {
const MAX_LINE: usize = 8192;
loop {
let eol = find_crlf(buf);
if eol.is_none() {
if buf.len() > MAX_LINE {
buf.clear();
}
return None;
}
let eol = eol.unwrap();
if eol > MAX_LINE {
advance_by(buf, eol + 2);
continue;
}
let line = buf[..eol].to_vec();
advance_by(buf, eol + 2);
return Some(line);
}
}
fn find_crlf(buf: &BytesMut) -> Option<usize> {
buf.windows(2).position(|w| w == b"\r\n")
}
pub(crate) fn advance_by(buf: &mut BytesMut, n: usize) {
use bytes::Buf;
let n = n.min(buf.len());
Buf::advance(buf, n);
}
pub(crate) fn split_command(line: &[u8]) -> Option<(&str, &str)> {
let s = std::str::from_utf8(line).ok()?;
let s = s.trim_end();
if s.is_empty() {
return None;
}
if let Some(sp) = s.find(|c: char| c.is_whitespace()) {
let (verb, rest) = s.split_at(sp);
Some((verb, rest.trim_start()))
} else {
Some((s, ""))
}
}
pub(crate) fn split_reply(line: &[u8]) -> Option<(u16, &str, bool)> {
let s = std::str::from_utf8(line).ok()?;
if s.len() < 4 {
return None;
}
let code: u16 = s[..3].parse().ok()?;
let separator = s.as_bytes()[3];
let text = s[4..].trim_end();
Some((code, text, separator == b' '))
}
pub(crate) fn extract_envelope_address(args: &str) -> Option<String> {
let after_colon = args.split_once(':').map(|(_, r)| r).unwrap_or(args);
let trimmed = after_colon.trim_start();
if !trimmed.starts_with('<') {
return None;
}
let end = trimmed.find('>')?;
Some(trimmed[1..end].to_string())
}
pub(crate) fn decode_auth_plain(b64: &str) -> Option<(String, String)> {
let raw = base64::engine::general_purpose::STANDARD
.decode(b64.trim())
.ok()?;
let mut parts = raw.split(|&b| b == 0);
let _authzid = parts.next();
let authcid = parts.next()?;
let password = parts.next()?;
Some((
String::from_utf8_lossy(authcid).to_string(),
String::from_utf8_lossy(password).to_string(),
))
}
pub(crate) fn decode_auth_login_token(b64: &str) -> Option<String> {
let raw = base64::engine::general_purpose::STANDARD
.decode(b64.trim())
.ok()?;
Some(String::from_utf8_lossy(&raw).to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extract_line_handles_one_line() {
let mut buf = BytesMut::from(&b"EHLO example.org\r\n"[..]);
let line = extract_line(&mut buf).expect("line");
assert_eq!(line, b"EHLO example.org");
assert!(buf.is_empty());
}
#[test]
fn extract_line_drops_overlong() {
let mut buf = BytesMut::from(vec![b'X'; 10_000].as_slice());
let _ = extract_line(&mut buf);
assert!(buf.is_empty());
}
#[test]
fn split_command_basic() {
let (v, a) = split_command(b"MAIL FROM:<alice@x.com>").unwrap();
assert_eq!(v, "MAIL");
assert_eq!(a, "FROM:<alice@x.com>");
}
#[test]
fn split_command_tab_separated() {
let (v, a) = split_command(b"EHLO\texample.org").unwrap();
assert_eq!(v, "EHLO");
assert_eq!(a, "example.org");
}
#[test]
fn split_reply_final_line() {
let (code, text, is_final) = split_reply(b"250 OK").unwrap();
assert_eq!(code, 250);
assert_eq!(text, "OK");
assert!(is_final);
}
#[test]
fn split_reply_intermediate_line() {
let (code, _, is_final) = split_reply(b"250-PIPELINING").unwrap();
assert_eq!(code, 250);
assert!(!is_final);
}
#[test]
fn extract_envelope_address_basic() {
let addr = extract_envelope_address("FROM:<alice@example.com>").unwrap();
assert_eq!(addr, "alice@example.com");
}
#[test]
fn extract_envelope_address_with_size_param() {
let addr = extract_envelope_address("FROM:<bob@x.com> SIZE=1024").unwrap();
assert_eq!(addr, "bob@x.com");
}
#[test]
fn extract_envelope_address_null_sender() {
let addr = extract_envelope_address("FROM:<>").unwrap();
assert_eq!(addr, "");
}
#[test]
fn extract_envelope_address_no_angles_is_none() {
assert!(extract_envelope_address("FROM:alice@x.com").is_none());
}
#[test]
fn decode_auth_plain_basic() {
let b64 = base64::engine::general_purpose::STANDARD.encode("\0alice\0secret");
let (user, pass) = decode_auth_plain(&b64).expect("decode");
assert_eq!(user, "alice");
assert_eq!(pass, "secret");
}
#[test]
fn decode_auth_plain_with_authzid() {
let b64 = base64::engine::general_purpose::STANDARD.encode("admin\0alice\0secret");
let (user, pass) = decode_auth_plain(&b64).expect("decode");
assert_eq!(user, "alice");
assert_eq!(pass, "secret");
}
#[test]
fn decode_auth_login_token_basic() {
let b64 = base64::engine::general_purpose::STANDARD.encode("alice");
assert_eq!(decode_auth_login_token(&b64), Some("alice".to_string()));
}
#[test]
fn decode_invalid_base64_returns_none() {
assert!(decode_auth_plain("!!!not-base64!!!").is_none());
assert!(decode_auth_login_token("!!!").is_none());
}
}