1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
use crate::{Error, Result};
use std::collections::HashMap;

mod parsers;
use parsers::get_header;
pub use parsers::{Segment, Subject};

#[doc(hidden)]
pub type HeaderMap = HashMap<String, String>;

/// Message ID of an email in a thread
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Id(pub String);

impl Id {
  pub fn from(headers: &HeaderMap, key: &str) -> Result<Self> {
    get_header(&headers, key).and_then(|h| match h {
      Header::Single(h) => Ok(Self(h)),
      _ => Err(Error::FailedParsing),
    })
  }
}

/// A semi typed header value for mail
pub(crate) enum Header {
  /// A single header value
  Single(String),
  /// A set of values that was separated by `,`
  Multi(Vec<String>),
}

impl Header {
  fn single(self) -> Result<String> {
    match self {
      Self::Single(s) => Ok(s),
      Self::Multi(_) => Err(Error::FailedParsing),
    }
  }
}

/// A single patch, metadata and email body
///
/// This type is constructed from a single email in a thread, and can
/// then be combined into a [PatchSet](struct.PatchSet.html) via the
/// [PatchTree](struct.PatchTree.html) builder.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Patch<'mail> {
  pub id: Id,
  pub reply_to: Option<Id>,
  pub subject: Subject,
  pub headers: HeaderMap,
  pub raw: &'mail str,
}

impl<'mail> Patch<'mail> {

  /// Hidden function that should only be used 
  #[doc(hidden)]
  pub fn preprocess(raw: &'mail str) -> HeaderMap {
    let mail = mailparse::parse_mail(raw.as_bytes())
      .map_err(|_| Error::FailedParsing)
      .unwrap();
    mail
      .headers
      .into_iter()
      .fold(HashMap::new(), |mut acc, header| {
        let key = header.get_key().unwrap();
        let val = header.get_value().unwrap();
        acc.insert(key, val);
        acc
      })
  }

  pub fn new(raw: &'mail str) -> Result<Self> {
    let mail = mailparse::parse_mail(raw.as_bytes())
      .map_err(|_| Error::FailedParsing)?;
    let headers =
      mail
        .headers
        .into_iter()
        .fold(HashMap::new(), |mut acc, header| {
          let key = header.get_key().unwrap();
          let val = header.get_value().unwrap();
          acc.insert(key, val);
          acc
        });

    get_header(&headers, "X-Mailer").and_then(|h| match h {
      Header::Single(s) if s.contains("git-send-email") => Ok(()),
      _ => Err(Error::NotAGitMail),
    })?;

    Ok(Self {
      id: Id::from(&headers, "Message-Id")?,
      reply_to: Id::from(&headers, "In-Reply-To")
        .map(|id| Some(id))
        .unwrap_or(None),
      subject: get_header(&headers, "Subject")
        .and_then(|h| h.single())
        .and_then(|s| Subject::from(s))?,
      headers,
      raw,
    })
  }
}