1 /* -*- Mode: c++; -*- */
2 /*  --------------------------------------------------------------------
3  *  Filename:
4  *    operator-append.cc
5  *
6  *  Description:
7  *    Implementation of the APPEND 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 <algorithm>
39 #include <string>
40 
41 #include <fcntl.h>
42 
43 #include "depot.h"
44 #include "io.h"
45 #include "mailbox.h"
46 #include "operators.h"
47 #include "recursivedescent.h"
48 #include "pendingupdates.h"
49 #include "session.h"
50 
51 using namespace ::std;
52 using namespace Binc;
53 
54 //----------------------------------------------------------------------
AppendOperator(void)55 AppendOperator::AppendOperator(void)
56 {
57 }
58 
59 //----------------------------------------------------------------------
~AppendOperator(void)60 AppendOperator::~AppendOperator(void)
61 {
62 }
63 
64 //----------------------------------------------------------------------
getName(void) const65 const string AppendOperator::getName(void) const
66 {
67   return "APPEND";
68 }
69 
70 //----------------------------------------------------------------------
getState(void) const71 int AppendOperator::getState(void) const
72 {
73   return Session::AUTHENTICATED | Session::SELECTED;
74 }
75 
76 //------------------------------------------------------------------------
process(Depot & depot,Request & command)77 Operator::ProcessResult AppendOperator::process(Depot &depot,
78 						Request &command)
79 {
80   IO &com = IOFactory::getInstance().get(1);
81 
82   Session &session = Session::getInstance();
83 
84   const string &srcmailbox = command.getMailbox();
85   const string &canonmailbox = toCanonMailbox(srcmailbox);
86   Mailbox *mailbox = 0;
87 
88   if ((mailbox = depot.get(canonmailbox)) == 0) {
89     session.setResponseCode("TRYCREATE");
90     session.setLastError("invalid destination mailbox "
91 			 + toImapString(srcmailbox));
92     return NO;
93   }
94 
95   // mask all passed flags together
96   unsigned int newflags = (unsigned int) Message::F_NONE;
97   vector<string>::const_iterator f_i = command.flags.begin();
98   while (f_i != command.flags.end()) {
99     if (*f_i == "\\Deleted") newflags |= Message::F_DELETED;
100     if (*f_i == "\\Answered") newflags |= Message::F_ANSWERED;
101     if (*f_i == "\\Seen") newflags |= Message::F_SEEN;
102     if (*f_i == "\\Draft") newflags |= Message::F_DRAFT;
103     if (*f_i == "\\Flagged") newflags |= Message::F_FLAGGED;
104     ++f_i;
105   }
106 
107   int mday, year, hour, minute, second;
108   char month[4];
109 
110   struct tm mytm;
111   if (command.getDate() != "") {
112     sscanf(command.getDate().c_str(), "%2i-%3s-%4i %2i:%2i:%2i",
113 	   &mday, month, &year, &hour, &minute, &second);
114 
115     month[3] = '\0';
116     string monthstr = month;
117     lowercase(monthstr);
118     mytm.tm_sec = second;
119     mytm.tm_min = minute;
120     mytm.tm_hour = hour;
121     mytm.tm_year = year - 1900;
122     mytm.tm_mday = mday;
123     if (monthstr == "jan") mytm.tm_mon = 0;
124     else if (monthstr == "feb") mytm.tm_mon = 1;
125     else if (monthstr == "mar") mytm.tm_mon = 2;
126     else if (monthstr == "apr") mytm.tm_mon = 3;
127     else if (monthstr == "may") mytm.tm_mon = 4;
128     else if (monthstr == "jun") mytm.tm_mon = 5;
129     else if (monthstr == "jul") mytm.tm_mon = 6;
130     else if (monthstr == "aug") mytm.tm_mon = 7;
131     else if (monthstr == "sep") mytm.tm_mon = 8;
132     else if (monthstr == "oct") mytm.tm_mon = 9;
133     else if (monthstr == "nov") mytm.tm_mon = 10;
134     else if (monthstr == "dec") mytm.tm_mon = 11;
135     mytm.tm_isdst = -1;
136   }
137 
138   // Read number of characters in literal. Literal is required here.
139   if (com.readChar() != '{') {
140     session.setLastError("expected literal");
141     return BAD;
142   }
143 
144   string nr;
145   while (1) {
146     int c = com.readChar();
147     if (c == -1) {
148       session.setLastError("unexcepted EOF");
149       return BAD;
150     }
151 
152     if (c == '}')
153       break;
154     nr += (char) c;
155   }
156 
157   int nchars = atoi(nr.c_str());
158   if (nchars < 0) {
159     session.setLastError("expected positive size of appended message");
160     return BAD;
161   }
162 
163   if (com.readChar() != '\r') {
164     session.setLastError("expected CR");
165     return BAD;
166   }
167 
168   if (com.readChar() != '\n') {
169     session.setLastError("expected LF");
170     return BAD;
171   }
172 
173   time_t newtime = (command.getDate() != "") ? mktime(&mytm) : time(0);
174   if (newtime == -1) newtime = time(0);
175   Message *dest = mailbox->createMessage(depot.mailboxToFilename(canonmailbox),
176 					 newtime);
177   if (!dest) {
178     session.setLastError(mailbox->getLastError());
179     return NO;
180   }
181 
182   com << "+ go ahead with " << nchars << " characters" << endl;
183   com.flushContent();
184   com.disableInputLimit();
185 
186   while (nchars > 0) {
187     // Read in chunks of 8192, followed by an optional chunk at the
188     // end which is < 8192 bytes.
189     string s;
190     int bytesToRead = nchars > 8192 ? 8192 : nchars;
191     int readBytes = com.readStr(s, bytesToRead);
192     if (readBytes <= 0) {
193       mailbox->rollBackNewMessages();
194       session.setLastError(com.getLastError());
195       return NO;
196     }
197 
198     // Expect the exact number of bytes from readStr.
199     if (readBytes != bytesToRead) {
200       mailbox->rollBackNewMessages();
201       session.setLastError("expected " + toString(nchars)
202 			   + " bytes, but got " + toString(readBytes));
203       return NO;
204     }
205 
206     // Write the chunk to the message.
207     if (!dest->appendChunk(s)) {
208       mailbox->rollBackNewMessages();
209       session.setLastError(dest->getLastError());
210       return NO;
211     }
212 
213     // Update the message count.
214     nchars -= readBytes;
215   }
216 
217   // Read the trailing CRLF after the message data.
218   if (com.readChar() != '\r') {
219     mailbox->rollBackNewMessages();
220     session.setLastError("expected CR");
221     return BAD;
222   }
223 
224   if (com.readChar() != '\n') {
225     mailbox->rollBackNewMessages();
226     session.setLastError("expected LF");
227     return BAD;
228   }
229 
230   // Commit the message.
231   dest->close();
232   dest->setStdFlag(newflags);
233   dest->setInternalDate(mktime(&mytm));
234 
235   if (!mailbox->commitNewMessages(depot.mailboxToFilename(canonmailbox))) {
236     session.setLastError("failed to commit after successful APPEND: "
237 			 + mailbox->getLastError());
238     return NO;
239   }
240 
241   if (mailbox == depot.getSelected()) {
242     pendingUpdates(mailbox, PendingUpdates::EXISTS
243 		   | PendingUpdates::RECENT
244 		   | PendingUpdates::FLAGS, true, false, true);
245   }
246 
247   return OK;
248 }
249 
250 //----------------------------------------------------------------------
parse(Request & c_in) const251 Operator::ParseResult AppendOperator::parse(Request &c_in) const
252 {
253   Session &session = Session::getInstance();
254   Operator::ParseResult res;
255 
256   if (c_in.getUidMode())
257     return REJECT;
258 
259   if ((res = expectSPACE()) != ACCEPT) {
260     session.setLastError("Expected SPACE after APPEND");
261     return res;
262   }
263 
264   string mailbox;
265   if ((res = expectMailbox(mailbox)) != ACCEPT) {
266     session.setLastError("Expected mailbox after APPEND SPACE");
267     return res;
268   }
269 
270   c_in.setMailbox(mailbox);
271 
272   if ((res = expectSPACE()) != ACCEPT) {
273     session.setLastError("Expected SPACE after APPEND SPACE mailbox");
274     return res;
275   }
276 
277   if ((res = expectThisString("(")) == ACCEPT) {
278     if ((res = expectFlag(c_in.getFlags())) == ACCEPT)
279       while (1) {
280 	if ((res = expectSPACE()) != ACCEPT)
281 	  break;
282 	if ((res = expectFlag(c_in.getFlags())) != ACCEPT) {
283 	  session.setLastError("expected a flag after the '('");
284 	  return res;
285 	}
286       }
287 
288     if ((res = expectThisString(")")) != ACCEPT) {
289       session.setLastError("expected a ')'");
290       return res;
291     }
292 
293     if ((res = expectSPACE()) != ACCEPT) {
294       session.setLastError("expected a SPACE after the flag list");
295       return res;
296     }
297   }
298 
299   string date;
300   if ((res = expectDateTime(date)) == ACCEPT)
301     if ((res = expectSPACE()) != ACCEPT) {
302       session.setLastError("expected a SPACE after date_time");
303       return res;
304     }
305 
306   c_in.setDate(date);
307   c_in.setName("APPEND");
308   return ACCEPT;
309 }
310