mbox: Add different readers for mbox{o,rd,cl,cl2}
parent
01d83d8088
commit
674073899d
21
meli.conf.5
21
meli.conf.5
|
@ -236,6 +236,27 @@ example:
|
||||||
.\" default value
|
.\" default value
|
||||||
.Pq Em false
|
.Pq Em false
|
||||||
.El
|
.El
|
||||||
|
.Sh mbox only
|
||||||
|
mbox specific options are:
|
||||||
|
.Bl -tag -width 36n
|
||||||
|
.It Ic prefer_mbox_type Ar String
|
||||||
|
(optional) prefer specific mbox format reader for each message. Default is mboxcl2 format. If the preferred format fails, the message is retried with mboxrd and then if it fails again there's a recover attempt, which discards the invalid message.
|
||||||
|
Valid values are:
|
||||||
|
.Bl -bullet -compact
|
||||||
|
.It
|
||||||
|
.Ar auto
|
||||||
|
.It
|
||||||
|
.Ar mboxo
|
||||||
|
.It
|
||||||
|
.Ar mboxrd
|
||||||
|
.It
|
||||||
|
.Ar mboxcl
|
||||||
|
.It
|
||||||
|
.Ar mboxcl2
|
||||||
|
.El
|
||||||
|
.\" default value
|
||||||
|
.Pq Em auto
|
||||||
|
.El
|
||||||
.Sh mailboxes
|
.Sh mailboxes
|
||||||
.Bl -tag -width 36n
|
.Bl -tag -width 36n
|
||||||
.It Ic alias Ar String
|
.It Ic alias Ar String
|
||||||
|
|
|
@ -38,7 +38,11 @@ use crate::get_path_hash;
|
||||||
use crate::shellexpand::ShellExpandTrait;
|
use crate::shellexpand::ShellExpandTrait;
|
||||||
use libc;
|
use libc;
|
||||||
use memmap::{Mmap, Protection};
|
use memmap::{Mmap, Protection};
|
||||||
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::character::complete::digit1;
|
||||||
|
use nom::combinator::map_res;
|
||||||
use nom::{self, error::ErrorKind, IResult};
|
use nom::{self, error::ErrorKind, IResult};
|
||||||
|
|
||||||
extern crate notify;
|
extern crate notify;
|
||||||
use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
|
use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
|
||||||
use std::collections::hash_map::{DefaultHasher, HashMap};
|
use std::collections::hash_map::{DefaultHasher, HashMap};
|
||||||
|
@ -48,6 +52,7 @@ use std::io::BufReader;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
|
||||||
|
@ -269,117 +274,382 @@ impl BackendOp for MboxOp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum MboxReader {
|
||||||
|
MboxO,
|
||||||
|
MboxRd,
|
||||||
|
MboxCl,
|
||||||
|
MboxCl2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MboxReader {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::MboxCl2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! find_From__line {
|
||||||
|
($input:expr) => {{
|
||||||
|
//debug!("find_From__line invocation");
|
||||||
|
let input = $input;
|
||||||
|
let mut ptr = 0;
|
||||||
|
let mut found = None;
|
||||||
|
while ptr < input.len() {
|
||||||
|
// Find next From_ candidate line.
|
||||||
|
const TAG: &'static [u8] = b"\n\nFrom ";
|
||||||
|
if let Some(end) = input[ptr..].find(TAG) {
|
||||||
|
// This candidate is a valid From_ if it ends in a new line and the next line is
|
||||||
|
// a header.
|
||||||
|
if let Some(line_end) = input[ptr + end + TAG.len()..].find(b"\n") {
|
||||||
|
if crate::email::parser::headers::header(
|
||||||
|
&input[ptr + end + TAG.len() + line_end + 1..],
|
||||||
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
found = Some(ptr + end);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
/* Ignore invalid From_ line. */
|
||||||
|
ptr += end + TAG.len() + line_end;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Ignore invalid From_ line. */
|
||||||
|
ptr += end + TAG.len();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
found = Some(input.len());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
found
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MboxReader {
|
||||||
|
fn parse<'i>(&self, input: &'i [u8]) -> IResult<&'i [u8], Envelope> {
|
||||||
|
let orig_input = input;
|
||||||
|
let mut input = input;
|
||||||
|
match self {
|
||||||
|
Self::MboxO => {
|
||||||
|
let next_offset: Option<(usize, usize)> = find_From__line!(input)
|
||||||
|
.and_then(|end| input.find(b"\n").and_then(|start| Some((start + 1, end))));
|
||||||
|
|
||||||
|
if let Some((start, len)) = next_offset {
|
||||||
|
match Envelope::from_bytes(&input[start..len], None) {
|
||||||
|
Ok(mut env) => {
|
||||||
|
let mut flags = Flag::empty();
|
||||||
|
if env.other_headers().contains_key("Status") {
|
||||||
|
if env.other_headers()["Status"].contains("F") {
|
||||||
|
flags.set(Flag::FLAGGED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["Status"].contains("A") {
|
||||||
|
flags.set(Flag::REPLIED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["Status"].contains("R") {
|
||||||
|
flags.set(Flag::SEEN, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["Status"].contains("D") {
|
||||||
|
flags.set(Flag::TRASHED, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if env.other_headers().contains_key("X-Status") {
|
||||||
|
if env.other_headers()["X-Status"].contains("F") {
|
||||||
|
flags.set(Flag::FLAGGED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("A") {
|
||||||
|
flags.set(Flag::REPLIED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("R") {
|
||||||
|
flags.set(Flag::SEEN, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("D") {
|
||||||
|
flags.set(Flag::TRASHED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("T") {
|
||||||
|
flags.set(Flag::DRAFT, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env.set_flags(flags);
|
||||||
|
if len == input.len() {
|
||||||
|
Ok((&[], env))
|
||||||
|
} else {
|
||||||
|
input = &input[len + 2..];
|
||||||
|
Ok((input, env))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
debug!("Could not parse mail {:?}", err);
|
||||||
|
Err(nom::Err::Error((input, ErrorKind::Tag)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let start: Offset = input.find(b"\n").map(|v| v + 1).unwrap_or(0);
|
||||||
|
match Envelope::from_bytes(&input[start..], None) {
|
||||||
|
Ok(mut env) => {
|
||||||
|
let mut flags = Flag::empty();
|
||||||
|
if env.other_headers().contains_key("Status") {
|
||||||
|
if env.other_headers()["Status"].contains("F") {
|
||||||
|
flags.set(Flag::FLAGGED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["Status"].contains("A") {
|
||||||
|
flags.set(Flag::REPLIED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["Status"].contains("R") {
|
||||||
|
flags.set(Flag::SEEN, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["Status"].contains("D") {
|
||||||
|
flags.set(Flag::TRASHED, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if env.other_headers().contains_key("X-Status") {
|
||||||
|
if env.other_headers()["X-Status"].contains("F") {
|
||||||
|
flags.set(Flag::FLAGGED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("A") {
|
||||||
|
flags.set(Flag::REPLIED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("R") {
|
||||||
|
flags.set(Flag::SEEN, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("D") {
|
||||||
|
flags.set(Flag::TRASHED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("T") {
|
||||||
|
flags.set(Flag::DRAFT, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env.set_flags(flags);
|
||||||
|
Ok((&[], env))
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
debug!("Could not parse mail at {:?}", err);
|
||||||
|
Err(nom::Err::Error((input, ErrorKind::Tag)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::MboxRd => {
|
||||||
|
let next_offset: Option<(usize, usize)> = find_From__line!(input)
|
||||||
|
.and_then(|end| input.find(b"\n").and_then(|start| Some((start + 1, end))));
|
||||||
|
|
||||||
|
if let Some((start, len)) = next_offset {
|
||||||
|
match Envelope::from_bytes(&input[start..len], None) {
|
||||||
|
Ok(mut env) => {
|
||||||
|
let mut flags = Flag::empty();
|
||||||
|
if env.other_headers().contains_key("Status") {
|
||||||
|
if env.other_headers()["Status"].contains("F") {
|
||||||
|
flags.set(Flag::FLAGGED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["Status"].contains("A") {
|
||||||
|
flags.set(Flag::REPLIED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["Status"].contains("R") {
|
||||||
|
flags.set(Flag::SEEN, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["Status"].contains("D") {
|
||||||
|
flags.set(Flag::TRASHED, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if env.other_headers().contains_key("X-Status") {
|
||||||
|
if env.other_headers()["X-Status"].contains("F") {
|
||||||
|
flags.set(Flag::FLAGGED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("A") {
|
||||||
|
flags.set(Flag::REPLIED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("R") {
|
||||||
|
flags.set(Flag::SEEN, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("D") {
|
||||||
|
flags.set(Flag::TRASHED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("T") {
|
||||||
|
flags.set(Flag::DRAFT, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env.set_flags(flags);
|
||||||
|
if len == input.len() {
|
||||||
|
Ok((&[], env))
|
||||||
|
} else {
|
||||||
|
input = &input[len + 2..];
|
||||||
|
Ok((input, env))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
debug!("Could not parse mail {:?}", err);
|
||||||
|
Err(nom::Err::Error((input, ErrorKind::Tag)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let start: Offset = input.find(b"\n").map(|v| v + 1).unwrap_or(0);
|
||||||
|
match Envelope::from_bytes(&input[start..], None) {
|
||||||
|
Ok(mut env) => {
|
||||||
|
let mut flags = Flag::empty();
|
||||||
|
if env.other_headers().contains_key("Status") {
|
||||||
|
if env.other_headers()["Status"].contains("F") {
|
||||||
|
flags.set(Flag::FLAGGED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["Status"].contains("A") {
|
||||||
|
flags.set(Flag::REPLIED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["Status"].contains("R") {
|
||||||
|
flags.set(Flag::SEEN, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["Status"].contains("D") {
|
||||||
|
flags.set(Flag::TRASHED, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if env.other_headers().contains_key("X-Status") {
|
||||||
|
if env.other_headers()["X-Status"].contains("F") {
|
||||||
|
flags.set(Flag::FLAGGED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("A") {
|
||||||
|
flags.set(Flag::REPLIED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("R") {
|
||||||
|
flags.set(Flag::SEEN, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("D") {
|
||||||
|
flags.set(Flag::TRASHED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("T") {
|
||||||
|
flags.set(Flag::DRAFT, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env.set_flags(flags);
|
||||||
|
Ok((&[], env))
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
debug!("Could not parse mail {:?}", err);
|
||||||
|
Err(nom::Err::Error((input, ErrorKind::Tag)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::MboxCl | Self::MboxCl2 => {
|
||||||
|
let start: Offset = input.find(b"\n").map(|v| v + 1).unwrap_or(0);
|
||||||
|
input = &input[start..];
|
||||||
|
let headers_end: usize = input.find(b"\n\n").unwrap_or(input.len());
|
||||||
|
let content_length = if let Some(v) = input[..headers_end].find(b"Content-Length: ")
|
||||||
|
{
|
||||||
|
v
|
||||||
|
} else {
|
||||||
|
// Is not MboxCl{,2}
|
||||||
|
return Self::MboxRd.parse(orig_input);
|
||||||
|
};
|
||||||
|
let (_input, _) = if let Ok(s) = tag::<_, &[u8], (&[u8], nom::error::ErrorKind)>(
|
||||||
|
"Content-Length:",
|
||||||
|
)(&input[content_length..])
|
||||||
|
{
|
||||||
|
s
|
||||||
|
} else {
|
||||||
|
return Self::MboxRd.parse(orig_input);
|
||||||
|
};
|
||||||
|
let (_input, bytes) = if let Ok(s) =
|
||||||
|
map_res::<&[u8], _, _, (&[u8], nom::error::ErrorKind), _, _, _>(
|
||||||
|
digit1,
|
||||||
|
|s: &[u8]| String::from_utf8_lossy(s).parse::<usize>(),
|
||||||
|
)(_input.ltrim())
|
||||||
|
{
|
||||||
|
s
|
||||||
|
} else {
|
||||||
|
return Self::MboxRd.parse(orig_input);
|
||||||
|
};
|
||||||
|
|
||||||
|
match Envelope::from_bytes(&input[..headers_end + bytes], None) {
|
||||||
|
Ok(mut env) => {
|
||||||
|
let mut flags = Flag::empty();
|
||||||
|
if env.other_headers().contains_key("Status") {
|
||||||
|
if env.other_headers()["Status"].contains("F") {
|
||||||
|
flags.set(Flag::FLAGGED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["Status"].contains("A") {
|
||||||
|
flags.set(Flag::REPLIED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["Status"].contains("R") {
|
||||||
|
flags.set(Flag::SEEN, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["Status"].contains("D") {
|
||||||
|
flags.set(Flag::TRASHED, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if env.other_headers().contains_key("X-Status") {
|
||||||
|
if env.other_headers()["X-Status"].contains("F") {
|
||||||
|
flags.set(Flag::FLAGGED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("A") {
|
||||||
|
flags.set(Flag::REPLIED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("R") {
|
||||||
|
flags.set(Flag::SEEN, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("D") {
|
||||||
|
flags.set(Flag::TRASHED, true);
|
||||||
|
}
|
||||||
|
if env.other_headers()["X-Status"].contains("T") {
|
||||||
|
flags.set(Flag::DRAFT, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env.set_flags(flags);
|
||||||
|
if headers_end + 2 + bytes >= input.len() {
|
||||||
|
Ok((&[], env))
|
||||||
|
} else {
|
||||||
|
input = &input[headers_end + 3 + bytes..];
|
||||||
|
Ok((input, env))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_err) => {
|
||||||
|
return Self::MboxRd.parse(orig_input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn mbox_parse(
|
pub fn mbox_parse(
|
||||||
index: Arc<Mutex<HashMap<EnvelopeHash, (Offset, Length)>>>,
|
index: Arc<Mutex<HashMap<EnvelopeHash, (Offset, Length)>>>,
|
||||||
input: &[u8],
|
input: &[u8],
|
||||||
file_offset: usize,
|
file_offset: usize,
|
||||||
|
reader: Option<MboxReader>,
|
||||||
) -> IResult<&[u8], Vec<Envelope>> {
|
) -> IResult<&[u8], Vec<Envelope>> {
|
||||||
if input.is_empty() {
|
if input.is_empty() {
|
||||||
return Err(nom::Err::Error((input, ErrorKind::Tag)));
|
return Err(nom::Err::Error((input, ErrorKind::Tag)));
|
||||||
}
|
}
|
||||||
let mut input = input;
|
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
let mut index = index.lock().unwrap();
|
let mut index = index.lock().unwrap();
|
||||||
let mut envelopes = Vec::with_capacity(32);
|
let mut envelopes = Vec::with_capacity(32);
|
||||||
while !input.is_empty() {
|
|
||||||
let next_offset: Option<(usize, usize)> = input
|
|
||||||
.find(b"\n\nFrom ")
|
|
||||||
.and_then(|end| input.find(b"\n").and_then(|start| Some((start + 1, end))));
|
|
||||||
|
|
||||||
if let Some((start, len)) = next_offset {
|
let reader = reader.unwrap_or(MboxReader::MboxCl2);
|
||||||
match Envelope::from_bytes(&input[start..len], None) {
|
while !input[offset + file_offset..].is_empty() {
|
||||||
Ok(mut env) => {
|
let (next_input, env) = match reader.parse(&input[offset + file_offset..]) {
|
||||||
let mut flags = Flag::empty();
|
Ok(v) => v,
|
||||||
if env.other_headers().contains_key("Status") {
|
Err(e) => {
|
||||||
if env.other_headers()["Status"].contains("F") {
|
// Try to recover from this error by finding a new candidate From_ line
|
||||||
flags.set(Flag::FLAGGED, true);
|
if let Some(next_offset) = find_From__line!(&input[offset + file_offset..]) {
|
||||||
}
|
offset += next_offset;
|
||||||
if env.other_headers()["Status"].contains("A") {
|
if offset != input.len() {
|
||||||
flags.set(Flag::REPLIED, true);
|
// If we are not at EOF, we will be at this point
|
||||||
}
|
// "\n\nFrom ..."
|
||||||
if env.other_headers()["Status"].contains("R") {
|
// ↑
|
||||||
flags.set(Flag::SEEN, true);
|
// So, skip those two newlines.
|
||||||
}
|
offset += 2;
|
||||||
if env.other_headers()["Status"].contains("D") {
|
|
||||||
flags.set(Flag::TRASHED, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if env.other_headers().contains_key("X-Status") {
|
} else {
|
||||||
if env.other_headers()["X-Status"].contains("F") {
|
Err(e)?;
|
||||||
flags.set(Flag::FLAGGED, true);
|
|
||||||
}
|
|
||||||
if env.other_headers()["X-Status"].contains("A") {
|
|
||||||
flags.set(Flag::REPLIED, true);
|
|
||||||
}
|
|
||||||
if env.other_headers()["X-Status"].contains("R") {
|
|
||||||
flags.set(Flag::SEEN, true);
|
|
||||||
}
|
|
||||||
if env.other_headers()["X-Status"].contains("D") {
|
|
||||||
flags.set(Flag::TRASHED, true);
|
|
||||||
}
|
|
||||||
if env.other_headers()["X-Status"].contains("T") {
|
|
||||||
flags.set(Flag::DRAFT, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
env.set_flags(flags);
|
|
||||||
index.insert(env.hash(), (offset + file_offset + start, len - start));
|
|
||||||
envelopes.push(env);
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
debug!("Could not parse mail at byte offset {}", offset);
|
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
offset += len + 2;
|
};
|
||||||
input = &input[len + 2..];
|
let start: Offset = input[offset + file_offset..]
|
||||||
} else {
|
.find(b"\n")
|
||||||
let start: Offset = input.find(b"\n").map(|v| v + 1).unwrap_or(0);
|
.map(|v| v + 1)
|
||||||
match Envelope::from_bytes(&input[start..], None) {
|
.unwrap_or(0);
|
||||||
Ok(mut env) => {
|
let len = input.len() - next_input.len() - offset - file_offset - start;
|
||||||
let mut flags = Flag::empty();
|
index.insert(env.hash(), (offset + file_offset + start, len));
|
||||||
if env.other_headers().contains_key("Status") {
|
offset += len + start;
|
||||||
if env.other_headers()["Status"].contains("F") {
|
|
||||||
flags.set(Flag::FLAGGED, true);
|
envelopes.push(env);
|
||||||
}
|
|
||||||
if env.other_headers()["Status"].contains("A") {
|
|
||||||
flags.set(Flag::REPLIED, true);
|
|
||||||
}
|
|
||||||
if env.other_headers()["Status"].contains("R") {
|
|
||||||
flags.set(Flag::SEEN, true);
|
|
||||||
}
|
|
||||||
if env.other_headers()["Status"].contains("D") {
|
|
||||||
flags.set(Flag::TRASHED, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if env.other_headers().contains_key("X-Status") {
|
|
||||||
if env.other_headers()["X-Status"].contains("F") {
|
|
||||||
flags.set(Flag::FLAGGED, true);
|
|
||||||
}
|
|
||||||
if env.other_headers()["X-Status"].contains("A") {
|
|
||||||
flags.set(Flag::REPLIED, true);
|
|
||||||
}
|
|
||||||
if env.other_headers()["X-Status"].contains("R") {
|
|
||||||
flags.set(Flag::SEEN, true);
|
|
||||||
}
|
|
||||||
if env.other_headers()["X-Status"].contains("D") {
|
|
||||||
flags.set(Flag::TRASHED, true);
|
|
||||||
}
|
|
||||||
if env.other_headers()["X-Status"].contains("T") {
|
|
||||||
flags.set(Flag::DRAFT, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
env.set_flags(flags);
|
|
||||||
index.insert(
|
|
||||||
env.hash(),
|
|
||||||
(offset + file_offset + start, input.len() - start),
|
|
||||||
);
|
|
||||||
envelopes.push(env);
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
debug!("Could not parse mail at byte offset {}", offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return Ok((&[], envelopes));
|
return Ok((&[], envelopes));
|
||||||
}
|
}
|
||||||
|
@ -391,12 +661,14 @@ pub struct MboxType {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
index: Arc<Mutex<HashMap<EnvelopeHash, (Offset, Length)>>>,
|
index: Arc<Mutex<HashMap<EnvelopeHash, (Offset, Length)>>>,
|
||||||
mailboxes: Arc<Mutex<HashMap<MailboxHash, MboxMailbox>>>,
|
mailboxes: Arc<Mutex<HashMap<MailboxHash, MboxMailbox>>>,
|
||||||
|
prefer_mbox_type: Option<MboxReader>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MailBackend for MboxType {
|
impl MailBackend for MboxType {
|
||||||
fn is_online(&self) -> Result<()> {
|
fn is_online(&self) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&mut self, mailbox: &Mailbox) -> Async<Result<Vec<Envelope>>> {
|
fn get(&mut self, mailbox: &Mailbox) -> Async<Result<Vec<Envelope>>> {
|
||||||
let mut w = AsyncBuilder::new();
|
let mut w = AsyncBuilder::new();
|
||||||
let handle = {
|
let handle = {
|
||||||
|
@ -405,6 +677,7 @@ impl MailBackend for MboxType {
|
||||||
let mailbox_path = mailbox.path().to_string();
|
let mailbox_path = mailbox.path().to_string();
|
||||||
let mailbox_hash = mailbox.hash();
|
let mailbox_hash = mailbox.hash();
|
||||||
let mailboxes = self.mailboxes.clone();
|
let mailboxes = self.mailboxes.clone();
|
||||||
|
let prefer_mbox_type = self.prefer_mbox_type.clone();
|
||||||
let closure = move |_work_context| {
|
let closure = move |_work_context| {
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
let index = index.clone();
|
let index = index.clone();
|
||||||
|
@ -429,7 +702,7 @@ impl MailBackend for MboxType {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let payload = mbox_parse(index, contents.as_slice(), 0)
|
let payload = mbox_parse(index, contents.as_slice(), 0, prefer_mbox_type)
|
||||||
.map_err(|e| MeliError::from(e))
|
.map_err(|e| MeliError::from(e))
|
||||||
.map(|(_, v)| v);
|
.map(|(_, v)| v);
|
||||||
{
|
{
|
||||||
|
@ -470,6 +743,7 @@ impl MailBackend for MboxType {
|
||||||
};
|
};
|
||||||
let index = self.index.clone();
|
let index = self.index.clone();
|
||||||
let mailboxes = self.mailboxes.clone();
|
let mailboxes = self.mailboxes.clone();
|
||||||
|
let prefer_mbox_type = self.prefer_mbox_type.clone();
|
||||||
let handle = std::thread::Builder::new()
|
let handle = std::thread::Builder::new()
|
||||||
.name(format!("watching {}", self.account_name,))
|
.name(format!("watching {}", self.account_name,))
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
|
@ -520,6 +794,7 @@ impl MailBackend for MboxType {
|
||||||
index.clone(),
|
index.clone(),
|
||||||
&contents[mailbox_lock[&mailbox_hash].content.len()..],
|
&contents[mailbox_lock[&mailbox_hash].content.len()..],
|
||||||
mailbox_lock[&mailbox_hash].content.len(),
|
mailbox_lock[&mailbox_hash].content.len(),
|
||||||
|
prefer_mbox_type,
|
||||||
) {
|
) {
|
||||||
for env in envelopes {
|
for env in envelopes {
|
||||||
sender.send(RefreshEvent {
|
sender.send(RefreshEvent {
|
||||||
|
@ -621,6 +896,34 @@ impl MailBackend for MboxType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! get_conf_val {
|
||||||
|
($s:ident[$var:literal]) => {
|
||||||
|
$s.extra.get($var).ok_or_else(|| {
|
||||||
|
MeliError::new(format!(
|
||||||
|
"Configuration error ({}): mbox backend requires the field `{}` set",
|
||||||
|
$s.name.as_str(),
|
||||||
|
$var
|
||||||
|
))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
($s:ident[$var:literal], $default:expr) => {
|
||||||
|
$s.extra
|
||||||
|
.get($var)
|
||||||
|
.map(|v| {
|
||||||
|
<_>::from_str(v).map_err(|e| {
|
||||||
|
MeliError::new(format!(
|
||||||
|
"Configuration error ({}): Invalid value for field `{}`: {}\n{}",
|
||||||
|
$s.name.as_str(),
|
||||||
|
$var,
|
||||||
|
v,
|
||||||
|
e
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| Ok($default))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
impl MboxType {
|
impl MboxType {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
s: &AccountSettings,
|
s: &AccountSettings,
|
||||||
|
@ -634,9 +937,24 @@ impl MboxType {
|
||||||
s.name()
|
s.name()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
let prefer_mbox_type: String = get_conf_val!(s["prefer_mbox_type"], "auto".to_string())?;
|
||||||
let ret = MboxType {
|
let ret = MboxType {
|
||||||
account_name: s.name().to_string(),
|
account_name: s.name().to_string(),
|
||||||
path,
|
path,
|
||||||
|
prefer_mbox_type: match prefer_mbox_type.as_str() {
|
||||||
|
"auto" => None,
|
||||||
|
"mboxo" => Some(MboxReader::MboxO),
|
||||||
|
"mboxrd" => Some(MboxReader::MboxRd),
|
||||||
|
"mboxcl" => Some(MboxReader::MboxCl),
|
||||||
|
"mboxcl2" => Some(MboxReader::MboxCl2),
|
||||||
|
_ => {
|
||||||
|
return Err(MeliError::new(format!(
|
||||||
|
"{} invalid `prefer_mbox_type` value: `{}`",
|
||||||
|
s.name(),
|
||||||
|
prefer_mbox_type,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let name: String = ret
|
let name: String = ret
|
||||||
|
@ -720,6 +1038,9 @@ impl MboxType {
|
||||||
s.name()
|
s.name()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
let prefer_mbox_type: Result<String> =
|
||||||
|
get_conf_val!(s["prefer_mbox_type"], "auto".to_string());
|
||||||
|
prefer_mbox_type?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,10 @@ edition = "2018"
|
||||||
name = "emailparse"
|
name = "emailparse"
|
||||||
path = "src/email_parse.rs"
|
path = "src/email_parse.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "mboxparse"
|
||||||
|
path = "src/mboxparse.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "imapconn"
|
name = "imapconn"
|
||||||
path = "src/imap_conn.rs"
|
path = "src/imap_conn.rs"
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* meli - mboxparse.rs
|
||||||
|
*
|
||||||
|
* Copyright 2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern crate melib;
|
||||||
|
use melib::Result;
|
||||||
|
use melib::*;
|
||||||
|
|
||||||
|
/// Parses e-mail from files and prints the debug information of the parsed `Envelope`
|
||||||
|
///
|
||||||
|
/// # Example invocation
|
||||||
|
/// ```sh
|
||||||
|
/// ./mboxparse /path/to/mbox"
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
if std::env::args().len() == 1 {
|
||||||
|
eprintln!("Usage: ./mboxparse /path/to/mbox");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in std::env::args().skip(1) {
|
||||||
|
println!("Path is {}", i);
|
||||||
|
let filename = std::path::PathBuf::from(&i);
|
||||||
|
|
||||||
|
if filename.exists() && filename.is_file() {
|
||||||
|
let buffer = std::fs::read_to_string(&filename)
|
||||||
|
.expect(&format!("Something went wrong reading the file {}", i,));
|
||||||
|
let res =
|
||||||
|
melib::backends::mbox::mbox_parse(Default::default(), buffer.as_bytes(), 0, None);
|
||||||
|
match res {
|
||||||
|
Ok((_, v)) => {
|
||||||
|
println!("{} envelopes parsed", v.len());
|
||||||
|
}
|
||||||
|
Err(melib::nom::Err::Error(err)) => {
|
||||||
|
println!(
|
||||||
|
"Error in parsing {:?}",
|
||||||
|
unsafe { std::str::from_utf8_unchecked(err.0) }
|
||||||
|
.chars()
|
||||||
|
.take(150)
|
||||||
|
.collect::<String>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
println!("Error in parsing {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("{} is not a valid file.", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue