///|
/// Normalize every line ending to CRLF: `\r\n` stays, a lone `\r` becomes
/// `\r\n`, and a lone `\n` becomes `\r\n` (MM_CRLF_001).
pub fn normalize_crlf(s : String) -> String {
let b = string_to_bytes(s)
let out = FixedArray::make(b.length() * 2, b'\x00')
let mut i = 0
let mut j = 0
while i < b.length() {
if b[i] == b'\r' {
if i + 1 < b.length() && b[i + 1] == b'\n' {
out[j] = b'\r'
out[j + 1] = b'\n'
i += 2
j += 2
} else {
out[j] = b'\r'
out[j + 1] = b'\n'
i += 1
j += 2
}
} else if b[i] == b'\n' {
out[j] = b'\r'
out[j + 1] = b'\n'
i += 1
j += 2
} else {
out[j] = b[i]
i += 1
j += 1
}
}
bytes_to_string(Bytes::from_iter(out.iter())[0:j].to_owned())
}
///|
/// Split text into lines on `\n`, stripping a trailing `\r`. A trailing
/// newline does not produce an empty final line.
pub fn to_lines(s : String) -> Array[String] {
let lines = []
let b = string_to_bytes(s)
let mut start = 0
let mut saw_newline = false
for i = 0; i < b.length(); i = i + 1 {
if b[i] == b'\n' {
saw_newline = true
let end = if i > 0 && b[i - 1] == b'\r' { i - 1 } else { i }
lines.push(bytes_to_string(b[start:end].to_owned()))
start = i + 1
}
}
if start < b.length() || (start == 0 && b.length() == 0) {
lines.push(bytes_to_string(b[start:b.length()].to_owned()))
} else if start == b.length() && !saw_newline {
lines.push("")
}
lines
}
///|
/// Join lines with CRLF.
pub fn join_crlf(lines : Array[String]) -> String {
let buf = Buffer::Buffer()
for i, line in lines {
if i > 0 {
buf.write_string_utf16le("\r\n")
}
buf.write_string_utf16le(line)
}
buf.to_string()
}
///|
/// Dot-stuff every line of `s`: a line starting with `.` gets a leading `.`
/// prepended (MM_DOT_001). Operates on the already CRLF-normalized text.
pub fn dot_stuff(s : String) -> String {
let b = string_to_bytes(s)
let out = FixedArray::make(b.length() + 8, b'\x00')
let mut j = 0
let mut at_line_start = true
for i = 0; i < b.length(); i = i + 1 {
if at_line_start && b[i] == b'.' {
out[j] = b'.'
j += 1
}
out[j] = b[i]
j += 1
at_line_start = b[i] == b'\n'
}
bytes_to_string(Bytes::from_iter(out.iter())[0:j].to_owned())
}
///|
/// Check that every line of `s` (split on LF) is at most `max` bytes. Used to
/// enforce the RFC 5322 998-octet hard limit before transmission (MM_ENCOD_005).
pub fn check_line_lengths(s : String, max : Int) -> Unit raise MailFailure {
let lines = to_lines(s)
for line in lines {
if string_to_bytes(line).length() > max {
raise MailFailure::content_length(
"line length \{string_to_bytes(line).length()} exceeds limit \{max}",
)
}
}
}