mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
189 lines
6.6 KiB
Rust
189 lines
6.6 KiB
Rust
//! # format=flowed support.
|
|
//!
|
|
//! Format=flowed is defined in
|
|
//! [RFC 3676](https://tools.ietf.org/html/rfc3676).
|
|
//!
|
|
//! Older [RFC 2646](https://tools.ietf.org/html/rfc2646) is used
|
|
//! during formatting, i.e., DelSp parameter introduced in RFC 3676
|
|
//! is assumed to be set to "no".
|
|
//!
|
|
//! For received messages, DelSp parameter is honoured.
|
|
|
|
/// Wraps line to 72 characters using format=flowed soft breaks.
|
|
///
|
|
/// 72 characters is the limit recommended by RFC 3676.
|
|
///
|
|
/// The function breaks line only after SP and before non-whitespace
|
|
/// characters. It also does not insert breaks before ">" to avoid the
|
|
/// need to do space stuffing (see RFC 3676) for quotes.
|
|
///
|
|
/// If there are long words, line may still exceed the limits on line
|
|
/// length. However, this should be rare and should not result in
|
|
/// immediate mail rejection: SMTP (RFC 2821) limit is 998 characters,
|
|
/// and Spam Assassin limit is 78 characters.
|
|
fn format_line_flowed(line: &str, prefix: &str) -> String {
|
|
let mut result = String::new();
|
|
let mut buffer = prefix.to_string();
|
|
let mut after_space = false;
|
|
|
|
for c in line.chars() {
|
|
if c == ' ' {
|
|
buffer.push(c);
|
|
after_space = true;
|
|
} else if c == '>' {
|
|
if buffer.is_empty() {
|
|
// Space stuffing, see RFC 3676
|
|
buffer.push(' ');
|
|
}
|
|
buffer.push(c);
|
|
after_space = false;
|
|
} else {
|
|
if after_space && buffer.len() >= 72 && !c.is_whitespace() {
|
|
// Flush the buffer and insert soft break (SP CRLF).
|
|
result += &buffer;
|
|
result += "\r\n";
|
|
buffer = prefix.to_string();
|
|
}
|
|
buffer.push(c);
|
|
after_space = false;
|
|
}
|
|
}
|
|
result + &buffer
|
|
}
|
|
|
|
fn format_flowed_prefix(text: &str, prefix: &str) -> String {
|
|
let mut result = String::new();
|
|
|
|
for line in text.split('\n') {
|
|
if !result.is_empty() {
|
|
result += "\r\n";
|
|
}
|
|
let line = line.trim_end();
|
|
if prefix.len() + line.len() > 78 {
|
|
result += &format_line_flowed(line, prefix);
|
|
} else {
|
|
result += prefix;
|
|
if prefix.is_empty() && line.starts_with('>') {
|
|
// Space stuffing, see RFC 3676
|
|
result.push(' ');
|
|
}
|
|
result += line;
|
|
}
|
|
}
|
|
result
|
|
}
|
|
|
|
/// Returns text formatted according to RFC 3767 (format=flowed).
|
|
///
|
|
/// This function accepts text separated by LF, but returns text
|
|
/// separated by CRLF.
|
|
///
|
|
/// RFC 2646 technique is used to insert soft line breaks, so DelSp
|
|
/// SHOULD be set to "no" when sending.
|
|
pub fn format_flowed(text: &str) -> String {
|
|
format_flowed_prefix(text, "")
|
|
}
|
|
|
|
/// Same as format_flowed(), but adds "> " prefix to each line.
|
|
pub fn format_flowed_quote(text: &str) -> String {
|
|
format_flowed_prefix(text, "> ")
|
|
}
|
|
|
|
/// Joins lines in format=flowed text.
|
|
///
|
|
/// Lines must be separated by single LF.
|
|
///
|
|
/// Quote processing is not supported, it is assumed that they are
|
|
/// deleted during simplification.
|
|
///
|
|
/// Signature separator line is not processed here, it is assumed to
|
|
/// be stripped beforehand.
|
|
pub fn unformat_flowed(text: &str, delsp: bool) -> String {
|
|
let mut result = String::new();
|
|
let mut skip_newline = true;
|
|
|
|
for line in text.split('\n') {
|
|
// Revert space-stuffing
|
|
let line = line.strip_prefix(' ').unwrap_or(line);
|
|
|
|
if !skip_newline {
|
|
result.push('\n');
|
|
}
|
|
|
|
if let Some(line) = line.strip_suffix(' ') {
|
|
// Flowed line
|
|
result += line;
|
|
if !delsp {
|
|
result.push(' ');
|
|
}
|
|
skip_newline = true;
|
|
} else {
|
|
// Fixed line
|
|
result += line;
|
|
skip_newline = false;
|
|
}
|
|
}
|
|
result
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_format_flowed() {
|
|
let text = "Foo bar baz";
|
|
assert_eq!(format_flowed(text), "Foo bar baz");
|
|
|
|
let text = "This is the Autocrypt Setup Message used to transfer your key between clients.\n\
|
|
\n\
|
|
To decrypt and use your key, open the message in an Autocrypt-compliant client and enter the setup code presented on the generating device.";
|
|
let expected = "This is the Autocrypt Setup Message used to transfer your key between clients.\r\n\
|
|
\r\n\
|
|
To decrypt and use your key, open the message in an Autocrypt-compliant \r\n\
|
|
client and enter the setup code presented on the generating device.";
|
|
assert_eq!(format_flowed(text), expected);
|
|
|
|
let text = "> Not a quote";
|
|
assert_eq!(format_flowed(text), " > Not a quote");
|
|
|
|
// Test space stuffing of wrapped lines
|
|
let text = "> This is the Autocrypt Setup Message used to transfer your key between clients.\n\
|
|
> \n\
|
|
> To decrypt and use your key, open the message in an Autocrypt-compliant client and enter the setup code presented on the generating device.";
|
|
let expected = "\x20> This is the Autocrypt Setup Message used to transfer your key between \r\n\
|
|
clients.\r\n\
|
|
\x20>\r\n\
|
|
\x20> To decrypt and use your key, open the message in an Autocrypt-compliant \r\n\
|
|
client and enter the setup code presented on the generating device.";
|
|
assert_eq!(format_flowed(text), expected);
|
|
}
|
|
|
|
#[test]
|
|
fn test_unformat_flowed() {
|
|
let text = "this is a very long message that should be wrapped using format=flowed and \n\
|
|
unwrapped on the receiver";
|
|
let expected =
|
|
"this is a very long message that should be wrapped using format=flowed and \
|
|
unwrapped on the receiver";
|
|
assert_eq!(unformat_flowed(text, false), expected);
|
|
}
|
|
|
|
#[test]
|
|
fn test_format_flowed_quote() {
|
|
let quote = "this is a quoted line";
|
|
let expected = "> this is a quoted line";
|
|
assert_eq!(format_flowed_quote(quote), expected);
|
|
|
|
let quote = "> foo bar baz";
|
|
let expected = "> > foo bar baz";
|
|
assert_eq!(format_flowed_quote(quote), expected);
|
|
|
|
let quote = "this is a very long quote that should be wrapped using format=flowed and unwrapped on the receiver";
|
|
let expected =
|
|
"> this is a very long quote that should be wrapped using format=flowed and \r\n\
|
|
> unwrapped on the receiver";
|
|
assert_eq!(format_flowed_quote(quote), expected);
|
|
}
|
|
}
|