///|
pub fn parse_message(raw : String) -> ParseResult {
let split = split_header_body(raw)
let headers = parse_header_block(split.0)
let root = parse_part_from_headers(headers, split.1)
let msg = {
headers,
from: parse_address_list(header_value(headers, "From")),
to: parse_address_list(header_value(headers, "To")),
cc: parse_address_list(header_value(headers, "Cc")),
subject: normalize_subject(header_value(headers, "Subject")),
date: header_value(headers, "Date"),
message_id: header_value(headers, "Message-ID"),
root,
}
{ message: msg, issues: collect_message_issues(msg) }
}
///|
fn split_header_body(raw : String) -> (String, String) {
let lines = split_lines(raw)
let mut header_text = ""
let mut body_text = ""
let mut in_body = false
for line in lines {
if !in_body && line == "" {
in_body = true
} else if in_body {
body_text = body_text + line + "\n"
} else {
header_text = header_text + line + "\n"
}
}
(header_text, body_text)
}
///|
fn parse_part(raw : String) -> MailPart {
let split = split_header_body(raw)
let headers = parse_header_block(split.0)
parse_part_from_headers(headers, split.1)
}
///|
fn parse_part_from_headers(headers : Array[Header], body : String) -> MailPart {
let ct = content_type_from_headers(headers)
let enc = encoding_from_headers(headers)
let disp = disposition_from_headers(headers)
let filename = filename_from_headers(headers, ct)
if ct.media_type == "multipart" && ct.boundary != "" {
let children = parse_multipart(body, ct.boundary)
mail_part(headers, ct, enc, disp, filename, body, "", children)
} else {
mail_part(headers, ct, enc, disp, filename, body, decode_body(body, enc), [])
}
}
///|
fn parse_multipart(body : String, boundary : String) -> Array[MailPart] {
let marker = "--" + boundary
let closing = marker + "--"
let lines = split_lines(body)
let parts : Array[MailPart] = []
let mut current = ""
let mut inside = false
for line in lines {
if line == marker {
if inside && current != "" {
parts.push(parse_part(current))
}
current = ""
inside = true
} else if line == closing {
if inside && current != "" {
parts.push(parse_part(current))
}
inside = false
current = ""
} else if inside {
current = current + line + "\n"
}
}
parts
}
///|
fn collect_message_issues(message : MailMessage) -> Array[ParseIssue] {
let issues : Array[ParseIssue] = []
if message.from.length() == 0 {
issues.push(parse_issue("missing.from", "message has no From header"))
}
if message.to.length() == 0 {
issues.push(parse_issue("missing.to", "message has no To header"))
}
if message.subject == "" {
issues.push(parse_issue("missing.subject", "message has no Subject header"))
}
collect_part_issues(message.root, issues)
issues
}
///|
fn collect_part_issues(part : MailPart, issues : Array[ParseIssue]) -> Unit {
if part.content_type.media_type == "multipart" &&
part.content_type.boundary == "" {
issues.push(
parse_issue("missing.boundary", "multipart part has no boundary"),
)
}
for child in part.children {
collect_part_issues(child, issues)
}
}