// common/Message.cc
// This file is part of Decimail; see http://decimail.org
// (C) 2004-2007 Philip Endecott
 
// This program 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 2 of the License, or
// any later version.
//
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
#include "Message.hh"

#include "Exception.hh"
#include "mimetic_utils.hh"



// public:


void Message::MalformedMessage::report(ostream& s) const
{
  s << "Malformed message: " << reason << endl;
}



Message::Message(int i):
  id(i), size_valid(false), entire_message_valid(false),
  blank_line_pos_valid(false), body_valid(false), mimeentity_header_valid(false),
  mimeentity_all_valid(false), headers_valid(false),
  subject_valid(false), date_valid(false), from_valid(false),
  to_valid(false), cc_valid(false), bcc_valid(false),
  rfc822_messageid_valid(false)
{}

Message::~Message(void)
{}


int Message::get_id(void) const
{
  return id;
}

int Message::get_size(void) const
{
  obtain_size();
  return size;
}

Message::msgpart Message::get_entire_message(void) const
{
  obtain_entire_message();
  return entire_message;
}

Message::msgpart Message::get_body(void) const
{
  obtain_body();
  return body;
}

const mimetic::MimeEntity& Message::get_mimeentity(void) const
{
  obtain_mimeentity_all();
  return mimeentity;
}

const mimetic::Header& Message::get_mime_header(void) const
{
  obtain_mimeentity_header();
  return mimeentity.header();
}

Message::msgpart Message::get_headers(void) const
{
  obtain_headers();
  return headers;
}

string Message::get_subject(void) const
{
  obtain_subject();
  return subject;
}

time_t Message::get_date(void) const
{
  obtain_date();
  return date;
}

string Message::get_str_date(void) const
{
  time_t t = get_date();
  struct tm tm;
  localtime_r(&t,&tm);
  const int sz=26;
  char buf[sz];
  strftime(buf,sz,"%Y-%m-%d %H:%M:%S %z",&tm);
  return string(buf);
}

Message::email_address Message::get_from(void) const
{
  obtain_from();
  return from;
}

Message::email_address_list Message::get_to(void) const
{
  obtain_to();
  return to;
}

Message::email_address_list Message::get_cc(void) const
{
  obtain_cc();
  return cc;
}

Message::email_address_list Message::get_bcc(void) const
{
  obtain_bcc();
  return bcc;
}

string Message::get_rfc822_messageid(void) const
{
  obtain_rfc822_messageid();
  return rfc822_messageid;
}

// protected


void Message::obtain_size(void) const
{
  if (!size_valid) {
    obtain_entire_message();
    size=entire_message.size();
    size_valid=true;
  }
}


void Message::find_blank_line(void) const
{
  if (!blank_line_pos_valid) {
    obtain_entire_message();
    blank_line_pos=entire_message.find("\r\n\r\n");
    if (blank_line_pos==entire_message.npos) {
      throw MalformedMessage("No blank line to distinguish headers from body");
    }
    blank_line_pos_valid=true;
  }
}


void Message::obtain_body(void) const
{
  if (!body_valid) {
    obtain_entire_message();
    find_blank_line();
    body=entire_message.substr(blank_line_pos+4);
    body_valid=true;
  }
}


void Message::obtain_mimeentity_header(void) const
{
  if (!mimeentity_header_valid) {
    obtain_headers();
    istringstream s(headers+"\r\n\r\n");
    mimeentity.load(s,mimetic::imBody);
    mimeentity_header_valid=true;
  }
}


void Message::obtain_mimeentity_all(void) const
{
  if (!mimeentity_all_valid) {
    obtain_entire_message();
    istringstream s(entire_message);
    mimeentity.load(s);
    mimeentity_header_valid=true;
    mimeentity_all_valid=true;
  }
}


void Message::obtain_headers(void) const
{
  if (!headers_valid) {
    find_blank_line();
    headers=entire_message.substr(0,blank_line_pos);
    headers_valid=true;
  }
}


void Message::obtain_subject(void) const
{
  if (!subject_valid) {
    obtain_mimeentity_header();
    subject = rfc2047_to_utf8(mimeentity.header().subject());
    subject_valid = true;
  }
}


class InvalidDate: public Exception {
private:
  string date;
public:
  InvalidDate(string d): date(d) {}
  void report(ostream& s) const {
    s << "Invalid date: \"" << date << "\"" << endl;
  }
};


void Message::obtain_date(void) const
{
  if (!date_valid) {
    obtain_mimeentity_header();
    if (!mimeentity.header().hasField("date")) {
      throw MalformedMessage("No date header");
    }
    string s = mimeentity.header().field("date").value();
    // Parse RFC822 date
    // Case in point: Mon, 15 Dec 2003 09:46:00 +0000 (GMT)
    // Would be OK with just +0000 or (GMT) but both together
    // is not acceptable.  But note that the () are actually a
    // comment.  So we strip comments.
    unsigned int start = 0;
    while(1) {
      start = s.find('(',start);
      if (start==s.npos) {
	break;
      }
      unsigned int end = s.find(')',start);
      s.erase(start,end+1-start);
    }

    struct tm t;

    const char* const date_formats[] = {
    " %a, %d %b %y %H:%M:%S",    // RFC822 with 2-digit year
    " %a, %d %b %Y %H:%M:%S",    // RFC822
    " %d %b %y %H:%M:%S",        // easyjet.com "31 Jan 01 21:00:00 GMT"
    " %d %b %Y %H:%M:%S",        // RFC822 without day of week
    " %a %b %d %H:%M:%S GMT %Y", // Co-op bank "Thu Apr 24 11:10:04 GMT 2003"
    " %a %b %d %H:%M:%S MSD %Y", // Kirill "Tue Jun 27 21:08:10 MSD 2000"
    " %a %b %d %H:%M:%S BST %Y", // Bytemark "Tue Apr 20 19:07:44 BST 2004"
    " %a %b %d %H:%M:%S CEST %Y",// SNCF "Thu Sep 28 14:37:14 CEST 2006"
    " %a %b %d %H:%M:%S PST %Y", // Shoppingzilla "Thu Nov 30 15:55:52 PST 2006"
    " %a, %d %b %y %H:%M",       // no secs, 2d year: "Fri, 30 Jan 98 14:06 GMT"
    " %a, %d %b %Y %H:%M",       // no secs: Oxfam "Wed, 5 Sep 2001 08:03 -0700"
    " %d-%b-%Y",                 // register.com "18-Jun-2002"
    " %m/%d/%y",                 // k7.com "11/20/02"
    " %d %b %Y",                 // Bletchly Park "22 February 2004"
    " %a %b %d %H:%M:%S %Y",     // Spam "Sat Jan 13 21:56:01 2007"
      0
    };
    char* rc = NULL;
    int i=0;
    while(!rc) {
      const char* const fmt  = date_formats[i++];
      if(!fmt) {
	break;
      }
      t.tm_sec=0;
      t.tm_min=0;
      t.tm_hour=0;
      rc = strptime(s.c_str(), fmt, &t);
    }
    if (!rc) {
      throw InvalidDate(s);
    }
    char tzsign[2];
    unsigned int tzhours;
    unsigned int tzmins;
    int tzscan = sscanf(rc," %1[+-]%2u%2u",tzsign,&tzhours,&tzmins);
    if (tzscan==3) {
      switch(tzsign[0]) {
      case '+':
	t.tm_hour -= tzhours;
	t.tm_min -= tzmins;
	break;
      case '-':
	t.tm_hour += tzhours;
	t.tm_min += tzmins;
	break;
      }
    } else {
      // Warning, couldn't parse timezone.  Probably "BST" or "EST" or
      // something like that.  Ignore it.
    }
    // Note: the following line used to use mktime(), until summer time
    // started and everything went off by an hour.  This timezone stuff
    // is unpleasant.
    date = timegm(&t);
    if (date==(time_t)(-1)) {
      throw InvalidDate(s);
    }

    date_valid = true;
  }
}

void Message::obtain_from(void) const
{
  if (!from_valid) {
    obtain_mimeentity_header();
    from = mimeentity.header().from().str();
    // Mimetic from() returns a LIST of addresses - probably is allowed
    // by the RFCs, but I ignore it and assume a singleton list.
    from_valid = true;
  }
}

void Message::obtain_to(void) const
{
  if (!to_valid) {
    obtain_mimeentity_header();
    to = mimeentity.header().to();
    to_valid = true;
  }
}

void Message::obtain_cc(void) const
{
  if (!cc_valid) {
    obtain_mimeentity_header();
    cc = mimeentity.header().cc();
    cc_valid = true;
  }
}

void Message::obtain_bcc(void) const
{
  if (!bcc_valid) {
    obtain_mimeentity_header();
    bcc = mimeentity.header().bcc();
    bcc_valid = true;
  }
}

void Message::obtain_rfc822_messageid(void) const
{
  if (!rfc822_messageid_valid) {
    obtain_mimeentity_header();
    rfc822_messageid = mimeentity.header().messageid().str();
    rfc822_messageid_valid = true;
  }
}

