Skip to content

Package: SharedByteArrayOutputStream

SharedByteArrayOutputStream

nameinstructionbranchcomplexitylinemethod
SharedByteArrayOutputStream(int)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
toStream()
M: 9 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0, which is available at
6: * http://www.eclipse.org/legal/epl-2.0.
7: *
8: * This Source Code may also be made available under the following Secondary
9: * Licenses when the conditions for such availability set forth in the
10: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11: * version 2 with the GNU Classpath Exception, which is available at
12: * https://www.gnu.org/software/classpath/license.html.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: package org.eclipse.angus.mail.mbox;
18:
19: import jakarta.mail.Address;
20: import jakarta.mail.Flags;
21: import jakarta.mail.Folder;
22: import jakarta.mail.FolderNotFoundException;
23: import jakarta.mail.Message;
24: import jakarta.mail.MessageRemovedException;
25: import jakarta.mail.MessagingException;
26: import jakarta.mail.URLName;
27: import jakarta.mail.event.ConnectionEvent;
28: import jakarta.mail.event.FolderEvent;
29: import jakarta.mail.internet.InternetAddress;
30: import jakarta.mail.internet.InternetHeaders;
31: import jakarta.mail.internet.MimeMessage;
32: import jakarta.mail.internet.SharedInputStream;
33: import jakarta.mail.util.SharedByteArrayInputStream;
34: import org.eclipse.angus.mail.util.LineInputStream;
35:
36: import java.io.BufferedOutputStream;
37: import java.io.ByteArrayOutputStream;
38: import java.io.EOFException;
39: import java.io.File;
40: import java.io.FileInputStream;
41: import java.io.FileNotFoundException;
42: import java.io.FileOutputStream;
43: import java.io.IOException;
44: import java.io.InputStream;
45: import java.io.OutputStream;
46: import java.io.PrintStream;
47: import java.util.ArrayList;
48: import java.util.Date;
49: import java.util.List;
50: import java.util.StringTokenizer;
51:
52: /**
53: * This class represents a mailbox file containing RFC822 style email messages.
54: *
55: * @author John Mani
56: * @author Bill Shannon
57: */
58:
59: public class MboxFolder extends Folder {
60:
61: private String name; // null => the default folder
62: private boolean is_inbox = false;
63: private int total; // total number of messages in mailbox
64: private volatile boolean opened = false;
65: private List<MessageMetadata> messages;
66: private TempFile temp;
67: private MboxStore mstore;
68: private MailFile folder;
69: private long file_size; // the size the last time we read or wrote it
70: private long saved_file_size; // size at the last open, close, or expunge
71: private boolean special_imap_message;
72:
73: private static final boolean homeRelative =
74: Boolean.getBoolean("mail.mbox.homerelative");
75:
76: /**
77: * Metadata for each message, to avoid instantiating MboxMessage
78: * objects for messages we're not going to look at. <p>
79: *
80: * MboxFolder keeps an array of these objects corresponding to
81: * each message in the folder. Access to the array elements is
82: * synchronized, but access to contents of this object is not.
83: * The metadata stored here is only accessed if the message field
84: * is null; otherwise the MboxMessage object contains the metadata.
85: */
86: static final class MessageMetadata {
87: public long start; // offset in temp file of start of this message
88: // public long end;        // offset in temp file of end of this message
89: public long dataend; // offset of end of message data, <= "end"
90: public MboxMessage message; // the message itself
91: public boolean recent; // message is recent?
92: public boolean deleted; // message is marked deleted?
93: public boolean imap; // special imap message?
94: }
95:
96: public MboxFolder(MboxStore store, String name) {
97: super(store);
98: this.mstore = store;
99: this.name = name;
100:
101: if (name != null && name.equalsIgnoreCase("INBOX"))
102: is_inbox = true;
103:
104: folder = mstore.getMailFile(name == null ? "~" : name);
105: if (folder.exists())
106: saved_file_size = folder.length();
107: else
108: saved_file_size = -1;
109: }
110:
111: public char getSeparator() {
112: return File.separatorChar;
113: }
114:
115: public Folder[] list(String pattern) throws MessagingException {
116: if (!folder.isDirectory())
117: throw new MessagingException("not a directory");
118:
119: if (name == null)
120: return list(null, pattern, true);
121: else
122: return list(name + File.separator, pattern, false);
123: }
124:
125: /*
126: * Version of list shared by MboxStore and MboxFolder.
127: */
128: protected Folder[] list(String ref, String pattern, boolean fromStore)
129: throws MessagingException {
130: if (ref != null && ref.length() == 0)
131: ref = null;
132: int i;
133: String refdir = null;
134: String realdir = null;
135:
136: pattern = canonicalize(ref, pattern);
137: if ((i = indexOfAny(pattern, "%*")) >= 0) {
138: refdir = pattern.substring(0, i);
139: } else {
140: refdir = pattern;
141: }
142: if ((i = refdir.lastIndexOf(File.separatorChar)) >= 0) {
143: // get rid of anything after directory name
144: refdir = refdir.substring(0, i + 1);
145: realdir = mstore.mb.filename(mstore.user, refdir);
146: } else if (refdir.length() == 0 || refdir.charAt(0) != '~') {
147: // no separator and doesn't start with "~" => home or cwd
148: refdir = null;
149: if (homeRelative)
150: realdir = mstore.home;
151: else
152: realdir = ".";
153: } else {
154: realdir = mstore.mb.filename(mstore.user, refdir);
155: }
156: List<String> flist = new ArrayList<String>();
157: listWork(realdir, refdir, pattern, fromStore ? 0 : 1, flist);
158: if (Match.path("INBOX", pattern, '\0'))
159: flist.add("INBOX");
160:
161: Folder[] fl = new Folder[flist.size()];
162: for (i = 0; i < fl.length; i++) {
163: fl[i] = createFolder(mstore, flist.get(i));
164: }
165: return fl;
166: }
167:
168: public String getName() {
169: if (name == null)
170: return "";
171: else if (is_inbox)
172: return "INBOX";
173: else
174: return folder.getName();
175: }
176:
177: public String getFullName() {
178: if (name == null)
179: return "";
180: else
181: return name;
182: }
183:
184: public Folder getParent() {
185: if (name == null)
186: return null;
187: else if (is_inbox)
188: return createFolder(mstore, null);
189: else
190: // XXX - have to recognize other folders under default folder
191: return createFolder(mstore, folder.getParent());
192: }
193:
194: public boolean exists() {
195: return folder.exists();
196: }
197:
198: public int getType() {
199: if (folder.isDirectory())
200: return HOLDS_FOLDERS;
201: else
202: return HOLDS_MESSAGES;
203: }
204:
205: public Flags getPermanentFlags() {
206: return MboxStore.permFlags;
207: }
208:
209: public synchronized boolean hasNewMessages() {
210: if (folder instanceof UNIXFile) {
211: UNIXFile f = (UNIXFile) folder;
212: if (f.length() > 0) {
213: long atime = f.lastAccessed();
214: long mtime = f.lastModified();
215: //System.out.println(name + " atime " + atime + " mtime " + mtime);
216: return atime < mtime;
217: }
218: return false;
219: }
220: long current_size;
221: if (folder.exists())
222: current_size = folder.length();
223: else
224: current_size = -1;
225: // if we've never opened the folder, remember the size now
226: // (will cause us to return false the first time)
227: if (saved_file_size < 0)
228: saved_file_size = current_size;
229: return current_size > saved_file_size;
230: }
231:
232: public synchronized Folder getFolder(String name)
233: throws MessagingException {
234: if (folder.exists() && !folder.isDirectory())
235: throw new MessagingException("not a directory");
236: return createFolder(mstore,
237: (this.name == null ? "~" : this.name) + File.separator + name);
238: }
239:
240: public synchronized boolean create(int type) throws MessagingException {
241: switch (type) {
242: case HOLDS_FOLDERS:
243: if (!folder.mkdirs()) {
244: return false;
245: }
246: break;
247:
248: case HOLDS_MESSAGES:
249: if (folder.exists()) {
250: return false;
251: }
252: try {
253: (new FileOutputStream((File) folder)).close();
254: } catch (FileNotFoundException fe) {
255: File parent = new File(folder.getParent());
256: if (!parent.mkdirs())
257: throw new
258: MessagingException("can't create folder: " + name);
259: try {
260: (new FileOutputStream((File) folder)).close();
261: } catch (IOException ex3) {
262: throw new
263: MessagingException("can't create folder: " + name, ex3);
264: }
265: } catch (IOException e) {
266: throw new
267: MessagingException("can't create folder: " + name, e);
268: }
269: break;
270:
271: default:
272: throw new MessagingException("type not supported");
273: }
274: notifyFolderListeners(FolderEvent.CREATED);
275: return true;
276: }
277:
278: public synchronized boolean delete(boolean recurse)
279: throws MessagingException {
280: checkClosed();
281: if (name == null)
282: throw new MessagingException("can't delete default folder");
283: boolean ret = true;
284: if (recurse && folder.isDirectory())
285: ret = delete(new File(folder.getPath()));
286: if (ret && folder.delete()) {
287: notifyFolderListeners(FolderEvent.DELETED);
288: return true;
289: }
290: return false;
291: }
292:
293: /**
294: * Recursively delete the specified file/directory.
295: */
296: private boolean delete(File f) {
297: File[] files = f.listFiles();
298: boolean ret = true;
299: for (int i = 0; ret && i < files.length; i++) {
300: if (files[i].isDirectory())
301: ret = delete(files[i]);
302: else
303: ret = files[i].delete();
304: }
305: return ret;
306: }
307:
308: public synchronized boolean renameTo(Folder f)
309: throws MessagingException {
310: checkClosed();
311: if (name == null)
312: throw new MessagingException("can't rename default folder");
313: if (!(f instanceof MboxFolder))
314: throw new MessagingException("can't rename to: " + f.getName());
315: String newname = ((MboxFolder) f).folder.getPath();
316: if (folder.renameTo(new File(newname))) {
317: notifyFolderRenamedListeners(f);
318: return true;
319: }
320: return false;
321: }
322:
323: /* Ensure the folder is open */
324: void checkOpen() throws IllegalStateException {
325: if (!opened)
326: throw new IllegalStateException("Folder is not Open");
327: }
328:
329: /* Ensure the folder is not open */
330: private void checkClosed() throws IllegalStateException {
331: if (opened)
332: throw new IllegalStateException("Folder is Open");
333: }
334:
335: /*
336: * Check that the given message number is within the range
337: * of messages present in this folder. If the message
338: * number is out of range, we check to see if new messages
339: * have arrived.
340: */
341: private void checkRange(int msgno) throws MessagingException {
342: if (msgno < 1) // message-numbers start at 1
343: throw new IndexOutOfBoundsException("message number < 1");
344:
345: if (msgno <= total)
346: return;
347:
348: // Out of range, let's check if there are any new messages.
349: getMessageCount();
350:
351: if (msgno > total) // Still out of range ? Throw up ...
352: throw new IndexOutOfBoundsException(msgno + " > " + total);
353: }
354:
355: /* Ensure the folder is open & readable */
356: private void checkReadable() throws IllegalStateException {
357: if (!opened || (mode != READ_ONLY && mode != READ_WRITE))
358: throw new IllegalStateException("Folder is not Readable");
359: }
360:
361: /* Ensure the folder is open & writable */
362: private void checkWritable() throws IllegalStateException {
363: if (!opened || mode != READ_WRITE)
364: throw new IllegalStateException("Folder is not Writable");
365: }
366:
367: public boolean isOpen() {
368: return opened;
369: }
370:
371: /*
372: * Open the folder in the specified mode.
373: */
374: public synchronized void open(int mode) throws MessagingException {
375: if (opened)
376: throw new IllegalStateException("Folder is already Open");
377:
378: if (!folder.exists())
379: throw new FolderNotFoundException(this, "Folder doesn't exist: " +
380: folder.getPath());
381: this.mode = mode;
382: switch (mode) {
383: case READ_WRITE:
384: default:
385: if (!folder.canWrite())
386: throw new MessagingException("Open Failure, can't write: " +
387: folder.getPath());
388: // fall through...
389:
390: case READ_ONLY:
391: if (!folder.canRead())
392: throw new MessagingException("Open Failure, can't read: " +
393: folder.getPath());
394: break;
395: }
396:
397: if (is_inbox && folder instanceof InboxFile) {
398: InboxFile inf = (InboxFile) folder;
399: if (!inf.openLock(mode == READ_WRITE ? "rw" : "r"))
400: throw new MessagingException("Failed to lock INBOX");
401: }
402: if (!folder.lock("r"))
403: throw new MessagingException("Failed to lock folder: " + name);
404: messages = new ArrayList<MessageMetadata>();
405: total = 0;
406: Message[] msglist = null;
407: try {
408: temp = new TempFile(null);
409: saved_file_size = folder.length();
410: msglist = load(0L, false);
411: } catch (IOException e) {
412: throw new MessagingException("IOException", e);
413: } finally {
414: folder.unlock();
415: }
416: notifyConnectionListeners(ConnectionEvent.OPENED);
417: if (msglist != null)
418: notifyMessageAddedListeners(msglist);
419: opened = true; // now really opened
420: }
421:
422: public synchronized void close(boolean expunge) throws MessagingException {
423: checkOpen();
424:
425: try {
426: if (mode == READ_WRITE) {
427: try {
428: writeFolder(true, expunge);
429: } catch (IOException e) {
430: throw new MessagingException("I/O Exception", e);
431: }
432: }
433: messages = null;
434: } finally {
435: opened = false;
436: if (is_inbox && folder instanceof InboxFile) {
437: InboxFile inf = (InboxFile) folder;
438: inf.closeLock();
439: }
440: temp.close();
441: temp = null;
442: notifyConnectionListeners(ConnectionEvent.CLOSED);
443: }
444: }
445:
446: /**
447: * Re-write the folder with the current contents of the messages.
448: * If closing is true, turn off the RECENT flag. If expunge is
449: * true, don't write out deleted messages (only used from close()
450: * when the message cache won't be accessed again).
451: *
452: * Return the number of messages written.
453: */
454: protected int writeFolder(boolean closing, boolean expunge)
455: throws IOException, MessagingException {
456:
457: /*
458: * First, see if there have been any changes.
459: */
460: int modified = 0, deleted = 0, recent = 0;
461: for (int msgno = 1; msgno <= total; msgno++) {
462: MessageMetadata md = messages.get(messageIndexOf(msgno));
463: MboxMessage msg = md.message;
464: if (msg != null) {
465: Flags flags = msg.getFlags();
466: if (msg.isModified() || !msg.origFlags.equals(flags))
467: modified++;
468: if (flags.contains(Flags.Flag.DELETED))
469: deleted++;
470: if (flags.contains(Flags.Flag.RECENT))
471: recent++;
472: } else {
473: if (md.deleted)
474: deleted++;
475: if (md.recent)
476: recent++;
477: }
478: }
479: if ((!closing || recent == 0) && (!expunge || deleted == 0) &&
480: modified == 0)
481: return 0;
482:
483: /*
484: * Have to save any new mail that's been appended to the
485: * folder since we last loaded it.
486: */
487: if (!folder.lock("rw"))
488: throw new MessagingException("Failed to lock folder: " + name);
489: int oldtotal = total; // XXX
490: Message[] msglist = null;
491: if (folder.length() != file_size)
492: msglist = load(file_size, !closing);
493: // don't use the folder's FD, need to re-open in order to trunc the file
494: OutputStream os =
495: new BufferedOutputStream(new FileOutputStream((File) folder));
496: int wr = 0;
497: boolean keep = true;
498: try {
499: if (special_imap_message)
500: appendStream(getMessageStream(0), os);
501: for (int msgno = 1; msgno <= total; msgno++) {
502: MessageMetadata md = messages.get(messageIndexOf(msgno));
503: MboxMessage msg = md.message;
504: if (msg != null) {
505: if (expunge && msg.isSet(Flags.Flag.DELETED))
506: continue; // skip it;
507: if (closing && msgno <= oldtotal &&
508: msg.isSet(Flags.Flag.RECENT))
509: msg.setFlag(Flags.Flag.RECENT, false);
510: writeMboxMessage(msg, os);
511: } else {
512: if (expunge && md.deleted)
513: continue; // skip it;
514: if (closing && msgno <= oldtotal && md.recent) {
515: // have to instantiate message so that we can
516: // clear the recent flag
517: msg = (MboxMessage) getMessage(msgno);
518: msg.setFlag(Flags.Flag.RECENT, false);
519: writeMboxMessage(msg, os);
520: } else {
521: appendStream(getMessageStream(msgno), os);
522: }
523: }
524: folder.touchlock();
525: wr++;
526: }
527: // If no messages in the mailbox, and we're closing,
528: // maybe we should remove the mailbox.
529: if (wr == 0 && closing) {
530: String skeep = ((MboxStore) store).getSession().
531: getProperty("mail.mbox.deleteEmpty");
532: if (skeep != null && skeep.equalsIgnoreCase("true"))
533: keep = false;
534: }
535: } catch (IOException | MessagingException e) {
536: throw e;
537: } catch (Exception e) {
538: e.printStackTrace();
539: throw new MessagingException("unexpected exception " + e);
540: } finally {
541: // close the folder, flushing out the data
542: try {
543: os.close();
544: file_size = saved_file_size = folder.length();
545: if (!keep) {
546: folder.delete();
547: file_size = 0;
548: }
549: } catch (IOException ex) {
550: }
551:
552: if (keep) {
553: // make sure the access time is greater than the mod time
554: // XXX - would be nice to have utime()
555: try {
556: Thread.sleep(1000); // sleep for a second
557: } catch (InterruptedException ex) {
558: }
559: InputStream is = null;
560: try {
561: is = new FileInputStream((File) folder);
562: is.read(); // read a byte
563: } catch (IOException ex) {
564: } // ignore errors
565: try {
566: if (is != null)
567: is.close();
568: is = null;
569: } catch (IOException ex) {
570: } // ignore errors
571: }
572:
573: folder.unlock();
574: if (msglist != null)
575: notifyMessageAddedListeners(msglist);
576: }
577: return wr;
578: }
579:
580: /**
581: * Append the input stream to the output stream, closing the
582: * input stream when done.
583: */
584: private static final void appendStream(InputStream is, OutputStream os)
585: throws IOException {
586: try {
587: byte[] buf = new byte[64 * 1024];
588: int len;
589: while ((len = is.read(buf)) > 0)
590: os.write(buf, 0, len);
591: } finally {
592: is.close();
593: }
594: }
595:
596: /**
597: * Write a MimeMessage to the specified OutputStream in a
598: * format suitable for a UNIX mailbox, i.e., including a correct
599: * Content-Length header and with the local platform's line
600: * terminating convention. <p>
601: *
602: * If the message is really a MboxMessage, use its writeToFile
603: * method, which has access to the UNIX From line. Otherwise, do
604: * all the work here, creating an appropriate UNIX From line.
605: */
606: public static void writeMboxMessage(MimeMessage msg, OutputStream os)
607: throws IOException, MessagingException {
608: try {
609: if (msg instanceof MboxMessage) {
610: ((MboxMessage) msg).writeToFile(os);
611: } else {
612: // XXX - modify the message to preserve the flags in headers
613: MboxMessage.setHeadersFromFlags(msg);
614: ContentLengthCounter cos = new ContentLengthCounter();
615: NewlineOutputStream nos = new NewlineOutputStream(cos);
616: msg.writeTo(nos);
617: nos.flush();
618: os = new NewlineOutputStream(os, true);
619: os = new ContentLengthUpdater(os, cos.getSize());
620: PrintStream pos = new PrintStream(os, false, "iso-8859-1");
621: pos.println(getUnixFrom(msg));
622: msg.writeTo(pos);
623: pos.flush();
624: }
625: } catch (MessagingException | IOException me) {
626: throw me;
627: }
628: }
629:
630: /**
631: * Construct an appropriately formatted UNIX From line using
632: * the sender address and the date in the message.
633: */
634: protected static String getUnixFrom(MimeMessage msg) {
635: Address[] afrom;
636: String from;
637: Date ddate;
638: String date;
639: try {
640: if ((afrom = msg.getFrom()) == null ||
641: !(afrom[0] instanceof InternetAddress) ||
642: (from = ((InternetAddress) afrom[0]).getAddress()) == null)
643: from = "UNKNOWN";
644: if ((ddate = msg.getReceivedDate()) == null ||
645: (ddate = msg.getSentDate()) == null)
646: ddate = new Date();
647: } catch (MessagingException e) {
648: from = "UNKNOWN";
649: ddate = new Date();
650: }
651: date = ddate.toString();
652: // date is of the form "Sat Aug 12 02:30:00 PDT 1995"
653: // need to strip out the timezone
654: return "From " + from + " " +
655: date.substring(0, 20) + date.substring(24);
656: }
657:
658: public synchronized int getMessageCount() throws MessagingException {
659: if (!opened)
660: return -1;
661:
662: boolean locked = false;
663: Message[] msglist = null;
664: try {
665: if (folder.length() != file_size) {
666: if (!folder.lock("r"))
667: throw new MessagingException("Failed to lock folder: " +
668: name);
669: locked = true;
670: msglist = load(file_size, true);
671: }
672: } catch (IOException e) {
673: throw new MessagingException("I/O Exception", e);
674: } finally {
675: if (locked) {
676: folder.unlock();
677: if (msglist != null)
678: notifyMessageAddedListeners(msglist);
679: }
680: }
681: return total;
682: }
683:
684: /**
685: * Get the specified message. Note that messages are numbered
686: * from 1.
687: */
688: public synchronized Message getMessage(int msgno)
689: throws MessagingException {
690: checkReadable();
691: checkRange(msgno);
692:
693: MessageMetadata md = messages.get(messageIndexOf(msgno));
694: MboxMessage m = md.message;
695: if (m == null) {
696: InputStream is = getMessageStream(msgno);
697: try {
698: m = loadMessage(is, msgno, mode == READ_WRITE);
699: } catch (IOException ex) {
700: MessagingException mex =
701: new MessageRemovedException("mbox message gone", ex);
702: throw mex;
703: }
704: md.message = m;
705: }
706: return m;
707: }
708:
709: private final int messageIndexOf(int msgno) {
710: return special_imap_message ? msgno : msgno - 1;
711: }
712:
713: private InputStream getMessageStream(int msgno) {
714: int index = messageIndexOf(msgno);
715: MessageMetadata md = messages.get(index);
716: return temp.newStream(md.start, md.dataend);
717: }
718:
719: public synchronized void appendMessages(Message[] msgs)
720: throws MessagingException {
721: if (!folder.lock("rw"))
722: throw new MessagingException("Failed to lock folder: " + name);
723:
724: OutputStream os = null;
725: boolean err = false;
726: try {
727: os = new BufferedOutputStream(
728: new FileOutputStream(((File) folder).getPath(), true));
729: // XXX - should use getAbsolutePath()?
730: for (int i = 0; i < msgs.length; i++) {
731: if (msgs[i] instanceof MimeMessage) {
732: writeMboxMessage((MimeMessage) msgs[i], os);
733: } else {
734: err = true;
735: continue;
736: }
737: folder.touchlock();
738: }
739: } catch (IOException e) {
740: throw new MessagingException("I/O Exception", e);
741: } catch (MessagingException e) {
742: throw e;
743: } catch (Exception e) {
744: e.printStackTrace();
745: throw new MessagingException("unexpected exception " + e);
746: } finally {
747: if (os != null)
748: try {
749: os.close();
750: } catch (IOException e) {
751: // ignored
752: }
753: folder.unlock();
754: }
755: if (opened)
756: getMessageCount(); // loads new messages as a side effect
757: if (err)
758: throw new MessagingException("Can't append non-Mime message");
759: }
760:
761: public synchronized Message[] expunge() throws MessagingException {
762: checkWritable();
763:
764: /*
765: * First, write out the folder to make sure we have permission,
766: * disk space, etc.
767: */
768: int wr = total; // number of messages written out
769: try {
770: wr = writeFolder(false, true);
771: } catch (IOException e) {
772: throw new MessagingException("expunge failed", e);
773: }
774: if (wr == total) // wrote them all => nothing expunged
775: return new Message[0];
776:
777: /*
778: * Now, actually get rid of the expunged messages.
779: */
780: int del = 0;
781: Message[] msglist = new Message[total - wr];
782: int msgno = 1;
783: while (msgno <= total) {
784: MessageMetadata md = messages.get(messageIndexOf(msgno));
785: MboxMessage msg = md.message;
786: if (msg != null) {
787: if (msg.isSet(Flags.Flag.DELETED)) {
788: msg.setExpunged(true);
789: msglist[del] = msg;
790: del++;
791: messages.remove(messageIndexOf(msgno));
792: total--;
793: } else {
794: msg.setMessageNumber(msgno); // update message number
795: msgno++;
796: }
797: } else {
798: if (md.deleted) {
799: // have to instantiate it for the notification
800: msg = (MboxMessage) getMessage(msgno);
801: msg.setExpunged(true);
802: msglist[del] = msg;
803: del++;
804: messages.remove(messageIndexOf(msgno));
805: total--;
806: } else {
807: msgno++;
808: }
809: }
810: }
811: if (del != msglist.length) // this is really an assert
812: throw new MessagingException("expunge delete count wrong");
813: notifyMessageRemovedListeners(true, msglist);
814: return msglist;
815: }
816:
817: /*
818: * Load more messages from the folder starting at the specified offset.
819: */
820: private Message[] load(long offset, boolean notify)
821: throws MessagingException, IOException {
822: int oldtotal = total;
823: MessageLoader loader = new MessageLoader(temp);
824: int loaded = loader.load(folder.getFD(), offset, messages);
825: total += loaded;
826: file_size = folder.length();
827:
828: if (offset == 0 && loaded > 0) {
829: /*
830: * If the first message is the special message that the
831: * IMAP server adds to the mailbox, remember that we've
832: * seen it so it won't be shown to the user.
833: */
834: MessageMetadata md = messages.get(0);
835: if (md.imap) {
836: special_imap_message = true;
837: total--;
838: }
839: }
840: if (notify) {
841: Message[] msglist = new Message[total - oldtotal];
842: for (int i = oldtotal, j = 0; i < total; i++, j++)
843: msglist[j] = getMessage(i + 1);
844: return msglist;
845: } else
846: return null;
847: }
848:
849: /**
850: * Parse the input stream and return an appropriate message object.
851: * The InputStream must be a SharedInputStream.
852: */
853: private MboxMessage loadMessage(InputStream is, int msgno,
854: boolean writable) throws MessagingException, IOException {
855: LineInputStream in = new LineInputStream(is);
856:
857: /*
858: * Read lines until a UNIX From line,
859: * skipping blank lines.
860: */
861: String line;
862: String unix_from = null;
863: while ((line = in.readLine()) != null) {
864: if (line.trim().length() == 0)
865: continue;
866: if (line.startsWith("From ")) {
867: /*
868: * A UNIX From line looks like:
869: * From address Day Mon DD HH:MM:SS YYYY
870: */
871: unix_from = line;
872: int i;
873: // find the space after the address, before the date
874: i = unix_from.indexOf(' ', 5);
875: if (i < 0)
876: continue; // not a valid UNIX From line
877: break;
878: }
879: throw new MessagingException("Garbage in mailbox: " + line);
880: }
881:
882: if (unix_from == null)
883: throw new EOFException("end of mailbox");
884:
885: /*
886: * Now load the RFC822 headers into an InternetHeaders object.
887: */
888: InternetHeaders hdrs = new InternetHeaders(is);
889:
890: // the rest is the message content
891: SharedInputStream sis = (SharedInputStream) is;
892: InputStream stream = sis.newStream(sis.getPosition(), -1);
893: return new MboxMessage(this, hdrs, stream, msgno, unix_from, writable);
894: }
895:
896: /*
897: * Only here to make accessible to MboxMessage.
898: */
899: protected void notifyMessageChangedListeners(int type, Message m) {
900: super.notifyMessageChangedListeners(type, m);
901: }
902:
903:
904: /**
905: * this is an exact duplicate of the Folder.getURL except it doesn't
906: * add a beginning '/' to the URLName.
907: */
908: public URLName getURLName() {
909: // XXX - note: this should not be done this way with the
910: // new jakarta.mail apis.
911:
912: URLName storeURL = getStore().getURLName();
913: if (name == null)
914: return storeURL;
915:
916: char separator = getSeparator();
917: String fullname = getFullName();
918: StringBuilder encodedName = new StringBuilder();
919:
920: // We need to encode each of the folder's names, and replace
921: // the store's separator char with the URL char '/'.
922: StringTokenizer tok = new StringTokenizer(
923: fullname, Character.toString(separator), true);
924:
925: while (tok.hasMoreTokens()) {
926: String s = tok.nextToken();
927: if (s.charAt(0) == separator)
928: encodedName.append("/");
929: else
930: // XXX - should encode, but since there's no decoder...
931: //encodedName.append(java.net.URLEncoder.encode(s));
932: encodedName.append(s);
933: }
934:
935: return new URLName(storeURL.getProtocol(), storeURL.getHost(),
936: storeURL.getPort(), encodedName.toString(),
937: storeURL.getUsername(),
938: null /* no password */);
939: }
940:
941: /**
942: * Create an MboxFolder object, or a subclass thereof.
943: * Can be overridden by subclasses of MboxFolder so that
944: * the appropriate subclass is created by the list method.
945: */
946: protected Folder createFolder(MboxStore store, String name) {
947: return new MboxFolder(store, name);
948: }
949:
950: /*
951: * Support routines for list().
952: */
953:
954: /**
955: * Return a canonicalized pattern given a reference name and a pattern.
956: */
957: private static String canonicalize(String ref, String pat) {
958: if (ref == null)
959: return pat;
960: try {
961: if (pat.length() == 0) {
962: return ref;
963: } else if (pat.charAt(0) == File.separatorChar) {
964: return ref.substring(0, ref.indexOf(File.separatorChar)) + pat;
965: } else {
966: return ref + pat;
967: }
968: } catch (StringIndexOutOfBoundsException e) {
969: return pat;
970: }
971: }
972:
973: /**
974: * Return the first index of any of the characters in "any" in "s",
975: * or -1 if none are found.
976: *
977: * This should be a method on String.
978: */
979: private static int indexOfAny(String s, String any) {
980: try {
981: int len = s.length();
982: for (int i = 0; i < len; i++) {
983: if (any.indexOf(s.charAt(i)) >= 0)
984: return i;
985: }
986: return -1;
987: } catch (StringIndexOutOfBoundsException e) {
988: return -1;
989: }
990: }
991:
992: /**
993: * The recursive part of generating the list of mailboxes.
994: * realdir is the full pathname to the directory to search.
995: * dir is the name the user uses, often a relative name that's
996: * relative to the user's home directory. dir (if not null) always
997: * has a trailing file separator character.
998: *
999: * @param realdir real pathname of directory to start looking in
1000: * @param dir user's name for realdir
1001: * @param pat pattern to match against
1002: * @param level level of the directory hierarchy we're in
1003: * @param flist list to which to add folder names that match
1004: */
1005: // Derived from the c-client listWork() function.
1006: private void listWork(String realdir, String dir, String pat,
1007: int level, List<String> flist) {
1008: String[] sl;
1009: File fdir = new File(realdir);
1010: try {
1011: sl = fdir.list();
1012: } catch (SecurityException e) {
1013: return; // can't read it, ignore it
1014: }
1015:
1016: if (level == 0 && dir != null &&
1017: Match.path(dir, pat, File.separatorChar))
1018: flist.add(dir);
1019:
1020: if (sl == null)
1021: return; // nothing return, we're done
1022:
1023: if (realdir.charAt(realdir.length() - 1) != File.separatorChar)
1024: realdir += File.separator;
1025:
1026: for (int i = 0; i < sl.length; i++) {
1027: if (sl[i].charAt(0) == '.')
1028: continue; // ignore all "dot" files for now
1029: String md = realdir + sl[i];
1030: File mf = new File(md);
1031: if (!mf.exists())
1032: continue;
1033: String name;
1034: if (dir != null)
1035: name = dir + sl[i];
1036: else
1037: name = sl[i];
1038: if (mf.isDirectory()) {
1039: if (Match.path(name, pat, File.separatorChar)) {
1040: flist.add(name);
1041: name += File.separator;
1042: } else {
1043: name += File.separator;
1044: if (Match.path(name, pat, File.separatorChar))
1045: flist.add(name);
1046: }
1047: if (Match.dir(name, pat, File.separatorChar))
1048: listWork(md, name, pat, level + 1, flist);
1049: } else {
1050: if (Match.path(name, pat, File.separatorChar))
1051: flist.add(name);
1052: }
1053: }
1054: }
1055: }
1056:
1057: /**
1058: * Pattern matching support class for list().
1059: * Should probably be more public.
1060: */
1061: // Translated from the c-client functions pmatch_full() and dmatch().
1062: class Match {
1063: /**
1064: * Pathname pattern match
1065: *
1066: * @param s base string
1067: * @param pat pattern string
1068: * @param delim delimiter character
1069: * @return true if base matches pattern
1070: */
1071: static public boolean path(String s, String pat, char delim) {
1072: try {
1073: return path(s, 0, s.length(), pat, 0, pat.length(), delim);
1074: } catch (StringIndexOutOfBoundsException e) {
1075: return false;
1076: }
1077: }
1078:
1079: static private boolean path(String s, int s_index, int s_len,
1080: String pat, int p_index, int p_len, char delim)
1081: throws StringIndexOutOfBoundsException {
1082:
1083: while (p_index < p_len) {
1084: char c = pat.charAt(p_index);
1085: switch (c) {
1086: case '%':
1087: if (++p_index >= p_len) // % at end of pattern
1088: // ok if no delimiters
1089: return delim == 0 || s.indexOf(delim, s_index) < 0;
1090: // scan remainder until delimiter
1091: do {
1092: if (path(s, s_index, s_len, pat, p_index, p_len, delim))
1093: return true;
1094: } while (s.charAt(s_index) != delim && ++s_index < s_len);
1095: // ran into a delimiter or ran out of string without a match
1096: return false;
1097:
1098: case '*':
1099: if (++p_index >= p_len) // end of pattern?
1100: return true; // unconditional match
1101: do {
1102: if (path(s, s_index, s_len, pat, p_index, p_len, delim))
1103: return true;
1104: } while (++s_index < s_len);
1105: // ran out of string without a match
1106: return false;
1107:
1108: default:
1109: // if ran out of string or no match, fail
1110: if (s_index >= s_len || c != s.charAt(s_index))
1111: return false;
1112:
1113: // try the next string and pattern characters
1114: s_index++;
1115: p_index++;
1116: }
1117: }
1118: return s_index >= s_len;
1119: }
1120:
1121: /**
1122: * Directory pattern match
1123: *
1124: * @param s base string
1125: * @param pat pattern string
1126: * @return true if base is a matching directory of pattern
1127: */
1128: static public boolean dir(String s, String pat, char delim) {
1129: try {
1130: return dir(s, 0, s.length(), pat, 0, pat.length(), delim);
1131: } catch (StringIndexOutOfBoundsException e) {
1132: return false;
1133: }
1134: }
1135:
1136: static private boolean dir(String s, int s_index, int s_len,
1137: String pat, int p_index, int p_len, char delim)
1138: throws StringIndexOutOfBoundsException {
1139:
1140: while (p_index < p_len) {
1141: char c = pat.charAt(p_index);
1142: switch (c) {
1143: case '%':
1144: if (s_index >= s_len) // end of base?
1145: return true; // subset match
1146: if (++p_index >= p_len) // % at end of pattern?
1147: return false; // no inferiors permitted
1148: do {
1149: if (dir(s, s_index, s_len, pat, p_index, p_len, delim))
1150: return true;
1151: } while (s.charAt(s_index) != delim && ++s_index < s_len);
1152:
1153: if (s_index + 1 == s_len) // s ends with a delimiter
1154: return true; // must be a subset of pattern
1155: return dir(s, s_index, s_len, pat, p_index, p_len, delim);
1156:
1157: case '*':
1158: return true; // unconditional match
1159:
1160: default:
1161: if (s_index >= s_len) // end of base?
1162: return c == delim; // matched if at delimiter
1163:
1164: if (c != s.charAt(s_index))
1165: return false;
1166:
1167: // try the next string and pattern characters
1168: s_index++;
1169: p_index++;
1170: }
1171: }
1172: return s_index >= s_len;
1173: }
1174: }
1175:
1176: /**
1177: * A ByteArrayOutputStream that allows us to share the byte array
1178: * rather than copy it. Eventually could replace this with something
1179: * that doesn't require a single contiguous byte array.
1180: */
1181: class SharedByteArrayOutputStream extends ByteArrayOutputStream {
1182: public SharedByteArrayOutputStream(int size) {
1183: super(size);
1184: }
1185:
1186: public InputStream toStream() {
1187: return new SharedByteArrayInputStream(buf, 0, count);
1188: }
1189: }