///|
/// Transfer-encoding choice for a body part (RFC 2045 section 6.1).
pub(all) enum ContentTransferEncoding {
/// 7bit ASCII only.
SevenBit
/// 8bit (raw) bytes.
EightBit
/// Base64.
Base64
/// Quoted-Printable.
QuotedPrintable
/// Choose automatically during render.
Auto
} derive(Eq, Debug)
///|
pub fn ContentTransferEncoding::to_string(
self : ContentTransferEncoding,
) -> String {
match self {
SevenBit => "7bit"
EightBit => "8bit"
Base64 => "base64"
QuotedPrintable => "quoted-printable"
Auto => "auto"
}
}
///|
pub impl Show for ContentTransferEncoding with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
/// True when every byte of `text` is ASCII (<= 127).
pub fn is_pure_ascii(s : String) -> Bool {
for b in string_to_bytes(s) {
if b.to_int() > 127 {
return false
}
}
true
}
///|
/// Check a 7bit body: every byte must be <= 127 (MM_ENCOD_003).
pub fn check_seven_bit(s : String) -> Unit raise MailFailure {
guard is_pure_ascii(s) else {
raise MailFailure::of(
Encode,
MM_ENCOD_003,
"7bit body contains non-ASCII bytes",
)
}
}
///|
/// Encode text content with an explicit transfer encoding.
/// Returns the encoded body (which already uses CRLF line endings) together
/// with the effective encoding that was actually applied (relevant for `Auto`).
pub fn encode_body(
content : String,
encoding : ContentTransferEncoding,
) -> (String, ContentTransferEncoding) raise MailFailure {
match encoding {
SevenBit => {
check_seven_bit(content)
(normalize_crlf(content), SevenBit)
}
EightBit => (normalize_crlf(content), EightBit)
Base64 => (base64_mime_encode(string_to_bytes(content)), Base64)
QuotedPrintable => (qp_encode(content), QuotedPrintable)
Auto => auto_encode_text(content)
}
}
///|
/// Pick a deterministic encoding for text: 7bit when the content is pure ASCII
/// and every line fits in 76 characters, otherwise Quoted-Printable.
fn auto_encode_text(content : String) -> (String, ContentTransferEncoding) {
if is_pure_ascii(content) && max_line_bytes(content) <= 76 {
(normalize_crlf(content), SevenBit)
} else {
(qp_encode(content), QuotedPrintable)
}
}
///|
/// Maximum byte length of a single line in `content`.
fn max_line_bytes(content : String) -> Int {
let lines = to_lines(content)
let mut m = 0
for line in lines {
let l = string_to_bytes(line).length()
if l > m {
m = l
}
}
m
}