melib: Add standard heeder constants in email::headers

Like `http` crate does
pull/223/head
Manos Pitsidianakis 2023-05-28 17:40:50 +03:00
parent 1eea8bab77
commit 235fceaf21
11 changed files with 1008 additions and 303 deletions

View File

@ -56,7 +56,9 @@ pub fn override_derive(filenames: &[(&str, &str)]) {
#![allow(clippy::derivable_impls)]
//! This module is automatically generated by config_macros.rs.
use super::*;
use melib::HeaderName;
"##
.to_string();

View File

@ -21,6 +21,7 @@
/*! Compose a `Draft`, with MIME and attachment support */
use std::{
convert::TryFrom,
ffi::OsStr,
io::Read,
path::{Path, PathBuf},
@ -59,18 +60,18 @@ impl Default for Draft {
fn default() -> Self {
let mut headers = HeaderMap::default();
headers.insert(
HeaderName::new_unchecked("Date"),
HeaderName::DATE,
crate::datetime::timestamp_to_string(
crate::datetime::now(),
Some(crate::datetime::RFC822_DATE),
true,
),
);
headers.insert(HeaderName::new_unchecked("From"), "".into());
headers.insert(HeaderName::new_unchecked("To"), "".into());
headers.insert(HeaderName::new_unchecked("Cc"), "".into());
headers.insert(HeaderName::new_unchecked("Bcc"), "".into());
headers.insert(HeaderName::new_unchecked("Subject"), "".into());
headers.insert(HeaderName::FROM, "".into());
headers.insert(HeaderName::TO, "".into());
headers.insert(HeaderName::CC, "".into());
headers.insert(HeaderName::BCC, "".into());
headers.insert(HeaderName::SUBJECT, "".into());
Draft {
headers,
@ -118,12 +119,20 @@ impl Draft {
Ok(ret)
}
pub fn set_header(&mut self, header: &str, value: String) -> &mut Self {
self.headers
.insert(HeaderName::new_unchecked(header), value);
pub fn set_header(&mut self, header: HeaderName, value: String) -> &mut Self {
self.headers.insert(header, value);
self
}
pub fn try_set_header(
&mut self,
header: &str,
value: String,
) -> std::result::Result<&mut Self, InvalidHeaderName> {
self.headers.insert(HeaderName::try_from(header)?, value);
Ok(self)
}
pub fn set_wrap_header_preamble(&mut self, value: Option<(String, String)>) -> &mut Self {
self.wrap_header_preamble = value;
self
@ -160,7 +169,7 @@ impl Draft {
pub fn new_reply(envelope: &Envelope, bytes: &[u8], reply_to_all: bool) -> Self {
let mut ret = Draft::default();
ret.headers_mut().insert(
HeaderName::new_unchecked("References"),
HeaderName::REFERENCES,
format!(
"{} {}",
envelope
@ -177,7 +186,7 @@ impl Draft {
),
);
ret.headers_mut().insert(
HeaderName::new_unchecked("In-Reply-To"),
HeaderName::IN_REPLY_TO,
envelope.message_id_display().into(),
);
// "Mail-Followup-To/(To+Cc+(Mail-Reply-To/Reply-To/From)) for follow-up,
@ -186,33 +195,27 @@ impl Draft {
if reply_to_all {
if let Some(reply_to) = envelope.other_headers().get("Mail-Followup-To") {
ret.headers_mut()
.insert(HeaderName::new_unchecked("To"), reply_to.to_string());
.insert(HeaderName::TO, reply_to.to_string());
} else if let Some(reply_to) = envelope.other_headers().get("Reply-To") {
ret.headers_mut()
.insert(HeaderName::new_unchecked("To"), reply_to.to_string());
.insert(HeaderName::TO, reply_to.to_string());
} else {
ret.headers_mut().insert(
HeaderName::new_unchecked("To"),
envelope.field_from_to_string(),
);
ret.headers_mut()
.insert(HeaderName::TO, envelope.field_from_to_string());
}
// FIXME: add To/Cc
} else if let Some(reply_to) = envelope.other_headers().get("Mail-Reply-To") {
ret.headers_mut()
.insert(HeaderName::new_unchecked("To"), reply_to.to_string());
.insert(HeaderName::TO, reply_to.to_string());
} else if let Some(reply_to) = envelope.other_headers().get("Reply-To") {
ret.headers_mut()
.insert(HeaderName::new_unchecked("To"), reply_to.to_string());
.insert(HeaderName::TO, reply_to.to_string());
} else {
ret.headers_mut().insert(
HeaderName::new_unchecked("To"),
envelope.field_from_to_string(),
);
ret.headers_mut()
.insert(HeaderName::TO, envelope.field_from_to_string());
}
ret.headers_mut().insert(
HeaderName::new_unchecked("Cc"),
envelope.field_cc_to_string(),
);
ret.headers_mut()
.insert(HeaderName::CC, envelope.field_cc_to_string());
let body = envelope.body_bytes(bytes);
ret.body = {
let reply_body_bytes = body.decode_rec(Default::default());
@ -221,7 +224,7 @@ impl Draft {
let mut ret = format!(
"On {} {} wrote:\n",
envelope.date_as_str(),
&ret.headers()["To"]
&ret.headers()[HeaderName::TO]
);
for l in lines {
ret.push('>');
@ -304,10 +307,8 @@ impl Draft {
if let Ok((_, addr)) = super::parser::address::mailbox(self.headers["From"].as_bytes())
{
if let Some(fqdn) = addr.get_fqdn() {
self.headers.insert(
HeaderName::new_unchecked("Message-ID"),
random::gen_message_id(&fqdn),
);
self.headers
.insert(HeaderName::MESSAGE_ID, random::gen_message_id(&fqdn));
}
}
}
@ -559,8 +560,8 @@ mod tests {
default
.set_wrap_header_preamble(Some(("<!--".to_string(), "-->".to_string())))
.set_body("αδφαφσαφασ".to_string())
.set_header("Subject", "test_update()".into())
.set_header("Date", "Sun, 16 Jun 2013 17:56:45 +0200".into());
.set_header(HeaderName::SUBJECT, "test_update()".into())
.set_header(HeaderName::DATE, "Sun, 16 Jun 2013 17:56:45 +0200".into());
let original = default.clone();
let s = default.to_edit_string();

View File

@ -20,102 +20,18 @@
*/
/*! Wrapper type `HeaderName` for case-insensitive comparisons */
pub mod names;
use std::{
borrow::Borrow,
cmp::{Eq, PartialEq},
convert::TryFrom,
fmt,
convert::{TryFrom, TryInto},
hash::{Hash, Hasher},
ops::{Deref, DerefMut},
};
use indexmap::IndexMap;
use smallvec::SmallVec;
use crate::error::Error;
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct HeaderNameType<S>(S);
/// Case insensitive wrapper for a header name. As of `RFC5322` it's
/// guaranteed to be ASCII.
pub type HeaderName = HeaderNameType<SmallVec<[u8; 32]>>;
impl HeaderName {
pub fn new_unchecked(from: &str) -> Self {
HeaderNameType(from.as_bytes().into())
}
}
impl<S: AsRef<[u8]>> fmt::Display for HeaderNameType<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.normalize())
}
}
impl<S: AsRef<[u8]>> fmt::Debug for HeaderNameType<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl<S: AsRef<[u8]>> PartialEq<[u8]> for HeaderNameType<S> {
fn eq(&self, other: &[u8]) -> bool {
self.0.as_ref().eq_ignore_ascii_case(other)
}
}
impl<S: AsRef<[u8]>> PartialEq<&str> for HeaderNameType<S> {
fn eq(&self, other: &&str) -> bool {
self.0.as_ref().eq_ignore_ascii_case(other.as_bytes())
}
}
impl<S1: AsRef<[u8]>, S2: AsRef<[u8]>> PartialEq<HeaderNameType<S2>> for HeaderNameType<S1> {
fn eq(&self, other: &HeaderNameType<S2>) -> bool {
self.0.as_ref().eq_ignore_ascii_case(other.0.as_ref())
}
}
impl<S: AsRef<[u8]>> Eq for HeaderNameType<S> {}
impl<S: AsRef<[u8]>> Hash for HeaderNameType<S> {
fn hash<H: Hasher>(&self, state: &mut H) {
for b in self.0.as_ref().iter() {
b.to_ascii_lowercase().hash(state);
}
}
}
impl TryFrom<&[u8]> for HeaderName {
type Error = Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value.is_ascii() {
Ok(HeaderNameType(value.into()))
} else {
Err(Error::new(format!(
"Header value is not ascii: {:?}",
value
)))
}
}
}
impl TryFrom<&str> for HeaderName {
type Error = Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
if value.is_ascii() {
Ok(HeaderNameType(value.as_bytes().into()))
} else {
Err(Error::new(format!(
"Header value is not ascii: {:?}",
value
)))
}
}
}
pub use names::{HeaderName, InvalidHeaderName, Protocol, Standard, StandardHeader, Status};
trait HeaderKey {
fn to_key(&self) -> &[u8];
@ -137,9 +53,9 @@ impl PartialEq for dyn HeaderKey + '_ {
impl Eq for dyn HeaderKey + '_ {}
impl<S: AsRef<[u8]>> HeaderKey for HeaderNameType<S> {
impl HeaderKey for HeaderName {
fn to_key(&self) -> &[u8] {
self.0.as_ref()
self.as_bytes()
}
}
@ -151,119 +67,94 @@ impl<'a> Borrow<dyn HeaderKey + 'a> for HeaderName {
}
}
impl<S: AsRef<[u8]>> HeaderNameType<S> {
pub fn as_str(&self) -> &str {
// HeadersType are ascii so valid utf8
unsafe { std::str::from_utf8_unchecked(self.0.as_ref()) }
}
pub fn normalize(&self) -> &str {
if self == &b"subject"[..] {
"Subject"
} else if self == &b"from"[..] {
"From"
} else if self == &b"to"[..] {
"To"
} else if self == &b"cc"[..] {
"Cc"
} else if self == &b"bcc"[..] {
"Bcc"
} else if self == &b"reply-to"[..] {
"Reply-To"
} else if self == &b"in-reply-to"[..] {
"In-Reply-To"
} else if self == &b"references"[..] {
"References"
} else if self == &b"sender"[..] {
"Sender"
} else if self == &b"mail-reply-to"[..] {
"Mail-Reply-To"
} else if self == &b"mail-followup-to"[..] {
"Mail-Followup-To"
} else if self == &b"mime-version"[..] {
"MIME-Version"
} else if self == &b"content-disposition"[..] {
"Content-Disposition"
} else if self == &b"content-transfer-encoding"[..] {
"Content-Transfer-Encoding"
} else if self == &b"content-type"[..] {
"Content-Type"
} else if self == &b"content-id"[..] {
"Content-ID"
} else if self == &b"content-description"[..] {
"Content-Description"
} else if self == &b"authentication-results"[..] {
"Authentication-Results"
} else if self == &b"dkim-signature"[..] {
"DKIM-Signature"
} else if self == &b"delivered-to"[..] {
"Delivered-To"
} else if self == &b"message-id"[..] {
"Message-ID"
} else if self == &b"comments"[..] {
"Comments"
} else if self == &b"keywords"[..] {
"Keywords"
} else if self == &b"resent-from"[..] {
"Resent-From"
} else if self == &b"resent-sender"[..] {
"Resent-Sender"
} else if self == &b"resent-to"[..] {
"Resent-To"
} else if self == &b"resent-cc"[..] {
"Resent-Cc"
} else if self == &b"resent-bcc"[..] {
"Resent-Bcc"
} else if self == &b"resent-date"[..] {
"Resent-Date"
} else if self == &b"resent-message-id"[..] {
"Resent-Message-ID"
} else if self == &b"resent-reply-to"[..] {
"Resent-Reply-To"
} else if self == &b"return-path"[..] {
"Return-Path"
} else if self == &b"received"[..] {
"Received"
} else {
self.as_str()
}
}
}
/// Map of mail headers and values.
///
/// Can be indexed by:
///
/// - `usize`
/// - `&[u8]`, which panics if it's not a valid header value.
/// - `&str`, which also panics if it's not a valid header value.
/// - [HeaderName], which is guaranteed to be valid.
///
/// # Panics
///
/// Except for the above, indexing will also panic if index is out of range or
/// header key is not present in the map.
#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct HeaderMap(indexmap::IndexMap<HeaderName, String>);
impl std::ops::Index<usize> for HeaderMap {
type Output = str;
fn index(&self, k: usize) -> &Self::Output {
(self.0)[k].as_str()
}
}
impl std::ops::Index<&[u8]> for HeaderMap {
type Output = str;
fn index(&self, k: &[u8]) -> &Self::Output {
(self.0)[HeaderNameType(k).borrow() as &dyn HeaderKey].as_str()
(self.0)[HeaderName::try_from(k)
.expect("Invalid bytes in header name.")
.borrow() as &dyn HeaderKey]
.as_str()
}
}
impl std::ops::Index<&str> for HeaderMap {
type Output = str;
fn index(&self, k: &str) -> &Self::Output {
(self.0)[HeaderNameType(k).borrow() as &dyn HeaderKey].as_str()
(self.0)[&HeaderName::try_from(k).expect("Invalid bytes in header name.")].as_str()
}
}
impl std::ops::Index<&HeaderName> for HeaderMap {
type Output = str;
fn index(&self, k: &HeaderName) -> &Self::Output {
(self.0)[k].as_str()
}
}
impl std::ops::Index<HeaderName> for HeaderMap {
type Output = str;
fn index(&self, k: HeaderName) -> &Self::Output {
(self.0)[&k].as_str()
}
}
impl HeaderMap {
pub fn get_mut(&mut self, key: &str) -> Option<&mut String> {
(self.0).get_mut(HeaderNameType(key).borrow() as &dyn HeaderKey)
pub fn get_mut<T: TryInto<HeaderName> + std::fmt::Debug>(
&mut self,
key: T,
) -> Option<&mut String>
where
<T as TryInto<HeaderName>>::Error: std::fmt::Debug,
{
let k = key.try_into().expect("Invalid bytes in header name.");
(self.0).get_mut(&k)
}
pub fn get(&self, key: &str) -> Option<&str> {
(self.0)
.get(HeaderNameType(key).borrow() as &dyn HeaderKey)
.map(|x| x.as_str())
pub fn get<T: TryInto<HeaderName> + std::fmt::Debug>(&self, key: T) -> Option<&str>
where
<T as TryInto<HeaderName>>::Error: std::fmt::Debug,
{
let k = key.try_into().expect("Invalid bytes in header name.");
(self.0).get(&k).map(|x| x.as_str())
}
pub fn contains_key(&self, key: &str) -> bool {
(self.0).contains_key(HeaderNameType(key).borrow() as &dyn HeaderKey)
pub fn contains_key<T: TryInto<HeaderName> + std::fmt::Debug>(&self, key: T) -> bool
where
<T as TryInto<HeaderName>>::Error: std::fmt::Debug,
{
let k = key.try_into().expect("Invalid bytes in header name.");
(self.0).contains_key(&k)
}
pub fn remove(&mut self, key: &str) -> Option<String> {
(self.0).remove(HeaderNameType(key).borrow() as &dyn HeaderKey)
pub fn remove<T: TryInto<HeaderName> + std::fmt::Debug>(&mut self, key: T) -> Option<String>
where
<T as TryInto<HeaderName>>::Error: std::fmt::Debug,
{
let k = key.try_into().expect("Invalid bytes in header name.");
(self.0).remove(&k)
}
}

View File

@ -0,0 +1,800 @@
/*
* meli - melib crate.
*
* Copyright 2023 Manos Pitsidianakis
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* meli is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
/*! E-mail header names. Also referred to as Fields in [RFC5322] */
#![allow(non_upper_case_globals)]
use std::{
borrow::{Borrow, Cow},
convert::TryFrom,
error::Error,
hash::{Hash, Hasher},
str::FromStr,
};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use smallvec::SmallVec;
use crate::email::parser::BytesExt;
bitflags! {
#[derive(Default, Serialize, Deserialize)]
pub struct Protocol: u32 {
const None = 0b00000001;
const Mail = Self::None.bits() << 1;
const NNTP = Self::Mail.bits() << 1;
const MIME = Self::NNTP.bits() << 1;
}
}
/// Case insensitive wrapper for a header name. As of `RFC5322` it's
/// guaranteed to be ASCII.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct HeaderName {
inner: Repr<Custom>,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
enum Repr<T> {
Standard(StandardHeader),
Custom(T),
}
// Used to hijack the Hash impl
#[derive(Debug, Clone, Eq, PartialEq)]
struct Custom(SmallVec<[u8; 32]>);
/// A possible error when converting a `HeaderName` from another type.
pub struct InvalidHeaderName;
impl Error for InvalidHeaderName {}
impl std::fmt::Debug for InvalidHeaderName {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(fmt, "{}", "Invalid header name.")
}
}
impl std::fmt::Display for InvalidHeaderName {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(fmt, "{}", stringify!(InvalidHeaderName))
}
}
macro_rules! standard_headers {
(
$(
$(#[$docs:meta])*
($konst:ident, $upcase:ident, $name:literal, $template:expr, $(Protocol::$var:tt)|+,$status:expr,$standards:expr);
)+
) => {
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum StandardHeader {
$(
$konst,
)+
}
$(
$(#[$docs])*
pub const $upcase: HeaderName = HeaderName {
inner: Repr::Standard(StandardHeader::$konst),
};
)+
impl HeaderName {
$(
pub const $upcase: Self = $upcase;
)+
}
impl StandardHeader {
#[inline]
pub const fn as_str(&self) -> &'static str {
match *self {
$(
Self::$konst => $name,
)+
}
}
#[inline]
pub const fn protocol(&self) -> Protocol {
match *self {
$(
Self::$konst => Protocol::from_bits_truncate($(Protocol::$var.bits()|)* u32::MAX),
)+
}
}
#[inline]
pub const fn status(&self) -> Status {
match *self {
$(
Self::$konst => $status,
)+
}
}
#[inline]
pub const fn standards(&self) -> &[Standard] {
match *self {
$(
Self::$konst => $standards,
)+
}
}
pub fn from_bytes(name_bytes: &[u8]) -> Option<Self> {
match name_bytes {
$(
_ if name_bytes.eq_ignore_ascii_case($name.as_bytes()) => Some(Self::$konst),
)+
_ => None,
}
}
}
#[cfg(test)]
const TEST_HEADERS: &[(StandardHeader, &str)] = &[
$(
(StandardHeader::$konst, $name),
)+
];
#[test]
fn test_parse_standard_headers() {
for &(std, name) in TEST_HEADERS {
// Test lower case
assert_eq!(HeaderName::from_bytes(name.as_bytes()).unwrap(), HeaderName::from(std));
// Test upper case
let upper = std::str::from_utf8(name.as_bytes()).expect("byte string constants are all utf-8").to_uppercase();
assert_eq!(HeaderName::from_bytes(upper.as_bytes()).unwrap(), HeaderName::from(std));
}
}
#[test]
fn test_standard_headers_into_bytes() {
//for &(std, name) in TEST_HEADERS {
//let name = std::str::from_utf8(name.as_bytes()).unwrap();
//let std = HeaderName::from(std);
// Test lower case
//let bytes: Bytes =
// HeaderName::from_bytes(name.as_bytes()).unwrap().inner.into();
//assert_eq!(bytes, name);
//assert_eq!(HeaderName::from_bytes(name.as_bytes()).unwrap(), std);
// Test upper case
// let upper = name.to_uppercase();
//let bytes: Bytes =
// HeaderName::from_bytes(upper.as_bytes()).unwrap().inner.into();
//assert_eq!(bytes, name.as_bytes());
//assert_eq!(HeaderName::from_bytes(upper.as_bytes()).unwrap(),
// std);
//}
}
}
}
macro_rules! standards {
(
$(
$(#[$docs:meta])*
($konst:ident, $upcase:ident, $name:literal, $lowername:literal );
)+
) => {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Standard {
$(
$konst,
)+
}
$(
$(#[$docs])*
pub const $upcase: Standard = Standard::$konst;
)+
impl Standard {
#[inline]
pub const fn as_str(&self) -> &'static str {
match *self {
$(
Self::$konst => $name,
)+
}
}
#[inline]
pub const fn url(&self) -> &str {
match *self {
$(
Self::$konst => concat!("https://www.rfc-editor.org/rfc/", $lowername, ".html"),
)+
}
}
pub fn from_bytes(name_bytes: &[u8]) -> Option<Self> {
match name_bytes {
$(
_ if name_bytes.eq_ignore_ascii_case($name.as_bytes()) => Some(Self::$konst),
)+
_ => None,
}
}
}
};
}
standards! {
(RFC0850, RFC0850, "RFC0850", "rfc0850");
(RFC1808, RFC1808, "RFC1808", "rfc1808");
(RFC1849, RFC1849, "RFC1849", "rfc1849");
(RFC2068, RFC2068, "RFC2068", "rfc2068");
(RFC2076, RFC2076, "RFC2076", "rfc2076");
(RFC2110, RFC2110, "RFC2110", "rfc2110");
(RFC2156, RFC2156, "RFC2156", "rfc2156");
(RFC2557, RFC2557, "RFC2557", "rfc2557");
(RFC2616, RFC2616, "RFC2616", "rfc2616");
(RFC2980, RFC2980, "RFC2980", "rfc2980");
(RFC3798, RFC3798, "RFC3798", "rfc3798");
(RFC3834, RFC3834, "RFC3834", "rfc3834");
(RFC3865, RFC3865, "RFC3865", "rfc3865");
(RFC3977, RFC3977, "RFC3977", "rfc3977");
(RFC4021, RFC4021, "RFC4021", "rfc4021");
(RFC5064, RFC5064, "RFC5064", "rfc5064");
(RFC5321, RFC5321, "RFC5321", "rfc5321");
(RFC5322, RFC5322, "RFC5322", "rfc5322");
(RFC5337, RFC5337, "RFC5337", "rfc5337");
(RFC5504, RFC5504, "RFC5504", "rfc5504");
(RFC5518, RFC5518, "RFC5518", "rfc5518");
(RFC5536, RFC5536, "RFC5536", "rfc5536");
(RFC5537, RFC5537, "RFC5537", "rfc5537");
(RFC5703, RFC5703, "RFC5703", "rfc5703");
(RFC6017, RFC6017, "RFC6017", "rfc6017");
(RFC6068, RFC6068, "RFC6068", "rfc6068");
(RFC6109, RFC6109, "RFC6109", "rfc6109");
(RFC6376, RFC6376, "RFC6376", "rfc6376");
(RFC6477, RFC6477, "RFC6477", "rfc6477");
(RFC6758, RFC6758, "RFC6758", "rfc6758");
(RFC6854, RFC6854, "RFC6854", "rfc6854");
(RFC6857, RFC6857, "RFC6857", "rfc6857");
(RFC7208, RFC7208, "RFC7208", "rfc7208");
(RFC7259, RFC7259, "RFC7259", "rfc7259");
(RFC7293, RFC7293, "RFC7293", "rfc7293");
(RFC7444, RFC7444, "RFC7444", "rfc7444");
(RFC7681, RFC7681, "RFC7681", "rfc7681");
(RFC8058, RFC8058, "RFC8058", "rfc8058");
(RFC8255, RFC8255, "RFC8255", "rfc8255");
(RFC8315, RFC8315, "RFC8315", "rfc8315");
(RFC8460, RFC8460, "RFC8460", "rfc8460");
(RFC8601, RFC8601, "RFC8601", "rfc8601");
(RFC8617, RFC8617, "RFC8617", "rfc8617");
(RFC8689, RFC8689, "RFC8689", "rfc8689");
(RFC9057, RFC9057, "RFC9057", "rfc9057");
(RFC9228, RFC9228, "RFC9228", "rfc9228");
}
/// Status of field at the moment of writing.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum Status {
/// Deprecated,
Deprecated,
/// Experimental,
Experimental,
/// Informational,
Informational,
/// None,
None,
/// Obsoleted,
Obsoleted,
/// Reserved,
Reserved,
/// Standard,
Standard,
}
// Generate constants for all standard e-mail field headers.
standard_headers! {
/* Unit Variant |Constant ident |Actual field value |Template value |Protocols |Status |Standards */
/* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
(Subject, SUBJECT, "Subject", None, Protocol::Mail | Protocol::NNTP, Status::Standard, &[Standard::RFC5536, Standard::RFC5322]);
(ReplyTo, REPLY_TO, "Reply-To", None, Protocol::Mail | Protocol::NNTP, Status::Standard, &[Standard::RFC5536, Standard::RFC5322]);
(InReplyTo, IN_REPLY_TO, "In-Reply-To", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322]);
(References, REFERENCES, "References", None, Protocol::Mail | Protocol::NNTP, Status::Standard, &[Standard::RFC5536, Standard::RFC5322]);
(MailReplyTo, MAIL_REPLY_TO, "Mail-Reply-To", None, Protocol::Mail, Status::None, &[]);
(MailFollowupTo, MAIL_FOLLOWUP_TO, "Mail-Followup-To", None, Protocol::Mail, Status::None, &[]);
(DeliveredTo, DELIVERED_TO, "Delivered-To", None, Protocol::Mail, Status::None, &[Standard::RFC9228]);
(Comments, COMMENTS, "Comments", None, Protocol::Mail, Status::None, &[]);
(Keywords, KEYWORDS, "Keywords", None, Protocol::Mail, Status::None, &[]);
(Received, RECEIVED, "Received", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322, Standard::RFC5321]);
(ContentLanguage, CONTENT_LANGUAGE, "Content-Language", None, Protocol::MIME, Status::None, &[Standard::RFC4021]);
(ContentLength, CONTENT_LENGTH, "Content-Length", None, Protocol::Mail, Status::None, &[]);
(Forwarded, FORWARDED, "Forwarded", None, Protocol::Mail, Status::None, &[]);
(AcceptLanguage, ACCEPT_LANGUAGE, "Accept-Language", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(AlsoControl, ALSO_CONTROL, "Also-Control", None, Protocol::NNTP, Status::Obsoleted, &[Standard::RFC1849, Standard::RFC5536]);
(AlternateRecipient, ALTERNATE_RECIPIENT, "Alternate-Recipient", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(Approved, APPROVED, "Approved", None, Protocol::NNTP, Status::Standard, &[Standard::RFC5536]);
(ArcAuthenticationResults, ARC_AUTHENTICATION_RESULTS, "ARC-Authentication-Results", None, Protocol::Mail, Status::Experimental, &[Standard::RFC8617]);
(ArcMessageSignature, ARC_MESSAGE_SIGNATURE, "ARC-Message-Signature", None, Protocol::Mail, Status::Experimental, &[Standard::RFC8617]);
(ArcSeal, ARC_SEAL, "ARC-Seal", None, Protocol::Mail, Status::Experimental, &[Standard::RFC8617]);
(Archive, ARCHIVE, "Archive", None, Protocol::NNTP, Status::Standard, &[Standard::RFC5536]);
(ArchivedAt, ARCHIVED_AT, "Archived-At", None, Protocol::Mail | Protocol::NNTP, Status::Standard, &[Standard::RFC5064]);
(ArticleNames, ARTICLE_NAMES, "Article-Names", None, Protocol::NNTP, Status::Obsoleted, &[Standard::RFC1849, Standard::RFC5536]);
(ArticleUpdates, ARTICLE_UPDATES, "Article-Updates", None, Protocol::NNTP, Status::Obsoleted, &[Standard::RFC1849, Standard::RFC5536]);
(AuthenticationResults, AUTHENTICATION_RESULTS, "Authentication-Results", None, Protocol::Mail, Status::Standard, &[Standard::RFC8601]);
(AutoSubmitted, AUTO_SUBMITTED, "Auto-Submitted", None, Protocol::Mail, Status::Standard, &[Standard::RFC3834]);
(Autoforwarded, AUTOFORWARDED, "Autoforwarded", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(Autosubmitted, AUTOSUBMITTED, "Autosubmitted", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(Base, BASE, "Base", None, Protocol::MIME, Status::Obsoleted, &[Standard::RFC1808, Standard::RFC2068]);
(Bcc, BCC, "Bcc", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322]);
(Body, BODY, "Body", None, Protocol::None, Status::Reserved, &[Standard::RFC6068]);
(CancelKey, CANCEL_KEY, "Cancel-Key", None, Protocol::NNTP, Status::Standard, &[Standard::RFC8315]);
(CancelLock, CANCEL_LOCK, "Cancel-Lock", None, Protocol::NNTP, Status::Standard, &[Standard::RFC8315]);
(Cc, CC, "Cc", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322]);
(ContentAlternative, CONTENT_ALTERNATIVE, "Content-Alternative", None, Protocol::MIME, Status::None, &[Standard::RFC4021]);
(ContentBase, CONTENT_BASE, "Content-Base", None, Protocol::MIME, Status::Obsoleted, &[Standard::RFC2110, Standard::RFC2557]);
(ContentDescription, CONTENT_DESCRIPTION, "Content-Description", None, Protocol::MIME, Status::None, &[Standard::RFC4021]);
(ContentDisposition, CONTENT_DISPOSITION, "Content-Disposition", None, Protocol::MIME, Status::None, &[Standard::RFC4021]);
(ContentDuration, CONTENT_DURATION, "Content-Duration", None, Protocol::MIME, Status::None, &[Standard::RFC4021]);
(ContentFeatures, CONTENT_FEATURES, "Content-Features", None, Protocol::MIME, Status::None, &[Standard::RFC4021]);
(ContentId, CONTENT_ID, "Content-ID", None, Protocol::MIME, Status::None, &[Standard::RFC4021]);
(ContentIdentifier, CONTENT_IDENTIFIER, "Content-Identifier", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(ContentLocation, CONTENT_LOCATION, "Content-Location", None, Protocol::MIME, Status::None, &[Standard::RFC4021]);
(ContentMd5, CONTENT_MD5, "Content-MD5", None, Protocol::MIME, Status::None, &[Standard::RFC4021]);
(ContentReturn, CONTENT_RETURN, "Content-Return", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(ContentTransferEncoding, CONTENT_TRANSFER_ENCODING, "Content-Transfer-Encoding", None, Protocol::MIME, Status::None, &[Standard::RFC4021]);
(ContentTranslationType, CONTENT_TRANSLATION_TYPE, "Content-Translation-Type", None, Protocol::MIME, Status::Standard, &[Standard::RFC8255]);
(ContentType, CONTENT_TYPE, "Content-Type", None, Protocol::MIME, Status::None, &[Standard::RFC4021]);
(Control, CONTROL, "Control", None, Protocol::NNTP, Status::Standard, &[Standard::RFC5536]);
(Conversion, CONVERSION, "Conversion", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(ConversionWithLoss, CONVERSION_WITH_LOSS, "Conversion-With-Loss", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(DlExpansionHistory, DL_EXPANSION_HISTORY, "DL-Expansion-History", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(Date, DATE, "Date", None, Protocol::Mail | Protocol::NNTP, Status::Standard, &[Standard::RFC5536, Standard::RFC5322]);
(DateReceived, DATE_RECEIVED, "Date-Received", None, Protocol::NNTP, Status::Obsoleted, &[Standard::RFC0850, Standard::RFC5536]);
(DeferredDelivery, DEFERRED_DELIVERY, "Deferred-Delivery", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(DeliveryDate, DELIVERY_DATE, "Delivery-Date", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(DiscardedX400IpmsExtensions, DISCARDED_X400_IPMS_EXTENSIONS, "Discarded-X400-IPMS-Extensions", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(DiscardedX400MtsExtensions, DISCARDED_X400_MTS_EXTENSIONS, "Discarded-X400-MTS-Extensions", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(DiscloseRecipients, DISCLOSE_RECIPIENTS, "Disclose-Recipients", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(DispositionNotificationOptions, DISPOSITION_NOTIFICATION_OPTIONS, "Disposition-Notification-Options", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(DispositionNotificationTo, DISPOSITION_NOTIFICATION_TO, "Disposition-Notification-To", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(Distribution, DISTRIBUTION, "Distribution", None, Protocol::NNTP, Status::Standard, &[Standard::RFC5536]);
(DkimSignature, DKIM_SIGNATURE, "DKIM-Signature", None, Protocol::Mail, Status::Standard, &[Standard::RFC6376]);
(DowngradedBcc, DOWNGRADED_BCC, "Downgraded-Bcc", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5504, Standard::RFC6857]);
(DowngradedCc, DOWNGRADED_CC, "Downgraded-Cc", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5504, Standard::RFC6857]);
(DowngradedDispositionNotificationTo, DOWNGRADED_DISPOSITION_NOTIFICATION_TO, "Downgraded-Disposition-Notification-To", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5504, Standard::RFC6857]);
(DowngradedFinalRecipient, DOWNGRADED_FINAL_RECIPIENT, "Downgraded-Final-Recipient", None, Protocol::Mail, Status::Standard, &[Standard::RFC6857]);
(DowngradedFrom, DOWNGRADED_FROM, "Downgraded-From", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5504, Standard::RFC6857]);
(DowngradedInReplyTo, DOWNGRADED_IN_REPLY_TO, "Downgraded-In-Reply-To", None, Protocol::Mail, Status::Standard, &[Standard::RFC6857]);
(DowngradedMailFrom, DOWNGRADED_MAIL_FROM, "Downgraded-Mail-From", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5504, Standard::RFC6857]);
(DowngradedMessageId, DOWNGRADED_MESSAGE_ID, "Downgraded-Message-Id", None, Protocol::Mail, Status::Standard, &[Standard::RFC6857]);
(DowngradedOriginalRecipient, DOWNGRADED_ORIGINAL_RECIPIENT, "Downgraded-Original-Recipient", None, Protocol::Mail, Status::Standard, &[Standard::RFC6857]);
(DowngradedRcptTo, DOWNGRADED_RCPT_TO, "Downgraded-Rcpt-To", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5504, Standard::RFC6857]);
(DowngradedReferences, DOWNGRADED_REFERENCES, "Downgraded-References", None, Protocol::Mail, Status::Standard, &[Standard::RFC6857]);
(DowngradedReplyTo, DOWNGRADED_REPLY_TO, "Downgraded-Reply-To", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5504, Standard::RFC6857]);
(DowngradedResentBcc, DOWNGRADED_RESENT_BCC, "Downgraded-Resent-Bcc", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5504, Standard::RFC6857]);
(DowngradedResentCc, DOWNGRADED_RESENT_CC, "Downgraded-Resent-Cc", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5504, Standard::RFC6857]);
(DowngradedResentFrom, DOWNGRADED_RESENT_FROM, "Downgraded-Resent-From", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5504, Standard::RFC6857]);
(DowngradedResentReplyTo, DOWNGRADED_RESENT_REPLY_TO, "Downgraded-Resent-Reply-To", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5504, Standard::RFC6857]);
(DowngradedResentSender, DOWNGRADED_RESENT_SENDER, "Downgraded-Resent-Sender", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5504, Standard::RFC6857]);
(DowngradedResentTo, DOWNGRADED_RESENT_TO, "Downgraded-Resent-To", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5504, Standard::RFC6857]);
(DowngradedReturnPath, DOWNGRADED_RETURN_PATH, "Downgraded-Return-Path", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5504, Standard::RFC6857]);
(DowngradedSender, DOWNGRADED_SENDER, "Downgraded-Sender", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5504, Standard::RFC6857]);
(DowngradedTo, DOWNGRADED_TO, "Downgraded-To", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5504, Standard::RFC6857]);
(Encoding, ENCODING, "Encoding", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(Encrypted, ENCRYPTED, "Encrypted", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(Expires, EXPIRES, "Expires", None, Protocol::Mail | Protocol::NNTP, Status::None, &[Standard::RFC4021, Standard::RFC5536]);
(ExpiryDate, EXPIRY_DATE, "Expiry-Date", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(FollowupTo, FOLLOWUP_TO, "Followup-To", None, Protocol::NNTP, Status::Standard, &[Standard::RFC5536]);
(From, FROM, "From", None, Protocol::Mail | Protocol::NNTP, Status::Standard, &[Standard::RFC5322, Standard::RFC6854]);
(GenerateDeliveryReport, GENERATE_DELIVERY_REPORT, "Generate-Delivery-Report", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(Importance, IMPORTANCE, "Importance", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(IncompleteCopy, INCOMPLETE_COPY, "Incomplete-Copy", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(InjectionDate, INJECTION_DATE, "Injection-Date", None, Protocol::NNTP, Status::Standard, &[Standard::RFC5536]);
(InjectionInfo, INJECTION_INFO, "Injection-Info", None, Protocol::NNTP, Status::Standard, &[Standard::RFC5536]);
(Language, LANGUAGE, "Language", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(LatestDeliveryTime, LATEST_DELIVERY_TIME, "Latest-Delivery-Time", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(Lines, LINES, "Lines", None, Protocol::NNTP, Status::Deprecated, &[Standard::RFC5536, Standard::RFC3977]);
(ListArchive, LIST_ARCHIVE, "List-Archive", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(ListHelp, LIST_HELP, "List-Help", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(ListId, LIST_ID, "List-ID", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(ListOwner, LIST_OWNER, "List-Owner", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(ListPost, LIST_POST, "List-Post", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(ListSubscribe, LIST_SUBSCRIBE, "List-Subscribe", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(ListUnsubscribe, LIST_UNSUBSCRIBE, "List-Unsubscribe", Some("perm/list-unsubscribe"), Protocol::Mail, Status::None, &[Standard::RFC4021]);
(ListUnsubscribePost, LIST_UNSUBSCRIBE_POST, "List-Unsubscribe-Post", None, Protocol::Mail, Status::Standard, &[Standard::RFC8058]);
(MessageContext, MESSAGE_CONTEXT, "Message-Context", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(MessageId, MESSAGE_ID, "Message-ID", None, Protocol::Mail | Protocol::NNTP, Status::Standard, &[Standard::RFC5322, Standard::RFC5536]);
(MessageType, MESSAGE_TYPE, "Message-Type", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(MimeVersion, MIME_VERSION, "MIME-Version", None, Protocol::MIME, Status::None, &[Standard::RFC4021]);
(MtPriority, MT_PRIORITY, "MT-Priority", None, Protocol::Mail, Status::Standard, &[Standard::RFC6758]);
(Newsgroups, NEWSGROUPS, "Newsgroups", None, Protocol::NNTP, Status::Standard, &[Standard::RFC5536]);
(NntpPostingDate, NNTP_POSTING_DATE, "NNTP-Posting-Date", None, Protocol::NNTP, Status::Obsoleted, &[Standard::RFC5536]);
(NntpPostingHost, NNTP_POSTING_HOST, "NNTP-Posting-Host", None, Protocol::NNTP, Status::Obsoleted, &[Standard::RFC2980, Standard::RFC5536]);
(Obsoletes, OBSOLETES, "Obsoletes", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(Organization, ORGANIZATION, "Organization", None, Protocol::Mail | Protocol::NNTP, Status::Informational, &[Standard::RFC7681, Standard::RFC5536]);
(OriginalEncodedInformationTypes, ORIGINAL_ENCODED_INFORMATION_TYPES, "Original-Encoded-Information-Types", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(OriginalFrom, ORIGINAL_FROM, "Original-From", None, Protocol::Mail, Status::Standard, &[Standard::RFC5703]);
(OriginalMessageId, ORIGINAL_MESSAGE_ID, "Original-Message-ID", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(OriginalRecipient, ORIGINAL_RECIPIENT, "Original-Recipient", Some("perm/original-recipient"),Protocol::Mail, Status::Standard, &[Standard::RFC3798, Standard::RFC5337]);
(OriginalSender, ORIGINAL_SENDER, "Original-Sender", None, Protocol::NNTP, Status::Standard, &[Standard::RFC5537]);
(OriginatorReturnAddress, ORIGINATOR_RETURN_ADDRESS, "Originator-Return-Address", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(OriginalSubject, ORIGINAL_SUBJECT, "Original-Subject", None, Protocol::Mail, Status::Standard, &[Standard::RFC5703]);
(Path, PATH, "Path", None, Protocol::NNTP, Status::Standard, &[Standard::RFC5536]);
(PicsLabel, PICS_LABEL, "PICS-Label", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(PostingVersion, POSTING_VERSION, "Posting-Version", None, Protocol::NNTP, Status::Obsoleted, &[Standard::RFC0850, Standard::RFC5536]);
(PreventNondeliveryReport, PREVENT_NONDELIVERY_REPORT, "Prevent-NonDelivery-Report", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(Priority, PRIORITY, "Priority", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(ReceivedSpf, RECEIVED_SPF, "Received-SPF", None, Protocol::Mail, Status::Standard, &[Standard::RFC7208]);
(RelayVersion, RELAY_VERSION, "Relay-Version", None, Protocol::NNTP, Status::Obsoleted, &[Standard::RFC0850, Standard::RFC5536]);
(ReplyBy, REPLY_BY, "Reply-By", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(RequireRecipientValidSince, REQUIRE_RECIPIENT_VALID_SINCE, "Require-Recipient-Valid-Since", None, Protocol::Mail, Status::Standard, &[Standard::RFC7293]);
(ResentBcc, RESENT_BCC, "Resent-Bcc", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322]);
(ResentCc, RESENT_CC, "Resent-Cc", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322]);
(ResentDate, RESENT_DATE, "Resent-Date", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322]);
(ResentFrom, RESENT_FROM, "Resent-From", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322, Standard::RFC6854]);
(ResentMessageId, RESENT_MESSAGE_ID, "Resent-Message-ID", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322]);
(ResentReplyTo, RESENT_REPLY_TO, "Resent-Reply-To", None, Protocol::Mail, Status::Obsoleted, &[Standard::RFC5322]);
(ResentSender, RESENT_SENDER, "Resent-Sender", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322, Standard::RFC6854]);
(ResentTo, RESENT_TO, "Resent-To", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322]);
(ReturnPath, RETURN_PATH, "Return-Path", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322]);
(SeeAlso, SEE_ALSO, "See-Also", None, Protocol::NNTP, Status::Obsoleted, &[Standard::RFC1849, Standard::RFC5536]);
(Sender, SENDER, "Sender", None, Protocol::Mail | Protocol::NNTP, Status::Standard, &[Standard::RFC5322, Standard::RFC6854]);
(Sensitivity, SENSITIVITY, "Sensitivity", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(Solicitation, SOLICITATION, "Solicitation", None, Protocol::Mail, Status::None, &[Standard::RFC3865]);
(Summary, SUMMARY, "Summary", None, Protocol::NNTP, Status::Standard, &[Standard::RFC5536]);
(Supersedes, SUPERSEDES, "Supersedes", None, Protocol::Mail | Protocol::NNTP, Status::None, &[Standard::RFC5536, Standard::RFC2156]);
(TlsReportDomain, TLS_REPORT_DOMAIN, "TLS-Report-Domain", None, Protocol::Mail, Status::Standard, &[Standard::RFC8460]);
(TlsReportSubmitter, TLS_REPORT_SUBMITTER, "TLS-Report-Submitter", None, Protocol::Mail, Status::Standard, &[Standard::RFC8460]);
(TlsRequired, TLS_REQUIRED, "TLS-Required", None, Protocol::Mail, Status::Standard, &[Standard::RFC8689]);
(To, TO, "To", None, Protocol::Mail, Status::Standard, &[Standard::RFC5322]);
(UserAgent, USER_AGENT, "User-Agent", None, Protocol::NNTP, Status::Standard, &[Standard::RFC5536, Standard::RFC2616]);
(VbrInfo, VBR_INFO, "VBR-Info", None, Protocol::Mail, Status::Standard, &[Standard::RFC5518]);
(X400ContentIdentifier, X400_CONTENT_IDENTIFIER, "X400-Content-Identifier", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(X400ContentReturn, X400_CONTENT_RETURN, "X400-Content-Return", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(X400ContentType, X400_CONTENT_TYPE, "X400-Content-Type", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(X400MtsIdentifier, X400_MTS_IDENTIFIER, "X400-MTS-Identifier", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(X400Originator, X400_ORIGINATOR, "X400-Originator", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(X400Received, X400_RECEIVED, "X400-Received", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(X400Recipients, X400_RECIPIENTS, "X400-Recipients", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(X400Trace, X400_TRACE, "X400-Trace", None, Protocol::Mail, Status::None, &[Standard::RFC4021]);
(Xref, XREF, "Xref", None, Protocol::NNTP, Status::Standard, &[Standard::RFC5536]);
(ApparentlyTo, APPARENTLY_TO, "Apparently-To", Some("prov/apparently-to"), Protocol::Mail, Status::None, &[Standard::RFC2076]);
(Author, AUTHOR, "Author", None, Protocol::Mail, Status::None, &[Standard::RFC9057]);
(EdiintFeatures, EDIINT_FEATURES, "EDIINT-Features", None, Protocol::Mail, Status::None, &[Standard::RFC6017]);
(EesstVersion, EESST_VERSION, "Eesst-Version", None, Protocol::Mail, Status::None, &[Standard::RFC7681]);
(ErrorsTo, ERRORS_TO, "Errors-To", Some("prov/errors-to"), Protocol::Mail, Status::None, &[Standard::RFC2076]);
(JabberId, JABBER_ID, "Jabber-ID", Some("prov/jabber-id"), Protocol::Mail | Protocol::NNTP, Status::None, &[Standard::RFC7259]);
(SioLabel, SIO_LABEL, "SIO-Label", None, Protocol::Mail, Status::None, &[Standard::RFC7444]);
(SioLabelHistory, SIO_LABEL_HISTORY, "SIO-Label-History", None, Protocol::Mail, Status::None, &[Standard::RFC7444]);
(XArchivedAt, X_ARCHIVED_AT, "X-Archived-At", Some("prov/x-archived-at"), Protocol::Mail | Protocol::NNTP, Status::Deprecated, &[Standard::RFC5064]);
(XMittente, X_MITTENTE, "X-Mittente", None, Protocol::Mail, Status::None, &[Standard::RFC6109]);
(XRicevuta, X_RICEVUTA, "X-Ricevuta", None, Protocol::Mail, Status::None, &[Standard::RFC6109]);
(XRiferimentoMessageId, X_RIFERIMENTO_MESSAGE_ID, "X-Riferimento-Message-ID", None, Protocol::Mail, Status::None, &[Standard::RFC6109]);
(XTiporicevuta, X_TIPORICEVUTA, "X-TipoRicevuta", None, Protocol::Mail, Status::None, &[Standard::RFC6109]);
(XTrasporto, X_TRASPORTO, "X-Trasporto", None, Protocol::Mail, Status::None, &[Standard::RFC6109]);
(XVerificasicurezza, X_VERIFICASICUREZZA, "X-VerificaSicurezza", None, Protocol::Mail, Status::None, &[Standard::RFC6109]);
}
/// Valid header name ASCII bytes
///
/// Source: [RFC5322 3.6.8.](https://datatracker.ietf.org/doc/html/rfc5322#autoid-35)
/// ```text
/// field-name = 1*ftext
///
/// ftext = %d33-57 / ; Printable US-ASCII
/// %d59-126 ; characters not including
/// ; ":".
/// ```
const HEADER_CHARS: [u8; 128] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2x
0, 0, 0, b'!', b'"', b'#', b'$', b'%', b'&', b'\'', // 3x
0, 0, b'*', b'+', 0, b'-', b'.', 0, b'0', b'1', // 4x
b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', 0, 0, // 5x
0, 0, 0, 0, 0, b'a', b'b', b'c', b'd', b'e', // 6x
b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', // 7x
b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', // 8x
b'z', 0, 0, 0, b'^', b'_', b'`', b'a', b'b', b'c', // 9x
b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', // 10x
b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', // 11x
b'x', b'y', b'z', 0, b'|', 0, b'~', 0, // 128
];
impl HeaderName {
/// Returns a `str` representation of the header.
///
/// The returned string will always be lower case.
#[inline]
pub fn as_str(&self) -> &str {
match self.inner {
Repr::Standard(v) => v.as_str(),
Repr::Custom(ref v) => unsafe { std::str::from_utf8_unchecked(&*v.0) },
}
}
/// Returns a `&[u8]` representation of the header.
///
/// The returned slice will always be lower case.
#[inline]
pub fn as_bytes(&self) -> &[u8] {
match self.inner {
Repr::Standard(v) => v.as_str().as_bytes(),
Repr::Custom(ref v) => v.0.as_ref(),
}
}
pub fn from_bytes(src: &[u8]) -> Result<Self, InvalidHeaderName> {
if let Some(std) = StandardHeader::from_bytes(src.trim()) {
Ok(Self {
inner: Repr::Standard(std),
})
} else {
let mut buf = SmallVec::<[u8; 32]>::new();
for b in src {
if let Some(b) = HEADER_CHARS.get(*b as usize).filter(|b| **b != 0) {
buf.push(*b);
} else {
return Err(InvalidHeaderName::new());
}
}
Ok(Self {
inner: Repr::Custom(Custom(buf)),
})
}
}
}
impl FromStr for HeaderName {
type Err = InvalidHeaderName;
fn from_str(s: &str) -> Result<HeaderName, InvalidHeaderName> {
HeaderName::from_bytes(s.as_bytes()).map_err(|_| InvalidHeaderName::new())
}
}
impl AsRef<str> for HeaderName {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<[u8]> for HeaderName {
fn as_ref(&self) -> &[u8] {
self.as_str().as_bytes()
}
}
impl Borrow<str> for HeaderName {
fn borrow(&self) -> &str {
self.as_str()
}
}
impl std::fmt::Display for HeaderName {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(fmt, "{}", self.as_str())
}
}
impl<'de> Deserialize<'de> for HeaderName {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum Helper {
S(String),
B(Vec<u8>),
}
if let Ok(s) = <Helper>::deserialize(deserializer) {
Self::from_bytes(match &s {
Helper::S(v) => v.as_bytes(),
Helper::B(v) => v.as_slice(),
})
.map_err(|_| de::Error::custom("invalid header name value"))
} else {
Err(de::Error::custom("invalid header name value"))
}
}
}
impl Serialize for HeaderName {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl InvalidHeaderName {
const fn new() -> InvalidHeaderName {
InvalidHeaderName
}
}
impl<'a> From<&'a HeaderName> for HeaderName {
fn from(src: &'a HeaderName) -> Self {
src.clone()
}
}
impl From<&HeaderName> for Cow<'static, str> {
fn from(src: &HeaderName) -> Self {
match src.inner {
Repr::Standard(s) => Cow::Borrowed(s.as_str()),
Repr::Custom(_) => Cow::Owned(src.to_string()),
}
}
}
impl<'a> TryFrom<&'a str> for HeaderName {
type Error = InvalidHeaderName;
#[inline]
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
Self::from_bytes(s.as_bytes()).map_err(|_| InvalidHeaderName::new())
}
}
impl<'a> TryFrom<&'a String> for HeaderName {
type Error = InvalidHeaderName;
#[inline]
fn try_from(s: &'a String) -> Result<Self, Self::Error> {
Self::from_bytes(s.as_bytes()).map_err(|_| InvalidHeaderName::new())
}
}
impl<'a> TryFrom<&'a [u8]> for HeaderName {
type Error = InvalidHeaderName;
#[inline]
fn try_from(s: &'a [u8]) -> Result<Self, Self::Error> {
Self::from_bytes(s).map_err(|_| InvalidHeaderName::new())
}
}
impl TryFrom<String> for HeaderName {
type Error = InvalidHeaderName;
#[inline]
fn try_from(s: String) -> Result<Self, Self::Error> {
Self::from_bytes(s.as_bytes()).map_err(|_| InvalidHeaderName::new())
}
}
impl TryFrom<Vec<u8>> for HeaderName {
type Error = InvalidHeaderName;
#[inline]
fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> {
Self::from_bytes(&vec).map_err(|_| InvalidHeaderName::new())
}
}
#[doc(hidden)]
impl From<StandardHeader> for HeaderName {
fn from(src: StandardHeader) -> HeaderName {
HeaderName {
inner: Repr::Standard(src),
}
}
}
#[doc(hidden)]
impl From<Custom> for HeaderName {
fn from(src: Custom) -> HeaderName {
HeaderName {
inner: Repr::Custom(src),
}
}
}
impl<'a> PartialEq<&'a HeaderName> for HeaderName {
#[inline]
fn eq(&self, other: &&'a HeaderName) -> bool {
*self == **other
}
}
impl<'a> PartialEq<HeaderName> for &'a HeaderName {
#[inline]
fn eq(&self, other: &HeaderName) -> bool {
*other == *self
}
}
impl PartialEq<str> for HeaderName {
/// Performs a case-insensitive comparison of the string against the header
/// name
///
/// # Examples
///
/// ```
/// use melib::email::headers::HeaderName;
///
/// assert_eq!(HeaderName::CONTENT_LENGTH, "content-length");
/// assert_eq!(HeaderName::CONTENT_LENGTH, "Content-Length");
/// assert_ne!(HeaderName::CONTENT_LENGTH, "content length");
/// ```
#[inline]
fn eq(&self, other: &str) -> bool {
self.as_str().eq_ignore_ascii_case(other)
}
}
impl PartialEq<HeaderName> for str {
/// Performs a case-insensitive comparison of the string against the header
/// name
///
/// # Examples
///
/// ```
/// use std::convert::TryFrom;
///
/// use melib::email::headers::HeaderName;
///
/// assert_eq!(HeaderName::CONTENT_LENGTH, "content-length");
/// assert_eq!(HeaderName::CONTENT_LENGTH, "Content-Length");
/// assert_ne!(HeaderName::CONTENT_LENGTH, "content length");
/// assert_eq!(
/// HeaderName::CONTENT_LENGTH,
/// HeaderName::try_from("content-length").unwrap()
/// );
/// ```
#[inline]
fn eq(&self, other: &HeaderName) -> bool {
*other == *self
}
}
impl<'a> PartialEq<&'a str> for HeaderName {
/// Performs a case-insensitive comparison of the string against the header
/// name
#[inline]
fn eq(&self, other: &&'a str) -> bool {
*self == **other
}
}
impl<'a> PartialEq<HeaderName> for &'a str {
/// Performs a case-insensitive comparison of the string against the header
/// name
#[inline]
fn eq(&self, other: &HeaderName) -> bool {
*other == *self
}
}
impl Hash for Custom {
#[inline]
fn hash<H: Hasher>(&self, hasher: &mut H) {
for b in self.0.as_slice() {
hasher.write_u8(b.to_ascii_lowercase())
}
}
}

View File

@ -43,11 +43,11 @@ impl From<Mailto> for Draft {
bcc,
body,
} = val;
ret.set_header("Subject", subject.unwrap_or_default());
ret.set_header("Cc", cc.unwrap_or_default());
ret.set_header("Bcc", bcc.unwrap_or_default());
ret.set_header(HeaderName::SUBJECT, subject.unwrap_or_default());
ret.set_header(HeaderName::CC, cc.unwrap_or_default());
ret.set_header(HeaderName::BCC, bcc.unwrap_or_default());
ret.set_header(HeaderName::TO, address.to_string());
ret.set_body(body.unwrap_or_default());
ret.set_header("To", address.to_string());
ret
}
}

View File

@ -306,6 +306,7 @@ impl From<isahc::http::StatusCode> for NetworkErrorKind {
}
#[derive(Debug, Copy, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum ErrorKind {
None,
External,
@ -317,6 +318,7 @@ pub enum ErrorKind {
OSError,
NotImplemented,
NotSupported,
ValueError,
}
impl fmt::Display for ErrorKind {
@ -325,16 +327,17 @@ impl fmt::Display for ErrorKind {
fmt,
"{}",
match self {
ErrorKind::None => "None",
ErrorKind::External => "External",
ErrorKind::Authentication => "Authentication",
ErrorKind::Bug => "Bug, please report this!",
ErrorKind::Network(ref inner) => inner.as_str(),
ErrorKind::Timeout => "Timeout",
ErrorKind::OSError => "OS Error",
ErrorKind::Configuration => "Configuration",
ErrorKind::NotImplemented => "Not implemented",
ErrorKind::NotSupported => "Not supported",
Self::None => "None",
Self::External => "External",
Self::Authentication => "Authentication",
Self::Bug => "Bug, please report this!",
Self::Network(ref inner) => inner.as_str(),
Self::Timeout => "Timeout",
Self::OSError => "OS Error",
Self::Configuration => "Configuration",
Self::NotImplemented => "Not implemented",
Self::NotSupported => "Not supported",
Self::ValueError => "Invalid value",
}
)
}
@ -342,15 +345,15 @@ impl fmt::Display for ErrorKind {
impl ErrorKind {
pub fn is_network(&self) -> bool {
matches!(self, ErrorKind::Network(_))
matches!(self, Self::Network(_))
}
pub fn is_timeout(&self) -> bool {
matches!(self, ErrorKind::Timeout)
matches!(self, Self::Timeout)
}
pub fn is_authentication(&self) -> bool {
matches!(self, ErrorKind::Authentication)
matches!(self, Self::Authentication)
}
}
@ -691,6 +694,15 @@ impl From<nom::Err<(&str, nom::error::ErrorKind)>> for Error {
}
}
impl From<crate::email::InvalidHeaderName> for Error {
#[inline]
fn from(kind: crate::email::InvalidHeaderName) -> Error {
Error::new(kind.to_string())
.set_source(Some(Arc::new(kind)))
.set_kind(ErrorKind::Network(NetworkErrorKind::InvalidTLSConnection))
}
}
impl<'a> From<&'a mut Error> for Error {
#[inline]
fn from(kind: &'a mut Error) -> Error {

View File

@ -141,7 +141,7 @@ impl fmt::Display for Composer {
write!(
f,
"reply: {}",
(&self.draft.headers()["Subject"]).trim_at_boundary(8)
(&self.draft.headers()[HeaderName::SUBJECT]).trim_at_boundary(8)
)
} else {
write!(f, "composing")
@ -204,11 +204,11 @@ impl Composer {
if v.is_empty() {
continue;
}
ret.draft.set_header(h, v.into());
ret.draft.set_header(h.into(), v.into());
}
if *account_settings!(context[account_hash].composing.insert_user_agent) {
ret.draft.set_header(
"User-Agent",
HeaderName::USER_AGENT,
format!("meli {}", option_env!("CARGO_PKG_VERSION").unwrap_or("0.0")),
);
}
@ -296,9 +296,9 @@ impl Composer {
subject.to_string()
}
};
ret.draft.set_header("Subject", subject);
ret.draft.set_header(HeaderName::SUBJECT, subject);
ret.draft.set_header(
"References",
HeaderName::REFERENCES,
format!(
"{} {}",
envelope
@ -314,17 +314,19 @@ impl Composer {
envelope.message_id_display()
),
);
ret.draft
.set_header("In-Reply-To", envelope.message_id_display().into());
ret.draft.set_header(
HeaderName::IN_REPLY_TO,
envelope.message_id_display().into(),
);
if let Some(reply_to) = envelope.other_headers().get("To") {
if let Some(reply_to) = envelope.other_headers().get(HeaderName::TO) {
let to: &str = reply_to;
let extra_identities = &account.settings.account.extra_identities;
if let Some(extra) = extra_identities
.iter()
.find(|extra| to.contains(extra.as_str()))
{
ret.draft.set_header("From", extra.into());
ret.draft.set_header(HeaderName::FROM, extra.into());
}
}
@ -348,13 +350,13 @@ impl Composer {
}
if let Some(reply_to) = envelope
.other_headers()
.get("Mail-Followup-To")
.get(HeaderName::MAIL_FOLLOWUP_TO)
.and_then(|v| v.try_into().ok())
{
to.insert(reply_to);
} else if let Some(reply_to) = envelope
.other_headers()
.get("Reply-To")
.get(HeaderName::REPLY_TO)
.and_then(|v| v.try_into().ok())
{
to.insert(reply_to);
@ -371,7 +373,7 @@ impl Composer {
) {
to.remove(&ours);
}
ret.draft.set_header("To", {
ret.draft.set_header(HeaderName::TO, {
let mut ret: String =
to.into_iter()
.fold(String::new(), |mut s: String, n: Address| {
@ -383,13 +385,15 @@ impl Composer {
ret.pop();
ret
});
ret.draft.set_header("Cc", envelope.field_cc_to_string());
} else if let Some(reply_to) = envelope.other_headers().get("Mail-Reply-To") {
ret.draft.set_header("To", reply_to.to_string());
} else if let Some(reply_to) = envelope.other_headers().get("Reply-To") {
ret.draft.set_header("To", reply_to.to_string());
ret.draft
.set_header(HeaderName::CC, envelope.field_cc_to_string());
} else if let Some(reply_to) = envelope.other_headers().get(HeaderName::MAIL_REPLY_TO) {
ret.draft.set_header(HeaderName::TO, reply_to.to_string());
} else if let Some(reply_to) = envelope.other_headers().get(HeaderName::REPLY_TO) {
ret.draft.set_header(HeaderName::TO, reply_to.to_string());
} else {
ret.draft.set_header("To", envelope.field_from_to_string());
ret.draft
.set_header(HeaderName::TO, envelope.field_from_to_string());
}
ret.draft.body = {
let mut ret = attribution_string(
@ -495,7 +499,7 @@ impl Composer {
) -> Self {
let mut composer = Composer::with_account(coordinates.0, context);
let mut draft: Draft = Draft::default();
draft.set_header("Subject", format!("Fwd: {}", env.subject()));
draft.set_header(HeaderName::SUBJECT, format!("Fwd: {}", env.subject()));
let preamble = format!(
r#"
---------- Forwarded message ---------
@ -557,8 +561,15 @@ To: {}
self.form.set_cursor(old_cursor);
let headers = self.draft.headers();
let account_hash = self.account_hash;
for &k in &["Date", "From", "To", "Cc", "Bcc", "Subject"] {
if k == "To" || k == "Cc" || k == "Bcc" {
for k in &[
HeaderName::DATE,
HeaderName::FROM,
HeaderName::TO,
HeaderName::CC,
HeaderName::BCC,
HeaderName::SUBJECT,
] {
if k == HeaderName::TO || k == HeaderName::CC || k == HeaderName::BCC {
self.form.push_cl((
k.into(),
headers[k].to_string(),
@ -571,7 +582,7 @@ To: {}
.collect::<Vec<AutoCompleteEntry>>()
}),
));
} else if k == "From" {
} else if k == HeaderName::FROM {
self.form.push_cl((
k.into(),
headers[k].to_string(),
@ -812,10 +823,11 @@ impl Component for Composer {
context[self.account_hash].pgp.auto_sign
));
}
if !self.draft.headers().contains_key("From") || self.draft.headers()["From"].is_empty()
if !self.draft.headers().contains_key(HeaderName::FROM)
|| self.draft.headers()[HeaderName::FROM].is_empty()
{
self.draft.set_header(
"From",
HeaderName::FROM,
context.accounts[&self.account_hash]
.settings
.account()
@ -1230,7 +1242,8 @@ impl Component for Composer {
UIEvent::FinishedUIDialog(id, ref mut result),
) if selector.id() == *id => {
if let Some(to_val) = result.downcast_mut::<String>() {
self.draft.set_header("To", std::mem::take(to_val));
self.draft
.set_header(HeaderName::TO, std::mem::take(to_val));
self.update_form();
}
self.mode = ViewMode::Edit;
@ -1381,28 +1394,6 @@ impl Component for Composer {
UIEvent::Resize => {
self.set_dirty(true);
}
/*
/* Switch e-mail From: field to the `left` configured account. */
UIEvent::Input(Key::Left) if self.cursor == Cursor::From => {
self.draft.headers_mut().insert(
"From".into(),
get_display_name(context, self.account_hash),
);
self.dirty = true;
return true;
}
/* Switch e-mail From: field to the `right` configured account. */
UIEvent::Input(Key::Right) if self.cursor == Cursor::From => {
if self.account_cursor + 1 < context.accounts.len() {
self.account_cursor += 1;
self.draft.headers_mut().insert(
"From".into(),
get_display_name(context, self.account_cursor),
);
self.dirty = true;
}
return true;
}*/
UIEvent::Input(ref key)
if self.mode.is_edit()
&& shortcut!(key == shortcuts[Shortcuts::COMPOSING]["scroll_up"]) =>
@ -2494,9 +2485,12 @@ hello world.
&mut context,
false,
);
assert_eq!(&composer.draft.headers()["Subject"], "RE: your e-mail");
assert_eq!(
&composer.draft.headers()["To"],
&composer.draft.headers()[HeaderName::SUBJECT],
"RE: your e-mail"
);
assert_eq!(
&composer.draft.headers()[HeaderName::TO],
r#"some name <some@example.com>"#
);
let raw_mail = r#"From: "some name" <some@example.com>
@ -2520,9 +2514,12 @@ hello world.
&mut context,
false,
);
assert_eq!(&composer.draft.headers()["Subject"], "Re: your e-mail");
assert_eq!(
&composer.draft.headers()["To"],
&composer.draft.headers()[HeaderName::SUBJECT],
"Re: your e-mail"
);
assert_eq!(
&composer.draft.headers()[HeaderName::TO],
r#"some name <some@example.com>"#
);
}

View File

@ -275,8 +275,8 @@ mod tests {
let mut draft = Draft::default();
draft
.set_body("αδφαφσαφασ".to_string())
.set_header("Subject", "test_update()".into())
.set_header("Date", "Sun, 16 Jun 2013 17:56:45 +0200".into());
.set_header(HeaderName::SUBJECT, "test_update()".into())
.set_header(HeaderName::DATE, "Sun, 16 Jun 2013 17:56:45 +0200".into());
println!("Check that past Date header value produces a warning…");
#[allow(const_item_mutation)]
let err_msg = PASTDATEWARN(&mut ctx, &mut draft).unwrap_err().to_string();
@ -299,8 +299,8 @@ mod tests {
let mut draft = Draft::default();
draft
.set_body("αδφαφσαφασ".to_string())
.set_header("Subject", "test_update()".into())
.set_header("Date", "Sun sds16 Jun 2013 17:56:45 +0200".into());
.set_header(HeaderName::SUBJECT, "test_update()".into())
.set_header(HeaderName::DATE, "Sun sds16 Jun 2013 17:56:45 +0200".into());
let mut hook = HEADERWARN;
println!("Check for missing/empty From header value…");
@ -312,7 +312,7 @@ mod tests {
"HEADERWARN should complain about From value being empty: {}",
err_msg
);
draft.set_header("From", "user <user@example.com>".into());
draft.set_header(HeaderName::FROM, "user <user@example.com>".into());
println!("Check for missing/empty To header value…");
let err_msg = hook(&mut ctx, &mut draft).unwrap_err().to_string();
@ -323,7 +323,7 @@ mod tests {
"HEADERWARN should complain about To value being empty: {}",
err_msg
);
draft.set_header("To", "other user <user@example.com>".into());
draft.set_header(HeaderName::TO, "other user <user@example.com>".into());
println!("Check for invalid Date header value…");
let err_msg = hook(&mut ctx, &mut draft).unwrap_err().to_string();
@ -337,11 +337,11 @@ mod tests {
draft = Draft::default();
draft
.set_body("αδφαφσαφασ".to_string())
.set_header("From", "user <user@example.com>".into())
.set_header("To", "other user <user@example.com>".into())
.set_header("Subject", "test_update()".into());
.set_header(HeaderName::FROM, "user <user@example.com>".into())
.set_header(HeaderName::TO, "other user <user@example.com>".into())
.set_header(HeaderName::SUBJECT, "test_update()".into());
hook(&mut ctx, &mut draft).unwrap();
draft.set_header("From", "user user@example.com>".into());
draft.set_header(HeaderName::FROM, "user user@example.com>".into());
println!("Check for invalid From header value…");
let err_msg = hook(&mut ctx, &mut draft).unwrap_err().to_string();
@ -361,8 +361,8 @@ mod tests {
let mut draft = Draft::default();
draft
.set_body("αδφαφσαφασ".to_string())
.set_header("Subject", "Attachments included".into())
.set_header("Date", "Sun, 16 Jun 2013 17:56:45 +0200".into());
.set_header(HeaderName::SUBJECT, "Attachments included".into())
.set_header(HeaderName::DATE, "Sun, 16 Jun 2013 17:56:45 +0200".into());
let mut hook = MISSINGATTACHMENTWARN;
@ -378,7 +378,7 @@ mod tests {
);
draft
.set_header("Subject", "Hello.".into())
.set_header(HeaderName::SUBJECT, "Hello.".into())
.set_body("Attachments included".to_string());
println!(
"Check that mentioning attachments in body produces a warning if draft has no \
@ -394,7 +394,7 @@ mod tests {
println!(
"Check that mentioning attachments produces no warnings if draft has attachments…"
);
draft.set_header("Subject", "Attachments included".into());
draft.set_header(HeaderName::SUBJECT, "Attachments included".into());
let mut attachment = AttachmentBuilder::new(b"");
attachment
.set_raw(b"foobar".to_vec())
@ -414,7 +414,7 @@ mod tests {
let tempdir = tempfile::tempdir().unwrap();
let mut ctx = Context::new_mock(&tempdir);
let mut draft = Draft::default();
draft.set_header("Date", "Sun, 16 Jun 2013 17:56:45 +0200".into());
draft.set_header(HeaderName::DATE, "Sun, 16 Jun 2013 17:56:45 +0200".into());
let mut hook = EMPTYDRAFTWARN;
@ -427,7 +427,7 @@ mod tests {
);
println!("Check that non-empty draft produces no warning…");
draft.set_header("Subject", "Ping".into());
draft.set_header(HeaderName::SUBJECT, "Ping".into());
hook(&mut ctx, &mut draft).unwrap();
}
}

View File

@ -2663,7 +2663,7 @@ impl Component for MailView {
if let Ok(mailto) = Mailto::try_from(*email) {
let mut draft: Draft = mailto.into();
draft.set_header(
"From",
HeaderName::FROM,
context.accounts[&self.coordinates.0]
.settings
.account()

View File

@ -22,7 +22,7 @@
//! Configuration for composing email.
use std::collections::HashMap;
use melib::ToggleFlag;
use melib::{email::HeaderName, ToggleFlag};
use super::{
default_vals::{ask, false_val, none, true_val},
@ -60,7 +60,7 @@ pub struct ComposingSettings {
/// Set default header values for new drafts
/// Default: empty
#[serde(default, alias = "default-header-values")]
pub default_header_values: HashMap<String, String>,
pub default_header_values: HashMap<HeaderName, String>,
/// Wrap header preample when editing a draft in an editor. This allows you
/// to write non-plain text email without the preamble creating syntax
/// errors. They are stripped when you return from the editor. The

View File

@ -23,7 +23,9 @@
#![allow(clippy::derivable_impls)]
//! This module is automatically generated by config_macros.rs.
use super::*;
use melib::HeaderName;
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct PagerSettingsOverride { # [doc = " Number of context lines when going to next page."] # [doc = " Default: 0"] # [serde (alias = "pager-context")] # [serde (default)] pub pager_context : Option < usize > , # [doc = " Stop at the end instead of displaying next mail."] # [doc = " Default: false"] # [serde (alias = "pager-stop")] # [serde (default)] pub pager_stop : Option < bool > , # [doc = " Always show headers when scrolling."] # [doc = " Default: true"] # [serde (alias = "headers-sticky")] # [serde (default)] pub headers_sticky : Option < bool > , # [doc = " The height of the pager in mail view, in percent."] # [doc = " Default: 80"] # [serde (alias = "pager-ratio")] # [serde (default)] pub pager_ratio : Option < usize > , # [doc = " A command to pipe mail output through for viewing in pager."] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string")] # [serde (default)] pub filter : Option < Option < String > > , # [doc = " A command to pipe html output before displaying it in a pager"] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string" , alias = "html-filter")] # [serde (default)] pub html_filter : Option < Option < String > > , # [doc = " Respect \"format=flowed\""] # [doc = " Default: true"] # [serde (alias = "format-flowed")] # [serde (default)] pub format_flowed : Option < bool > , # [doc = " Split long lines that would overflow on the x axis."] # [doc = " Default: true"] # [serde (alias = "split-long-lines")] # [serde (default)] pub split_long_lines : Option < bool > , # [doc = " Minimum text width in columns."] # [doc = " Default: 80"] # [serde (alias = "minimum-width")] # [serde (default)] pub minimum_width : Option < usize > , # [doc = " Choose `text/html` alternative if `text/plain` is empty in"] # [doc = " `multipart/alternative` attachments."] # [doc = " Default: true"] # [serde (alias = "auto-choose-multipart-alternative")] # [serde (default)] pub auto_choose_multipart_alternative : Option < ToggleFlag > , # [doc = " Show Date: in my timezone"] # [doc = " Default: true"] # [serde (alias = "show-date-in-my-timezone")] # [serde (default)] pub show_date_in_my_timezone : Option < ToggleFlag > , # [doc = " A command to launch URLs with. The URL will be given as the first"] # [doc = " argument of the command. Default: None"] # [serde (deserialize_with = "non_empty_opt_string")] # [serde (default)] pub url_launcher : Option < Option < String > > , # [doc = " A command to open html files."] # [doc = " Default: None"] # [serde (deserialize_with = "non_empty_opt_string" , alias = "html-open")] # [serde (default)] pub html_open : Option < Option < String > > } impl Default for PagerSettingsOverride { fn default () -> Self { PagerSettingsOverride { pager_context : None , pager_stop : None , headers_sticky : None , pager_ratio : None , filter : None , html_filter : None , format_flowed : None , split_long_lines : None , minimum_width : None , auto_choose_multipart_alternative : None , show_date_in_my_timezone : None , url_launcher : None , html_open : None } } }
@ -33,7 +35,7 @@ use super::*;
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ShortcutsOverride { # [serde (default)] pub general : Option < GeneralShortcuts > , # [serde (default)] pub listing : Option < ListingShortcuts > , # [serde (default)] pub composing : Option < ComposingShortcuts > , # [serde (alias = "contact-list")] # [serde (default)] pub contact_list : Option < ContactListShortcuts > , # [serde (alias = "envelope-view")] # [serde (default)] pub envelope_view : Option < EnvelopeViewShortcuts > , # [serde (alias = "thread-view")] # [serde (default)] pub thread_view : Option < ThreadViewShortcuts > , # [serde (default)] pub pager : Option < PagerShortcuts > } impl Default for ShortcutsOverride { fn default () -> Self { ShortcutsOverride { general : None , listing : None , composing : None , contact_list : None , envelope_view : None , thread_view : None , pager : None } } }
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ComposingSettingsOverride { # [doc = " A command to pipe new emails to"] # [doc = " Required"] # [serde (default)] pub send_mail : Option < SendMail > , # [doc = " Command to launch editor. Can have arguments. Draft filename is given as"] # [doc = " the last argument. If it's missing, the environment variable $EDITOR is"] # [doc = " looked up."] # [serde (alias = "editor-command" , alias = "editor-cmd" , alias = "editor_cmd")] # [serde (default)] pub editor_command : Option < Option < String > > , # [doc = " Embed editor (for terminal interfaces) instead of forking and waiting."] # [serde (default)] pub embed : Option < bool > , # [doc = " Set \"format=flowed\" in plain text attachments."] # [doc = " Default: true"] # [serde (alias = "format-flowed")] # [serde (default)] pub format_flowed : Option < bool > , # [doc = "Set User-Agent"] # [doc = "Default: empty"] # [serde (alias = "insert_user_agent")] # [serde (default)] pub insert_user_agent : Option < bool > , # [doc = " Set default header values for new drafts"] # [doc = " Default: empty"] # [serde (alias = "default-header-values")] # [serde (default)] pub default_header_values : Option < HashMap < String , String > > , # [doc = " Wrap header preample when editing a draft in an editor. This allows you"] # [doc = " to write non-plain text email without the preamble creating syntax"] # [doc = " errors. They are stripped when you return from the editor. The"] # [doc = " values should be a two element array of strings, a prefix and suffix."] # [doc = " Default: None"] # [serde (alias = "wrap-header-preample")] # [serde (default)] pub wrap_header_preamble : Option < Option < (String , String) > > , # [doc = " Store sent mail after successful submission. This setting is meant to be"] # [doc = " disabled for non-standard behaviour in gmail, which auto-saves sent"] # [doc = " mail on its own. Default: true"] # [serde (default)] pub store_sent_mail : Option < bool > , # [doc = " The attribution line appears above the quoted reply text."] # [doc = " The format specifiers for the replied address are:"] # [doc = " - `%+f` — the sender's name and email address."] # [doc = " - `%+n` — the sender's name (or email address, if no name is included)."] # [doc = " - `%+a` — the sender's email address."] # [doc = " The format string is passed to strftime(3) with the replied envelope's"] # [doc = " date. Default: \"On %a, %0e %b %Y %H:%M, %+f wrote:%n\""] # [serde (default)] pub attribution_format_string : Option < Option < String > > , # [doc = " Whether the strftime call for the attribution string uses the POSIX"] # [doc = " locale instead of the user's active locale"] # [doc = " Default: true"] # [serde (default)] pub attribution_use_posix_locale : Option < bool > , # [doc = " Forward emails as attachment? (Alternative is inline)"] # [doc = " Default: ask"] # [serde (alias = "forward-as-attachment")] # [serde (default)] pub forward_as_attachment : Option < ToggleFlag > , # [doc = " Alternative lists of reply prefixes (etc. [\"Re:\", \"RE:\", ...]) to strip"] # [doc = " Default: `[\"Re:\", \"RE:\", \"Fwd:\", \"Fw:\", \"回复:\", \"回覆:\", \"SV:\", \"Sv:\","] # [doc = " \"VS:\", \"Antw:\", \"Doorst:\", \"VS:\", \"VL:\", \"REF:\", \"TR:\", \"TR:\", \"AW:\","] # [doc = " \"WG:\", \"ΑΠ:\", \"Απ:\", \"απ:\", \"ΠΡΘ:\", \"Πρθ:\", \"πρθ:\", \"ΣΧΕΤ:\", \"Σχετ:\","] # [doc = " \"σχετ:\", \"ΠΡΘ:\", \"Πρθ:\", \"πρθ:\", \"Vá:\", \"Továbbítás:\", \"R:\", \"I:\","] # [doc = " \"RIF:\", \"FS:\", \"BLS:\", \"TRS:\", \"VS:\", \"VB:\", \"RV:\", \"RES:\", \"Res\","] # [doc = " \"ENC:\", \"Odp:\", \"PD:\", \"YNT:\", \"İLT:\", \"ATB:\", \"YML:\"]`"] # [serde (alias = "reply-prefix-list-to-strip")] # [serde (default)] pub reply_prefix_list_to_strip : Option < Option < Vec < String > > > , # [doc = " The prefix to use in reply subjects. The de facto prefix is \"Re:\"."] # [serde (alias = "reply-prefix")] # [serde (default)] pub reply_prefix : Option < String > , # [doc = " Custom `compose-hooks`."] # [serde (alias = "custom-compose-hooks")] # [serde (default)] pub custom_compose_hooks : Option < Vec < ComposeHook > > , # [doc = " Disabled `compose-hooks`."] # [serde (alias = "disabled-compose-hooks")] # [serde (default)] pub disabled_compose_hooks : Option < Vec < String > > } impl Default for ComposingSettingsOverride { fn default () -> Self { ComposingSettingsOverride { send_mail : None , editor_command : None , embed : None , format_flowed : None , insert_user_agent : None , default_header_values : None , wrap_header_preamble : None , store_sent_mail : None , attribution_format_string : None , attribution_use_posix_locale : None , forward_as_attachment : None , reply_prefix_list_to_strip : None , reply_prefix : None , custom_compose_hooks : None , disabled_compose_hooks : None } } }
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ComposingSettingsOverride { # [doc = " A command to pipe new emails to"] # [doc = " Required"] # [serde (default)] pub send_mail : Option < SendMail > , # [doc = " Command to launch editor. Can have arguments. Draft filename is given as"] # [doc = " the last argument. If it's missing, the environment variable $EDITOR is"] # [doc = " looked up."] # [serde (alias = "editor-command" , alias = "editor-cmd" , alias = "editor_cmd")] # [serde (default)] pub editor_command : Option < Option < String > > , # [doc = " Embed editor (for terminal interfaces) instead of forking and waiting."] # [serde (default)] pub embed : Option < bool > , # [doc = " Set \"format=flowed\" in plain text attachments."] # [doc = " Default: true"] # [serde (alias = "format-flowed")] # [serde (default)] pub format_flowed : Option < bool > , # [doc = "Set User-Agent"] # [doc = "Default: empty"] # [serde (alias = "insert_user_agent")] # [serde (default)] pub insert_user_agent : Option < bool > , # [doc = " Set default header values for new drafts"] # [doc = " Default: empty"] # [serde (alias = "default-header-values")] # [serde (default)] pub default_header_values : Option < HashMap < HeaderName , String > > , # [doc = " Wrap header preample when editing a draft in an editor. This allows you"] # [doc = " to write non-plain text email without the preamble creating syntax"] # [doc = " errors. They are stripped when you return from the editor. The"] # [doc = " values should be a two element array of strings, a prefix and suffix."] # [doc = " Default: None"] # [serde (alias = "wrap-header-preample")] # [serde (default)] pub wrap_header_preamble : Option < Option < (String , String) > > , # [doc = " Store sent mail after successful submission. This setting is meant to be"] # [doc = " disabled for non-standard behaviour in gmail, which auto-saves sent"] # [doc = " mail on its own. Default: true"] # [serde (default)] pub store_sent_mail : Option < bool > , # [doc = " The attribution line appears above the quoted reply text."] # [doc = " The format specifiers for the replied address are:"] # [doc = " - `%+f` — the sender's name and email address."] # [doc = " - `%+n` — the sender's name (or email address, if no name is included)."] # [doc = " - `%+a` — the sender's email address."] # [doc = " The format string is passed to strftime(3) with the replied envelope's"] # [doc = " date. Default: \"On %a, %0e %b %Y %H:%M, %+f wrote:%n\""] # [serde (default)] pub attribution_format_string : Option < Option < String > > , # [doc = " Whether the strftime call for the attribution string uses the POSIX"] # [doc = " locale instead of the user's active locale"] # [doc = " Default: true"] # [serde (default)] pub attribution_use_posix_locale : Option < bool > , # [doc = " Forward emails as attachment? (Alternative is inline)"] # [doc = " Default: ask"] # [serde (alias = "forward-as-attachment")] # [serde (default)] pub forward_as_attachment : Option < ToggleFlag > , # [doc = " Alternative lists of reply prefixes (etc. [\"Re:\", \"RE:\", ...]) to strip"] # [doc = " Default: `[\"Re:\", \"RE:\", \"Fwd:\", \"Fw:\", \"回复:\", \"回覆:\", \"SV:\", \"Sv:\","] # [doc = " \"VS:\", \"Antw:\", \"Doorst:\", \"VS:\", \"VL:\", \"REF:\", \"TR:\", \"TR:\", \"AW:\","] # [doc = " \"WG:\", \"ΑΠ:\", \"Απ:\", \"απ:\", \"ΠΡΘ:\", \"Πρθ:\", \"πρθ:\", \"ΣΧΕΤ:\", \"Σχετ:\","] # [doc = " \"σχετ:\", \"ΠΡΘ:\", \"Πρθ:\", \"πρθ:\", \"Vá:\", \"Továbbítás:\", \"R:\", \"I:\","] # [doc = " \"RIF:\", \"FS:\", \"BLS:\", \"TRS:\", \"VS:\", \"VB:\", \"RV:\", \"RES:\", \"Res\","] # [doc = " \"ENC:\", \"Odp:\", \"PD:\", \"YNT:\", \"İLT:\", \"ATB:\", \"YML:\"]`"] # [serde (alias = "reply-prefix-list-to-strip")] # [serde (default)] pub reply_prefix_list_to_strip : Option < Option < Vec < String > > > , # [doc = " The prefix to use in reply subjects. The de facto prefix is \"Re:\"."] # [serde (alias = "reply-prefix")] # [serde (default)] pub reply_prefix : Option < String > , # [doc = " Custom `compose-hooks`."] # [serde (alias = "custom-compose-hooks")] # [serde (default)] pub custom_compose_hooks : Option < Vec < ComposeHook > > , # [doc = " Disabled `compose-hooks`."] # [serde (alias = "disabled-compose-hooks")] # [serde (default)] pub disabled_compose_hooks : Option < Vec < String > > } impl Default for ComposingSettingsOverride { fn default () -> Self { ComposingSettingsOverride { send_mail : None , editor_command : None , embed : None , format_flowed : None , insert_user_agent : None , default_header_values : None , wrap_header_preamble : None , store_sent_mail : None , attribution_format_string : None , attribution_use_posix_locale : None , forward_as_attachment : None , reply_prefix_list_to_strip : None , reply_prefix : None , custom_compose_hooks : None , disabled_compose_hooks : None } } }
# [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct TagsSettingsOverride { # [serde (deserialize_with = "tag_color_de")] # [serde (default)] pub colors : Option < HashMap < TagHash , Color > > , # [serde (deserialize_with = "tag_set_de" , alias = "ignore-tags")] # [serde (default)] pub ignore_tags : Option < HashSet < TagHash > > } impl Default for TagsSettingsOverride { fn default () -> Self { TagsSettingsOverride { colors : None , ignore_tags : None } } }