/* -*- Mode: c++; -*- */
/*  --------------------------------------------------------------------
 *  Filename:
 *    operator-fetch.cc
 *  
 *  Description:
 *    Implementation of the FETCH command.
 *
 *  Authors:
 *    Andreas Aardal Hanssen <andreas-binc curly bincimap spot org>
 *
 *  Bugs:
 *
 *  ChangeLog:
 *
 *  --------------------------------------------------------------------
 *  Copyright 2002-2005 Andreas Aardal Hanssen
 *
 *  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
 *  (at your option) 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 Street #330, Boston, MA 02111-1307, USA.
 *  --------------------------------------------------------------------
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string>

#include "depot.h"
#include "io.h"
#include "mailbox.h"
#include "operators.h"
#include "imapparser.h"
#include "pendingupdates.h"
#include "recursivedescent.h"
#include "session.h"
#include "convert.h"

using namespace ::std;
using namespace Binc;

namespace {
  void outputFlags(const Message & message) 
  {
    IO &com = IOFactory::getInstance().get(1);

    com << "FLAGS ";
	  
    com << "(";
    int flags = message.getStdFlags();
    vector<string> flagv;
    if (flags & Message::F_SEEN) flagv.push_back("\\Seen");
    if (flags & Message::F_ANSWERED) flagv.push_back("\\Answered");
    if (flags & Message::F_DELETED) flagv.push_back("\\Deleted");
    if (flags & Message::F_DRAFT) flagv.push_back("\\Draft");
    if (flags & Message::F_RECENT) flagv.push_back("\\Recent");
    if (flags & Message::F_FLAGGED) flagv.push_back("\\Flagged");
    for (vector<string>::const_iterator k 
	   = flagv.begin(); k != flagv.end(); ++k) {
      if (k != flagv.begin()) com << " ";
      com << *k;
    }
    com << ")";
  }

}

//----------------------------------------------------------------------
FetchOperator::FetchOperator(void)
{
}

//----------------------------------------------------------------------
FetchOperator::~FetchOperator(void)
{
}

//----------------------------------------------------------------------
const string FetchOperator::getName(void) const
{
  return "FETCH";
}

//----------------------------------------------------------------------
int FetchOperator::getState(void) const
{
  return Session::SELECTED;
}

//------------------------------------------------------------------------
Operator::ProcessResult FetchOperator::process(Depot &depot,
					       Request &request)
{
  IO &com = IOFactory::getInstance().get(1);
  Session &session = Session::getInstance();

  bool updateFlags = false;
  Request req = request;

  Mailbox *mailbox = depot.getSelected();

  // If this is a UID FETCH, check if the UID attribute is fetched. If
  // it is not, then add it to the list of fetch attributes.
  vector<BincImapParserFetchAtt>::const_iterator f_i;
  bool uidfetched = false;
  if (request.getUidMode()) {
    f_i = request.fatt.begin();
    while (f_i != request.fatt.end()) {
      if ((*f_i).type == "UID") {
	uidfetched = true;
	break;
      }
      
      f_i++;
    }

    if (!uidfetched) {
      BincImapParserFetchAtt b;
      b.type = "UID";
      req.fatt.push_back(b);
    }
  }

  // Convert macros ALL, FULL and FAST
  f_i = request.fatt.begin();
  while (f_i != request.fatt.end()) {
    const string &type = (*f_i).type;
    if (type == "ALL") {
      req.fatt.push_back(BincImapParserFetchAtt("FLAGS"));
      req.fatt.push_back(BincImapParserFetchAtt("INTERNALDATE"));
      req.fatt.push_back(BincImapParserFetchAtt("RFC822.SIZE"));
      req.fatt.push_back(BincImapParserFetchAtt("ENVELOPE"));
    } else if (type == "FULL") {
      req.fatt.push_back(BincImapParserFetchAtt("FLAGS"));
      req.fatt.push_back(BincImapParserFetchAtt("INTERNALDATE"));
      req.fatt.push_back(BincImapParserFetchAtt("RFC822.SIZE"));
      req.fatt.push_back(BincImapParserFetchAtt("ENVELOPE"));
      req.fatt.push_back(BincImapParserFetchAtt("BODY"));
    } else if (type == "FAST") {
      req.fatt.push_back(BincImapParserFetchAtt("FLAGS"));
      req.fatt.push_back(BincImapParserFetchAtt("INTERNALDATE"));
      req.fatt.push_back(BincImapParserFetchAtt("RFC822.SIZE"));
    }

    ++f_i;
  }

  int mode;
  if (req.getUidMode())
    mode = Mailbox::UID_MODE;
  else
    mode = Mailbox::SQNR_MODE;

  Mailbox::iterator i
    = mailbox->begin(req.bset, Mailbox::SKIP_EXPUNGED | mode);

  for (; i != mailbox->end(); ++i) {
    Message &message = *i;
    
    com << "* " << i.getSqnr() << " FETCH (";
    bool hasprinted = false;
    f_i = req.fatt.begin();
    while (f_i != req.fatt.end()) {
      BincImapParserFetchAtt fatt = *f_i;

      string prefix = "";
      if (hasprinted)
	prefix = " ";
	
      if (fatt.type == "FLAGS") {
	// FLAGS
	hasprinted = true;
	com << prefix;

	outputFlags(message);
      } else if (fatt.type == "UID") {
	// UID
	hasprinted = true;
	com << prefix << "UID " << message.getUID();
      } else if (fatt.type == "RFC822.SIZE") {
	// RFC822.SIZE
	hasprinted = true;
	com << prefix << "RFC822.SIZE " << message.getSize(true);
      } else if (fatt.type == "ENVELOPE") {
	// ENVELOPE
	hasprinted = true;
	com << prefix << "ENVELOPE ";
	message.printEnvelope();
      } else if (fatt.type == "BODYSTRUCTURE") {
	// BODYSTRUCTURE
	hasprinted = true;
	com << prefix << "BODYSTRUCTURE ";
	message.printBodyStructure(true);
      } else if (fatt.type == "BODY" && !fatt.hassection) {
	// BODY with no section
	hasprinted = true;
	session.addBody();
	com << prefix << "BODY ";
	message.printBodyStructure(false);
      } else if (fatt.type == "INTERNALDATE") {
	// INTERNALDATE
	hasprinted = true;
	com << prefix << "INTERNALDATE ";

	time_t iDate = message.getInternalDate();
	struct tm *_tm = gmtime(&iDate);
	char internal[64];
	string iDateStr;
	if (strftime(internal, sizeof(internal),
		     "%d-%b-%Y %H:%M:%S %z", _tm) != 0)
	  iDateStr = internal;
	else
	  iDateStr = "NIL";

	com << toImapString(iDateStr);
      } else if (fatt.type == "BODY" || fatt.type == "BODY.PEEK") {
	// BODY & BODY.PEEK
	hasprinted = true;
	session.addBody();

	com << prefix;
	bool peek = (fatt.type == "BODY.PEEK");
	com << fatt.toString();

	bool includeheaders = true;
	bool fullheader = false;
	bool bodyfetch = false;

	if (fatt.section != "" || fatt.sectiontext == ""
	    || fatt.sectiontext == "TEXT") {
	  bodyfetch = true;
	  fullheader = true;
	}

	if (fatt.sectiontext == "HEADER.FIELDS.NOT")
	  includeheaders = false;

	if (fatt.sectiontext == "HEADER"
	    || fatt.sectiontext == "HEADER.FIELDS"
	    || fatt.sectiontext == "HEADER.FIELDS.NOT"
	    || fatt.sectiontext == "MIME") {

	  vector<string> v;
	  
	  if (fatt.sectiontext == "MIME") {
	    v.push_back("content-type");
	    v.push_back("content-transfer-encoding");
	    v.push_back("content-disposition");
	    v.push_back("content-description");
	  } else
	    v = fatt.headerlist;
    
	  string dummy;
	  unsigned int size = fullheader
	    ? message.getHeaderSize(fatt.section, v,
				    true, 
				    fatt.offsetstart, 
				    fatt.offsetlength, fatt.sectiontext == "MIME")
	    : message.getHeaderSize(fatt.section, fatt.headerlist,
				    includeheaders, 
				    fatt.offsetstart,
				    fatt.offsetlength, fatt.sectiontext == "MIME");

	  com << "{" << size << "}\r\n";
	  
	  if (fullheader) {
	    message.printHeader(fatt.section, v,
				true, 
				fatt.offsetstart,
				fatt.offsetlength, fatt.sectiontext == "MIME");
	  } else {
	    message.printHeader(fatt.section, fatt.headerlist,
				includeheaders,
				fatt.offsetstart, 
				fatt.offsetlength, fatt.sectiontext == "MIME");
	  }
	} else {
	  unsigned int size;
	  if ((fatt.sectiontext == "" || fatt.sectiontext == "TEXT")
	      && fatt.section == "")
	    size = message.getDocSize(fatt.offsetstart,
				      fatt.offsetlength,
				      fatt.sectiontext == "TEXT");
	  else
	    size = message.getBodySize(fatt.section, 
				       fatt.offsetstart, 
				       fatt.offsetlength);
	  
	  com << "{" << size << "}\r\n";
	  
	  if ((fatt.sectiontext == "" || fatt.sectiontext == "TEXT")
	      && fatt.section == "")
	    message.printDoc(fatt.offsetstart, 
			     fatt.offsetlength,
			     fatt.sectiontext == "TEXT");
	  else
	    message.printBody(fatt.section, fatt.offsetstart, 
			      fatt.offsetlength);
	}

	// set the \Seen flag if .PEEK is not used.
	if (!peek)
	  if ((message.getStdFlags() & Message::F_SEEN) == 0)
	    message.setStdFlag(Message::F_SEEN);

      } else if (fatt.type == "RFC822") {
	com << prefix;
	hasprinted = true;
	session.addBody();

	com << fatt.toString();

	unsigned int size = message.getDocSize(fatt.offsetstart, 
					       fatt.offsetlength);

	com << " {" << size << "}\r\n";

	message.printDoc(fatt.offsetstart, fatt.offsetlength);
	    
	// set the \Seen flag
	if ((message.getStdFlags() & Message::F_SEEN) == 0)
	  message.setStdFlag(Message::F_SEEN);

      } else if (fatt.type == "RFC822.HEADER") {
	com << prefix;
	hasprinted = true;

	com << fatt.toString();

	vector<string> v;
	string dummy;

	unsigned int size = message.getHeaderSize("", v, true,
						  fatt.offsetstart, 
						  fatt.offsetlength);

	com << " {" << size << "}\r\n";

	message.printHeader("", v, true, fatt.offsetstart, 
			    fatt.offsetlength);
	    
      } else if (fatt.type == "RFC822.TEXT") {
	// RFC822.TEXT
	com << prefix;
	hasprinted = true;
	session.addBody();

	com << fatt.toString();

	bool bodyfetch = false;
	bodyfetch = true;

	unsigned int size;
	if (fatt.sectiontext == "" && fatt.section == "")
	  size = message.getDocSize(fatt.offsetstart, 
				fatt.offsetlength, true);
	else
	  size = message.getBodySize(fatt.section, fatt.offsetstart, 
				     fatt.offsetlength);
	    
	com << " {" << size << "}\r\n";
	    
	if (fatt.sectiontext == "" && fatt.section == "")
	  message.printDoc(fatt.offsetstart, 
			   fatt.offsetlength, true);
	else
	  message.printBody(fatt.section, fatt.offsetstart, 
			    fatt.offsetlength);

	// set the \Seen flag
	if ((message.getStdFlags() & Message::F_SEEN) == 0)
	  message.setStdFlag(Message::F_SEEN);
      } else {
	// Unrecognized fetch_att, this is stopped by the parser
	// so we never get here.
      }

      f_i++;
    }

    // FIXME: how are parse error passed back?

    com << ")" << endl;

    if (message.hasFlagsChanged()) {
      updateFlags = true;
      com << "* " << i.getSqnr() << " FETCH (";
      outputFlags(message);
      com << ")" << endl;
      message.setFlagsUnchanged();
    }
  }
  
  if (updateFlags) mailbox->updateFlags();

  if (!pendingUpdates(mailbox,
		      PendingUpdates::FLAGS
		      | PendingUpdates::EXISTS
		      | PendingUpdates::RECENT
		      | PendingUpdates::EXPUNGE,
		      true)) {
    IO &logger = IOFactory::getInstance().get(2);
    logger << "when scanning mailbox: "
	   << session.getLastError() << endl;
    com << "* BYE " << session.getLastError() << endl;
    com.flushContent();
    session.setState(Session::LOGOUT);
    return NOTHING;
  }

  return OK;
}

//----------------------------------------------------------------------
Operator::ParseResult FetchOperator::parse(Request &c_in) const
{
  Session &session = Session::getInstance();

  Operator::ParseResult res;
  if ((res = expectSPACE()) != ACCEPT) {
    session.setLastError("Expected SPACE after FETCH");
    return res;
  }

  if ((res = expectSet(c_in.getSet())) != ACCEPT) {
    session.setLastError("Expected sequence set after FETCH SPACE");
    return res;
  }

  if ((res = expectSPACE()) != ACCEPT) {
    session.setLastError("Expected SPACE after FETCH SPACE set");
    return res;
  }

  BincImapParserFetchAtt f;

  if ((res = expectThisString("ALL")) == ACCEPT) {
    f.type = "ALL";
    c_in.fatt.push_back(f);
  } else if ((res = expectThisString("FULL")) == ACCEPT) {
    f.type = "FULL";
    c_in.fatt.push_back(f);
  } else if ((res = expectThisString("FAST")) == ACCEPT) {
    f.type = "FAST";
    c_in.fatt.push_back(f);
  } else if ((res = expectFetchAtt(f)) == ACCEPT) {
    c_in.fatt.push_back(f);
  } else if ((res = expectThisString("(")) == ACCEPT) {
    while (1) {
      BincImapParserFetchAtt ftmp;
      if ((res = expectFetchAtt(ftmp)) != ACCEPT) {
	session.setLastError("Expected fetch_att");
	return res;
      }
	
      c_in.fatt.push_back(ftmp);
      
      if ((res = expectSPACE()) == REJECT)
	break;
      else if (res == ERROR)
	return ERROR;
    }

    if ((res = expectThisString(")")) != ACCEPT) {
      session.setLastError("Expected )");
      return res;
    }
  } else {
    session.setLastError("Expected ALL, FULL, FAST, fetch_att or (");
    return res;
  }

  if ((res = expectCRLF()) != ACCEPT) {
    session.setLastError("Expected CRLF");
    return res;
  }

  c_in.setName("FETCH");
  return ACCEPT;
}

//----------------------------------------------------------------------
Operator::ParseResult
FetchOperator::expectSectionText(BincImapParserFetchAtt &f_in) const
{
  Session &session = Session::getInstance();

  Operator::ParseResult res;
  if ((res = expectThisString("HEADER")) == ACCEPT) {
    f_in.sectiontext = "HEADER";
    
    if ((res = expectThisString(".FIELDS")) == ACCEPT) {
      f_in.sectiontext += ".FIELDS";
      
      if ((res = expectThisString(".NOT")) == ACCEPT)
	f_in.sectiontext += ".NOT";
      
      if ((res = expectSPACE()) != ACCEPT) {
	session.setLastError("expected SPACE");
	return res;
      }
      
      if ((res = expectHeaderList(f_in)) != ACCEPT) {
	session.setLastError("Expected header_list");
	return res;
      }
    }
  } else if ((res = expectThisString("TEXT")) == ACCEPT)
    f_in.sectiontext = "TEXT";
  else
    return REJECT;

  return ACCEPT;

}

//----------------------------------------------------------------------
Operator::ParseResult
FetchOperator::expectSection(BincImapParserFetchAtt &f_in) const
{
  Session &session = Session::getInstance();

  Operator::ParseResult res;
  if ((res = expectThisString("[")) != ACCEPT)
    return REJECT;
    
  if ((res = expectSectionText(f_in)) != ACCEPT) {
    unsigned int n;
    if ((res = expectNZNumber(n)) == ACCEPT) {
      
      BincStream nstr;
	
      nstr << n;

      f_in.section = nstr.str();
      
      bool gotadotalready = false;
      while (1) {
	if ((res = expectThisString(".")) != ACCEPT)
	  break;
	
	if ((res = expectNZNumber(n)) != ACCEPT) {
	  gotadotalready = true;
	  break;
	}
	
	f_in.section += ".";	
	
	BincStream nstr;
	
	nstr << n;
	
	f_in.section += nstr.str();	 
      }
      
      if (gotadotalready || (res = expectThisString(".")) == ACCEPT) {
	if ((res = expectThisString("MIME")) == ACCEPT) {
	  f_in.sectiontext = "MIME";
	} else if ((res = expectSectionText(f_in)) != ACCEPT) {
	  session.setLastError("Expected MIME or section_text");
	  return res;
	}
      }
    }
  }

  if ((res = expectThisString("]")) != ACCEPT) {
    session.setLastError("Expected ]");
    return res;
  }

  return ACCEPT;
}

//----------------------------------------------------------------------
Operator::ParseResult
FetchOperator::expectHeaderList(BincImapParserFetchAtt &f_in) const
{
  Session &session = Session::getInstance();

  Operator::ParseResult res;
  if ((res = expectThisString("(")) != ACCEPT)
    return REJECT;

  string header_fld_name;
  while (1) {
    if ((res = expectAstring(header_fld_name)) != ACCEPT) {
      session.setLastError("Expected header_fld_name");
      return res;
    }

    f_in.headerlist.push_back(header_fld_name);

    if ((res = expectSPACE()) == ACCEPT)
      continue;
    else break;
  }

  if ((res = expectThisString(")")) != ACCEPT) {
    session.setLastError("Expected )");
    return res;
  }

  return ACCEPT;
}

//----------------------------------------------------------------------
Operator::ParseResult
FetchOperator::expectOffset(BincImapParserFetchAtt &f_in) const
{
  Session &session = Session::getInstance();
  Operator::ParseResult res;

  if ((res = expectThisString("<")) != ACCEPT)
    return REJECT;

  unsigned int i;
  if ((res = expectNumber(i)) != ACCEPT) {
    session.setLastError("Expected number");
    return res;
  }

  if ((res = expectThisString(".")) != ACCEPT) {
    session.setLastError("Expected .");
    return res;
  }

  unsigned int j;
  if ((res = expectNZNumber(j)) != ACCEPT) {
    session.setLastError("expected nz_number");
    return res;
  }

  if ((res = expectThisString(">")) != ACCEPT) {
    session.setLastError("Expected >");
    return res;
  }

  f_in.offsetstart = i;
  f_in.offsetlength = j;
  return ACCEPT;
}

//----------------------------------------------------------------------
Operator::ParseResult
FetchOperator::expectFetchAtt(BincImapParserFetchAtt &f_in) const
{
  Operator::ParseResult res;

  Session &session = Session::getInstance();

  if ((res = expectThisString("ENVELOPE")) == ACCEPT) f_in.type = "ENVELOPE";
  else if ((res = expectThisString("FLAGS")) == ACCEPT) f_in.type = "FLAGS";
  else if ((res = expectThisString("INTERNALDATE")) == ACCEPT)
    f_in.type = "INTERNALDATE";
  else if ((res = expectThisString("UID")) == ACCEPT) f_in.type = "UID";
  else if ((res = expectThisString("RFC822")) == ACCEPT) {
    f_in.type = "RFC822";
    if ((res = expectThisString(".HEADER")) == ACCEPT) f_in.type += ".HEADER";
    else if ((res = expectThisString(".SIZE")) == ACCEPT) f_in.type += ".SIZE";
    else if ((res = expectThisString(".TEXT")) == ACCEPT) f_in.type += ".TEXT";
    else if ((res = expectThisString(".")) == ACCEPT) {
      session.setLastError("Expected RFC822, RFC822.HEADER,"
			   " RFC822.SIZE or RFC822.TEXT");
      return ERROR;
    }

  } else if ((res = expectThisString("BODY")) == ACCEPT) {
    f_in.type = "BODY";

    if ((res = expectThisString("STRUCTURE")) == ACCEPT) f_in.type += "STRUCTURE";
    else if ((res = expectThisString(".PEEK")) == ACCEPT) f_in.type += ".PEEK";
      
    if ((res = expectSection(f_in)) != ACCEPT)
      f_in.hassection = false;
    else {
      f_in.hassection = true;

      if ((res = expectOffset(f_in)) == ERROR)
	return ERROR;
    } 
  } else
    return REJECT;

  return ACCEPT;
}


syntax highlighted by Code2HTML, v. 0.9.1