///|
/// A single SMTP reply line: a 3-digit code, an optional enhanced status code
/// (RFC 3463, e.g. `2.1.0`), the text, and whether more lines follow.
pub struct ReplyLine {
code : Int
enhanced_code : String?
text : String
more : Bool
} derive(Eq, Debug)
///|
/// Parse a single SMTP reply line of the form `ddd[- ]text` where `-` means
/// more lines follow and ` ` means this is the last line.
pub fn parse_reply_line(line : String) -> ReplyLine? {
let b = string_to_bytes(line)
if b.length() < 3 {
return None
}
let mut code = 0
for i = 0; i < 3; i = i + 1 {
let c = b[i].to_int()
if c < 0x30 || c > 0x39 {
return None
}
code = code * 10 + (c - 0x30)
}
if b.length() == 3 {
return Some({ code, enhanced_code: None, text: "", more: false })
}
let sep = b[3]
if sep != b'-' && sep != b' ' {
return None
}
let more = sep == b'-'
let text = line[4:line.length()].to_owned()
let enhanced = parse_enhanced_code(text)
Some({ code, enhanced_code: enhanced, text, more })
}
///|
/// Extract an enhanced status code `X.Y.Z` from the start of the text.
fn parse_enhanced_code(text : String) -> String? {
if text.length() < 5 {
return None
}
let b = string_to_bytes(text)
let d1 = b[0]
let d2 = b[2]
let d3 = b[4]
if b.length() < 5 || b[1] != b'.' || b[3] != b'.' {
return None
}
if !is_digit_byte(d1) || !is_digit_byte(d2) || !is_digit_byte(d3) {
return None
}
Some(text[0:5].to_owned())
}
///|
/// A fully collected server reply (all lines of a multi-line response).
pub struct ServerReply {
code : Int
enhanced_code : String?
lines : Array[String]
text : String
} derive(Eq, Debug)
///|
pub fn ServerReply::code(self : ServerReply) -> Int {
self.code
}
///|
pub fn ServerReply::enhanced_code(self : ServerReply) -> String? {
self.enhanced_code
}
///|
pub fn ServerReply::lines(self : ServerReply) -> Array[String] {
self.lines
}
///|
pub fn ServerReply::text(self : ServerReply) -> String {
self.text
}
///|
/// The reply carries a 2xx positive completion code.
pub fn ServerReply::is_positive(self : ServerReply) -> Bool {
self.code >= 200 && self.code < 300
}
///|
/// The reply carries a 3xx intermediate code.
pub fn ServerReply::is_intermediate(self : ServerReply) -> Bool {
self.code >= 300 && self.code < 400
}
///|
/// The reply carries a 4xx transient negative completion code.
pub fn ServerReply::is_transient(self : ServerReply) -> Bool {
self.code >= 400 && self.code < 500
}
///|
/// The reply carries a 5xx permanent negative completion code.
pub fn ServerReply::is_permanent(self : ServerReply) -> Bool {
self.code >= 500 && self.code < 600
}
///|
/// Build a `ServerReply` from the collected reply lines (the raw lines that
/// were received, including code prefixes).
pub fn ServerReply::from_lines(lines : Array[String]) -> ServerReply {
let first = match parse_reply_line(lines[0]) {
Some(r) => r
None => { code: 0, enhanced_code: None, text: "", more: false }
}
let mut text_parts = []
for i, l in lines {
let parsed = parse_reply_line(l)
let t = match parsed {
Some(r) => r.text
None => l
}
if i > 0 {
text_parts.push(t)
} else {
text_parts = [t]
}
}
let text = text_parts.join("\n")
{ code: first.code, enhanced_code: first.enhanced_code, lines, text }
}
///|
pub impl Show for ServerReply with fn output(self, logger) {
logger.write_string(self.lines.join("\r\n"))
}
///|
/// Strip the leading `ddd[- ]` reply-code prefix from a line, returning just
/// the text (e.g. `250-8BITMIME` -> `8BITMIME`).
pub fn strip_reply_code(line : String) -> String {
if line.length() > 4 {
line[4:line.length()].to_owned()
} else {
""
}
}
///|
fn is_digit_byte(b : Byte) -> Bool {
b.to_int() >= 0x30 && b.to_int() <= 0x39
}