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