diff options
Diffstat (limited to 'libmail/imap.C')
| -rw-r--r-- | libmail/imap.C | 1709 | 
1 files changed, 1709 insertions, 0 deletions
| diff --git a/libmail/imap.C b/libmail/imap.C new file mode 100644 index 0000000..3d13270 --- /dev/null +++ b/libmail/imap.C @@ -0,0 +1,1709 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "imap.H" +#include "driver.H" +#include "smap.H" +#include "smapfetch.H" +#include "smapfetchattr.H" +#include "imapfolder.H" +#include "imaplogin.H" +#include "logininfo.H" +#include "imapfetchhandler.H" +#include "base64.H" +#include "structure.H" +#include "misc.H" +#include "genericdecode.H" + +#include <unicode/unicode.h> +#include <sstream> +#include <iomanip> +#include <algorithm> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <stdio.h> + +//#define DEBUG 1 + +using namespace std; + +///////////////////////////////////////////////////////////////////////////// + +LIBMAIL_START + +static bool open_imap(mail::account *&accountRet, +		      mail::account::openInfo &oi, +		      mail::callback &callback, +		      mail::callback::disconnect &disconnectCallback) +{ +	mail::loginInfo imapLoginInfo; + +	if (!mail::loginUrlDecode(oi.url, imapLoginInfo)) +		return false; + +	if (imapLoginInfo.method != "imap" && +	    imapLoginInfo.method != "imaps") +		return false; + +	accountRet=new mail::imap(oi.url, oi.pwd, +				  oi.certificates, +				  oi.loginCallbackObj, +				  callback, +				  disconnectCallback); +	return true; +} + + +static bool imap_remote(string url, bool &flag) +{ +	mail::loginInfo nntpLoginInfo; + +	if (!mail::loginUrlDecode(url, nntpLoginInfo)) +		return false; + +	if (nntpLoginInfo.method != "imap" && +	    nntpLoginInfo.method != "imaps") +		return false; + +	flag=true; +	return true; +} + +driver imap_driver= { &open_imap, &imap_remote }; + +LIBMAIL_END + +///////////////////////////////////////////////////////////////////////////// + +mail::imapHandler::imapHandler( int timeoutValArg) +	: myimap(NULL), installedFlag(false), isBackgroundTask(false), +	  handlerTimeoutVal(timeoutValArg) +{ +	time(&timeoutAt); +} + +// Default message handler timeout function invokes the application +// callback. + +void mail::imapHandler::callbackTimedOut(mail::callback &callback, +					 const char *errmsg) +{ +	callback.fail(errmsg ? errmsg:"Server timed out."); +} + +mail::imapHandler::~imapHandler() +{ +	if (installedFlag)  // If I'm in the active handler list, take me out +		myimap->remove(this); +} + +void mail::imapHandler::anotherHandlerInstalled(mail::imap &imapAccount) +{ +} + +bool mail::imapHandler::getTimeout(mail::imap &imapAccount, int &ioTimeout) +{ +	ioTimeout=getTimeout(imapAccount) * 1000; + +	return (ioTimeout == 0); +} + +int mail::imapHandler::getTimeout(mail::imap &imapAccount) +{ +	time_t t; + +	time(&t); + +	if (handlerTimeoutVal == 0) +	{ +		handlerTimeoutVal=imapAccount.timeoutSetting; +		timeoutAt=t + handlerTimeoutVal; +	} + +	return timeoutAt > t ? timeoutAt - t:0; +} + +void mail::imapHandler::setTimeout(int t) +{ +	handlerTimeoutVal=t; +	setTimeout(); +} + +void mail::imapHandler::setTimeout() +{ +	if (handlerTimeoutVal == 0 && myimap) +		handlerTimeoutVal=myimap->timeoutSetting; + +	time(&timeoutAt); +	timeoutAt += handlerTimeoutVal; +} + +///////////////////////////////////////////////////////////////////////// +// +// mail::imap constructor +// + + +mail::imap::imap(string url, +		 string passwd, +		 std::vector<std::string> &certificates, +		 mail::loginCallback *callback_func, +		 mail::callback &callback, +		 mail::callback::disconnect &disconnectCallback) +	: mail::fd(disconnectCallback, certificates), +	  orderlyShutdown(false), sameServerFolderPtr(NULL), +	  smap(false), +	  smapProtocolHandlerFunc(&smapHandler::singleLineProcess), +	  smapBinaryCount(0) +{ +	cmdcounter=0; +	currentFolder=NULL; +	currentFetch=NULL; +	wantIdleMode=false; + +	mail::loginInfo loginInfo; + +	loginInfo.loginCallbackFunc=callback_func; +	loginInfo.callbackPtr= &callback; + +	if (!loginUrlDecode(url, loginInfo)) +	{ +		loginInfo.callbackPtr->fail("Invalid IMAP URL."); +		return; +	} + + +	timeoutSetting=getTimeoutSetting(loginInfo, "timeout", 60, +					 30, 600); +	noopSetting=getTimeoutSetting(loginInfo, "noop", 600, +				      5, 1800); + +	noIdle=loginInfo.options.count("noidle") != 0; +	loginInfo.use_ssl= loginInfo.method == "imaps"; + +	if (passwd.size() > 0) +		loginInfo.pwd=passwd; + + +	string errmsg=socketConnect(loginInfo, "imap", "imaps"); + +	if (errmsg.size() > 0) +	{ +		loginInfo.callbackPtr->fail(errmsg); +		return; +	} + +	// Expect a server greeting as the first order of business. + +	installBackgroundTask(new mail::imapGreetingHandler(loginInfo)); +} + +mail::imap::~imap() +{ +	removeAllHandlers(false, NULL); + +	disconnect(); +} + +// Verbotten characters + +#if 0 +void mail::imap::mkverbotten(std::vector<unicode_char> &verbotten) +{ +	// Some chars should be modutf7- +	// encoded, just to be on the safe +	// side. + +	verbotten.push_back('/'); +	verbotten.push_back('\\'); +	verbotten.push_back('%'); +	verbotten.push_back('*'); +	verbotten.push_back(':'); +	verbotten.push_back('~'); +	verbotten.push_back(0); +} +#endif + +// +// Prefix an increasing counter to an imap tag, and send the entire command +// + +void mail::imap::imapcmd(string cmd, string arg) +{ +	if (cmd.size() == 0) +	{ +		socketWrite(arg); +		return; +	} + +	if (smap) +	{ +		socketWrite(cmd + " " + arg); +		return; +	} + +	ostringstream o; + +	cmdcounter=(cmdcounter + 1) % 1000000; +	o << setw(6) << setfill('0') << cmdcounter +	  << cmd << " " << arg; + +	socketWrite(o.str()); +} + +/////////////////////////////////////////////////////////////////////////// +// +// Install a new handler to process replies from the IMAP server +// +// If there's already an active foreground task handler, push the handler +// onto the task queue. + +void mail::imap::installForegroundTask(mail::imapHandler *handler) +{ +	imapHandler *foregroundTask; + +	handler->setBackgroundTask(false); + +	if ((foregroundTask=hasForegroundTask()) != NULL) +	{ +		if (!task_queue.empty()) +			foregroundTask=NULL; + +		task_queue.push(handler); + +		if (foregroundTask) +			foregroundTask->anotherHandlerInstalled(*this); +		return; +	} + +	insertHandler(handler); +} + +mail::imapHandler *mail::imap::hasForegroundTask() +{ +	handlerMapType::iterator b=handlers.begin(), e=handlers.end(); +	imapHandler *h; + +	while (b != e) +		if (!(h=(*b++).second)->getBackgroundTask()) +			return h; +	return NULL; +} + +// Unconditionally install a new message handler. +// (any existing message handler with the same name is removed). + +void mail::imap::insertHandler(mail::imapHandler *handler) +{ +	if (!handler) +		LIBMAIL_THROW("mail::imap:: out of memory."); + +	const char *name=handler->getName(); +				 +	if (handlers.count(name) > 0) +	{ +		mail::imapHandler *oldHandler= +			&*handlers.find(name)->second; + +		uninstallHandler(oldHandler); +	} + +	handlers.insert(make_pair(name, handler)); +	handler->installedFlag=true; +	handler->myimap=this; +	handler->setTimeout(); +	handler->installed( *this ); // Tell the handler the show's on. +} + +// Install a new message handler, and mark it as a background handler, +// so that foreground message handlers can still be installed. +// (any existing message handler with the same name is removed). + +void mail::imap::installBackgroundTask(mail::imapHandler *handler) +{ +	handler->setBackgroundTask(true); +	insertHandler(handler); +	current_handler=handler; +} + +// Unconditionally remove a handler.  The handler MUST be currently installed +// as active (it cannot be on the task queue + +void mail::imap::uninstallHandler(mail::imapHandler *handler) +{ +	if (!handler->installedFlag) +	{ +		delete handler; +		return; +	} + +	remove(handler); +	delete handler; +} + +// Remove an active handler. + +void mail::imap::remove(mail::imapHandler *handler) +{ +	const char *name=handler->getName(); + +	if (current_handler != NULL && +	    strcmp(current_handler->getName(), handler->getName()) == 0) +		current_handler=NULL; // Removed the current handler. + +	handlers.erase(name); +	handler->installedFlag=false; + +	// If we just popped off a foreground task, and there are other +	// foreground tasks waiting, install the next foreground task. + +	if (!handler->getBackgroundTask()) +	{ +		if (!task_queue.empty()) +		{ +			mail::imapHandler *nextTask=task_queue.front(); +			task_queue.pop(); + +			insertHandler(nextTask); +		} +		else if (wantIdleMode) +		{ +			updateNotify(NULL); +		} +	} +} + +void mail::imap::fatalError(string errmsg) +{ +	disconnect_callback.servererror(errmsg.c_str()); +} + +bool mail::imap::ready(mail::callback &callback) +{ +	if (socketConnected()) +		return true; + +	callback.fail("Account connection closed."); +	return false; +} + +void mail::imap::resumed() +{ +	handlerMapType::iterator bb=handlers.begin(), +		ee=handlers.end(); + +	while (bb != ee) +	{ +		if (strcasecmp(bb->first, mail::imapFOLDER::name) && +		    strcasecmp(bb->first, mail::smapFOLDER::name)) +			bb->second->setTimeout(); +		++bb; +	} +} + +/////////////////////////////////////////////////////////////////////////// +// +// Main event dispatcher.  Read any response received from the IMAP server, +// and process it.  Write any pending output to the imap server +// +// r, w: initialized to indicate any I/O status that must occur before +// process() should be called again.  r, w should be used as arguments to +// select(2) when process() returns. + +void mail::imap::handler(vector<pollfd> &fds, int &ioTimeout) +{ +	bool timedoutFlag=false; +	int fd_save=getfd(); + +	handlerMapType::iterator bb=handlers.begin(), +		ee=handlers.end(); + +	writtenFlag=false; + +	MONITOR(mail::imap); + +	while (bb != ee) +	{ +		int t; + +		if (bb->second->getTimeout(*this, t)) +			timedoutFlag=true; + +		if (t < ioTimeout) +			ioTimeout=t; +		bb++; +	} + +	if (timedoutFlag) +	{ +		timeoutdisconnect(); +		return; +	} + +	if (mail::fd::process(fds, ioTimeout) < 0) +		return; + +	if (DESTROYED()) +		ioTimeout=0; + +	if (DESTROYED() || getfd() < 0) +	{ +		size_t i; + +		for (i=fds.size(); i; ) +		{ +			--i; + +			if (fds[i].fd == fd_save) +			{ +				fds.erase(fds.begin()+i, fds.begin()+i+1); +				break; +			} +		} +		return; +	} + +	if (writtenFlag) +		ioTimeout=0; + +	return; +} + +// +// Figure out where a response from the server should go. + +int mail::imap::socketRead(const string &readbuffer) +{ +	MONITOR(mail::imap); + +	string buffer_cpy=readbuffer; + +	// Try whatever handler accepted previous folder output. + +	if (current_handler != NULL) +	{ +#if DEBUG +		cerr << "Handler: " << current_handler->getName() +		     << endl; +#endif +		int n; + +		n=current_handler->process(*this, buffer_cpy); + + +#if DEBUG +		cerr << "  Processed " << n << " bytes." << endl; +#endif +		if (DESTROYED()) +			return 0; + +		if (n > 0) +			return n;  // The handler took the input +	} + +	// If the last handler didn't want it, step through the active +	// handlers, until someone picks it up. + +	int processed=0; + +	handlerMapType::iterator bb=handlers.begin(), +		ee=handlers.end(); + +	while (bb != ee) +	{ +#if DEBUG +		cerr << "Trying " << bb->second->getName() << " ... " +		     << endl; + +		if (strcmp(bb->second->getName(), "SELECT") == 0) +		{ +			printf("OK\n"); +		} +#endif +		current_handler=bb->second; + +		processed=bb->second->process(*this, buffer_cpy); + +		if (DESTROYED()) +			return 0; + +		if (processed > 0) +		{ +#if DEBUG +			cerr << "   ... processed " << processed +			     << " of " << readbuffer.size() +			     << " bytes." << endl; +#endif +			break; +		} +		current_handler=NULL; +		bb++; +	} + +	if (processed > 0) +		return processed; + +	size_t pos=buffer_cpy.find('\n'); + +	if (pos == std::string::npos) +	{ +		if (buffer_cpy.size() > 16000) +			return buffer_cpy.size() - 16000; +		// SANITY CHECK - DON'T LET HOSTILE SERVER DOS us + +		return 0;  // Read more +	} + +	// Ok, there are some server responses we know how to do ourselves. + +	buffer_cpy.erase(pos); + +	size_t processedCnt=pos+1; + + +	while ((pos=buffer_cpy.find('\r')) != std::string::npos) +		buffer_cpy.erase(buffer_cpy.begin() + pos, +				 buffer_cpy.begin() + pos + 1); + +	string::iterator mb=buffer_cpy.begin(), me=buffer_cpy.end(); + +	if ( mb != me && *mb == '*' ) +	{ +		++mb; + +		string::iterator save_mb=mb; + +		string msg=get_word(&mb, &me); + +		upper(msg); + +		if (msg == "CAPABILITY") +		{ +			clearCapabilities(); +			string status; + +			while ((status=get_word(&mb, &me)).length() > 0) +				setCapability(status); +			return processedCnt; +		} + +		if (msg == "OK") +		{ +			size_t p=buffer_cpy.find('['); +			size_t q=buffer_cpy.find(']'); +			string brk, brkw; +			string::iterator bb, be; + +			if (p != std::string::npos && q != std::string::npos && +			    q > p && ((brk=string(buffer_cpy.begin()+p, +						  buffer_cpy.begin()+q)), +				      bb=brk.begin(), +				      be=brk.end(), +				      brkw=get_word(&bb, &be), upper(brkw), +				      brkw) != "ALERT") +			{ +				if (brkw == "PERMANENTFLAGS") +					setPermanentFlags(bb, be); + +				return processedCnt; +			} +		} + +		if (msg == "NO" || msg == "OK") +		{ +			if (serverMsgs.size() < 15) +			{ +				if (msg == "NO") +					mb=save_mb; + +				serverMsgs.push_back(std::string(mb, me)); +			} + +			return processedCnt; +		} + +		if (msg != "BYE") +			mb=save_mb; + +		while (mb != me && unicode_isspace((unsigned char)*mb)) +			++mb; + +		buffer_cpy.erase(buffer_cpy.begin(), mb); +	} + +	fatalError(buffer_cpy); + +	if (DESTROYED()) +		return 0; + +	return processedCnt; +} + +void mail::imap::setPermanentFlags(string::iterator flagb, +				   string::iterator flage) +{ +	string w; + +	permanentFlags.clear(); + +	while (flagb != flage && unicode_isspace((unsigned char)*flagb)) +		++flagb; + +	if (flagb == flage) +		return; + +	if (*flagb != '(') +	{ +		w=get_word(&flagb, &flage); +		upper(w); +		if (w != "NIL") +			permanentFlags.insert(w); +		return; +	} +	++flagb; + +	while ((w=get_word(&flagb, &flage)).size() > 0) +	{ +		upper(w); + +		permanentFlags.insert(w); +	} +} + +void mail::imap::timeoutdisconnect() +{ +	const char *errmsg="Server connection closed due to a timeout."; + +	removeAllHandlers(true, errmsg); +	disconnect(errmsg); +} + +void mail::imap::disconnect(const char *errmsg) +{ +	string errmsg_cpy=errmsg ? errmsg:""; + +	if (getfd() >= 0) +	{ +		string errmsg2=socketDisconnect(); + +		if (errmsg2.size() > 0) +			errmsg_cpy=errmsg2; + +		removeAllHandlers(true, errmsg_cpy.size() == 0 ? +				  "Connection closed by remote host." +				  :errmsg_cpy.c_str()); + + +		if (orderlyShutdown) +			errmsg_cpy=""; +		else if (errmsg_cpy.size() == 0) +			errmsg_cpy="Connection closed by remote host."; + +		disconnect_callback.disconnected(errmsg_cpy.c_str()); +	} +	else +	{ +		removeAllHandlers(true, errmsg_cpy.size() == 0 ? +				  "Connection closed by remote host.":errmsg); +	} +} + +void mail::imap::setCapability(string status) +{ +	upper(status); +	capabilities.insert(status); +} + +bool mail::imap::hasCapability(string status) +{ +	upper(status); + +	if (status == LIBMAIL_SINGLEFOLDER) +		return false; + +	if (status == "ACL") // Special logic +	{ +		if (capabilities.count("ACL") > 0) +			return true; + +		set<string>::iterator p; + +		for (p=capabilities.begin(); p != capabilities.end(); ++p) +		{ +			string s= *p; +			upper(s); +			if (strncmp(s.c_str(), "ACL2=", 5) == 0) +				return true; +		} +		return false; +	} + +	return (capabilities.count(status) > 0); +} + +string mail::imap::getCapability(string name) +{ +	upper(name); + +	if (name == "ACL") // Special logic +	{ +		set<string>::iterator p; + +		for (p=capabilities.begin(); p != capabilities.end(); ++p) +		{ +			string s= *p; +			upper(s); +			if (strncmp(s.c_str(), "ACL2=", 5) == 0) +				return s; +		} +		if (capabilities.count("ACL") > 0) +			return name; +		return ""; +	} + +	if (name == LIBMAIL_SERVERTYPE) +	{ +		return servertype.size() == 0 ? "imap":servertype; +	} + +	if (name == LIBMAIL_SERVERDESCR) +	{ +		return serverdescr.size() == 0 ? "IMAP server":serverdescr; +	} + +	return (capabilities.count(name) > 0 ? "1":""); +} + +// Utility functions... + +string mail::imap::get_word(string::iterator *b, string::iterator *e) +{ +	string s=""; + +	while (*b != *e && unicode_isspace((unsigned char)**b)) +		++*b; + +	while (*b != *e && !unicode_isspace((unsigned char)**b) && +	       strchr(")]", **b) == NULL) +	{ +		s.append(&**b, 1); +		++*b; +	} +	return s; +} + +// IMAP tags have are prefixed with a numerical counter.  Throw it away + +string mail::imap::get_cmdreply(string::iterator *b, string::iterator *e) +{ +	while (*b != *e && isdigit((int)(unsigned char)**b)) +		++*b; +	return (get_word(b, e)); +} + + +string mail::imap::quoteSimple(string s) +{ +	string::iterator b, e; + +#if 0 +	b=s.begin(); +	e=s.end(); + +	while (b != e) +	{ +		if (*b >= 127 || *b < ' ') +		{ +			string buffer; + +			{ +				ostringstream o; + +				o << "{" << s.size() << "}\r\n"; +				buffer=o.str(); +			} + +			return buffer + s; +		} +		b++; +	} +#endif + +	string t="\""; + +	b=s.begin(); +	e=s.end(); + +	while (b != e) +	{ +		if (*b == '"' || *b == '\\') +			t.append("\\"); +		t.append(&*b, 1); +		b++; +	} +	t.append("\""); +	return t; +} + +string mail::imap::quoteSMAP(string s) +{ +	string::iterator b, e; + +	string t="\""; + +	b=s.begin(); +	e=s.end(); + +	while (b != e) +	{ +		if (*b == '"') +			t.append("\""); +		t.append(&*b, 1); +		b++; +	} +	t.append("\""); +	return t; +} + +// +// Remove all handlers.  Possible reasons: destructor, connection timeout, +// orderly shutdown. +// + +void mail::imap::removeAllHandlers(bool timedOutFlag, // true if conn timeout +				   const char *errmsg) +{ +	while (!handlers.empty()) +	{ +		mail::imapHandler *handler=handlers.begin()->second; + +		if (timedOutFlag) +			handler->timedOut(errmsg); + +		handlers.erase(handlers.begin()->first); +		handler->installedFlag=false; +		delete handler; +	} + +	while (!task_queue.empty()) +	{ +		mail::imapHandler *p=task_queue.front(); + +		task_queue.pop(); + +		if (timedOutFlag) +			p->timedOut(errmsg); +		delete p; +	} +} + +mail::imap::msgSet::msgSet() +{ +} + +mail::imap::msgSet::~msgSet() +{ +} + +// Convert an array of message numbers to imap-style msg set + +mail::imap::msgSetRange::msgSetRange(mail::imap *pArg, +				     const vector<size_t> &messages) +	: p(pArg) +{ +	msglist=messages; + +	sort(msglist.begin(), msglist.end()); +	nextMsg=msglist.begin(); +} + +mail::imap::msgSetRange::~msgSetRange() +{ +} + +size_t mail::imap::msgSetRange::uidNum(mail::imap *i, size_t j) +{ +	if (i->currentFolder == NULL || j >= i->currentFolder->index.size()) +		return 0; + +	// mail::imapFolder constructs the abstract libmail.a uid as +	// UIDVALIDITY/UID, so find the UID part of it. + +	string uid=i->currentFolder->index[j].uid; + +	size_t n=uid.find('/'); + +	if (n == string::npos) +		return 0; + +	uid=uid.substr(n+1); + +	n=0; + +	istringstream(uid.c_str()) >> n; + +	return n; +} + +bool mail::imap::msgSetRange::getNextRange(size_t &first, size_t &last) +{ +	do +	{ +		if (nextMsg == msglist.end()) +			return false; +	} while ((first=last= uidNum(p, *nextMsg++)) == 0); + + +	while (nextMsg != msglist.end() && last + 1 == uidNum(p, *nextMsg)) +		last = uidNum(p, *nextMsg++); + +	return true; +} + +void mail::imap::messagecmd(const vector<size_t> &messages, string parameters, +			    string operation, +			    string name, +			    mail::callback::message &callback) +{ +	msgSetRange setRange(this, messages); + +	messagecmd(setRange, parameters, operation, name, callback); +} + +void mail::imap::messagecmd(msgSet &msgRange, string parameters, +			    string operation, +			    string name, +			    mail::callback::message &callback) +{ +	if (!ready(callback)) +		return; + +	// Prevent excessively huge IMAP commands that result from large +	// message sets.  The message set is capped at about 100 characters, +	// and multiple commands are generated, if this is not enough. +	// +	// mail::imapFetchHandler is told how many commands went out, so +	// it knows to count the number of server replies. + +	mail::imapFetchHandler *cmd=new mail::imapFetchHandler(callback, name); + +	size_t first, last; + +	string s=""; + +	try { +		if (!cmd) +			LIBMAIL_THROW(strerror(errno)); + +		while (msgRange.getNextRange(first, last)) +		{ +			cmd->messageCntTotal += last + 1 - first; + +			if (s.length() > 0) +			{ +				if (s.length() > 100) +				{ +					cmd->commands +						.push( make_pair(name, +								 operation +								 + " " + s +								 + parameters +								 + "\r\n")); +					++cmd->counter; +					s=""; +				} +				else +					s += ","; +			} + +			string buffer; + +			if (first != last) +			{ +				ostringstream o; + +				o << first << ":" << last; +				buffer=o.str(); +			} +			else +			{ +				ostringstream o; + +				o << first; +				buffer=o.str(); +			} + +			s += buffer; +		} + +		if (s.size() == 0) +		{ +			delete cmd; +			cmd=NULL; +			callback.success("No messages to process."); +			return; +		} + +		cmd->commands.push( make_pair(name, +					      operation + " " + s + parameters +					      + "\r\n")); +		++cmd->counter; + +		installForegroundTask(cmd); +	} catch (...) { + +		if (cmd) +			delete cmd; + +		callback.fail("An exception occured while attempting to start a command."); +	} +} + + + + +//////////////////////////////////////////////////////////////////////// +// + +void mail::imap::readMessageAttributes(const vector<size_t> &messages, +				       MessageAttributes attributes, +				       mail::callback::message &callback) +{ +	if (smap) +	{ +		installForegroundTask(new smapFETCHATTR(messages, +							attributes, +							callback, +							*this)); +		return; +	} + +	string attrList=""; + +	if (attributes & ARRIVALDATE) +		attrList += " INTERNALDATE"; + +	if (attributes & MESSAGESIZE) +		attrList += " RFC822.SIZE"; + +	if (attributes & ENVELOPE) +		attrList += " ENVELOPE BODY.PEEK[HEADER.FIELDS (References)]"; + +	if (attributes & MIMESTRUCTURE) +		attrList += " BODYSTRUCTURE"; + +	if (attrList.size() == 0) +	{ +		callback.success("No attributes were requested."); +		return; +	} + +	attrList[0]='('; + +	messagecmd(messages, " " + attrList + ")", +		   "UID FETCH", "FETCHENV", +		   callback); +} + +void mail::imap::readMessageContent(const vector<size_t> &messages, +				    bool peek, +				    enum mail::readMode readType, +				    mail::callback::message &callback) +{ +	if (smap) +	{ +		installForegroundTask(new smapFETCH(messages, +						    peek, +						    "", +						    readType, +						    "", +						    callback, +						    *this)); +		return; +	} + +	doReadMessageContent(messages, peek, NULL, readType, callback); +} + +////////////////////////////////////////////////////////////////////////// +// +// There is no single RFC 2060 command that returns the headers of a +// MIME section, a blank line, then contents.  Therefore, we punt: first, +// we request a .MIME, then when this command returns, we request the body. +// This is implemented using the following helper object. +// + +LIBMAIL_START + +class imapComboFetchCallback +	: public mail::callback::message { + +	mail::callback::message &originalCallback; +	bool origPeek; +	imap &origImap; +	size_t origMessageNum; + +	string mime_id; + +	void reportProgress(size_t bytesCompleted, +			    size_t bytesEstimatedTotal, + +			    size_t messagesCompleted, +			    size_t messagesEstimatedTotal); + +public: +	imapComboFetchCallback(mail::callback::message &callback, +			       bool peek, +			       size_t messageNum, +			       const mimestruct &msginfo, +			       imap &imapAccount); +	~imapComboFetchCallback(); +	void messageTextCallback(size_t n, string text); +	void fail(string message); +	void success(string message); +}; + +LIBMAIL_END + +mail::imapComboFetchCallback::imapComboFetchCallback(mail::callback::message +						     &callback, +						     bool peek, +						     size_t messageNum, +						     const mimestruct &msginfo, +						     imap &imapAccount) +	: originalCallback(callback), origPeek(peek), origImap(imapAccount), +	  origMessageNum(messageNum), +	  mime_id(msginfo.mime_id) +{ +} + +mail::imapComboFetchCallback::~imapComboFetchCallback() +{ +} + +void mail::imapComboFetchCallback::messageTextCallback(size_t n, string text) +{ +	originalCallback.messageTextCallback(n, text); +} + +void mail::imapComboFetchCallback::fail(string message) +{ +	originalCallback.fail(message); +	delete this; +} + +void mail::imapComboFetchCallback::success(string message) +{ +	vector<size_t> messageVector; + +	messageVector.push_back(origMessageNum); + +	string cmd="BODY"; + +	if (origPeek) +		cmd="BODY.PEEK"; + +	origImap.messagecmd(messageVector, +			    " (" + cmd + "[" + mime_id + "])", +			    "UID FETCH", +			    "CONTENTS", originalCallback); +	delete this; +} + +void mail::imapComboFetchCallback::reportProgress(size_t bytesCompleted, +						  size_t bytesEstimatedTotal, + +						  size_t messagesCompleted, +						  size_t messagesEstimatedTotal +						  ) +{ +	originalCallback.reportProgress(bytesCompleted, +					bytesEstimatedTotal, +					messagesCompleted, +					messagesEstimatedTotal); +} + +void mail::imap::readMessageContent(size_t messageNum, +				    bool peek, +				    const mail::mimestruct &msginfo, +				    enum mail::readMode readType, +				    mail::callback::message &callback) +{ +	vector<size_t> messageVector; + +	messageVector.push_back(messageNum); + +	if (smap) +	{ +		installForegroundTask(new smapFETCH(messageVector, +						    peek, +						    msginfo.mime_id, +						    readType, +						    "", +						    callback, +						    *this)); +		return; +	} + +	if (readType == readBoth) +	{ +		const mail::mimestruct *parent=msginfo.getParent(); + +		if (parent != NULL && !parent->messagerfc822()) +		{ +			// There is no RFC2060 command that returns what we +			// want here: .MIME, then the body, so we punt + +			mail::imapComboFetchCallback *fakeCallback=new +				mail::imapComboFetchCallback(callback, +							     peek, +							     messageNum, +							     msginfo, +							     *this); + +			if (!fakeCallback) +				LIBMAIL_THROW("Out of memory."); + +			try { +				doReadMessageContent(messageVector, +						     peek, +						     &msginfo, +						     readHeaders, +						     *fakeCallback); +			} catch (...) { +				delete fakeCallback; +				LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); +			} +			return; +		} +	} +	doReadMessageContent(messageVector, peek, &msginfo, +			     readType, +			     callback); +} + +// +// readMessageContentDecoded is implemented by creating a proxy +// mail::generic::Decode object, that handles MIME decoding by itself, +// then forwards the decoded data to the original callback. + +void mail::imap::readMessageContentDecoded(size_t messageNum, +					   bool peek, +					   const mail::mimestruct &msginfo, +					   mail::callback::message +					   &callback) +{ +	if (smap) +	{ +		vector<size_t> messageVector; + +		messageVector.push_back(messageNum); + +		installForegroundTask(new smapFETCH(messageVector, +						    peek, +						    msginfo.mime_id, +						    readContents, +						    ".DECODED", +						    callback, +						    *this)); +		return; +	} + +	mail::generic::Decode *d= +		new mail::generic::Decode(callback, +					  msginfo.content_transfer_encoding); + +	if (!d) +		LIBMAIL_THROW("Out of memory."); + +	try { +		readMessageContent(messageNum, peek, msginfo, +				   readContents, *d); +	} catch (...) +	{ +		delete d; +		LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); +	} +} + +// +// Helper class for folding headers read from the IMAP server. +// + +LIBMAIL_START + +class imapFolderHeadersCallback : public mail::callback::message { + +	mail::callback::message &originalCallback; + +	bool doFolding; + +	bool prevWasNewline; +	bool prevFoldingSpace; + +	void reportProgress(size_t bytesCompleted, +			    size_t bytesEstimatedTotal, + +			    size_t messagesCompleted, +			    size_t messagesEstimatedTotal); + +public: +	imapFolderHeadersCallback(bool, mail::callback::message &); +	~imapFolderHeadersCallback(); + +	void messageTextCallback(size_t n, string text); +	void success(string message); +	void fail(string message); +}; + +LIBMAIL_END + +mail::imapFolderHeadersCallback +::imapFolderHeadersCallback(bool doFoldingArg, +			    mail::callback::message &origArg) +	: originalCallback(origArg), doFolding(doFoldingArg), +	  prevWasNewline(false), prevFoldingSpace(false) +{ +} + +mail::imapFolderHeadersCallback::~imapFolderHeadersCallback() +{ +} + +void mail::imapFolderHeadersCallback +::reportProgress(size_t bytesCompleted, +		 size_t bytesEstimatedTotal, + +		 size_t messagesCompleted, +		 size_t messagesEstimatedTotal) +{ +	originalCallback.reportProgress(bytesCompleted, bytesEstimatedTotal, +					messagesCompleted, +					messagesEstimatedTotal); +} + +// Intercept the data in the callback, and use it to fold header lines. + +void mail::imapFolderHeadersCallback +::messageTextCallback(size_t n, string text) +{ +	string::iterator b=text.begin(), e=text.end(), c=b; + +	string cpy=""; + +	while (b != e) +	{ +		if (prevWasNewline) // Prev char was \n, which was eaten. +		{ +			// This is the first character after a newline. + +			prevWasNewline=false; +			if (*b == '\n') // Blank line, ignore +			{ +				cpy += "\n"; // The eaten newline +				b++; +				c=b; +				continue; +			} + +			if ( doFolding && unicode_isspace((unsigned char)*b)) +			{ +				// Bingo. + +				prevFoldingSpace=true; +				cpy += " "; // Eaten newline replaced by spc +				b++; +				continue; +			} + +			cpy += "\n"; // The eaten newline. +			c=b; +			b++; +			continue; +		} + +		if (prevFoldingSpace) +		{ +			if ( *b != '\n' && unicode_isspace((unsigned char)*b)) +			{ +				b++; +				continue; // Eating all whitespace +			} + +			prevFoldingSpace=false; // Resume header. +			c=b; +		} + +		if (*b == '\n') +		{ +			cpy.insert(cpy.end(), c, b); +			prevWasNewline=true; +			b++; +			continue; +		} +		b++; +	} + +	if (!prevWasNewline && !prevFoldingSpace) +		cpy.insert(cpy.end(), c, b); +		 +	originalCallback.messageTextCallback(n, cpy); +} + +void mail::imapFolderHeadersCallback::success(string message) +{ +	originalCallback.success(message); +	delete this; +} + +void mail::imapFolderHeadersCallback::fail(string message) +{ +	originalCallback.fail(message); +	delete this; +} + +////// +void mail::imap::doReadMessageContent(const vector<size_t> &messages, +				      bool peek, +				      const mail::mimestruct *msginfo, +				      mail::readMode readType, +				      mail::callback::message &callback) +{ +	if (!ready(callback)) +		return; + +	bool needHeaderDecode=false; +	bool foldHeaders=false; + +	string mime_id; +	const mail::mimestruct *parent=msginfo ? msginfo->getParent():NULL; + +	// RFC 2060 mess. + +	if (readType == readBoth) +	{ +		if (parent == NULL) +			mime_id=""; +		else +			mime_id=parent->mime_id; +	} +	else if (readType == readHeadersFolded || +		 readType == readHeaders) +	{ +		needHeaderDecode=true; + +		if (readType == readHeadersFolded) +			foldHeaders=true; + +		if (parent == NULL) +			mime_id="HEADER"; +		else if (parent->messagerfc822()) +			mime_id=parent->mime_id + ".HEADER"; +		else +			mime_id=msginfo->mime_id + ".MIME"; +	} +	else +	{ +		// Retrieve content + +		if (parent == NULL) +			mime_id="TEXT"; +		else if (parent->messagerfc822()) +			mime_id=parent->mime_id + ".TEXT"; +		else +			mime_id=msginfo->mime_id; +	} + +	string cmd="BODY"; + +	if (peek) +		cmd="BODY.PEEK"; + +	mail::imapFolderHeadersCallback *decodeCallback=NULL; + +	if (needHeaderDecode) +	{ +		if ((decodeCallback=new mail::imapFolderHeadersCallback +		     (foldHeaders, callback)) == NULL) +		{ +			callback.fail("Out of memory."); +			return; +		} +	} + +	try { +		mail::callback::message *cb= &callback; + +		if (decodeCallback) +			cb=decodeCallback; + +		messagecmd(messages, +			   " (" + cmd + "[" + mime_id + "])", "UID FETCH", +			   "CONTENTS", *cb); + +	} catch (...) { +		if (decodeCallback) +			delete decodeCallback; +		LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); +	} +} + +///////////////////////////////////////////////////////////////////////// +// +// Helper class for most commands.  Determines whether the read line of +// input contains an untagged message (which results in untaggedMessage() +// getting invoked), or a tagged message (which results in taggedMessage() +// getting invoked. + +mail::imapCommandHandler::imapCommandHandler(int timeout) +	: mail::imapHandler(timeout) +{ +} + +mail::imapCommandHandler::~imapCommandHandler() +{ +} + +int mail::imapCommandHandler::process(mail::imap &imapAccount, string &buffer) +{ +	string::iterator b=buffer.begin(); +	string::iterator e=buffer.end(); + +	while (b != e) +	{ +		if (unicode_isspace((unsigned char)*b)) +		{ +			b++; +			continue; +		} +		if (*b != '*') +			break; + +		b++; + +		string w=imapAccount.get_word(&b, &e); + +		if (b == e) +			break; + +		size_t cnt= b - buffer.begin(); + +		mail::upper(w); + +		if (untaggedMessage(imapAccount, w)) +			return (cnt); +		return (0); +	} + +	size_t p=buffer.find('\n'); + +	if (p == std::string::npos) +		return (0); + +	string buffer_cpy=buffer; + +	buffer_cpy.erase(p); + +	size_t stripcr; + +	while ((stripcr=buffer_cpy.find('\r')) != std::string::npos) +		buffer_cpy.erase(buffer_cpy.begin() + stripcr, +				 buffer_cpy.begin() + stripcr + 1); + +	if (buffer_cpy.size() > 0 && +	    buffer_cpy[0] == (imapAccount.smap ? '>':'+')) +		return continuationRequest(imapAccount, buffer_cpy) ? p+1:0; + +	b=buffer_cpy.begin(); +	e=buffer_cpy.end(); + +	string w=imapAccount.get_cmdreply(&b, &e); + +	upper(w); + +	if (imapAccount.smap) +	{ +		while (b != e && unicode_isspace((unsigned char)*b)) +			b++; + +		buffer_cpy.erase(0, b - buffer_cpy.begin()); + +		return taggedMessage(imapAccount, +				     w, +				     buffer_cpy, +				     w == "+OK", +				     buffer_cpy) ? p+1:0; +	} + +	buffer_cpy.erase(0, b - buffer_cpy.begin()); + +	string errmsg=buffer_cpy; + +	b=errmsg.begin(); +	e=errmsg.end(); + +	string okfailWord=imapAccount.get_word(&b, &e); + +	upper(okfailWord); + +	while (b != e && unicode_isspace((unsigned char)*b)) +		b++; + +	errmsg.erase(0, b-errmsg.begin()); + +	b=errmsg.begin(); +	e=errmsg.end(); + +	if (b != e && *b == '[')  // Drop OK [APPENDUID ...], and friends +	{ +		while (b != e && *b != ']') +			b++; + +		if (b != e) +			b++; + +		while (b != e && unicode_isspace((unsigned char)*b)) +			b++; +		errmsg.erase(0, b-errmsg.begin()); +	} + +	ptr<mail::imap> acctPtr= &imapAccount; + +	int n=taggedMessage(imapAccount, w, buffer_cpy, +			    okfailWord == "OK", errmsg) ? p+1:0; + +	// Report any accumulated server messages in the response +	 +	if (!acctPtr.isDestroyed() && acctPtr->serverMsgs.size() > 0) +	{ +		vector<string>::iterator b, e; + +		b=acctPtr->serverMsgs.begin(); +		e=acctPtr->serverMsgs.end(); + +		errmsg=""; + +		while (b != e) +		{ +			if (errmsg.size() > 0) +				errmsg += "\n"; +			errmsg += *b++; +		} +		acctPtr->serverMsgs.clear(); +		acctPtr->fatalError(errmsg); +	} +	return n; +} + +// Subclasses must explicitly declare their intention to handle continuation +// requests. + +bool mail::imapCommandHandler::continuationRequest(mail::imap &imapAccount, +						   string request) +{ +	return false; +} | 
