View Javadoc

1   /*
2    * Copyright (c) 2004-2007 Creative Sphere Limited.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *
10   *   Creative Sphere - initial API and implementation
11   *
12   */
13  package org.abstracthorizon.mercury.imap.cmd;
14  
15  import java.io.IOException;
16  import java.util.ArrayList;
17  import java.util.GregorianCalendar;
18  import javax.mail.Flags;
19  import javax.mail.Folder;
20  import javax.mail.Message;
21  import javax.mail.MessagingException;
22  import javax.mail.UIDFolder;
23  import javax.mail.internet.MimeMessage;
24  import javax.mail.internet.MimePart;
25  import javax.mail.search.AndTerm;
26  import javax.mail.search.BodyTerm;
27  import javax.mail.search.ComparisonTerm;
28  import javax.mail.search.FlagTerm;
29  import javax.mail.search.FromStringTerm;
30  import javax.mail.search.HeaderTerm;
31  import javax.mail.search.NotTerm;
32  import javax.mail.search.OrTerm;
33  import javax.mail.search.ReceivedDateTerm;
34  import javax.mail.search.RecipientStringTerm;
35  import javax.mail.search.SearchTerm;
36  import javax.mail.search.SentDateTerm;
37  import javax.mail.search.SizeTerm;
38  import javax.mail.search.StringTerm;
39  import javax.mail.search.SubjectTerm;
40  
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  import org.abstracthorizon.mercury.common.command.CommandException;
45  import org.abstracthorizon.mercury.imap.IMAPSession;
46  import org.abstracthorizon.mercury.imap.response.SearchResponse;
47  import org.abstracthorizon.mercury.imap.util.ComposedSequence;
48  import org.abstracthorizon.mercury.imap.util.MessageUtilities;
49  import org.abstracthorizon.mercury.imap.util.ParserException;
50  import org.abstracthorizon.mercury.imap.util.IMAPScanner;
51  
52  /**
53   * Search IMAP command
54   *
55   * @author Daniel Sendula
56   */
57  public class Search extends UIDCommand {
58  
59      public static final Flags ANSWERED = new Flags(Flags.Flag.ANSWERED);
60      public static final Flags DELETED = new Flags(Flags.Flag.DELETED);
61      public static final Flags DRAFT = new Flags(Flags.Flag.DRAFT);
62      public static final Flags SEEN = new Flags(Flags.Flag.SEEN);
63      public static final Flags RECENT = new Flags(Flags.Flag.RECENT);
64      public static final Flags FLAGGED = new Flags(Flags.Flag.FLAGGED);
65  
66      /** Runs as UID */
67      protected boolean asuid = false;
68  
69      /** Charset */
70      protected StringBuffer charset = new StringBuffer();
71  
72      /** Logger */
73      protected static final Logger logger = LoggerFactory.getLogger(Search.class);
74  
75      /**
76       * Constructor
77       * @param mnemonic mnemonic
78       */
79      public Search(String mnemonic) {
80          super(mnemonic);
81      }
82  
83      /**
84       * Marks that command runs as UID command
85       */
86      public void setAsUID() {
87          asuid = true;
88      }
89  
90      /**
91       * Executes the command
92       * @param session
93       * @throws ParserException
94       * @throws MessagingException
95       * @throws CommandException
96       * @throws IOException
97       */
98      protected void execute(IMAPSession session) throws ParserException, MessagingException, CommandException, IOException {
99          IMAPScanner scanner = session.getScanner();
100         SearchTerm term = search(scanner);
101 
102         Folder f = session.getSelectedFolder();
103         Message[] ms = f.search(term);
104 
105         checkEOL(session);
106 
107         SearchResponse response = new SearchResponse(session);
108         if (ms.length > 0) {
109             for (int i=0; i<ms.length; i++) {
110                 if (asuid) {
111                     response.addMessageNumber(MessageUtilities.findUID(ms[i]));
112                 } else {
113                     response.addMessageNumber(ms[i].getMessageNumber());
114                 }
115             }
116         }
117         response.submit();
118         sendOK(session);
119     }
120 
121     public void process(IMAPSession session, MimeMessage m) throws MessagingException {
122     }
123 
124 
125     public SearchTerm search(IMAPScanner scanner) throws IOException, ParserException, MessagingException {
126         //search          = "SEARCH" [SP "CHARSET" SP astring] 1*(SP search-key)
127         //                    ; CHARSET argument to MUST be registered with IANA
128 
129         charset.delete(0, charset.length());
130         if (scanner.keyword("CHARSET")) {
131             if (!scanner.is_char(' ')) {
132                 throw new ParserException("<SP>");
133             }
134             if (!scanner.astring(charset)) {
135                 throw new ParserException("<charset>");
136             }
137         }
138 
139         ArrayList<SearchTerm> list = new ArrayList<SearchTerm>();
140         list.add(searchKey(scanner));
141         while (scanner.is_char(' ')) {
142             list.add(searchKey(scanner));
143         }
144         if (list.size() == 1) {
145             return (SearchTerm)list.get(0);
146         } else {
147             SearchTerm[] sts = new SearchTerm[list.size()];
148             sts = list.toArray(sts);
149             return new AndTerm(sts);
150         }
151     }
152 
153     protected SearchTerm searchKey(IMAPScanner scanner) throws IOException, ParserException, MessagingException {
154         //search-key      = "ALL" / "ANSWERED" / "BCC" SP astring /
155         //                  "BEFORE" SP date / "BODY" SP astring /
156         //                  "CC" SP astring / "DELETED" / "FLAGGED" /
157         //                  "FROM" SP astring / "KEYWORD" SP flag-keyword /
158         //                  "NEW" / "OLD" / "ON" SP date / "RECENT" / "SEEN" /
159         //                  "SINCE" SP date / "SUBJECT" SP astring /
160         //                  "TEXT" SP astring / "TO" SP astring /
161         //                  "UNANSWERED" / "UNDELETED" / "UNFLAGGED" /
162         //                  "UNKEYWORD" SP flag-keyword / "UNSEEN" /
163         //                    ; Above this line were in [IMAP2]
164         //                  "DRAFT" / "HEADER" SP header-fld-name SP astring /
165         //                  "LARGER" SP number / "NOT" SP search-key /
166         //                  "OR" SP search-key SP search-key /
167         //                  "SENTBEFORE" SP date / "SENTON" SP date /
168         //                  "SENTSINCE" SP date / "SMALLER" SP number /
169         //                  "UID" SP sequence-set / "UNDRAFT" / sequence-set /
170         //                  "(" search-key *(SP search-key) ")"
171 
172         if (scanner.is_char('(')) {
173             ArrayList<SearchTerm> list = new ArrayList<SearchTerm>();
174             list.add(search(scanner));
175             while (scanner.is_char(' ')) {
176                 list.add(search(scanner));
177             }
178             if (!scanner.is_char(')')) {
179                 throw new ParserException("')'");
180             }
181             if (list.size() == 1) {
182                 return (SearchTerm)list.get(0);
183             } else {
184                 SearchTerm[] sts = new SearchTerm[list.size()];
185                 sts = (SearchTerm[])list.toArray(sts);
186                 return new AndTerm(sts);
187             }
188         } else if (scanner.keyword("OR")) {
189             if (!scanner.is_char(' ')) {
190                 throw new ParserException("<SP>");
191             }
192             SearchTerm t1 = searchKey(scanner);
193             if (!scanner.is_char(' ')) {
194                 throw new ParserException("<SP>");
195             }
196             SearchTerm t2 = searchKey(scanner);
197             return new OrTerm(t1, t2);
198         } else if (scanner.keyword("OR")) {
199             if (!scanner.is_char(' ')) {
200                 throw new ParserException("<SP>");
201             }
202             SearchTerm t1 = searchKey(scanner);
203             return new NotTerm(t1);
204         } else if (scanner.keyword("ANSWERED")) {
205             return new FlagTerm(ANSWERED, true);
206         } else if (scanner.keyword("UNANSWERED")) {
207             return new FlagTerm(ANSWERED, false);
208         } else if (scanner.keyword("DRAFT")) {
209             return new FlagTerm(DRAFT, true);
210         } else if (scanner.keyword("UNDRAFT")) {
211             return new FlagTerm(DRAFT, false);
212         } else if (scanner.keyword("DELETED")) {
213             return new FlagTerm(DELETED, true);
214         } else if (scanner.keyword("UNDELETED")) {
215             return new FlagTerm(DELETED, false);
216         } else if (scanner.keyword("FLAGGED")) {
217             return new FlagTerm(FLAGGED, true);
218         } else if (scanner.keyword("UNFLAGGED")) {
219             return new FlagTerm(FLAGGED, false);
220         } else if (scanner.keyword("SEEN")) {
221             return new FlagTerm(SEEN, true);
222         } else if (scanner.keyword("UNSEEN")) {
223             return new FlagTerm(SEEN, false);
224         } else if (scanner.keyword("NEW")) {
225             return new AndTerm(new FlagTerm(RECENT, true), new FlagTerm(SEEN, false));
226         } else if (scanner.keyword("RECENT")) {
227             return new FlagTerm(RECENT, true);
228         } else if (scanner.keyword("OLD")) {
229             return new FlagTerm(RECENT, false);
230         } else if (scanner.keyword("BODY")) {
231             if (!scanner.is_char(' ')) {
232                 throw new ParserException("<SP>");
233             }
234             StringBuffer body = new StringBuffer();
235             if (!scanner.astring(body)) {
236                 throw new ParserException("<string>");
237             }
238             return new BodyTerm(body.toString());
239         } else if (scanner.keyword("TEXT")) {
240             if (!scanner.is_char(' ')) {
241                 throw new ParserException("<SP>");
242             }
243             StringBuffer body = new StringBuffer();
244             if (!scanner.astring(body)) {
245                 throw new ParserException("<string>");
246             }
247             return new TextTerm(body.toString());
248         } else if (scanner.keyword("SUBJECT")) {
249             if (!scanner.is_char(' ')) {
250                 throw new ParserException("<SP>");
251             }
252             StringBuffer subject = new StringBuffer();
253             if (!scanner.astring(subject)) {
254                 throw new ParserException("<string>");
255             }
256             return new SubjectTerm(subject.toString());
257         } else if (scanner.keyword("FROM")) {
258             if (!scanner.is_char(' ')) {
259                 throw new ParserException("<SP>");
260             }
261             StringBuffer from = new StringBuffer();
262             if (!scanner.astring(from)) {
263                 throw new ParserException("<string>");
264             }
265             return new FromStringTerm(from.toString());
266         } else if (scanner.keyword("TO")) {
267             if (!scanner.is_char(' ')) {
268                 throw new ParserException("<SP>");
269             }
270             StringBuffer to = new StringBuffer();
271             if (!scanner.astring(to)) {
272                 throw new ParserException("<string>");
273             }
274             return new RecipientStringTerm(Message.RecipientType.TO, to.toString());
275         } else if (scanner.keyword("CC")) {
276             if (!scanner.is_char(' ')) {
277                 throw new ParserException("<SP>");
278             }
279             StringBuffer cc = new StringBuffer();
280             if (!scanner.astring(cc)) {
281                 throw new ParserException("<string>");
282             }
283             return new RecipientStringTerm(Message.RecipientType.CC, cc.toString());
284         } else if (scanner.keyword("BCC")) {
285             if (!scanner.is_char(' ')) {
286                 throw new ParserException("<SP>");
287             }
288             StringBuffer bcc = new StringBuffer();
289             if (!scanner.astring(bcc)) {
290                 throw new ParserException("<string>");
291             }
292             return new RecipientStringTerm(Message.RecipientType.BCC, bcc.toString());
293         } else if (scanner.keyword("HEADER")) {
294             if (!scanner.is_char(' ')) {
295                 throw new ParserException("<SP>");
296             }
297             StringBuffer name = new StringBuffer();
298             if (!scanner.astring(name)) {
299                 throw new ParserException("<string>");
300             }
301             if (!scanner.is_char(' ')) {
302                 throw new ParserException("<SP>");
303             }
304             StringBuffer value = new StringBuffer();
305             if (!scanner.astring(value)) {
306                 throw new ParserException("<string>");
307             }
308             return new HeaderTerm(name.toString(), value.toString());
309         } else if (scanner.keyword("LARGER")) {
310             if (!scanner.is_char(' ')) {
311                 throw new ParserException("<SP>");
312             }
313             IMAPScanner.Number number = new IMAPScanner.Number();
314             if (!scanner.number(number)) {
315                 throw new ParserException("<number>");
316             }
317             return new SizeTerm(ComparisonTerm.GT, number.number);
318         } else if (scanner.keyword("SMALLER")) {
319             if (!scanner.is_char(' ')) {
320                 throw new ParserException("<SP>");
321             }
322             IMAPScanner.Number number = new IMAPScanner.Number();
323             if (!scanner.number(number)) {
324                 throw new ParserException("<number>");
325             }
326             return new SizeTerm(ComparisonTerm.LT, number.number);
327         } else if (scanner.keyword("BEFORE")) {
328             if (!scanner.is_char(' ')) {
329                 throw new ParserException("<SP>");
330             }
331             GregorianCalendar date = new GregorianCalendar();
332             if (!scanner.date(date)) {
333                 throw new ParserException("<date>");
334             }
335             return new ReceivedDateTerm(ComparisonTerm.LT, date.getTime());
336         } else if (scanner.keyword("ON")) {
337             if (!scanner.is_char(' ')) {
338                 throw new ParserException("<SP>");
339             }
340             GregorianCalendar date = new GregorianCalendar();
341             if (!scanner.date(date)) {
342                 throw new ParserException("<date>");
343             }
344             return new ReceivedDateTerm(ComparisonTerm.EQ, date.getTime());
345         } else if (scanner.keyword("SINCE")) {
346             if (!scanner.is_char(' ')) {
347                 throw new ParserException("<SP>");
348             }
349             GregorianCalendar date = new GregorianCalendar();
350             if (!scanner.date(date)) {
351                 throw new ParserException("<date>");
352             }
353             return new ReceivedDateTerm(ComparisonTerm.GT, date.getTime());
354         } else if (scanner.keyword("SENTBEFORE")) {
355             if (!scanner.is_char(' ')) {
356                 throw new ParserException("<SP>");
357             }
358             GregorianCalendar date = new GregorianCalendar();
359             if (!scanner.date(date)) {
360                 throw new ParserException("<date>");
361             }
362             return new SentDateTerm(ComparisonTerm.LT, date.getTime());
363         } else if (scanner.keyword("SENTON")) {
364             if (!scanner.is_char(' ')) {
365                 throw new ParserException("<SP>");
366             }
367             GregorianCalendar date = new GregorianCalendar();
368             if (!scanner.date(date)) {
369                 throw new ParserException("<date>");
370             }
371             return new SentDateTerm(ComparisonTerm.EQ, date.getTime());
372         } else if (scanner.keyword("SENTSINCE")) {
373             if (!scanner.is_char(' ')) {
374                 throw new ParserException("<SP>");
375             }
376             GregorianCalendar date = new GregorianCalendar();
377             if (!scanner.date(date)) {
378                 throw new ParserException("<date>");
379             }
380             return new SentDateTerm(ComparisonTerm.GT, date.getTime());
381         } else if (scanner.keyword("KEYWORD")) {
382             if (!scanner.is_char(' ')) {
383                 throw new ParserException("<SP>");
384             }
385             StringBuffer name = new StringBuffer();
386             if (!scanner.astring(name)) {
387                 throw new ParserException("<string>");
388             }
389             return new NoneTerm(); // we are not supporting user flags!
390         } else if (scanner.keyword("UNKEYWORD")) {
391             if (!scanner.is_char(' ')) {
392                 throw new ParserException("<SP>");
393             }
394             StringBuffer name = new StringBuffer();
395             if (!scanner.astring(name)) {
396                 throw new ParserException("<string>");
397             }
398             return new AllTerm(); // we are not supporting user flags!
399         } else if (scanner.keyword("ALL")) {
400             return new AllTerm();
401         } else if (scanner.keyword("UID")) {
402             if (!scanner.is_char(' ')) {
403                 throw new ParserException("<SP>");
404             }
405             ComposedSequence sequenceSet = new ComposedSequence();
406             if (!scanner.sequence_set(sequenceSet)) {
407                 throw new ParserException("<sequence_set>");
408             }
409             return new UIDTerm(ComparisonTerm.GT, sequenceSet.getMin());
410         } else {
411             throw new ParserException(
412                  "'ALL', 'ANSWERED', 'BCC', " +
413                  "'BEFORE', 'BODY', 'CC', 'DELETED', 'FLAGGED', " +
414                  "'FROM', 'KEYWORD', 'NEW', 'OLD', 'ON', 'RECENT', 'SEEN', " +
415                  "'SINCE', 'SUBJECT', 'TEXT', 'TO', 'UNANSWERED', 'UNDELETED', 'UNFLAGGED', " +
416                  "'UNKEYWORD', 'UNSEEN', " +
417                  // Above this line were in [IMAP2]
418                  "'DRAFT', 'HEADER', 'LARGER', 'NOT', 'OR', 'SENTBEFORE', "+
419                  "'SENTON', +'SENTSINCE', 'SMALLER', 'UNDRAFT', '('");
420         }
421 
422     }
423 
424     public class AllTerm extends SearchTerm {
425        public AllTerm() {
426        } // AllTerm
427 
428        public boolean match(Message m) {
429            return true;
430        }
431    }
432 
433    public class NoneTerm extends SearchTerm {
434        public NoneTerm() {
435        } // NoneTerm
436 
437        public boolean match(Message m) {
438            return false;
439        }
440    }
441 
442     public class TextTerm extends StringTerm {
443 
444 
445         public TextTerm(String searchText) {
446             super(searchText);
447         }
448 
449         public boolean match(Message m) {
450             if (m instanceof MimePart) {
451                 try {
452                     if (MessageUtilities.createHeaders((MimePart)m).indexOf(pattern) > 0) {
453                         return true;
454                     }
455                 } catch (MessagingException e) {
456                     logger.error("Search message error", e);
457                     // TODO !!! session.setKeepLog(true);
458                 }
459             }
460             BodyTerm bt = new BodyTerm(pattern);
461             return bt.match(m);
462         }
463 
464     }
465 
466     public class UIDTerm extends ComparisonTerm {
467         long uid;
468 
469         public UIDTerm(int comparison, long uid) {
470             this.comparison = comparison;
471             this.uid = uid;
472         }
473 
474         public boolean match(Message m) {
475             Folder f = m.getFolder();
476             if (f instanceof UIDFolder) {
477                 UIDFolder uf = (UIDFolder)f;
478                 long u;
479                 try {
480                     u = uf.getUID(m);
481                     if (comparison == ComparisonTerm.EQ) {
482                         return uid == u;
483                     } else if (comparison == ComparisonTerm.NE) {
484                         return uid != u;
485                     } else if (comparison == ComparisonTerm.LT) {
486                         return uid < u;
487                     } else if (comparison == ComparisonTerm.LE) {
488                         return uid <= u;
489                     } else if (comparison == ComparisonTerm.GT) {
490                         return uid > u;
491                     } else if (comparison == ComparisonTerm.GE) {
492                         return uid >= u;
493                     }
494                 } catch (MessagingException e) {
495                     logger.error("Search message error", e);
496                     // TODO !!! session.setKeepLog(true);
497                 }
498             }
499             return false;
500         }
501     }
502 
503 }