diff options
| author | Sam Varshavchik | 2013-08-19 16:39:41 -0400 |
|---|---|---|
| committer | Sam Varshavchik | 2013-08-25 14:43:51 -0400 |
| commit | 9c45d9ad13fdf439d44d7443ae75da15ea0223ed (patch) | |
| tree | 7a81a04cb51efb078ee350859a64be2ebc6b8813 /libmail/maildirfolder.C | |
| parent | a9520698b770168d1f33d6301463bb70a19655ec (diff) | |
| download | courier-libs-9c45d9ad13fdf439d44d7443ae75da15ea0223ed.tar.bz2 | |
Initial checkin
Imported from subversion report, converted to git. Updated all paths in
scripts and makefiles, reflecting the new directory hierarchy.
Diffstat (limited to 'libmail/maildirfolder.C')
| -rw-r--r-- | libmail/maildirfolder.C | 1003 |
1 files changed, 1003 insertions, 0 deletions
diff --git a/libmail/maildirfolder.C b/libmail/maildirfolder.C new file mode 100644 index 0000000..9c54e31 --- /dev/null +++ b/libmail/maildirfolder.C @@ -0,0 +1,1003 @@ +/* +** Copyright 2002-2004, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "maildir/config.h" +#include "maildir/maildirmisc.h" +#include "unicode/unicode.h" +#include "maildirfolder.H" +#include "maildiradd.H" +#include "mbox.H" +#include "misc.H" +#include <list> +#include <algorithm> +#include <errno.h> +#include <string.h> +#include <fcntl.h> + +#include <sys/stat.h> +#include <sys/types.h> +#include <iostream> + +#if HAVE_DIRENT_H +# include <dirent.h> +# define NAMLEN(dirent) strlen((dirent)->d_name) +#else +# define dirent direct +# define NAMLEN(dirent) (dirent)->d_namlen +# if HAVE_SYS_NDIR_H +# include <sys/ndir.h> +# endif +# if HAVE_SYS_DIR_H +# include <sys/dir.h> +# endif +# if HAVE_NDIR_H +# include <ndir.h> +# endif +#endif + +using namespace std; + +mail::maildir::folder::folder(mail::maildir *maildirArg, + string pathArg) + : mail::folder(maildirArg), + maildirAccount(maildirArg), + path(pathArg), + hasMessagesFlag(true), + hasSubfoldersFlag(true) +{ + name=pathArg; + + size_t p=name.rfind('.'); + + if (p != std::string::npos) + name=name.substr(p+1); + + // Convert the name of the folder from modified UTF-7 + // (Courier compatibility) to the current charset. + + char *s=libmail_u_convert_tobuf(name.c_str(), + unicode_x_imap_modutf7, + unicode_default_chset(), + NULL); + + if (s) + { + try { + name=s; + free(s); + } catch (...) { + free(s); + } + } +} + +mail::maildir::folder::~folder() +{ +} + +void mail::maildir::folder::sameServerAsHelperFunc() const +{ +} + +string mail::maildir::folder::getName() const +{ + if (path == "INBOX") + return hasSubFolders() ? "Folders":"INBOX"; + + return name; +} + +string mail::maildir::folder::getPath() const +{ + return path; +} + +bool mail::maildir::folder::hasMessages() const +{ + return hasMessagesFlag; +} + +bool mail::maildir::folder::hasSubFolders() const +{ + return hasSubfoldersFlag; +} + +bool mail::maildir::folder::isParentOf(string otherPath) const +{ + string s=path + "."; + + return (strncmp(otherPath.c_str(), s.c_str(), s.size()) == 0); +} + +void mail::maildir::folder::hasMessages(bool flag) +{ + hasMessagesFlag=flag; +} + +void mail::maildir::folder::hasSubFolders(bool flag) +{ + hasSubfoldersFlag=flag; +} + +class mail::maildir::indexSort { +public: + indexSort(); + ~indexSort(); + + bool operator()(const mail::maildir::maildirMessageInfo &a, + const mail::maildir::maildirMessageInfo &b); +}; + +mail::maildir::indexSort::indexSort() +{ +} + +mail::maildir::indexSort::~indexSort() +{ +} + +// Sort messages in some reasonable order. Rely on the timestamp component +// of the maildirfilename. + +bool mail::maildir::indexSort::operator() + (const mail::maildir::maildirMessageInfo &a, + const mail::maildir::maildirMessageInfo &b) +{ + unsigned long at=atol(a.lastKnownFilename.c_str()), + bt=atol(b.lastKnownFilename.c_str()); + + if ( at != bt) + return at < bt; + + return strcmp(a.lastKnownFilename.c_str(), + b.lastKnownFilename.c_str()) < 0; +} + +// Scan a maildirAccount + +bool mail::maildir::scan(string folderStr, vector<maildirMessageInfo> &index, + bool scanNew) +{ + string p; + + char *d=maildir_name2dir(path.c_str(), folderStr.c_str()); + + if (!d) + return false; + + try { + p=d; + free(d); + } catch (...) { + free(d); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + static const char * const subdirs[]={"/cur","/new"}; + + size_t i; + + for (i=0; i<(scanNew ? 2:1); i++) + { + string n=p + subdirs[i]; + + DIR *dirp=opendir(n.c_str()); + + try { + struct dirent *de; + + while (dirp && (de=readdir(dirp)) != NULL) + { + if (de->d_name[0] == '.') + continue; + + + maildirMessageInfo newInfo; + + newInfo.lastKnownFilename=de->d_name; + + // Use the filename as the uid + + newInfo.uid=de->d_name; + + size_t p=newInfo.uid.find(MDIRSEP[0]); + + if (p != std::string::npos) + newInfo.uid=newInfo.uid.substr(0, p); + + mail::maildir::updateFlags(de->d_name, + newInfo); + newInfo.recent= i > 0; + index.push_back(newInfo); + } + + if (dirp) + closedir(dirp); + } catch (...) { + if (dirp) + closedir(dirp); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + } + + sort(index.begin(), index.end(), indexSort()); + return true; +} + +void mail::maildir::folder::getParentFolder(callback::folderList &callback1, + callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + size_t n; + + n=path.rfind('.'); + + if (n == std::string::npos) + n=0; + + maildirAccount->findFolder(path.substr(0, n), + callback1, + callback2); +} + +void mail::maildir::folder::readFolderInfo( mail::callback::folderInfo + &callback1, + mail::callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + callback1.messageCount=0; + callback1.unreadCount=0; + + vector<maildirMessageInfo> dummyIndex; + + if (!maildirAccount->scan(path, dummyIndex, true)) + { + callback1.success(); + callback2.fail("Invalid folder"); + return; + } + + vector<maildirMessageInfo>::iterator b=dummyIndex.begin(), + e=dummyIndex.end(); + + while (b != e) + { + callback1.messageCount++; + if ( b->unread) + callback1.unreadCount++; + b++; + } + + callback1.success(); + callback2.success("OK"); +} + +mail::maildir::folder::listinfo::listinfo() +{ +} + +mail::maildir::folder::listinfo::~listinfo() +{ +} + +// Callback that lists maildirAccount folders. +// The callback filters only the folders under the list path + +void mail::maildir::folder::maildir_list_callback(const char *folder, + void *vp) +{ + mail::maildir::folder::listinfo *li= + (mail::maildir::folder::listinfo *)vp; + + if (strncmp(folder, li->path.c_str(), li->path.size()) || + folder[li->path.size()] != '.') + return; // Outside the hierarchy being listed. + + folder += li->path.size(); + ++folder; + + // If the remaining portion of the name has another period, there's + // a subdirectory there. Otherwise, it's a file. + // It's ok when we get multiple folders in the same subdirectory, + // subdirs is a STL set, which gets rid of duplicates + + const char *p=strchr(folder, '.'); + + if (p) + li->subdirs.insert(string(folder, p)); + else + li->list.insert(string(folder)); +} + +void mail::maildir::folder::readSubFolders( mail::callback::folderList + &callback1, + mail::callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + if (path.size() == 0) + { + maildirAccount->readTopLevelFolders(callback1, callback2); + return; + } + + listinfo li; + + li.path=path; + + maildir_list(maildirAccount->path.c_str(), + &mail::maildir::folder::maildir_list_callback, + &li); + + list<mail::folder *> folderList; + list<mail::folder *>::iterator b, e; + + try { + // Create a list of folder objects from the list of folder + // names in listinfo. Create a list in two passes. + + // First pass - build names of folders. If the folder is + // also found in the subdirectory list, make it a dual-purpose + // folder/directory. + + buildFolderList(folderList, &li.list, &li.subdirs); + + // Second pass - build remaining subdirs. + + buildFolderList(folderList, NULL, &li.subdirs); + + // Cleanup for the callback + vector<const mail::folder *> myList; + + b=folderList.begin(); + e=folderList.end(); + + while (b != e) + myList.push_back(*b++); + + callback1.success(myList); + callback2.success("OK"); + + } catch (...) { + b=folderList.begin(); + e=folderList.end(); + + while (b != e) + { + delete *b; + + b++; + } + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + b=folderList.begin(); + e=folderList.end(); + + while (b != e) + { + delete *b; + + b++; + } +} + +void mail::maildir::folder::buildFolderList(list<mail::folder *> &folderList, + set<string> *folders, + set<string> *dirs) const +{ + set<string>::iterator b, e; + + if (folders) + { + b=folders->begin(); + e=folders->end(); + } + else + { + b=dirs->begin(); + e=dirs->end(); + } + + while (b != e) + { + string name= *b++; + + folder *p=new folder(maildirAccount, path + "." + name); + + if (!p) + LIBMAIL_THROW(strerror(errno)); + + try { + if (folders) + { + p->hasMessages(true); + p->hasSubFolders(false); + + if (dirs->count(name) > 0) + // Also a subdir + { + p->hasSubFolders(true); + dirs->erase(name); + // Don't add this folder when we do + // a directory. + } + } + else + { + p->hasMessages(false); + p->hasSubFolders(true); + } + + folderList.push_back(p); + } catch (...) { + delete p; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + } +} + +mail::addMessage *mail::maildir::folder::addMessage(mail::callback + &callback) const +{ + if (isDestroyed(callback)) + return NULL; + + string folderPath; + + char *p=maildir_name2dir(maildirAccount->path.c_str(), path.c_str()); + + if (!p) + { + callback.fail(strerror(errno)); + return NULL; + } + + try { + folderPath=p; + free(p); + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + mail::maildir::addmessage *m=new + mail::maildir::addmessage(maildirAccount, folderPath, callback); + + if (!m) + { + callback.fail(strerror(errno)); + return NULL; + } + + return m; +} + +void mail::maildir::moveMessagesTo(const vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback) +{ + sameServerFolderPtr=NULL; + copyTo->sameServerAsHelperFunc(); + + if (sameServerFolderPtr == NULL) + { + mail::account::moveMessagesTo(messages, copyTo, callback); + return; + } + + string destFolderPath; + + char *p=maildir_name2dir(path.c_str(), + sameServerFolderPtr->path.c_str()); + + if (!p) + { + callback.fail(strerror(errno)); + return; + } + + try { + destFolderPath=p; + free(p); + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + vector<size_t>::const_iterator b=messages.begin(), e=messages.end(); + + while (b != e) + { + size_t n=*b++; + + string messageFn=getfilename(n); + + if (messageFn.size() > 0) + { + string destName= destFolderPath + + messageFn.substr(messageFn.rfind('/')); + + rename(messageFn.c_str(), destName.c_str()); + } + } + updateFolderIndexInfo(&callback, false); +} + +void mail::maildir::folder::createSubFolder(string name, bool isDirectory, + mail::callback::folderList + &callback1, + mail::callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + // The name of the folder is translated from the local charset + // to modified UTF-7 (Courier-IMAP compatibility), with the following + // blacklisted characters: + + char *p=libmail_u_convert_tobuf(name.c_str(), unicode_default_chset(), + unicode_x_imap_modutf7 " ./~:", NULL); + + if (!p) + { + callback2.fail(strerror(errno)); + return; + } + + std::string nameutf7; + + errno=ENOMEM; + try { + nameutf7=p; + free(p); + } catch (...) { + free(p); + callback2.fail(strerror(errno)); + return; + } + + mail::maildir::folder newFolder(maildirAccount, path + "." + nameutf7); + + newFolder.hasMessagesFlag= ! (newFolder.hasSubfoldersFlag= + isDirectory); + + if (!newFolder.doCreate(isDirectory)) + { + callback2.fail(strerror(errno)); + return; + } + + vector<const mail::folder *> folders; + + folders.push_back(&newFolder); + callback1.success( folders ); + callback2.success("Mail folder created"); +} + +bool mail::maildir::folder::doCreate(bool isDirectory) const +{ + if (isDirectory) + return true; // Pretend + + if (maildirAccount->ispop3maildrop) + { + errno=EPERM; + return false; // POP3 maildrops don't have folders. + } + + string subdir; + + char *d=maildir_name2dir(maildirAccount->path.c_str(), path.c_str()); + // Checks for name validity. + + if (!d) + return false; + + try { + subdir=d; + free(d); + } catch (...) { + free(d); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + return mail::maildir::maildirmake(subdir, true); +} + + +bool mail::maildir::maildirmake(string subdir, bool isFolder) +{ + string nsubdir=subdir + "/new", + csubdir=subdir + "/cur", + tsubdir=subdir + "/tmp"; + + if (mkdir(subdir.c_str(), 0700) == 0) + { + if (mkdir(nsubdir.c_str(), 0700) == 0) + { + if (mkdir(tsubdir.c_str(), 0700) == 0) + { + if (mkdir(csubdir.c_str(), 0700) == 0) + { + if (!isFolder) + return true; + + string f=subdir + + "/maildirfolder"; + + int fd=::open(f.c_str(), + O_CREAT | + O_RDWR, 0666); + + if (fd >= 0) + { + close(fd); + return true; + } + rmdir(csubdir.c_str()); + } + rmdir(tsubdir.c_str()); + } + rmdir(nsubdir.c_str()); + } + rmdir(subdir.c_str()); + } + + return false; +} + +void mail::maildir::folder::create(bool isDirectory, + mail::callback &callback) const +{ + if (!doCreate(isDirectory)) + { + callback.fail(strerror(errno)); + } + else + { + callback.success("Mail folder created"); + } +} + +void mail::maildir::folder::destroy(mail::callback &callback, + bool destroyDir) const +{ + if (isDestroyed(callback)) + return; + + if (!destroyDir) // Folder directories are imaginary, cannot be nuked + { + string s; + char *d=maildir_name2dir(maildirAccount->path.c_str(), + path.c_str()); + if (!d) + { + callback.fail(strerror(errno)); + return; + } + + try { + s=d; + free(d); + } catch (...) { + free(d); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + if (!mail::maildir::maildirdestroy(s)) + { + callback.fail(strerror(errno)); + return; + } + } + + callback.success("Mail folder deleted"); +} + +void mail::maildir::folder::renameFolder(const mail::folder *newParent, + std::string newName, + mail::callback::folderList &callback1, + mail::callback &callback2) const +{ + if (isDestroyed(callback2)) + return; + + if (maildirAccount->folderPath.size() > 0) + { + size_t l=path.size(); + + if (strncmp(maildirAccount->folderPath.c_str(), + path.c_str(), l) == 0 && + ((maildirAccount->folderPath.c_str())[l] == 0 || + (maildirAccount->folderPath.c_str())[l] == '.')) + { + callback2.fail("Cannot RENAME currently open folder."); + return; + } + } + + // The name of the folder is translated from the local charset + // to modified UTF-7 (Courier-IMAP compatibility), with the following + // blacklisted characters: + + char *s=libmail_u_convert_tobuf(newName.c_str(), + unicode_default_chset(), + unicode_x_imap_modutf7 " ./~:", NULL); + + if (!s) + { + callback2.fail(strerror(errno)); + return; + } + + std::string nameutf7; + + errno=ENOMEM; + try { + nameutf7=s; + free(s); + } catch (...) { + free(s); + callback2.fail(strerror(errno)); + return; + } + + mail::maildir::folder newFolder(maildirAccount, + (newParent ? + newParent->getPath() + ".": + string("")) + nameutf7); + + newFolder.hasMessages( hasMessages() ); + newFolder.hasSubFolders( hasSubFolders() ); + + vector<const mail::folder *> folders; + + // Paths are INBOX.foo + + string from, to; + + char *p=maildir_name2dir(".", path.c_str()); + + if (p) + try { + from=p+2; // Skip ./ + free(p); + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + p=maildir_name2dir(".", newFolder.path.c_str()); + if (p) + try { + to=p+2; // Skip ./ + free(p); + } catch (...) { + free(p); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + + if (from.size() > 0 && + to.size() > 0 && + maildir_rename(maildirAccount->path.c_str(), + from.c_str(), to.c_str(), + MAILDIR_RENAME_FOLDER | + MAILDIR_RENAME_SUBFOLDERS, NULL)) + { + callback2.fail(strerror(errno)); + } + else + { + folders.push_back(&newFolder); + callback1.success( folders ); + callback2.success("Mail folder renamed"); + } +} + +bool mail::maildir::maildirdestroy(string d) +{ + list<string> contents; + + DIR *dirp=opendir(d.c_str()); + + try { + struct dirent *de; + + while (dirp && (de=readdir(dirp)) != NULL) + { + if (strcmp(de->d_name, ".") == 0) + continue; + if (strcmp(de->d_name, "..") == 0) + continue; + + contents.push_back(de->d_name); + } + + if (dirp) + closedir(dirp); + } catch (...) { + if (dirp) + closedir(dirp); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + list<string>::iterator b=contents.begin(), e=contents.end(); + + while (b != e) + { + string s=d + "/" + *b++; + + if (unlink(s.c_str()) < 0 && errno != ENOENT) + { + if (errno == EISDIR) + { + if (!maildirdestroy(s)) + return false; + continue; + } + return false; + } + } + rmdir(d.c_str()); + return true; +} + +mail::folder *mail::maildir::folder::clone() const +{ + if (isDestroyed()) + return NULL; + + mail::maildir::folder *p=new mail::maildir::folder(maildirAccount, + path); + + if (p) + { + p->hasMessagesFlag=hasMessagesFlag; + p->hasSubfoldersFlag=hasSubfoldersFlag; + return p; + } + return NULL; +} + + +void mail::maildir::findFolder(string folder, + mail::callback::folderList &callback1, + mail::callback &callback2) +{ + mail::maildir::folder tempFolder(this, folder); + + vector<const mail::folder *> folderList; + + folderList.push_back(&tempFolder); + + callback1.success(folderList); + callback2.success("OK"); +} + +string mail::maildir::translatePath(string path) +{ + return mail::mbox::translatePathCommon(path, ":/.~", "."); +} + +static string encword(string s) +{ + string r=""; + + string::iterator b=s.begin(), e=s.end(); + string::iterator p=b; + + while ( b != e ) + { + if ( *b == ':' || *b == '\\') + { + r.insert(r.end(), p, b); + r += "\\"; + p=b; + } + b++; + } + + r.insert(r.end(), p, b); + return r; +} + + +static string getword(string &s) +{ + string r=""; + + string::iterator b=s.begin(), e=s.end(), p=b; + + while (b != e) + { + if (*b == ':') + break; + + if (*b == '\\') + { + r.insert(r.end(), p, b); + + b++; + p=b; + + if (b == e) + break; + } + b++; + } + + r.insert(r.end(), p, b); + + if (b != e) + { + b++; + s=string(b, s.end()); + } + + return r; +} + +string mail::maildir::folder::toString() const +{ + return encword(path) + ":" + encword(name) + ":" + + (hasMessagesFlag ? "M":"") + + (hasSubfoldersFlag ? "S":""); +} + + +mail::folder *mail::maildir::folderFromString(string folderName) +{ + string path=getword(folderName); + string name=getword(folderName); + + mail::maildir::folder *f=new mail::maildir::folder(this, path); + + if (!f) + return NULL; + + f->hasMessagesFlag= folderName.find('M') != std::string::npos; + f->hasSubfoldersFlag= folderName.find('S') != std::string::npos; + + return f; +} + +void mail::maildir::folder::open(mail::callback &openCallback, + mail::snapshot *restoreSnapshot, + mail::callback::folder &folderCallback) const +{ + if (isDestroyed(openCallback)) + return; + + maildirAccount->open(path, openCallback, folderCallback); +} + + +void mail::maildir::readTopLevelFolders(mail::callback::folderList &callback1, + mail::callback &callback2) +{ + mail::maildir::folder inbox(this, INBOX); + mail::maildir::folder folders(this, INBOX); + + inbox.hasSubfoldersFlag=false; + folders.hasMessagesFlag=false; + + vector<const mail::folder *> folderList; + + folderList.push_back(&inbox); + + if (!ispop3maildrop) + folderList.push_back(&folders); + + callback1.success(folderList); + callback2.success("OK"); +} |
