/** * @see * org.apache.james.imap.processor.AbstractMailboxProcessor#doProcess(org.apache.james.imap.api.message.request.ImapRequest, * org.apache.james.imap.api.process.ImapSession, java.lang.String, * org.apache.james.imap.api.ImapCommand, * org.apache.james.imap.api.process.ImapProcessor.Responder) */ protected void doProcess( M request, ImapSession session, String tag, ImapCommand command, Responder responder) { final String mailboxName = request.getMailboxName(); try { final MailboxPath fullMailboxPath = PathConverter.forSession(session).buildFullPath(mailboxName); respond(tag, command, session, fullMailboxPath, request, responder); } catch (MailboxNotFoundException e) { session.getLog().debug("Select failed as mailbox does not exist " + mailboxName, e); responder.respond( statusResponseFactory.taggedNo(tag, command, HumanReadableText.FAILURE_NO_SUCH_MAILBOX)); } catch (MailboxException e) { session.getLog().info("Select failed for mailbox " + mailboxName, e); no(command, tag, responder, HumanReadableText.SELECT); } }
@Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { ImapSession session = (ImapSession) attributes.get(ctx.getChannel()); ImapResponseComposer response = (ImapResponseComposer) ctx.getAttachment(); ImapMessage message = (ImapMessage) e.getMessage(); ChannelPipeline cp = ctx.getPipeline(); try { if (cp.get(NettyConstants.EXECUTION_HANDLER) != null) { cp.addBefore( NettyConstants.EXECUTION_HANDLER, NettyConstants.HEARTBEAT_HANDLER, heartbeatHandler); } else { cp.addBefore( NettyConstants.CORE_HANDLER, NettyConstants.HEARTBEAT_HANDLER, heartbeatHandler); } final ResponseEncoder responseEncoder = new ResponseEncoder(encoder, response, session); processor.process(message, responseEncoder, session); if (session.getState() == ImapSessionState.LOGOUT) { // Make sure we close the channel after all the buffers were flushed out Channel channel = ctx.getChannel(); if (channel.isConnected()) { channel.write(ChannelBuffers.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); } } final IOException failure = responseEncoder.getFailure(); if (failure != null) { final Logger logger = session.getLog(); logger.info(failure.getMessage()); if (logger.isDebugEnabled()) { logger.debug("Failed to write " + message, failure); } throw failure; } } finally { ctx.getPipeline().remove(NettyConstants.HEARTBEAT_HANDLER); } super.messageReceived(ctx, e); }
private void respond( String tag, ImapCommand command, ImapSession session, MailboxPath fullMailboxPath, AbstractMailboxSelectionRequest request, Responder responder) throws MailboxException, MessageRangeException { Long lastKnownUidValidity = request.getLastKnownUidValidity(); Long modSeq = request.getKnownModSeq(); IdRange[] knownSequences = request.getKnownSequenceSet(); UidRange[] knownUids = request.getKnownUidSet(); // Check if a QRESYNC parameter was used and if so if QRESYNC was enabled before. // If it was not enabled before its needed to return a BAD response // // From RFC5162 3.1. QRESYNC Parameter to SELECT/EXAMINE // // A server MUST respond with a tagged BAD response if the Quick // Resynchronization parameter to SELECT/EXAMINE command is specified // and the client hasn't issued "ENABLE QRESYNC" in the current // connection. if (lastKnownUidValidity != null && !EnableProcessor.getEnabledCapabilities(session) .contains(ImapConstants.SUPPORTS_QRESYNC)) { taggedBad(command, tag, responder, HumanReadableText.QRESYNC_NOT_ENABLED); return; } final MessageManager.MetaData metaData = selectMailbox(fullMailboxPath, session); final SelectedMailbox selected = session.getSelected(); MessageUid firstUnseen = metaData.getFirstUnseen(); flags(responder, selected); exists(responder, metaData); recent(responder, selected); uidValidity(responder, metaData); // try to write the UNSEEN message to the client and retry if we fail because of concurrent // sessions. // // See IMAP-345 int retryCount = 0; while (unseen(responder, firstUnseen, selected, ImapSessionUtils.getMailboxSession(session)) == false) { // if we not was able to get find the unseen within 5 retries we should just not send it if (retryCount == 5) { if (session.getLog().isInfoEnabled()) { session .getLog() .info( "Unable to uid for unseen message " + firstUnseen + " in mailbox " + selected.getPath()); } break; } firstUnseen = selectMailbox(fullMailboxPath, session).getFirstUnseen(); retryCount++; } permanentFlags(responder, metaData, selected); highestModSeq(responder, metaData, selected); uidNext(responder, metaData); if (request.getCondstore()) { condstoreEnablingCommand(session, responder, metaData, false); } // Now do the QRESYNC processing if necessary // // If the mailbox does not store the mod-sequence in a permanent way its needed to not process // the QRESYNC paramters // The same is true if none are given ;) if (metaData.isModSeqPermanent() && lastKnownUidValidity != null) { if (lastKnownUidValidity == metaData.getUidValidity()) { final MailboxManager mailboxManager = getMailboxManager(); final MailboxSession mailboxSession = ImapSessionUtils.getMailboxSession(session); final MessageManager mailbox = mailboxManager.getMailbox(fullMailboxPath, mailboxSession); // If the provided UIDVALIDITY matches that of the selected mailbox, the // server then checks the last known modification sequence. // // The server sends the client any pending flag changes (using FETCH // responses that MUST contain UIDs) and expunges those that have // occurred in this mailbox since the provided modification sequence. SearchQuery sq = new SearchQuery(); sq.andCriteria(SearchQuery.modSeqGreaterThan(request.getKnownModSeq())); UidRange[] uidSet = request.getUidSet(); if (uidSet == null) { // See mailbox had some messages stored before, if not we don't need to query at all MessageUid uidNext = metaData.getUidNext(); if (!uidNext.isFirst()) { // Use UIDNEXT -1 as max uid as stated in the QRESYNC RFC uidSet = new UidRange[] {new UidRange(MessageUid.MIN_VALUE, uidNext.previous())}; } } if (uidSet != null) { // RFC5162 3.1. QRESYNC Parameter to SELECT/EXAMINE // // Message sequence match data: // // A client MAY provide a parenthesized list of a message sequence set // and the corresponding UID sets. Both MUST be provided in ascending // order. The server uses this data to restrict the range for which it // provides expunged message information. // // // Conceptually, the client provides a small sample of sequence numbers // for which it knows the corresponding UIDs. The server then compares // each sequence number and UID pair the client provides with the // current state of the mailbox. If a pair matches, then the client // knows of any expunges up to, and including, the message, and thus // will not include that range in the VANISHED response, even if the // "mod-sequence-value" provided by the client is too old for the server // to have data of when those messages were expunged. // // Thus, if the Nth message number in the first set in the list is 4, // and the Nth UID in the second set in the list is 8, and the mailbox's // fourth message has UID 8, then no UIDs equal to or less than 8 are // present in the VANISHED response. If the (N+1)th message number is // 12, and the (N+1)th UID is 24, and the (N+1)th message in the mailbox // has UID 25, then the lowest UID included in the VANISHED response // would be 9. if (knownSequences != null && knownUids != null) { // Add all uids which are contained in the knownuidsset to a List so we can later access // them via the index List<MessageUid> knownUidsList = new ArrayList<MessageUid>(); for (UidRange range : knownUids) { for (MessageUid uid : range) { knownUidsList.add(uid); } } // loop over the known sequences and check the UID for MSN X again the known UID X MessageUid firstUid = MessageUid.MIN_VALUE; int index = 0; for (IdRange knownSequence : knownSequences) { boolean done = false; for (Long uid : knownSequence) { // Check if we have uids left to check against if (knownUidsList.size() > index++) { int msn = uid.intValue(); MessageUid knownUid = knownUidsList.get(index); // Check if the uid mathc if not we are done here if (selected.uid(msn).asSet().contains(knownUid)) { done = true; break; } else { firstUid = knownUid; } } else { done = true; break; } } // We found the first uid to start with if (done) { firstUid = firstUid.next(); // Ok now its time to filter out the IdRanges which we are not interested in List<UidRange> filteredUidSet = new ArrayList<UidRange>(); for (UidRange r : uidSet) { if (r.getLowVal().compareTo(firstUid) < 0) { if (r.getHighVal().compareTo(firstUid) > 0) { filteredUidSet.add(new UidRange(firstUid, r.getHighVal())); } } else { filteredUidSet.add(r); } } uidSet = filteredUidSet.toArray(new UidRange[0]); break; } } } List<MessageRange> ranges = new ArrayList<MessageRange>(); for (UidRange range : uidSet) { MessageRange messageSet = range.toMessageRange(); if (messageSet != null) { MessageRange normalizedMessageSet = normalizeMessageRange(session.getSelected(), messageSet); ranges.add(normalizedMessageSet); } } // TODO: Reconsider if we can do something to make the handling better. Maybe at least // cache the triplets for the expunged // while have the server running. This could maybe allow us to not return every // expunged message all the time // // As we don't store the <<MSN, UID>, <MODSEQ>> in a permanent way its the best to // just ignore it here. // // From RFC5162 4.1. Server Implementations That Don't Store Extra State // // // Strictly speaking, a server implementation that doesn't remember mod- // sequences associated with expunged messages can be considered // compliant with this specification. Such implementations return all // expunged messages specified in the UID set of the UID FETCH // (VANISHED) command every time, without paying attention to the // specified CHANGEDSINCE mod-sequence. Such implementations are // discouraged, as they can end up returning VANISHED responses that are // bigger than the result of a UID SEARCH command for the same UID set. // // Clients that use the message sequence match data can reduce the scope // of this VANISHED response substantially in the typical case where // expunges have not happened, or happen only toward the end of the // mailbox. // respondVanished(mailboxSession, mailbox, ranges, modSeq, metaData, responder); } taggedOk(responder, tag, command, metaData, HumanReadableText.SELECT); } else { taggedOk(responder, tag, command, metaData, HumanReadableText.QRESYNC_UIDVALIDITY_MISMATCH); } } else { taggedOk(responder, tag, command, metaData, HumanReadableText.SELECT); } // Reset the saved sequence-set after successful SELECT / EXAMINE // See RFC 5812 2.1. Normative Description of the SEARCHRES Extension SearchResUtil.resetSavedSequenceSet(session); }