mirror of
https://github.com/chatmail/core.git
synced 2026-04-21 15:36:30 +03:00
format-flowed: make quotes round-trip
This commit is contained in:
@@ -26,6 +26,7 @@
|
|||||||
- Fix STARTTLS connection and add a test for it #3907
|
- Fix STARTTLS connection and add a test for it #3907
|
||||||
- Trigger reconnection when failing to fetch existing messages #3911
|
- Trigger reconnection when failing to fetch existing messages #3911
|
||||||
- Do not retry fetching existing messages after failure, prevents infinite reconnection loop #3913
|
- Do not retry fetching existing messages after failure, prevents infinite reconnection loop #3913
|
||||||
|
- Ensure format=flowed formatting is always reversible on the receiver side #3880
|
||||||
|
|
||||||
|
|
||||||
## 1.104.0
|
## 1.104.0
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
fn format_line_flowed(line: &str, prefix: &str) -> String {
|
fn format_line_flowed(line: &str, prefix: &str) -> String {
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
let mut buffer = prefix.to_string();
|
let mut buffer = prefix.to_string();
|
||||||
let mut after_space = false;
|
let mut after_space = prefix.ends_with(' ');
|
||||||
|
|
||||||
for c in line.chars() {
|
for c in line.chars() {
|
||||||
if c == ' ' {
|
if c == ' ' {
|
||||||
@@ -55,7 +55,7 @@ fn format_line_flowed(line: &str, prefix: &str) -> String {
|
|||||||
result + &buffer
|
result + &buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns text formatted according to RFC 3767 (format=flowed).
|
/// Returns text formatted according to RFC 3676 (format=flowed).
|
||||||
///
|
///
|
||||||
/// This function accepts text separated by LF, but returns text
|
/// This function accepts text separated by LF, but returns text
|
||||||
/// separated by CRLF.
|
/// separated by CRLF.
|
||||||
@@ -70,23 +70,20 @@ pub fn format_flowed(text: &str) -> String {
|
|||||||
result += "\r\n";
|
result += "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
let line_no_prefix = line
|
let line = line.trim_end();
|
||||||
.strip_prefix('>')
|
let quote_depth = line.chars().take_while(|&c| c == '>').count();
|
||||||
.map(|line| line.strip_prefix(' ').unwrap_or(line));
|
let (prefix, mut line) = line.split_at(quote_depth);
|
||||||
let is_quote = line_no_prefix.is_some();
|
|
||||||
let line = line_no_prefix.unwrap_or(line).trim_end();
|
|
||||||
let prefix = if is_quote { "> " } else { "" };
|
|
||||||
|
|
||||||
if prefix.len() + line.len() > 78 {
|
let mut prefix = prefix.to_string();
|
||||||
result += &format_line_flowed(line, prefix);
|
|
||||||
} else {
|
if quote_depth > 0 {
|
||||||
result += prefix;
|
if let Some(s) = line.strip_prefix(' ') {
|
||||||
if prefix.is_empty() && (line.starts_with('>') || line.starts_with(' ')) {
|
line = s;
|
||||||
// Space stuffing, see RFC 3676
|
prefix += " ";
|
||||||
result.push(' ');
|
|
||||||
}
|
}
|
||||||
result += line;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result += &format_line_flowed(line, &prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
@@ -111,9 +108,6 @@ pub fn format_flowed_quote(text: &str) -> String {
|
|||||||
///
|
///
|
||||||
/// Lines must be separated by single LF.
|
/// 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
|
/// Signature separator line is not processed here, it is assumed to
|
||||||
/// be stripped beforehand.
|
/// be stripped beforehand.
|
||||||
pub fn unformat_flowed(text: &str, delsp: bool) -> String {
|
pub fn unformat_flowed(text: &str, delsp: bool) -> String {
|
||||||
@@ -121,6 +115,12 @@ pub fn unformat_flowed(text: &str, delsp: bool) -> String {
|
|||||||
let mut skip_newline = true;
|
let mut skip_newline = true;
|
||||||
|
|
||||||
for line in text.split('\n') {
|
for line in text.split('\n') {
|
||||||
|
let line = if !result.is_empty() && skip_newline {
|
||||||
|
line.trim_start_matches('>')
|
||||||
|
} else {
|
||||||
|
line
|
||||||
|
};
|
||||||
|
|
||||||
// Revert space-stuffing
|
// Revert space-stuffing
|
||||||
let line = line.strip_prefix(' ').unwrap_or(line);
|
let line = line.strip_prefix(' ').unwrap_or(line);
|
||||||
|
|
||||||
@@ -150,8 +150,20 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_format_flowed() {
|
fn test_format_flowed() {
|
||||||
|
let text = "";
|
||||||
|
assert_eq!(format_flowed(text), "");
|
||||||
|
|
||||||
let text = "Foo bar baz";
|
let text = "Foo bar baz";
|
||||||
assert_eq!(format_flowed(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\
|
let text = "This is the Autocrypt Setup Message used to transfer your key between clients.\n\
|
||||||
\n\
|
\n\
|
||||||
@@ -165,17 +177,33 @@ mod tests {
|
|||||||
let text = "> A quote";
|
let text = "> A quote";
|
||||||
assert_eq!(format_flowed(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
|
// Test space stuffing of wrapped lines
|
||||||
let text = "> This is the Autocrypt Setup Message used to transfer your key between clients.\n\
|
let text = "> This is the Autocrypt Setup Message used to transfer your key between clients.\n\
|
||||||
> \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.";
|
> 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\
|
let expected = "> This is the Autocrypt Setup Message used to transfer your key between \r\n\
|
||||||
> clients.\r\n\
|
> clients.\r\n\
|
||||||
> \r\n\
|
>\r\n\
|
||||||
> To decrypt and use your key, open the message in an Autocrypt-compliant \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.";
|
> client and enter the setup code presented on the generating device.";
|
||||||
assert_eq!(format_flowed(text), expected);
|
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.
|
// Test space stuffing of spaces.
|
||||||
let text = " Foo bar baz";
|
let text = " Foo bar baz";
|
||||||
assert_eq!(format_flowed(text), " Foo bar baz");
|
assert_eq!(format_flowed(text), " Foo bar baz");
|
||||||
@@ -202,6 +230,12 @@ mod tests {
|
|||||||
let text = " Foo bar";
|
let text = " Foo bar";
|
||||||
let expected = " Foo bar";
|
let expected = " Foo bar";
|
||||||
assert_eq!(unformat_flowed(text, false), expected);
|
assert_eq!(unformat_flowed(text, false), expected);
|
||||||
|
|
||||||
|
let text =
|
||||||
|
"> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx > \n> A";
|
||||||
|
let expected =
|
||||||
|
"> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx > A";
|
||||||
|
assert_eq!(unformat_flowed(text, false), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -10,10 +10,7 @@ fn round_trip(input: &str) -> String {
|
|||||||
fn main() {
|
fn main() {
|
||||||
check!().for_each(|data: &[u8]| {
|
check!().for_each(|data: &[u8]| {
|
||||||
if let Ok(input) = std::str::from_utf8(data.into()) {
|
if let Ok(input) = std::str::from_utf8(data.into()) {
|
||||||
let mut input = input.to_string();
|
let input = input.trim().to_string();
|
||||||
|
|
||||||
// Only consider inputs that don't contain quotes.
|
|
||||||
input.retain(|c| c != '>');
|
|
||||||
|
|
||||||
// Only consider inputs that are the result of unformatting format=flowed text.
|
// Only consider inputs that are the result of unformatting format=flowed text.
|
||||||
// At least this means that lines don't contain any trailing whitespace.
|
// At least this means that lines don't contain any trailing whitespace.
|
||||||
|
|||||||
@@ -2390,6 +2390,11 @@ mod tests {
|
|||||||
let received = bob.recv_msg(&sent).await;
|
let received = bob.recv_msg(&sent).await;
|
||||||
assert_eq!(received.text.as_deref(), Some(text));
|
assert_eq!(received.text.as_deref(), Some(text));
|
||||||
|
|
||||||
|
let text = "> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx > A";
|
||||||
|
let sent = alice.send_text(chat.id, text).await;
|
||||||
|
let received = bob.recv_msg(&sent).await;
|
||||||
|
assert_eq!(received.text.as_deref(), Some(text));
|
||||||
|
|
||||||
let python_program = "\
|
let python_program = "\
|
||||||
def hello():
|
def hello():
|
||||||
return 'Hello, world!'";
|
return 'Hello, world!'";
|
||||||
|
|||||||
Reference in New Issue
Block a user