/
IMAPFolder.java
4153 lines (3814 loc) · 128 KB
/
IMAPFolder.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 1997, 2020 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 com.sun.mail.imap;
import java.util.Date;
import java.util.Vector;
import java.util.Hashtable;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Locale;
import java.util.logging.Level;
import java.io.*;
import java.net.SocketTimeoutException;
import java.nio.channels.SocketChannel;
import jakarta.mail.*;
import jakarta.mail.event.*;
import jakarta.mail.internet.*;
import jakarta.mail.search.*;
import com.sun.mail.util.PropUtil;
import com.sun.mail.util.MailLogger;
import com.sun.mail.util.CRLFOutputStream;
import com.sun.mail.iap.*;
import com.sun.mail.imap.protocol.*;
/**
* This class implements an IMAP folder. <p>
*
* A closed IMAPFolder object shares a protocol connection with its IMAPStore
* object. When the folder is opened, it gets its own protocol connection. <p>
*
* Applications that need to make use of IMAP-specific features may cast
* a <code>Folder</code> object to an <code>IMAPFolder</code> object and
* use the methods on this class. <p>
*
* The {@link #getQuota getQuota} and
* {@link #setQuota setQuota} methods support the IMAP QUOTA extension.
* Refer to <A HREF="http://www.ietf.org/rfc/rfc2087.txt">RFC 2087</A>
* for more information. <p>
*
* The {@link #getACL getACL}, {@link #addACL addACL},
* {@link #removeACL removeACL}, {@link #addRights addRights},
* {@link #removeRights removeRights}, {@link #listRights listRights}, and
* {@link #myRights myRights} methods support the IMAP ACL extension.
* Refer to <A HREF="http://www.ietf.org/rfc/rfc2086.txt">RFC 2086</A>
* for more information. <p>
*
* The {@link #getSortedMessages getSortedMessages}
* methods support the IMAP SORT extension.
* Refer to <A HREF="http://www.ietf.org/rfc/rfc5256.txt">RFC 5256</A>
* for more information. <p>
*
* The {@link #open(int,com.sun.mail.imap.ResyncData) open(int,ResyncData)}
* method and {@link com.sun.mail.imap.ResyncData ResyncData} class supports
* the IMAP CONDSTORE and QRESYNC extensions.
* Refer to <A HREF="http://www.ietf.org/rfc/rfc4551.txt">RFC 4551</A>
* and <A HREF="http://www.ietf.org/rfc/rfc5162.txt">RFC 5162</A>
* for more information. <p>
*
* The {@link #doCommand doCommand} method and
* {@link IMAPFolder.ProtocolCommand IMAPFolder.ProtocolCommand}
* interface support use of arbitrary IMAP protocol commands. <p>
*
* See the <a href="package-summary.html">com.sun.mail.imap</a> package
* documentation for further information on the IMAP protocol provider. <p>
*
* <strong>WARNING:</strong> The APIs unique to this class should be
* considered <strong>EXPERIMENTAL</strong>. They may be changed in the
* future in ways that are incompatible with applications using the
* current APIs.
*
* @author John Mani
* @author Bill Shannon
* @author Jim Glennon
*/
/*
* The folder object itself serves as a lock for the folder's state
* EXCEPT for the message cache (see below), typically by using
* synchronized methods. When checking that a folder is open or
* closed, the folder's lock must be held. It's important that the
* folder's lock is acquired before the messageCacheLock (see below).
* Thus, the locking hierarchy is that the folder lock, while optional,
* must be acquired before the messageCacheLock, if it's acquired at
* all. Be especially careful of callbacks that occur while holding
* the messageCacheLock into (e.g.) superclass Folder methods that are
* synchronized. Note that methods in IMAPMessage will acquire the
* messageCacheLock without acquiring the folder lock. <p>
*
* When a folder is opened, it creates a messageCache (a Vector) of
* empty IMAPMessage objects. Each Message has a messageNumber - which
* is its index into the messageCache, and a sequenceNumber - which is
* its IMAP sequence-number. All operations on a Message which involve
* communication with the server, use the message's sequenceNumber. <p>
*
* The most important thing to note here is that the server can send
* unsolicited EXPUNGE notifications as part of the responses for "most"
* commands. Refer RFC 3501, sections 5.3 & 5.5 for gory details. Also,
* the server sends these notifications AFTER the message has been
* expunged. And once a message is expunged, the sequence-numbers of
* those messages after the expunged one are renumbered. This essentially
* means that the mapping between *any* Message and its sequence-number
* can change in the period when a IMAP command is issued and its responses
* are processed. Hence we impose a strict locking model as follows: <p>
*
* We define one mutex per folder - this is just a Java Object (named
* messageCacheLock). Any time a command is to be issued to the IMAP
* server (i.e., anytime the corresponding IMAPProtocol method is
* invoked), follow the below style:
*
* synchronized (messageCacheLock) { // ACQUIRE LOCK
* issue command ()
*
* // The response processing is typically done within
* // the handleResponse() callback. A few commands (Fetch,
* // Expunge) return *all* responses and hence their
* // processing is done here itself. Now, as part of the
* // processing unsolicited EXPUNGE responses, we renumber
* // the necessary sequence-numbers. Thus the renumbering
* // happens within this critical-region, surrounded by
* // locks.
* process responses ()
* } // RELEASE LOCK
*
* This technique is used both by methods in IMAPFolder and by methods
* in IMAPMessage and other classes that operate on data in the folder.
* Note that holding the messageCacheLock has the side effect of
* preventing the folder from being closed, and thus ensuring that the
* folder's protocol object is still valid. The protocol object should
* only be accessed while holding the messageCacheLock (except for calls
* to IMAPProtocol.isREV1(), which don't need to be protected because it
* doesn't access the server).
*
* Note that interactions with the Store's protocol connection do
* not have to be protected as above, since the Store's protocol is
* never in a "meaningful" SELECT-ed state.
*/
public class IMAPFolder extends Folder implements UIDFolder, ResponseHandler {
protected volatile String fullName; // full name
protected String name; // name
protected int type; // folder type.
protected char separator; // separator
protected Flags availableFlags; // available flags
protected Flags permanentFlags; // permanent flags
protected volatile boolean exists; // whether this folder really exists ?
protected boolean isNamespace = false; // folder is a namespace name
protected volatile String[] attributes;// name attributes from LIST response
protected volatile IMAPProtocol protocol; // this folder's protocol object
protected MessageCache messageCache;// message cache
// accessor lock for message cache
protected final Object messageCacheLock = new Object();
protected Hashtable<Long, IMAPMessage> uidTable; // UID->Message hashtable
/* An IMAP delimiter is a 7bit US-ASCII character. (except NUL).
* We use '\uffff' (a non 7bit character) to indicate that we havent
* yet determined what the separator character is.
* We use '\u0000' (NUL) to indicate that no separator character
* exists, i.e., a flat hierarchy
*/
static final protected char UNKNOWN_SEPARATOR = '\uffff';
private volatile boolean opened = false; // is this folder opened ?
/* This field tracks the state of this folder. If the folder is closed
* due to external causes (i.e, not thru the close() method), then
* this field will remain false. If the folder is closed thru the
* close() method, then this field is set to true.
*
* If reallyClosed is false, then a FolderClosedException is
* generated when a method is invoked on any Messaging object
* owned by this folder. If reallyClosed is true, then the
* IllegalStateException runtime exception is thrown.
*/
private boolean reallyClosed = true;
/*
* The idleState field supports the IDLE command.
* Normally when executing an IMAP command we hold the
* messageCacheLock and often the folder lock (see above).
* While executing the IDLE command we can't hold either
* of these locks or it would prevent other threads from
* entering Folder methods even far enough to check whether
* an IDLE command is in progress. We need to check before
* issuing another command so that we can abort the IDLE
* command.
*
* The idleState field is protected by the messageCacheLock.
* The RUNNING state is the normal state and means no IDLE
* command is in progress. The IDLE state means we've issued
* an IDLE command and are reading responses. The ABORTING
* state means we've sent the DONE continuation command and
* are waiting for the thread running the IDLE command to
* break out of its read loop.
*
* When an IDLE command is in progress, the thread calling
* the idle method will be reading from the IMAP connection
* while holding neither the folder lock nor the messageCacheLock.
* It's obviously critical that no other thread try to send a
* command or read from the connection while in this state.
* However, other threads can send the DONE continuation
* command that will cause the server to break out of the IDLE
* loop and send the ending tag response to the IDLE command.
* The thread in the idle method that's reading the responses
* from the IDLE command will see this ending response and
* complete the idle method, setting the idleState field back
* to RUNNING, and notifying any threads waiting to use the
* connection.
*
* All uses of the IMAP connection (IMAPProtocol object) must
* be done while holding the messageCacheLock and must be
* preceeded by a check to make sure an IDLE command is not
* running, and abort the IDLE command if necessary. While
* waiting for the IDLE command to complete, these other threads
* will give up the messageCacheLock, but might still be holding
* the folder lock. This check is done by the getProtocol()
* method, resulting in a typical usage pattern of:
*
* synchronized (messageCacheLock) {
* IMAPProtocol p = getProtocol(); // may block waiting for IDLE
* // ... use protocol
* }
*/
private static final int RUNNING = 0; // not doing IDLE command
private static final int IDLE = 1; // IDLE command in effect
private static final int ABORTING = 2; // IDLE command aborting
private int idleState = RUNNING;
private IdleManager idleManager;
private volatile int total = -1; // total number of messages in the
// message cache
private volatile int recent = -1; // number of recent messages
private int realTotal = -1; // total number of messages on
// the server
private long uidvalidity = -1; // UIDValidity
private long uidnext = -1; // UIDNext
private boolean uidNotSticky = false; // RFC 4315
private volatile long highestmodseq = -1; // RFC 4551 - CONDSTORE
private boolean doExpungeNotification = true; // used in expunge handler
private Status cachedStatus = null;
private long cachedStatusTime = 0;
private boolean hasMessageCountListener = false; // optimize notification
protected MailLogger logger;
private MailLogger connectionPoolLogger;
/**
* A fetch profile item for fetching headers.
* This inner class extends the <code>FetchProfile.Item</code>
* class to add new FetchProfile item types, specific to IMAPFolders.
*
* @see FetchProfile
*/
public static class FetchProfileItem extends FetchProfile.Item {
protected FetchProfileItem(String name) {
super(name);
}
/**
* HEADERS is a fetch profile item that can be included in a
* <code>FetchProfile</code> during a fetch request to a Folder.
* This item indicates that the headers for messages in the specified
* range are desired to be prefetched. <p>
*
* An example of how a client uses this is below:
* <blockquote><pre>
*
* FetchProfile fp = new FetchProfile();
* fp.add(IMAPFolder.FetchProfileItem.HEADERS);
* folder.fetch(msgs, fp);
*
* </pre></blockquote>
*/
public static final FetchProfileItem HEADERS =
new FetchProfileItem("HEADERS");
/**
* SIZE is a fetch profile item that can be included in a
* <code>FetchProfile</code> during a fetch request to a Folder.
* This item indicates that the sizes of the messages in the specified
* range are desired to be prefetched. <p>
*
* SIZE was moved to FetchProfile.Item in JavaMail 1.5.
*
* @deprecated
*/
@Deprecated
public static final FetchProfileItem SIZE =
new FetchProfileItem("SIZE");
/**
* MESSAGE is a fetch profile item that can be included in a
* <code>FetchProfile</code> during a fetch request to a Folder.
* This item indicates that the entire messages (headers and body,
* including all "attachments") in the specified
* range are desired to be prefetched. Note that the entire message
* content is cached in memory while the Folder is open. The cached
* message will be parsed locally to return header information and
* message content. <p>
*
* An example of how a client uses this is below:
* <blockquote><pre>
*
* FetchProfile fp = new FetchProfile();
* fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
* folder.fetch(msgs, fp);
*
* </pre></blockquote>
*
* @since JavaMail 1.5.2
*/
public static final FetchProfileItem MESSAGE =
new FetchProfileItem("MESSAGE");
/**
* INTERNALDATE is a fetch profile item that can be included in a
* <code>FetchProfile</code> during a fetch request to a Folder.
* This item indicates that the IMAP INTERNALDATE values
* (received date) of the messages in the specified
* range are desired to be prefetched. <p>
*
* An example of how a client uses this is below:
* <blockquote><pre>
*
* FetchProfile fp = new FetchProfile();
* fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
* folder.fetch(msgs, fp);
*
* </pre></blockquote>
*
* @since JavaMail 1.5.5
*/
public static final FetchProfileItem INTERNALDATE =
new FetchProfileItem("INTERNALDATE");
}
/**
* Constructor used to create a possibly non-existent folder.
*
* @param fullName fullname of this folder
* @param separator the default separator character for this
* folder's namespace
* @param store the Store
* @param isNamespace if this folder represents a namespace
*/
protected IMAPFolder(String fullName, char separator, IMAPStore store,
Boolean isNamespace) {
super(store);
if (fullName == null)
throw new NullPointerException("Folder name is null");
this.fullName = fullName;
this.separator = separator;
logger = new MailLogger(this.getClass(), "DEBUG IMAP",
store.getSession().getDebug(), store.getSession().getDebugOut());
connectionPoolLogger = store.getConnectionPoolLogger();
/*
* Work around apparent bug in Exchange. Exchange
* will return a name of "Public Folders/" from
* LIST "%".
*
* If name has one separator, and it's at the end,
* assume this is a namespace name and treat it
* accordingly. Usually this will happen as a result
* of the list method, but this also allows getFolder
* to work with namespace names.
*/
this.isNamespace = false;
if (separator != UNKNOWN_SEPARATOR && separator != '\0') {
int i = this.fullName.indexOf(separator);
if (i > 0 && i == this.fullName.length() - 1) {
this.fullName = this.fullName.substring(0, i);
this.isNamespace = true;
}
}
// if we were given a value, override default chosen above
if (isNamespace != null)
this.isNamespace = isNamespace.booleanValue();
}
/**
* Constructor used to create an existing folder.
*
* @param li the ListInfo for this folder
* @param store the store containing this folder
*/
protected IMAPFolder(ListInfo li, IMAPStore store) {
this(li.name, li.separator, store, null);
if (li.hasInferiors)
type |= HOLDS_FOLDERS;
if (li.canOpen)
type |= HOLDS_MESSAGES;
exists = true;
attributes = li.attrs;
}
/*
* Ensure that this folder exists. If 'exists' has been set to true,
* we don't attempt to validate it with the server again. Note that
* this can result in a possible loss of sync with the server.
* ASSERT: Must be called with this folder's synchronization lock held.
*/
protected void checkExists() throws MessagingException {
// If the boolean field 'exists' is false, check with the
// server by invoking exists() ..
if (!exists && !exists())
throw new FolderNotFoundException(
this, fullName + " not found");
}
/*
* Ensure the folder is closed.
* ASSERT: Must be called with this folder's synchronization lock held.
*/
protected void checkClosed() {
if (opened)
throw new IllegalStateException(
"This operation is not allowed on an open folder"
);
}
/*
* Ensure the folder is open.
* ASSERT: Must be called with this folder's synchronization lock held.
*/
protected void checkOpened() throws FolderClosedException {
assert Thread.holdsLock(this);
if (!opened) {
if (reallyClosed)
throw new IllegalStateException(
"This operation is not allowed on a closed folder"
);
else // Folder was closed "implicitly"
throw new FolderClosedException(this,
"Lost folder connection to server"
);
}
}
/*
* Check that the given message number is within the range
* of messages present in this folder. If the message
* number is out of range, we ping the server to obtain any
* pending new message notifications from the server.
*/
protected void checkRange(int msgno) throws MessagingException {
if (msgno < 1) // message-numbers start at 1
throw new IndexOutOfBoundsException("message number < 1");
if (msgno <= total)
return;
// Out of range, let's ping the server and see if
// the server has more messages for us.
synchronized(messageCacheLock) { // Acquire lock
try {
keepConnectionAlive(false);
} catch (ConnectionException cex) {
// Oops, lost connection
throw new FolderClosedException(this, cex.getMessage());
} catch (ProtocolException pex) {
throw new MessagingException(pex.getMessage(), pex);
}
} // Release lock
if (msgno > total) // Still out of range ? Throw up ...
throw new IndexOutOfBoundsException(msgno + " > " + total);
}
/*
* Check whether the given flags are supported by this server,
* and also verify that the folder allows setting flags.
*/
private void checkFlags(Flags flags) throws MessagingException {
assert Thread.holdsLock(this);
if (mode != READ_WRITE)
throw new IllegalStateException(
"Cannot change flags on READ_ONLY folder: " + fullName
);
/*
if (!availableFlags.contains(flags))
throw new MessagingException(
"These flags are not supported by this implementation"
);
*/
}
/**
* Get the name of this folder.
*/
@Override
public synchronized String getName() {
/* Return the last component of this Folder's full name.
* Folder components are delimited by the separator character.
*/
if (name == null) {
try {
name = fullName.substring(
fullName.lastIndexOf(getSeparator()) + 1
);
} catch (MessagingException mex) { }
}
return name;
}
/**
* Get the fullname of this folder.
*/
@Override
public String getFullName() {
return fullName;
}
/**
* Get this folder's parent.
*/
@Override
public synchronized Folder getParent() throws MessagingException {
char c = getSeparator();
int index;
if ((index = fullName.lastIndexOf(c)) != -1)
return ((IMAPStore)store).newIMAPFolder(
fullName.substring(0, index), c);
else
return new DefaultFolder((IMAPStore)store);
}
/**
* Check whether this folder really exists on the server.
*/
@Override
public synchronized boolean exists() throws MessagingException {
// Check whether this folder exists ..
ListInfo[] li = null;
final String lname;
if (isNamespace && separator != '\0')
lname = fullName + separator;
else
lname = fullName;
li = (ListInfo[])doCommand(new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p) throws ProtocolException {
return p.list("", lname);
}
});
if (li != null) {
int i = findName(li, lname);
fullName = li[i].name;
separator = li[i].separator;
int len = fullName.length();
if (separator != '\0' && len > 0 &&
fullName.charAt(len - 1) == separator) {
fullName = fullName.substring(0, len - 1);
}
type = 0;
if (li[i].hasInferiors)
type |= HOLDS_FOLDERS;
if (li[i].canOpen)
type |= HOLDS_MESSAGES;
exists = true;
attributes = li[i].attrs;
} else {
exists = opened;
attributes = null;
}
return exists;
}
/**
* Which entry in <code>li</code> matches <code>lname</code>?
* If the name contains wildcards, more than one entry may be
* returned.
*/
private int findName(ListInfo[] li, String lname) {
int i;
// if the name contains a wildcard, there might be more than one
for (i = 0; i < li.length; i++) {
if (li[i].name.equals(lname))
break;
}
if (i >= li.length) { // nothing matched exactly
// XXX - possibly should fail? But what if server
// is case insensitive and returns the preferred
// case of the name here?
i = 0; // use first one
}
return i;
}
/**
* List all subfolders matching the specified pattern.
*/
@Override
public Folder[] list(String pattern) throws MessagingException {
return doList(pattern, false);
}
/**
* List all subscribed subfolders matching the specified pattern.
*/
@Override
public Folder[] listSubscribed(String pattern) throws MessagingException {
return doList(pattern, true);
}
private synchronized Folder[] doList(final String pattern,
final boolean subscribed) throws MessagingException {
checkExists(); // insure that this folder does exist.
// Why waste a roundtrip to the server?
if (attributes != null && !isDirectory())
return new Folder[0];
final char c = getSeparator();
ListInfo[] li = (ListInfo[])doCommandIgnoreFailure(
new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
if (subscribed)
return p.lsub("", fullName + c + pattern);
else
return p.list("", fullName + c + pattern);
}
});
if (li == null)
return new Folder[0];
/*
* The UW based IMAP4 servers (e.g. SIMS2.0) include
* current folder (terminated with the separator), when
* the LIST pattern is '%' or '*'. i.e, <LIST "" mail/%>
* returns "mail/" as the first LIST response.
*
* Doesn't make sense to include the current folder in this
* case, so we filter it out. Note that I'm assuming that
* the offending response is the *first* one, my experiments
* with the UW & SIMS2.0 servers indicate that ..
*/
int start = 0;
// Check the first LIST response.
if (li.length > 0 && li[0].name.equals(fullName + c))
start = 1; // start from index = 1
IMAPFolder[] folders = new IMAPFolder[li.length - start];
IMAPStore st = (IMAPStore)store;
for (int i = start; i < li.length; i++)
folders[i-start] = st.newIMAPFolder(li[i]);
return folders;
}
/**
* Get the separator character.
*/
@Override
public synchronized char getSeparator() throws MessagingException {
if (separator == UNKNOWN_SEPARATOR) {
ListInfo[] li = null;
li = (ListInfo[])doCommand(new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
// REV1 allows the following LIST format to obtain
// the hierarchy delimiter of non-existent folders
if (p.isREV1()) // IMAP4rev1
return p.list(fullName, "");
else // IMAP4, note that this folder must exist for this
// to work :(
return p.list("", fullName);
}
});
if (li != null)
separator = li[0].separator;
else
separator = '/'; // punt !
}
return separator;
}
/**
* Get the type of this folder.
*/
@Override
public synchronized int getType() throws MessagingException {
if (opened) {
// never throw FolderNotFoundException if folder is open
if (attributes == null)
exists(); // try to fetch attributes
} else {
checkExists();
}
return type;
}
/**
* Check whether this folder is subscribed.
*/
@Override
public synchronized boolean isSubscribed() {
ListInfo[] li = null;
final String lname;
if (isNamespace && separator != '\0')
lname = fullName + separator;
else
lname = fullName;
try {
li = (ListInfo[])doProtocolCommand(new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
return p.lsub("", lname);
}
});
} catch (ProtocolException pex) {
}
if (li != null) {
int i = findName(li, lname);
return li[i].canOpen;
} else
return false;
}
/**
* Subscribe/Unsubscribe this folder.
*/
@Override
public synchronized void setSubscribed(final boolean subscribe)
throws MessagingException {
doCommandIgnoreFailure(new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p) throws ProtocolException {
if (subscribe)
p.subscribe(fullName);
else
p.unsubscribe(fullName);
return null;
}
});
}
/**
* Create this folder, with the specified type.
*/
@Override
public synchronized boolean create(final int type)
throws MessagingException {
char c = 0;
if ((type & HOLDS_MESSAGES) == 0) // only holds folders
c = getSeparator();
final char sep = c;
Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p)
throws ProtocolException {
if ((type & HOLDS_MESSAGES) == 0) // only holds folders
p.create(fullName + sep);
else {
p.create(fullName);
// Certain IMAP servers do not allow creation of folders
// that can contain messages *and* subfolders. So, if we
// were asked to create such a folder, we should verify
// that we could indeed do so.
if ((type & HOLDS_FOLDERS) != 0) {
// we want to hold subfolders and messages. Check
// whether we could create such a folder.
ListInfo[] li = p.list("", fullName);
if (li != null && !li[0].hasInferiors) {
// Hmm ..the new folder
// doesn't support Inferiors ? Fail
p.delete(fullName);
throw new ProtocolException("Unsupported type");
}
}
}
return Boolean.TRUE;
}
});
if (ret == null)
return false; // CREATE failure, maybe this
// folder already exists ?
// exists = true;
// this.type = type;
boolean retb = exists(); // set exists, type, and attributes
if (retb) // Notify listeners on self and our Store
notifyFolderListeners(FolderEvent.CREATED);
return retb;
}
/**
* Check whether this folder has new messages.
*/
@Override
public synchronized boolean hasNewMessages() throws MessagingException {
synchronized (messageCacheLock) {
if (opened) { // If we are open, we already have this information
// Folder is open, make sure information is up to date
// tickle the folder and store connections.
try {
keepConnectionAlive(true);
} catch (ConnectionException cex) {
throw new FolderClosedException(this, cex.getMessage());
} catch (ProtocolException pex) {
throw new MessagingException(pex.getMessage(), pex);
}
return recent > 0 ? true : false;
}
}
// First, the cheap way - use LIST and look for the \Marked
// or \Unmarked tag
ListInfo[] li = null;
final String lname;
if (isNamespace && separator != '\0')
lname = fullName + separator;
else
lname = fullName;
li = (ListInfo[])doCommandIgnoreFailure(new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p) throws ProtocolException {
return p.list("", lname);
}
});
// if folder doesn't exist, throw exception
if (li == null)
throw new FolderNotFoundException(this, fullName + " not found");
int i = findName(li, lname);
if (li[i].changeState == ListInfo.CHANGED)
return true;
else if (li[i].changeState == ListInfo.UNCHANGED)
return false;
// LIST didn't work. Try the hard way, using STATUS
try {
Status status = getStatus();
if (status.recent > 0)
return true;
else
return false;
} catch (BadCommandException bex) {
// Probably doesn't support STATUS, tough luck.
return false;
} catch (ConnectionException cex) {
throw new StoreClosedException(store, cex.getMessage());
} catch (ProtocolException pex) {
throw new MessagingException(pex.getMessage(), pex);
}
}
/**
* Get the named subfolder.
*/
@Override
public synchronized Folder getFolder(String name)
throws MessagingException {
// If we know that this folder is *not* a directory, don't
// send the request to the server at all ...
if (attributes != null && !isDirectory())
throw new MessagingException("Cannot contain subfolders");
char c = getSeparator();
return ((IMAPStore)store).newIMAPFolder(fullName + c + name, c);
}
/**
* Delete this folder.
*/
@Override
public synchronized boolean delete(boolean recurse)
throws MessagingException {
checkClosed(); // insure that this folder is closed.
if (recurse) {
// Delete all subfolders.
Folder[] f = list();
for (int i = 0; i < f.length; i++)
f[i].delete(recurse); // ignore intermediate failures
}
// Attempt to delete this folder
Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p) throws ProtocolException {
p.delete(fullName);
return Boolean.TRUE;
}
});
if (ret == null)
// Non-existent folder/No permission ??
return false;
// DELETE succeeded.
exists = false;
attributes = null;
// Notify listeners on self and our Store
notifyFolderListeners(FolderEvent.DELETED);
return true;
}
/**
* Rename this folder.
*/
@Override
public synchronized boolean renameTo(final Folder f)
throws MessagingException {
checkClosed(); // insure that we are closed.
checkExists();
if (f.getStore() != store)
throw new MessagingException("Can't rename across Stores");
Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol p) throws ProtocolException {
p.rename(fullName, f.getFullName());
return Boolean.TRUE;
}
});
if (ret == null)
return false;
exists = false;
attributes = null;
notifyFolderRenamedListeners(f);
return true;
}
/**
* Open this folder in the given mode.
*/
@Override
public synchronized void open(int mode) throws MessagingException {
open(mode, null);
}
/**
* Open this folder in the given mode, with the given
* resynchronization data.
*
* @param mode the open mode (Folder.READ_WRITE or Folder.READ_ONLY)
* @param rd the ResyncData instance
* @return a List of MailEvent instances, or null if none
* @exception MessagingException if the open fails
* @since JavaMail 1.5.1
*/
public synchronized List<MailEvent> open(int mode, ResyncData rd)
throws MessagingException {
checkClosed(); // insure that we are not already open
MailboxInfo mi = null;
// Request store for our own protocol connection.
protocol = ((IMAPStore)store).getProtocol(this);
List<MailEvent> openEvents = null;
synchronized(messageCacheLock) { // Acquire messageCacheLock