/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2012 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package com.sun.mail.imap.protocol; import java.util.*; import java.io.IOException; import javax.mail.*; import javax.mail.search.*; import com.sun.mail.iap.*; /** * 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 */ public class SearchSequence { /** * Generate the IMAP search sequence for the given search expression. */ 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 MessageIDTerm) // MessageID return messageid((MessageIDTerm)term, charset); else throw new SearchException("Search too complex"); } /* * * Check if the "text" terms in the given SearchTerm contain * non US-ASCII characters. */ public static boolean isAscii(SearchTerm term) { if (term instanceof AndTerm || term instanceof OrTerm) { SearchTerm[] terms; if (term instanceof AndTerm) terms = ((AndTerm)term).getTerms(); else terms = ((OrTerm)term).getTerms(); for (int i = 0; i < terms.length; i++) if (!isAscii(terms[i])) // outta here ! return false; } 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; } /** * Does this string contain only ASCII characters? */ 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 ... /** * 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) { StringBuffer s = new StringBuffer(); 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("SENTSINCE " + date); break; case ComparisonTerm.EQ: result.writeAtom("SENTON " + date); break; case ComparisonTerm.LT: result.writeAtom("SENTBEFORE " + date); break; case ComparisonTerm.GE: result.writeAtom("OR SENTSINCE " + date + " SENTON " + 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("SINCE " + date); break; case ComparisonTerm.EQ: result.writeAtom("ON " + date); break; case ComparisonTerm.LT: result.writeAtom("BEFORE " + date); break; case ComparisonTerm.GE: result.writeAtom("OR SINCE " + date + " ON " + 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; } }