/** * @file operator-append.cc * @brief Implementation of the APPEND command. * @author Andreas Aardal Hanssen * @date 2002-2005 */ #include "depot.h" #include "iodevice.h" #include "iofactory.h" #include "mailbox.h" #include "operators.h" #include "pendingupdates.h" #include "recursivedescent.h" #include "session.h" #include #include #include using namespace Binc; using std::string; AppendOperator::AppendOperator() {} AppendOperator::~AppendOperator() {} const string AppendOperator::getName() const { return "APPEND"; } Session::State AppendOperator::getState() const { return Session::State(Session::State::AUTHENTICATED | Session::State::SELECTED); } Operator::ProcessResult AppendOperator::process(Depot &depot, Request &command) { constexpr auto NO = ProcessResult::NO; constexpr auto ABORT = ProcessResult::ABORT; constexpr auto BAD = ProcessResult::BAD; Session &session = Session::getInstance(); const string &srcmailbox = command.getMailbox(); const string &canonmailbox = toCanonMailbox(srcmailbox); Mailbox *mailbox = nullptr; if ((mailbox = depot.get(canonmailbox)) == nullptr) { session.setResponseCode("TRYCREATE"); session.setLastError("invalid destination mailbox " + toImapString(srcmailbox)); return NO; } // mask all passed flags together unsigned int newflags = (unsigned int)Message::F_NONE; std::vector::const_iterator f_i = command.flags.begin(); while (f_i != command.flags.end()) { if (*f_i == "\\Deleted") newflags |= Message::F_DELETED; if (*f_i == "\\Answered") newflags |= Message::F_ANSWERED; if (*f_i == "\\Seen") newflags |= Message::F_SEEN; if (*f_i == "\\Draft") newflags |= Message::F_DRAFT; if (*f_i == "\\Flagged") newflags |= Message::F_FLAGGED; ++f_i; } int mday, year, hour, minute, second; char month[4]; struct tm mytm; if (command.getDate() != "") { sscanf(command.getDate().c_str(), "%2i-%3s-%4i %2i:%2i:%2i", &mday, month, &year, &hour, &minute, &second); month[3] = '\0'; string monthstr = month; lowercase(monthstr); mytm.tm_sec = second; mytm.tm_min = minute; mytm.tm_hour = hour; mytm.tm_year = year - 1900; mytm.tm_mday = mday; if (monthstr == "jan") mytm.tm_mon = 0; else if (monthstr == "feb") mytm.tm_mon = 1; else if (monthstr == "mar") mytm.tm_mon = 2; else if (monthstr == "apr") mytm.tm_mon = 3; else if (monthstr == "may") mytm.tm_mon = 4; else if (monthstr == "jun") mytm.tm_mon = 5; else if (monthstr == "jul") mytm.tm_mon = 6; else if (monthstr == "aug") mytm.tm_mon = 7; else if (monthstr == "sep") mytm.tm_mon = 8; else if (monthstr == "oct") mytm.tm_mon = 9; else if (monthstr == "nov") mytm.tm_mon = 10; else if (monthstr == "dec") mytm.tm_mon = 11; mytm.tm_isdst = -1; } // Read number of characters in literal. Literal is required here. char c; if (!bincClient.readChar(&c)) return ABORT; if (c != '{') { session.setLastError("expected literal"); return BAD; } string nr; bool literalPlus = false; while (1) { if (!bincClient.readChar(&c)) { session.setLastError("unexcepted EOF"); return BAD; } if (c == '}') break; // Support LITERAL+ if (c == '+' && !literalPlus) { literalPlus = true; continue; } if (!isdigit(c)) { session.setLastError("unexcepted non-digit character"); return BAD; } if (literalPlus) { session.setLastError("expected '}'"); return BAD; } nr += c; } int nchars = atoi(nr.c_str()); if (nchars < 0) { session.setLastError("expected positive size of appended message"); return BAD; } if (!bincClient.readChar(&c)) return ABORT; if (c != '\r') { session.setLastError("expected CR"); return BAD; } if (!bincClient.readChar(&c)) return ABORT; if (c != '\n') { session.setLastError("expected LF"); return BAD; } time_t newtime = (command.getDate() != "") ? mktime(&mytm) : time(nullptr); if (newtime == -1) newtime = time(nullptr); Message *dest = mailbox->createMessage(depot.mailboxToFilename(canonmailbox), newtime); if (!dest) { session.setLastError(mailbox->getLastError()); return NO; } if (!literalPlus) { bincClient << "+ go ahead with " << nchars << " characters" << std::endl; bincClient.flush(); } bincClient.clearFlags(IODevice::HasInputLimit); while (nchars > 0) { // Read in chunks of 8192, followed by an optional chunk at the // end which is < 8192 bytes. string s; int bytesToRead = nchars > 8192 ? 8192 : nchars; if (!bincClient.readStr(&s, bytesToRead)) { mailbox->rollBackNewMessages(); session.setLastError(bincClient.getLastErrorString()); return NO; } // Write the chunk to the message. if (!dest->appendChunk(s)) { mailbox->rollBackNewMessages(); session.setLastError(dest->getLastError()); return NO; } // Update the message count. nchars -= s.size(); } // Read the trailing CRLF after the message data. if (!bincClient.readChar(&c)) return ABORT; if (c != '\r') { mailbox->rollBackNewMessages(); session.setLastError("expected CR"); return BAD; } if (!bincClient.readChar(&c)) return ABORT; if (c != '\n') { mailbox->rollBackNewMessages(); session.setLastError("expected LF"); return BAD; } // Commit the message. dest->close(); dest->setStdFlag(newflags); dest->setInternalDate(mktime(&mytm)); if (!mailbox->commitNewMessages(depot.mailboxToFilename(canonmailbox))) { session.setLastError("failed to commit after successful APPEND: " + mailbox->getLastError()); return NO; } if (mailbox == depot.getSelected()) { pendingUpdates(mailbox, PendingUpdates::EXISTS | PendingUpdates::RECENT | PendingUpdates::FLAGS, true, false, true); } return ProcessResult::OK; } Parser::ParseResult AppendOperator::parse(Request &c_in) { constexpr auto ACCEPT = Parser::ParseResult::ACCEPT; Session &session = Session::getInstance(); Parser::ParseResult res; if (c_in.getUidMode()) return Parser::ParseResult::REJECT; if ((res = expectSPACE()) != ACCEPT) { session.setLastError("Expected SPACE after APPEND"); return res; } string mailbox; if ((res = expectMailbox(mailbox)) != ACCEPT) { session.setLastError("Expected mailbox after APPEND SPACE"); return res; } c_in.setMailbox(mailbox); if ((res = expectSPACE()) != ACCEPT) { session.setLastError("Expected SPACE after APPEND SPACE mailbox"); return res; } if ((res = expectThisString("(")) == ACCEPT) { if ((res = expectFlag(c_in.getFlags())) == ACCEPT) { while (1) { if ((res = expectSPACE()) != ACCEPT) break; if ((res = expectFlag(c_in.getFlags())) != ACCEPT) { session.setLastError("expected a flag after the '('"); return res; } } } if ((res = expectThisString(")")) != ACCEPT) { session.setLastError("expected a ')'"); return res; } if ((res = expectSPACE()) != ACCEPT) { session.setLastError("expected a SPACE after the flag list"); return res; } } string date; if ((res = expectDateTime(date)) == ACCEPT) { if ((res = expectSPACE()) != ACCEPT) { session.setLastError("expected a SPACE after date_time"); return res; } } c_in.setDate(date); c_in.setName("APPEND"); return ACCEPT; }