72 lines
2.5 KiB
Rust
72 lines
2.5 KiB
Rust
//! RFC 2047 encoding and folding of header values.
|
|
|
|
use std::fmt::Write;
|
|
use email_encoding::headers::writer::EmailWriter;
|
|
|
|
fn allowed_str(s: &str) -> bool {
|
|
s.bytes().all(allowed_char)
|
|
}
|
|
|
|
const fn allowed_char(c: u8) -> bool {
|
|
c >= 1 && c <= 9 || c == 11 || c == 12 || c >= 14 && c <= 127
|
|
}
|
|
|
|
fn encode(encode_buf: &mut String, writer: &mut EmailWriter) {
|
|
if encode_buf.is_empty() {
|
|
return;
|
|
}
|
|
|
|
let prefix = encode_buf.trim_end_matches(' ');
|
|
email_encoding::headers::rfc2047::encode(prefix, writer).expect("String is always writable");
|
|
|
|
// TODO: add a better API for doing this in email-encoding
|
|
let spaces = encode_buf.len() - prefix.len();
|
|
for _ in 0..spaces {
|
|
writer.space();
|
|
}
|
|
|
|
encode_buf.clear();
|
|
}
|
|
|
|
/// Encode `value` as the value of the header named `key`, ready to be written out.
|
|
///
|
|
/// Walks the value word by word. A word made entirely of allowed bytes is written as it is, with a
|
|
/// line break and indentation inserted when the line would grow too long (RFC 5322 folding).
|
|
/// Consecutive words containing anything else are collected and RFC 2047-encoded together, so a
|
|
/// value mixing plain and non-ASCII text keeps its plain parts readable.
|
|
///
|
|
/// `key` itself is not part of the result: only its length matters, for the length of the first
|
|
/// line.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use email_message::header::encode_header_value;
|
|
///
|
|
/// // Plain text is left alone.
|
|
/// assert_eq!(encode_header_value("Subject", "a test"), "a test");
|
|
///
|
|
/// // Only the words that need it are encoded.
|
|
/// assert_eq!(encode_header_value("Subject", "Grüße"), "=?utf-8?b?R3LDvMOfZQ==?=");
|
|
/// ```
|
|
pub fn encode_header_value(key: &str, value: &str) -> String {
|
|
let mut encoded_value = String::new();
|
|
let line_len = key.len() + ": ".len();
|
|
let mut writer = EmailWriter::new(&mut encoded_value, line_len, 0, false);
|
|
let mut encode_buf = String::new();
|
|
for next_word in value.split_inclusive(' ') {
|
|
if allowed_str(next_word) {
|
|
// This word only contains allowed characters
|
|
// the next word is allowed, but we may have accumulated some words to encode
|
|
encode(&mut encode_buf, &mut writer);
|
|
writer.folding().write_str(next_word).expect("String is always writable");
|
|
} else {
|
|
// This word contains unallowed characters
|
|
encode_buf.push_str(next_word);
|
|
}
|
|
}
|
|
encode(&mut encode_buf, &mut writer);
|
|
drop(writer);
|
|
encoded_value
|
|
}
|