AbdulElahGwaith's picture
Upload folder using huggingface_hub
0220cd3 verified
//! # 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.
#![cfg_attr(not(test), forbid(clippy::indexing_slicing))]
#![cfg_attr(not(test), forbid(clippy::string_slice))]
/// 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 = prefix.ends_with(' ');
for c in line.chars() {
if c == ' ' {
if buffer.is_empty() {
// Space stuffing, see RFC 3676
buffer.push(' ');
}
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
}
/// Returns text formatted according to RFC 3676 (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 {
let mut result = String::new();
for line in text.split('\n') {
if !result.is_empty() {
result += "\r\n";
}
let line = line.trim_end();
let quote_depth = line.chars().take_while(|&c| c == '>').count();
let (prefix, mut line) = line.split_at(quote_depth);
let mut prefix = prefix.to_string();
if quote_depth > 0 {
if let Some(s) = line.strip_prefix(' ') {
line = s;
prefix += " ";
}
}
result += &format_line_flowed(line, &prefix);
}
result
}
/// Same as format_flowed(), but adds "> " prefix to each line.
pub fn format_flowed_quote(text: &str) -> String {
let mut result = String::new();
for line in text.split('\n') {
if !result.is_empty() {
result += "\n";
}
result += "> ";
result += line;
}
format_flowed(&result)
}
/// Joins lines in format=flowed text.
///
/// Lines must be separated by single LF.
///
/// 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') {
let line = if !result.is_empty() && skip_newline {
line.trim_start_matches('>')
} else {
line
};
// 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 = "";
assert_eq!(format_flowed(text), "");
let text = "Foo bar baz";
assert_eq!(format_flowed(text), text);
let text = ">Foo bar";
assert_eq!(format_flowed(text), text);
let text = "> Foo bar";
assert_eq!(format_flowed(text), text);
let text = ">\n\nA";
assert_eq!(format_flowed(text), ">\r\n\r\nA");
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 = "> A quote";
assert_eq!(format_flowed(text), "> A quote");
let text = "> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx > A";
assert_eq!(
format_flowed(text),
"> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx > \r\n> A"
);
// 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 = "> This is the Autocrypt Setup Message used to transfer your key between \r\n\
> 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 = ">> 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 \r\n\
>> 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);
// Test space stuffing of spaces.
let text = " Foo bar baz";
assert_eq!(format_flowed(text), " Foo bar baz");
let text = " Foo bar baz";
assert_eq!(format_flowed(text), " Foo bar baz");
let text =
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAA";
let expected =
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA \r\nAAAAAA";
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);
let text = " Foo bar";
let expected = " Foo bar";
assert_eq!(unformat_flowed(text, false), expected);
let text =
"> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx > \n> A";
let expected =
"> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx > A";
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 = "first quoted line\nsecond quoted line";
let expected = "> first quoted line\r\n> second 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);
}
}