All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.angus.mail.imap.protocol.SearchSequence Maven / Gradle / Ivy

/*
 * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.eclipse.angus.mail.imap.protocol;

import jakarta.mail.Flags;
import jakarta.mail.Message;
import jakarta.mail.search.AddressTerm;
import jakarta.mail.search.AndTerm;
import jakarta.mail.search.BodyTerm;
import jakarta.mail.search.ComparisonTerm;
import jakarta.mail.search.DateTerm;
import jakarta.mail.search.FlagTerm;
import jakarta.mail.search.FromStringTerm;
import jakarta.mail.search.FromTerm;
import jakarta.mail.search.HeaderTerm;
import jakarta.mail.search.MessageIDTerm;
import jakarta.mail.search.NotTerm;
import jakarta.mail.search.OrTerm;
import jakarta.mail.search.ReceivedDateTerm;
import jakarta.mail.search.RecipientStringTerm;
import jakarta.mail.search.RecipientTerm;
import jakarta.mail.search.SearchException;
import jakarta.mail.search.SearchTerm;
import jakarta.mail.search.SentDateTerm;
import jakarta.mail.search.SizeTerm;
import jakarta.mail.search.StringTerm;
import jakarta.mail.search.SubjectTerm;
import org.eclipse.angus.mail.iap.Argument;
import org.eclipse.angus.mail.imap.ModifiedSinceTerm;
import org.eclipse.angus.mail.imap.OlderTerm;
import org.eclipse.angus.mail.imap.YoungerTerm;

import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

/**
 * This class traverses a search-tree and generates the
 * corresponding IMAP search sequence.
 *
 * Each IMAPProtocol instance contains an instance of this class,
 * which might be subclassed by subclasses of IMAPProtocol to add
 * support for additional product-specific search terms.
 *
 * @author John Mani
 * @author Bill Shannon
 */
public class SearchSequence {

    private IMAPProtocol protocol;    // for hasCapability checks; may be null

    /**
     * Create a SearchSequence for this IMAPProtocol.
     *
     * @param    p    the IMAPProtocol object for the server
     * @since JavaMail 1.6.0
     */
    public SearchSequence(IMAPProtocol p) {
        protocol = p;
    }

    /**
     * Create a SearchSequence.
     */
    @Deprecated
    public SearchSequence() {
    }

    /**
     * Generate the IMAP search sequence for the given search expression.
     *
     * @param    term    the search term
     * @param    charset    charset for the search
     * @return the SEARCH Argument
     * @exception SearchException    for failures
     * @exception IOException    for I/O errors
     */
    public Argument generateSequence(SearchTerm term, String charset)
            throws SearchException, IOException {
        /*
         * Call the appropriate handler depending on the type of
         * the search-term ...
         */
        if (term instanceof AndTerm)        // AND
            return and((AndTerm) term, charset);
        else if (term instanceof OrTerm)    // OR
            return or((OrTerm) term, charset);
        else if (term instanceof NotTerm)    // NOT
            return not((NotTerm) term, charset);
        else if (term instanceof HeaderTerm)    // HEADER
            return header((HeaderTerm) term, charset);
        else if (term instanceof FlagTerm)    // FLAG
            return flag((FlagTerm) term);
        else if (term instanceof FromTerm) {    // FROM
            FromTerm fterm = (FromTerm) term;
            return from(fterm.getAddress().toString(), charset);
        } else if (term instanceof FromStringTerm) { // FROM
            FromStringTerm fterm = (FromStringTerm) term;
            return from(fterm.getPattern(), charset);
        } else if (term instanceof RecipientTerm) { // RECIPIENT
            RecipientTerm rterm = (RecipientTerm) term;
            return recipient(rterm.getRecipientType(),
                    rterm.getAddress().toString(),
                    charset);
        } else if (term instanceof RecipientStringTerm) { // RECIPIENT
            RecipientStringTerm rterm = (RecipientStringTerm) term;
            return recipient(rterm.getRecipientType(),
                    rterm.getPattern(),
                    charset);
        } else if (term instanceof SubjectTerm)    // SUBJECT
            return subject((SubjectTerm) term, charset);
        else if (term instanceof BodyTerm)    // BODY
            return body((BodyTerm) term, charset);
        else if (term instanceof SizeTerm)    // SIZE
            return size((SizeTerm) term);
        else if (term instanceof SentDateTerm)    // SENTDATE
            return sentdate((SentDateTerm) term);
        else if (term instanceof ReceivedDateTerm) // INTERNALDATE
            return receiveddate((ReceivedDateTerm) term);
        else if (term instanceof OlderTerm)    // RFC 5032 OLDER
            return older((OlderTerm) term);
        else if (term instanceof YoungerTerm)    // RFC 5032 YOUNGER
            return younger((YoungerTerm) term);
        else if (term instanceof MessageIDTerm) // MessageID
            return messageid((MessageIDTerm) term, charset);
        else if (term instanceof ModifiedSinceTerm)    // RFC 4551 MODSEQ
            return modifiedSince((ModifiedSinceTerm) term);
        else
            throw new SearchException("Search too complex");
    }

    /**
     * Check if the "text" terms in the given SearchTerm contain
     * non US-ASCII characters.
     *
     * @param    term    the search term
     * @return true if only ASCII
     */
    public static boolean isAscii(SearchTerm term) {
        if (term instanceof AndTerm)
            return isAscii(((AndTerm) term).getTerms());
        else if (term instanceof OrTerm)
            return isAscii(((OrTerm) term).getTerms());
        else if (term instanceof NotTerm)
            return isAscii(((NotTerm) term).getTerm());
        else if (term instanceof StringTerm)
            return isAscii(((StringTerm) term).getPattern());
        else if (term instanceof AddressTerm)
            return isAscii(((AddressTerm) term).getAddress().toString());

        // Any other term returns true.
        return true;
    }

    /**
     * Check if any of the "text" terms in the given SearchTerms contain
     * non US-ASCII characters.
     *
     * @param    terms    the search terms
     * @return true if only ASCII
     */
    public static boolean isAscii(SearchTerm[] terms) {
        for (int i = 0; i < terms.length; i++)
            if (!isAscii(terms[i])) // outta here !
                return false;
        return true;
    }

    /**
     * Does this string contain only ASCII characters?
     *
     * @param    s    the string
     * @return true if only ASCII
     */
    public static boolean isAscii(String s) {
        int l = s.length();

        for (int i = 0; i < l; i++) {
            if ((int) s.charAt(i) > 0177) // non-ascii
                return false;
        }
        return true;
    }

    protected Argument and(AndTerm term, String charset)
            throws SearchException, IOException {
        // Combine the sequences for both terms
        SearchTerm[] terms = term.getTerms();
        // Generate the search sequence for the first term
        Argument result = generateSequence(terms[0], charset);
        // Append other terms
        for (int i = 1; i < terms.length; i++)
            result.append(generateSequence(terms[i], charset));
        return result;
    }

    protected Argument or(OrTerm term, String charset)
            throws SearchException, IOException {
        SearchTerm[] terms = term.getTerms();

        /* The IMAP OR operator takes only two operands. So if
         * we have more than 2 operands, group them into 2-operand
         * OR Terms.
         */
        if (terms.length > 2) {
            SearchTerm t = terms[0];

            // Include rest of the terms
            for (int i = 1; i < terms.length; i++)
                t = new OrTerm(t, terms[i]);

            term = (OrTerm) t;    // set 'term' to the new jumbo OrTerm we
            // just created
            terms = term.getTerms();
        }

        // 'term' now has only two operands
        Argument result = new Argument();

        // Add the OR search-key, if more than one term
        if (terms.length > 1)
            result.writeAtom("OR");

        /* If this term is an AND expression, we need to enclose it
         * within paranthesis.
         *
         * AND expressions are either AndTerms or FlagTerms
         */
        if (terms[0] instanceof AndTerm || terms[0] instanceof FlagTerm)
            result.writeArgument(generateSequence(terms[0], charset));
        else
            result.append(generateSequence(terms[0], charset));

        // Repeat the above for the second term, if there is one
        if (terms.length > 1) {
            if (terms[1] instanceof AndTerm || terms[1] instanceof FlagTerm)
                result.writeArgument(generateSequence(terms[1], charset));
            else
                result.append(generateSequence(terms[1], charset));
        }

        return result;
    }

    protected Argument not(NotTerm term, String charset)
            throws SearchException, IOException {
        Argument result = new Argument();

        // Add the NOT search-key
        result.writeAtom("NOT");

        /* If this term is an AND expression, we need to enclose it
         * within paranthesis.
         *
         * AND expressions are either AndTerms or FlagTerms
         */
        SearchTerm nterm = term.getTerm();
        if (nterm instanceof AndTerm || nterm instanceof FlagTerm)
            result.writeArgument(generateSequence(nterm, charset));
        else
            result.append(generateSequence(nterm, charset));

        return result;
    }

    protected Argument header(HeaderTerm term, String charset)
            throws SearchException, IOException {
        Argument result = new Argument();
        result.writeAtom("HEADER");
        result.writeString(term.getHeaderName());
        result.writeString(term.getPattern(), charset);
        return result;
    }

    protected Argument messageid(MessageIDTerm term, String charset)
            throws SearchException, IOException {
        Argument result = new Argument();
        result.writeAtom("HEADER");
        result.writeString("Message-ID");
        // XXX confirm that charset conversion ought to be done
        result.writeString(term.getPattern(), charset);
        return result;
    }

    protected Argument flag(FlagTerm term) throws SearchException {
        boolean set = term.getTestSet();

        Argument result = new Argument();

        Flags flags = term.getFlags();
        Flags.Flag[] sf = flags.getSystemFlags();
        String[] uf = flags.getUserFlags();
        if (sf.length == 0 && uf.length == 0)
            throw new SearchException("Invalid FlagTerm");

        for (int i = 0; i < sf.length; i++) {
            if (sf[i] == Flags.Flag.DELETED)
                result.writeAtom(set ? "DELETED" : "UNDELETED");
            else if (sf[i] == Flags.Flag.ANSWERED)
                result.writeAtom(set ? "ANSWERED" : "UNANSWERED");
            else if (sf[i] == Flags.Flag.DRAFT)
                result.writeAtom(set ? "DRAFT" : "UNDRAFT");
            else if (sf[i] == Flags.Flag.FLAGGED)
                result.writeAtom(set ? "FLAGGED" : "UNFLAGGED");
            else if (sf[i] == Flags.Flag.RECENT)
                result.writeAtom(set ? "RECENT" : "OLD");
            else if (sf[i] == Flags.Flag.SEEN)
                result.writeAtom(set ? "SEEN" : "UNSEEN");
        }

        for (int i = 0; i < uf.length; i++) {
            result.writeAtom(set ? "KEYWORD" : "UNKEYWORD");
            result.writeAtom(uf[i]);
        }

        return result;
    }

    protected Argument from(String address, String charset)
            throws SearchException, IOException {
        Argument result = new Argument();
        result.writeAtom("FROM");
        result.writeString(address, charset);
        return result;
    }

    protected Argument recipient(Message.RecipientType type,
                                 String address, String charset)
            throws SearchException, IOException {
        Argument result = new Argument();

        if (type == Message.RecipientType.TO)
            result.writeAtom("TO");
        else if (type == Message.RecipientType.CC)
            result.writeAtom("CC");
        else if (type == Message.RecipientType.BCC)
            result.writeAtom("BCC");
        else
            throw new SearchException("Illegal Recipient type");

        result.writeString(address, charset);
        return result;
    }

    protected Argument subject(SubjectTerm term, String charset)
            throws SearchException, IOException {
        Argument result = new Argument();

        result.writeAtom("SUBJECT");
        result.writeString(term.getPattern(), charset);
        return result;
    }

    protected Argument body(BodyTerm term, String charset)
            throws SearchException, IOException {
        Argument result = new Argument();

        result.writeAtom("BODY");
        result.writeString(term.getPattern(), charset);
        return result;
    }

    protected Argument size(SizeTerm term)
            throws SearchException {
        Argument result = new Argument();

        switch (term.getComparison()) {
            case ComparisonTerm.GT:
                result.writeAtom("LARGER");
                break;
            case ComparisonTerm.LT:
                result.writeAtom("SMALLER");
                break;
            default:
                // GT and LT is all we get from IMAP for size
                throw new SearchException("Cannot handle Comparison");
        }

        result.writeNumber(term.getNumber());
        return result;
    }

    // Date SEARCH stuff ...

    // NOTE: The built-in IMAP date comparisons are equivalent to
    //       "<" (BEFORE), "=" (ON), and ">=" (SINCE)!!!
    //       There is no built-in greater-than comparison!

    /**
     * Print an IMAP Date string, that is suitable for the Date
     * SEARCH commands.
     *
     * The IMAP Date string is :
     * date ::= date_day "-" date_month "-" date_year
     *
     * Note that this format does not contain the TimeZone
     */
    private static String[] monthTable = {
            "Jan", "Feb", "Mar", "Apr", "May", "Jun",
            "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    };

    // A GregorianCalendar object in the current timezone
    protected Calendar cal = new GregorianCalendar();

    protected String toIMAPDate(Date date) {
        StringBuilder s = new StringBuilder();

        cal.setTime(date);

        s.append(cal.get(Calendar.DATE)).append("-");
        s.append(monthTable[cal.get(Calendar.MONTH)]).append('-');
        s.append(cal.get(Calendar.YEAR));

        return s.toString();
    }

    protected Argument sentdate(DateTerm term)
            throws SearchException {
        Argument result = new Argument();
        String date = toIMAPDate(term.getDate());

        switch (term.getComparison()) {
            case ComparisonTerm.GT:
                result.writeAtom("NOT SENTON " + date + " SENTSINCE " + date);
                break;
            case ComparisonTerm.EQ:
                result.writeAtom("SENTON " + date);
                break;
            case ComparisonTerm.LT:
                result.writeAtom("SENTBEFORE " + date);
                break;
            case ComparisonTerm.GE:
                result.writeAtom("SENTSINCE " + date);
                break;
            case ComparisonTerm.LE:
                result.writeAtom("OR SENTBEFORE " + date + " SENTON " + date);
                break;
            case ComparisonTerm.NE:
                result.writeAtom("NOT SENTON " + date);
                break;
            default:
                throw new SearchException("Cannot handle Date Comparison");
        }

        return result;
    }

    protected Argument receiveddate(DateTerm term)
            throws SearchException {
        Argument result = new Argument();
        String date = toIMAPDate(term.getDate());

        switch (term.getComparison()) {
            case ComparisonTerm.GT:
                result.writeAtom("NOT ON " + date + " SINCE " + date);
                break;
            case ComparisonTerm.EQ:
                result.writeAtom("ON " + date);
                break;
            case ComparisonTerm.LT:
                result.writeAtom("BEFORE " + date);
                break;
            case ComparisonTerm.GE:
                result.writeAtom("SINCE " + date);
                break;
            case ComparisonTerm.LE:
                result.writeAtom("OR BEFORE " + date + " ON " + date);
                break;
            case ComparisonTerm.NE:
                result.writeAtom("NOT ON " + date);
                break;
            default:
                throw new SearchException("Cannot handle Date Comparison");
        }

        return result;
    }

    /**
     * Generate argument for OlderTerm.
     *
     * @param    term    the search term
     * @return the SEARCH Argument
     * @exception SearchException    for failures
     * @since JavaMail 1.5.1
     */
    protected Argument older(OlderTerm term) throws SearchException {
        if (protocol != null && !protocol.hasCapability("WITHIN"))
            throw new SearchException("Server doesn't support OLDER searches");
        Argument result = new Argument();
        result.writeAtom("OLDER");
        result.writeNumber(term.getInterval());
        return result;
    }

    /**
     * Generate argument for YoungerTerm.
     *
     * @param    term    the search term
     * @return the SEARCH Argument
     * @exception SearchException    for failures
     * @since JavaMail 1.5.1
     */
    protected Argument younger(YoungerTerm term) throws SearchException {
        if (protocol != null && !protocol.hasCapability("WITHIN"))
            throw new SearchException("Server doesn't support YOUNGER searches");
        Argument result = new Argument();
        result.writeAtom("YOUNGER");
        result.writeNumber(term.getInterval());
        return result;
    }

    /**
     * Generate argument for ModifiedSinceTerm.
     *
     * @param    term    the search term
     * @return the SEARCH Argument
     * @exception SearchException    for failures
     * @since JavaMail 1.5.1
     */
    protected Argument modifiedSince(ModifiedSinceTerm term)
            throws SearchException {
        if (protocol != null && !protocol.hasCapability("CONDSTORE"))
            throw new SearchException("Server doesn't support MODSEQ searches");
        Argument result = new Argument();
        result.writeAtom("MODSEQ");
        result.writeNumber(term.getModSeq());
        return result;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy