operator-list.cc (7575B)
1 /* -*- Mode: c++; -*- */ 2 /* -------------------------------------------------------------------- 3 * Filename: 4 * operator-list.cc 5 * 6 * Description: 7 * Implementation of the LIST command. 8 * 9 * Authors: 10 * Andreas Aardal Hanssen <andreas-binc curly bincimap spot org> 11 * 12 * Bugs: 13 * 14 * ChangeLog: 15 * 16 * -------------------------------------------------------------------- 17 * Copyright 2002-2005 Andreas Aardal Hanssen 18 * 19 * This program is free software; you can redistribute it and/or modify 20 * it under the terms of the GNU General Public License as published by 21 * the Free Software Foundation; either version 2 of the License, or 22 * (at your option) any later version. 23 * 24 * This program is distributed in the hope that it will be useful, 25 * but WITHOUT ANY WARRANTY; without even the implied warranty of 26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 27 * GNU General Public License for more details. 28 * 29 * You should have received a copy of the GNU General Public License 30 * along with this program; if not, write to the Free Software 31 * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. 32 * -------------------------------------------------------------------- 33 */ 34 #ifdef HAVE_CONFIG_H 35 #include <config.h> 36 #endif 37 38 #include <sys/types.h> 39 #include <dirent.h> 40 #include <sys/stat.h> 41 42 #include <string> 43 #include <iostream> 44 45 #include "io.h" 46 47 #include "mailbox.h" 48 49 #include "regmatch.h" 50 #include "convert.h" 51 52 #include "recursivedescent.h" 53 54 #include "session.h" 55 #include "depot.h" 56 #include "operators.h" 57 58 using namespace ::std; 59 using namespace Binc; 60 61 namespace { 62 const time_t LIST_CACHE_TIMEOUT = 10; 63 } 64 65 //---------------------------------------------------------------------- 66 ListOperator::ListOperator(void) 67 { 68 cacheTimeout = 0; 69 } 70 71 //---------------------------------------------------------------------- 72 ListOperator::~ListOperator(void) 73 { 74 } 75 76 //---------------------------------------------------------------------- 77 const string ListOperator::getName(void) const 78 { 79 return "LIST"; 80 } 81 82 //---------------------------------------------------------------------- 83 int ListOperator::getState(void) const 84 { 85 return Session::AUTHENTICATED | Session::SELECTED; 86 } 87 88 //------------------------------------------------------------------------ 89 Operator::ProcessResult ListOperator::process(Depot &depot, 90 Request &command) 91 { 92 IO &com = IOFactory::getInstance().get(1); 93 Session &session = Session::getInstance(); 94 const char delim = depot.getDelimiter(); 95 96 // special case: if the mailbox argument is empty, then give a 97 // hard coded reply. 98 string wildcard; 99 if ((wildcard = command.getListMailbox()) == "") { 100 com << "* LIST (\\Noselect) \"" << delim << "\" \"\"" << endl; 101 return OK; 102 } 103 104 // remove leading or trailing delimiter in wildcard 105 trim(wildcard, string(&delim, 1)); 106 107 // convert wildcard to regular expression 108 string regex = toRegex(wildcard, depot.getDelimiter()); 109 string wildcardLower = regex; 110 lowercase(wildcardLower); 111 if (wildcardLower.substr(0, 6) == "^inbox") 112 regex = "^[iI][nN][bB][oO][xX]" + regex.substr(6); 113 114 // remove leading or trailing delimiter in reference 115 string ref = command.getMailbox(); 116 trim(ref, string(&delim, 1)); 117 wildcardLower = ref; 118 lowercase(wildcardLower); 119 if (wildcardLower.substr(0, 5) == "inbox" 120 && (wildcardLower.length() == 5 || wildcardLower[5] == delim)) 121 ref = "INBOX" + ref.substr(5); 122 123 // a map from mailbox name to flags 124 map<string, unsigned int> mailboxes; 125 126 if (cacheTimeout == 0 || cacheTimeout < time(0) - LIST_CACHE_TIMEOUT 127 || session.mailboxchanges) { 128 session.mailboxchanges = false; 129 130 // read through all entries in depository. 131 for (Depot::iterator i = depot.begin("."); i != depot.end(); ++i) { 132 const string path = *i; 133 if (path == "") 134 continue; 135 136 const string mpath = depot.filenameToMailbox(path); 137 Mailbox *m = 0; 138 139 // skip entries that are not identified as mailboxes 140 if ((m = depot.get(mpath)) == 0) 141 continue; 142 143 // convert file name to mailbox name. skip it if there is no 144 // corresponding mailbox name. 145 string tmp = toCanonMailbox(depot.filenameToMailbox(path)); 146 trim(tmp, string(&delim, 1)); 147 if (tmp == "") continue; 148 else { 149 // inherit flags that were already set for this mailbox. 150 int flags = DIR_SELECT; 151 if (m->isMarked(path)) flags |= DIR_MARKED; 152 if (mailboxes.find(tmp) != mailboxes.end()) flags |= mailboxes[tmp]; 153 mailboxes[tmp] = flags; 154 } 155 156 // now add all superior mailboxes with no flags set if not 157 // added already. 158 string::size_type pos = tmp.rfind(delim); 159 while (pos != string::npos) { 160 tmp = tmp.substr(0, pos); 161 trim(tmp, string(&delim, 1)); 162 163 if (mailboxes.find(tmp) == mailboxes.end()) 164 mailboxes[tmp] = 0; 165 166 pos = tmp.rfind(delim); 167 } 168 } 169 170 // find leaf nodes O(N^2) 171 map<string, unsigned int>::iterator i; 172 for (i = mailboxes.begin(); i != mailboxes.end(); ++i) { 173 string mailbox = i->first; 174 mailbox += delim; 175 176 bool leaf = true; 177 map<string, unsigned int>::const_iterator j = mailboxes.begin(); 178 for (; j != mailboxes.end(); ++j) { 179 string::size_type pos = j->first.rfind(delim); 180 if (pos == string::npos) continue; 181 182 string base = j->first.substr(0, pos + 1); 183 184 if (mailbox == base) { 185 leaf = false; 186 break; 187 } 188 } 189 190 if (leaf) { 191 unsigned int flags = i->second; 192 flags |= DIR_LEAF; 193 i->second = flags; 194 } 195 } 196 197 cache = mailboxes; 198 cacheTimeout = time(0); 199 } else { 200 mailboxes = cache; 201 cacheTimeout = time(0); 202 } 203 204 // finally, print all mailbox entries with flags. 205 map<string, unsigned int>::iterator i = mailboxes.begin(); 206 for (; i != mailboxes.end(); ++i) { 207 if (ref == "" || (ref.length() <= i->first.length() && ref == i->first.substr(0, ref.length()))) 208 if (regexMatch(i->first.substr(ref.length()), regex) == 0) { 209 com << "* LIST ("; 210 string sep = ""; 211 212 int flags = i->second; 213 bool noselect = false; 214 215 if (!(flags & DIR_SELECT)) { 216 com << sep << "\\Noselect"; 217 sep = " "; 218 noselect = true; 219 } 220 221 if (!noselect) { 222 if (flags & DIR_MARKED) 223 com << sep << "\\Marked"; 224 else 225 com << sep << "\\Unmarked"; 226 sep = " "; 227 } 228 229 if (flags & DIR_NOINFERIORS) 230 com << sep << "\\Noinferiors"; 231 232 com << ") \"" << depot.getDelimiter() << "\" " 233 << toImapString(i->first) << endl; 234 } 235 } 236 237 return OK; 238 } 239 240 //---------------------------------------------------------------------- 241 Operator::ParseResult ListOperator::parse(Request &c_in) const 242 { 243 Session &session = Session::getInstance(); 244 245 if (c_in.getUidMode()) 246 return REJECT; 247 248 Operator::ParseResult res; 249 if ((res = expectSPACE()) != ACCEPT) { 250 session.setLastError("Expected SPACE after LIST"); 251 return res; 252 } 253 254 string mailbox; 255 if ((res = expectMailbox(mailbox)) != ACCEPT) { 256 session.setLastError("Expected mailbox after LIST SPACE"); 257 return res; 258 } 259 260 c_in.setMailbox(mailbox); 261 262 if ((res = expectSPACE()) != ACCEPT) { 263 session.setLastError("Expected SPACE after LIST SPACE mailbox"); 264 return res; 265 } 266 267 string listmailbox; 268 if ((res = expectListMailbox(listmailbox)) != ACCEPT) { 269 session.setLastError("Expected list_mailbox after LIST SPACE" 270 " mailbox SPACE"); 271 return res; 272 } 273 274 if ((res = expectCRLF()) != ACCEPT) { 275 session.setLastError("Expected CRLF after LIST SPACE mailbox" 276 " SPACE list_mailbox"); 277 return res; 278 } 279 280 c_in.setListMailbox(listmailbox); 281 c_in.setName("LIST"); 282 return ACCEPT; 283 }