-/*
- * Copyright (c) 1997, 2018 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.mbox;
-
-import javax.mail.*;
-import javax.mail.event.*;
-import javax.mail.internet.*;
-import javax.mail.util.*;
-import java.io.*;
-import java.util.*;
-import com.sun.mail.util.LineInputStream;
-
-/**
- * This class represents a mailbox file containing RFC822 style email messages.
- *
- * @author John Mani
- * @author Bill Shannon
- */
-
-public class MboxFolder extends Folder {
-
- private String name; // null => the default folder
- private boolean is_inbox = false;
- private int total; // total number of messages in mailbox
- private volatile boolean opened = false;
- private List<MessageMetadata> messages;
- private TempFile temp;
- private MboxStore mstore;
- private MailFile folder;
- private long file_size; // the size the last time we read or wrote it
- private long saved_file_size; // size at the last open, close, or expunge
- private boolean special_imap_message;
-
- private static final boolean homeRelative =
- Boolean.getBoolean("mail.mbox.homerelative");
-
- /**
- * Metadata for each message, to avoid instantiating MboxMessage
- * objects for messages we're not going to look at. <p>
- *
- * MboxFolder keeps an array of these objects corresponding to
- * each message in the folder. Access to the array elements is
- * synchronized, but access to contents of this object is not.
- * The metadata stored here is only accessed if the message field
- * is null; otherwise the MboxMessage object contains the metadata.
- */
- static final class MessageMetadata {
- public long start; // offset in temp file of start of this message
- // public long end; // offset in temp file of end of this message
- public long dataend; // offset of end of message data, <= "end"
- public MboxMessage message; // the message itself
- public boolean recent; // message is recent?
- public boolean deleted; // message is marked deleted?
- public boolean imap; // special imap message?
- }
-
- public MboxFolder(MboxStore store, String name) {
- super(store);
- this.mstore = store;
- this.name = name;
-
- if (name != null && name.equalsIgnoreCase("INBOX"))
- is_inbox = true;
-
- folder = mstore.getMailFile(name == null ? "~" : name);
- if (folder.exists())
- saved_file_size = folder.length();
- else
- saved_file_size = -1;
- }
-
- public char getSeparator() {
- return File.separatorChar;
- }
-
- public Folder[] list(String pattern) throws MessagingException {
- if (!folder.isDirectory())
- throw new MessagingException("not a directory");
-
- if (name == null)
- return list(null, pattern, true);
- else
- return list(name + File.separator, pattern, false);
- }
-
- /*
- * Version of list shared by MboxStore and MboxFolder.
- */
- protected Folder[] list(String ref, String pattern, boolean fromStore)
- throws MessagingException {
- if (ref != null && ref.length() == 0)
- ref = null;
- int i;
- String refdir = null;
- String realdir = null;
-
- pattern = canonicalize(ref, pattern);
- if ((i = indexOfAny(pattern, "%*")) >= 0) {
- refdir = pattern.substring(0, i);
- } else {
- refdir = pattern;
- }
- if ((i = refdir.lastIndexOf(File.separatorChar)) >= 0) {
- // get rid of anything after directory name
- refdir = refdir.substring(0, i + 1);
- realdir = mstore.mb.filename(mstore.user, refdir);
- } else if (refdir.length() == 0 || refdir.charAt(0) != '~') {
- // no separator and doesn't start with "~" => home or cwd
- refdir = null;
- if (homeRelative)
- realdir = mstore.home;
- else
- realdir = ".";
- } else {
- realdir = mstore.mb.filename(mstore.user, refdir);
- }
- List<String> flist = new ArrayList<String>();
- listWork(realdir, refdir, pattern, fromStore ? 0 : 1, flist);
- if (Match.path("INBOX", pattern, '\0'))
- flist.add("INBOX");
-
- Folder fl[] = new Folder[flist.size()];
- for (i = 0; i < fl.length; i++) {
- fl[i] = createFolder(mstore, flist.get(i));
- }
- return fl;
- }
-
- public String getName() {
- if (name == null)
- return "";
- else if (is_inbox)
- return "INBOX";
- else
- return folder.getName();
- }
-
- public String getFullName() {
- if (name == null)
- return "";
- else
- return name;
- }
-
- public Folder getParent() {
- if (name == null)
- return null;
- else if (is_inbox)
- return createFolder(mstore, null);
- else
- // XXX - have to recognize other folders under default folder
- return createFolder(mstore, folder.getParent());
- }
-
- public boolean exists() {
- return folder.exists();
- }
-
- public int getType() {
- if (folder.isDirectory())
- return HOLDS_FOLDERS;
- else
- return HOLDS_MESSAGES;
- }
-
- public Flags getPermanentFlags() {
- return MboxStore.permFlags;
- }
-
- public synchronized boolean hasNewMessages() {
- if (folder instanceof UNIXFile) {
- UNIXFile f = (UNIXFile)folder;
- if (f.length() > 0) {
- long atime = f.lastAccessed();
- long mtime = f.lastModified();
-//System.out.println(name + " atime " + atime + " mtime " + mtime);
- return atime < mtime;
- }
- return false;
- }
- long current_size;
- if (folder.exists())
- current_size = folder.length();
- else
- current_size = -1;
- // if we've never opened the folder, remember the size now
- // (will cause us to return false the first time)
- if (saved_file_size < 0)
- saved_file_size = current_size;
- return current_size > saved_file_size;
- }
-
- public synchronized Folder getFolder(String name)
- throws MessagingException {
- if (folder.exists() && !folder.isDirectory())
- throw new MessagingException("not a directory");
- return createFolder(mstore,
- (this.name == null ? "~" : this.name) + File.separator + name);
- }
-
- public synchronized boolean create(int type) throws MessagingException {
- switch (type) {
- case HOLDS_FOLDERS:
- if (!folder.mkdirs()) {
- return false;
- }
- break;
-
- case HOLDS_MESSAGES:
- if (folder.exists()) {
- return false;
- }
- try {
- (new FileOutputStream((File)folder)).close();
- } catch (FileNotFoundException fe) {
- File parent = new File(folder.getParent());
- if (!parent.mkdirs())
- throw new
- MessagingException("can't create folder: " + name);
- try {
- (new FileOutputStream((File)folder)).close();
- } catch (IOException ex3) {
- throw new
- MessagingException("can't create folder: " + name, ex3);
- }
- } catch (IOException e) {
- throw new
- MessagingException("can't create folder: " + name, e);
- }
- break;
-
- default:
- throw new MessagingException("type not supported");
- }
- notifyFolderListeners(FolderEvent.CREATED);
- return true;
- }
-
- public synchronized boolean delete(boolean recurse)
- throws MessagingException {
- checkClosed();
- if (name == null)
- throw new MessagingException("can't delete default folder");
- boolean ret = true;
- if (recurse && folder.isDirectory())
- ret = delete(new File(folder.getPath()));
- if (ret && folder.delete()) {
- notifyFolderListeners(FolderEvent.DELETED);
- return true;
- }
- return false;
- }
-
- /**
- * Recursively delete the specified file/directory.
- */
- private boolean delete(File f) {
- File[] files = f.listFiles();
- boolean ret = true;
- for (int i = 0; ret && i < files.length; i++) {
- if (files[i].isDirectory())
- ret = delete(files[i]);
- else
- ret = files[i].delete();
- }
- return ret;
- }
-
- public synchronized boolean renameTo(Folder f)
- throws MessagingException {
- checkClosed();
- if (name == null)
- throw new MessagingException("can't rename default folder");
- if (!(f instanceof MboxFolder))
- throw new MessagingException("can't rename to: " + f.getName());
- String newname = ((MboxFolder)f).folder.getPath();
- if (folder.renameTo(new File(newname))) {
- notifyFolderRenamedListeners(f);
- return true;
- }
- return false;
- }
-
- /* Ensure the folder is open */
- void checkOpen() throws IllegalStateException {
- if (!opened)
- throw new IllegalStateException("Folder is not Open");
- }
-
- /* Ensure the folder is not open */
- private void checkClosed() throws IllegalStateException {
- if (opened)
- throw new IllegalStateException("Folder is Open");
- }
-
- /*
- * 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 check to see if new messages
- * have arrived.
- */
- private 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 check if there are any new messages.
- getMessageCount();
-
- if (msgno > total) // Still out of range ? Throw up ...
- throw new IndexOutOfBoundsException(msgno + " > " + total);
- }
-
- /* Ensure the folder is open & readable */
- private void checkReadable() throws IllegalStateException {
- if (!opened || (mode != READ_ONLY && mode != READ_WRITE))
- throw new IllegalStateException("Folder is not Readable");
- }
-
- /* Ensure the folder is open & writable */
- private void checkWritable() throws IllegalStateException {
- if (!opened || mode != READ_WRITE)
- throw new IllegalStateException("Folder is not Writable");
- }
-
- public boolean isOpen() {
- return opened;
- }
-
- /*
- * Open the folder in the specified mode.
- */
- public synchronized void open(int mode) throws MessagingException {
- if (opened)
- throw new IllegalStateException("Folder is already Open");
-
- if (!folder.exists())
- throw new FolderNotFoundException(this, "Folder doesn't exist: " +
- folder.getPath());
- this.mode = mode;
- switch (mode) {
- case READ_WRITE:
- default:
- if (!folder.canWrite())
- throw new MessagingException("Open Failure, can't write: " +
- folder.getPath());
- // fall through...
-
- case READ_ONLY:
- if (!folder.canRead())
- throw new MessagingException("Open Failure, can't read: " +
- folder.getPath());
- break;
- }
-
- if (is_inbox && folder instanceof InboxFile) {
- InboxFile inf = (InboxFile)folder;
- if (!inf.openLock(mode == READ_WRITE ? "rw" : "r"))
- throw new MessagingException("Failed to lock INBOX");
- }
- if (!folder.lock("r"))
- throw new MessagingException("Failed to lock folder: " + name);
- messages = new ArrayList<MessageMetadata>();
- total = 0;
- Message[] msglist = null;
- try {
- temp = new TempFile(null);
- saved_file_size = folder.length();
- msglist = load(0L, false);
- } catch (IOException e) {
- throw new MessagingException("IOException", e);
- } finally {
- folder.unlock();
- }
- notifyConnectionListeners(ConnectionEvent.OPENED);
- if (msglist != null)
- notifyMessageAddedListeners(msglist);
- opened = true; // now really opened
- }
-
- public synchronized void close(boolean expunge) throws MessagingException {
- checkOpen();
-
- try {
- if (mode == READ_WRITE) {
- try {
- writeFolder(true, expunge);
- } catch (IOException e) {
- throw new MessagingException("I/O Exception", e);
- }
- }
- messages = null;
- } finally {
- opened = false;
- if (is_inbox && folder instanceof InboxFile) {
- InboxFile inf = (InboxFile)folder;
- inf.closeLock();
- }
- temp.close();
- temp = null;
- notifyConnectionListeners(ConnectionEvent.CLOSED);
- }
- }
-
- /**
- * Re-write the folder with the current contents of the messages.
- * If closing is true, turn off the RECENT flag. If expunge is
- * true, don't write out deleted messages (only used from close()
- * when the message cache won't be accessed again).
- *
- * Return the number of messages written.
- */
- protected int writeFolder(boolean closing, boolean expunge)
- throws IOException, MessagingException {
-
- /*
- * First, see if there have been any changes.
- */
- int modified = 0, deleted = 0, recent = 0;
- for (int msgno = 1; msgno <= total; msgno++) {
- MessageMetadata md = messages.get(messageIndexOf(msgno));
- MboxMessage msg = md.message;
- if (msg != null) {
- Flags flags = msg.getFlags();
- if (msg.isModified() || !msg.origFlags.equals(flags))
- modified++;
- if (flags.contains(Flags.Flag.DELETED))
- deleted++;
- if (flags.contains(Flags.Flag.RECENT))
- recent++;
- } else {
- if (md.deleted)
- deleted++;
- if (md.recent)
- recent++;
- }
- }
- if ((!closing || recent == 0) && (!expunge || deleted == 0) &&
- modified == 0)
- return 0;
-
- /*
- * Have to save any new mail that's been appended to the
- * folder since we last loaded it.
- */
- if (!folder.lock("rw"))
- throw new MessagingException("Failed to lock folder: " + name);
- int oldtotal = total; // XXX
- Message[] msglist = null;
- if (folder.length() != file_size)
- msglist = load(file_size, !closing);
- // don't use the folder's FD, need to re-open in order to trunc the file
- OutputStream os =
- new BufferedOutputStream(new FileOutputStream((File)folder));
- int wr = 0;
- boolean keep = true;
- try {
- if (special_imap_message)
- appendStream(getMessageStream(0), os);
- for (int msgno = 1; msgno <= total; msgno++) {
- MessageMetadata md = messages.get(messageIndexOf(msgno));
- MboxMessage msg = md.message;
- if (msg != null) {
- if (expunge && msg.isSet(Flags.Flag.DELETED))
- continue; // skip it;
- if (closing && msgno <= oldtotal &&
- msg.isSet(Flags.Flag.RECENT))
- msg.setFlag(Flags.Flag.RECENT, false);
- writeMboxMessage(msg, os);
- } else {
- if (expunge && md.deleted)
- continue; // skip it;
- if (closing && msgno <= oldtotal && md.recent) {
- // have to instantiate message so that we can
- // clear the recent flag
- msg = (MboxMessage)getMessage(msgno);
- msg.setFlag(Flags.Flag.RECENT, false);
- writeMboxMessage(msg, os);
- } else {
- appendStream(getMessageStream(msgno), os);
- }
- }
- folder.touchlock();
- wr++;
- }
- // If no messages in the mailbox, and we're closing,
- // maybe we should remove the mailbox.
- if (wr == 0 && closing) {
- String skeep = ((MboxStore)store).getSession().
- getProperty("mail.mbox.deleteEmpty");
- if (skeep != null && skeep.equalsIgnoreCase("true"))
- keep = false;
- }
- } catch (IOException e) {
- throw e;
- } catch (MessagingException e) {
- throw e;
- } catch (Exception e) {
-e.printStackTrace();
- throw new MessagingException("unexpected exception " + e);
- } finally {
- // close the folder, flushing out the data
- try {
- os.close();
- file_size = saved_file_size = folder.length();
- if (!keep) {
- folder.delete();
- file_size = 0;
- }
- } catch (IOException ex) {}
-
- if (keep) {
- // make sure the access time is greater than the mod time
- // XXX - would be nice to have utime()
- try {
- Thread.sleep(1000); // sleep for a second
- } catch (InterruptedException ex) {}
- InputStream is = null;
- try {
- is = new FileInputStream((File)folder);
- is.read(); // read a byte
- } catch (IOException ex) {} // ignore errors
- try {
- if (is != null)
- is.close();
- is = null;
- } catch (IOException ex) {} // ignore errors
- }
-
- folder.unlock();
- if (msglist != null)
- notifyMessageAddedListeners(msglist);
- }
- return wr;
- }
-
- /**
- * Append the input stream to the output stream, closing the
- * input stream when done.
- */
- private static final void appendStream(InputStream is, OutputStream os)
- throws IOException {
- try {
- byte[] buf = new byte[64 * 1024];
- int len;
- while ((len = is.read(buf)) > 0)
- os.write(buf, 0, len);
- } finally {
- is.close();
- }
- }
-
- /**
- * Write a MimeMessage to the specified OutputStream in a
- * format suitable for a UNIX mailbox, i.e., including a correct
- * Content-Length header and with the local platform's line
- * terminating convention. <p>
- *
- * If the message is really a MboxMessage, use its writeToFile
- * method, which has access to the UNIX From line. Otherwise, do
- * all the work here, creating an appropriate UNIX From line.
- */
- public static void writeMboxMessage(MimeMessage msg, OutputStream os)
- throws IOException, MessagingException {
- try {
- if (msg instanceof MboxMessage) {
- ((MboxMessage)msg).writeToFile(os);
- } else {
- // XXX - modify the message to preserve the flags in headers
- MboxMessage.setHeadersFromFlags(msg);
- ContentLengthCounter cos = new ContentLengthCounter();
- NewlineOutputStream nos = new NewlineOutputStream(cos);
- msg.writeTo(nos);
- nos.flush();
- os = new NewlineOutputStream(os, true);
- os = new ContentLengthUpdater(os, cos.getSize());
- PrintStream pos = new PrintStream(os, false, "iso-8859-1");
- pos.println(getUnixFrom(msg));
- msg.writeTo(pos);
- pos.flush();
- }
- } catch (MessagingException me) {
- throw me;
- } catch (IOException ioe) {
- throw ioe;
- }
- }
-
- /**
- * Construct an appropriately formatted UNIX From line using
- * the sender address and the date in the message.
- */
- protected static String getUnixFrom(MimeMessage msg) {
- Address[] afrom;
- String from;
- Date ddate;
- String date;
- try {
- if ((afrom = msg.getFrom()) == null ||
- !(afrom[0] instanceof InternetAddress) ||
- (from = ((InternetAddress)afrom[0]).getAddress()) == null)
- from = "UNKNOWN";
- if ((ddate = msg.getReceivedDate()) == null ||
- (ddate = msg.getSentDate()) == null)
- ddate = new Date();
- } catch (MessagingException e) {
- from = "UNKNOWN";
- ddate = new Date();
- }
- date = ddate.toString();
- // date is of the form "Sat Aug 12 02:30:00 PDT 1995"
- // need to strip out the timezone
- return "From " + from + " " +
- date.substring(0, 20) + date.substring(24);
- }
-
- public synchronized int getMessageCount() throws MessagingException {
- if (!opened)
- return -1;
-
- boolean locked = false;
- Message[] msglist = null;
- try {
- if (folder.length() != file_size) {
- if (!folder.lock("r"))
- throw new MessagingException("Failed to lock folder: " +
- name);
- locked = true;
- msglist = load(file_size, true);
- }
- } catch (IOException e) {
- throw new MessagingException("I/O Exception", e);
- } finally {
- if (locked) {
- folder.unlock();
- if (msglist != null)
- notifyMessageAddedListeners(msglist);
- }
- }
- return total;
- }
-
- /**
- * Get the specified message. Note that messages are numbered
- * from 1.
- */
- public synchronized Message getMessage(int msgno)
- throws MessagingException {
- checkReadable();
- checkRange(msgno);
-
- MessageMetadata md = messages.get(messageIndexOf(msgno));
- MboxMessage m = md.message;
- if (m == null) {
- InputStream is = getMessageStream(msgno);
- try {
- m = loadMessage(is, msgno, mode == READ_WRITE);
- } catch (IOException ex) {
- MessagingException mex =
- new MessageRemovedException("mbox message gone", ex);
- throw mex;
- }
- md.message = m;
- }
- return m;
- }
-
- private final int messageIndexOf(int msgno) {
- return special_imap_message ? msgno : msgno - 1;
- }
-
- private InputStream getMessageStream(int msgno) {
- int index = messageIndexOf(msgno);
- MessageMetadata md = messages.get(index);
- return temp.newStream(md.start, md.dataend);
- }
-
- public synchronized void appendMessages(Message[] msgs)
- throws MessagingException {
- if (!folder.lock("rw"))
- throw new MessagingException("Failed to lock folder: " + name);
-
- OutputStream os = null;
- boolean err = false;
- try {
- os = new BufferedOutputStream(
- new FileOutputStream(((File)folder).getPath(), true));
- // XXX - should use getAbsolutePath()?
- for (int i = 0; i < msgs.length; i++) {
- if (msgs[i] instanceof MimeMessage) {
- writeMboxMessage((MimeMessage)msgs[i], os);
- } else {
- err = true;
- continue;
- }
- folder.touchlock();
- }
- } catch (IOException e) {
- throw new MessagingException("I/O Exception", e);
- } catch (MessagingException e) {
- throw e;
- } catch (Exception e) {
-e.printStackTrace();
- throw new MessagingException("unexpected exception " + e);
- } finally {
- if (os != null)
- try {
- os.close();
- } catch (IOException e) {
- // ignored
- }
- folder.unlock();
- }
- if (opened)
- getMessageCount(); // loads new messages as a side effect
- if (err)
- throw new MessagingException("Can't append non-Mime message");
- }
-
- public synchronized Message[] expunge() throws MessagingException {
- checkWritable();
-
- /*
- * First, write out the folder to make sure we have permission,
- * disk space, etc.
- */
- int wr = total; // number of messages written out
- try {
- wr = writeFolder(false, true);
- } catch (IOException e) {
- throw new MessagingException("expunge failed", e);
- }
- if (wr == total) // wrote them all => nothing expunged
- return new Message[0];
-
- /*
- * Now, actually get rid of the expunged messages.
- */
- int del = 0;
- Message[] msglist = new Message[total - wr];
- int msgno = 1;
- while (msgno <= total) {
- MessageMetadata md = messages.get(messageIndexOf(msgno));
- MboxMessage msg = md.message;
- if (msg != null) {
- if (msg.isSet(Flags.Flag.DELETED)) {
- msg.setExpunged(true);
- msglist[del] = msg;
- del++;
- messages.remove(messageIndexOf(msgno));
- total--;
- } else {
- msg.setMessageNumber(msgno); // update message number
- msgno++;
- }
- } else {
- if (md.deleted) {
- // have to instantiate it for the notification
- msg = (MboxMessage)getMessage(msgno);
- msg.setExpunged(true);
- msglist[del] = msg;
- del++;
- messages.remove(messageIndexOf(msgno));
- total--;
- } else {
- msgno++;
- }
- }
- }
- if (del != msglist.length) // this is really an assert
- throw new MessagingException("expunge delete count wrong");
- notifyMessageRemovedListeners(true, msglist);
- return msglist;
- }
-
- /*
- * Load more messages from the folder starting at the specified offset.
- */
- private Message[] load(long offset, boolean notify)
- throws MessagingException, IOException {
- int oldtotal = total;
- MessageLoader loader = new MessageLoader(temp);
- int loaded = loader.load(folder.getFD(), offset, messages);
- total += loaded;
- file_size = folder.length();
-
- if (offset == 0 && loaded > 0) {
- /*
- * If the first message is the special message that the
- * IMAP server adds to the mailbox, remember that we've
- * seen it so it won't be shown to the user.
- */
- MessageMetadata md = messages.get(0);
- if (md.imap) {
- special_imap_message = true;
- total--;
- }
- }
- if (notify) {
- Message[] msglist = new Message[total - oldtotal];
- for (int i = oldtotal, j = 0; i < total; i++, j++)
- msglist[j] = getMessage(i + 1);
- return msglist;
- } else
- return null;
- }
-
- /**
- * Parse the input stream and return an appropriate message object.
- * The InputStream must be a SharedInputStream.
- */
- private MboxMessage loadMessage(InputStream is, int msgno,
- boolean writable) throws MessagingException, IOException {
- LineInputStream in = new LineInputStream(is);
-
- /*
- * Read lines until a UNIX From line,
- * skipping blank lines.
- */
- String line;
- String unix_from = null;
- while ((line = in.readLine()) != null) {
- if (line.trim().length() == 0)
- continue;
- if (line.startsWith("From ")) {
- /*
- * A UNIX From line looks like:
- * From address Day Mon DD HH:MM:SS YYYY
- */
- unix_from = line;
- int i;
- // find the space after the address, before the date
- i = unix_from.indexOf(' ', 5);
- if (i < 0)
- continue; // not a valid UNIX From line
- break;
- }
- throw new MessagingException("Garbage in mailbox: " + line);
- }
-
- if (unix_from == null)
- throw new EOFException("end of mailbox");
-
- /*
- * Now load the RFC822 headers into an InternetHeaders object.
- */
- InternetHeaders hdrs = new InternetHeaders(is);
-
- // the rest is the message content
- SharedInputStream sis = (SharedInputStream)is;
- InputStream stream = sis.newStream(sis.getPosition(), -1);
- return new MboxMessage(this, hdrs, stream, msgno, unix_from, writable);
- }
-
- /*
- * Only here to make accessible to MboxMessage.
- */
- protected void notifyMessageChangedListeners(int type, Message m) {
- super.notifyMessageChangedListeners(type, m);
- }
-
-
- /**
- * this is an exact duplicate of the Folder.getURL except it doesn't
- * add a beginning '/' to the URLName.
- */
- public URLName getURLName() {
- // XXX - note: this should not be done this way with the
- // new javax.mail apis.
-
- URLName storeURL = getStore().getURLName();
- if (name == null)
- return storeURL;
-
- char separator = getSeparator();
- String fullname = getFullName();
- StringBuilder encodedName = new StringBuilder();
-
- // We need to encode each of the folder's names, and replace
- // the store's separator char with the URL char '/'.
- StringTokenizer tok = new StringTokenizer(
- fullname, Character.toString(separator), true);
-
- while (tok.hasMoreTokens()) {
- String s = tok.nextToken();
- if (s.charAt(0) == separator)
- encodedName.append("/");
- else
- // XXX - should encode, but since there's no decoder...
- //encodedName.append(java.net.URLEncoder.encode(s));
- encodedName.append(s);
- }
-
- return new URLName(storeURL.getProtocol(), storeURL.getHost(),
- storeURL.getPort(), encodedName.toString(),
- storeURL.getUsername(),
- null /* no password */);
- }
-
- /**
- * Create an MboxFolder object, or a subclass thereof.
- * Can be overridden by subclasses of MboxFolder so that
- * the appropriate subclass is created by the list method.
- */
- protected Folder createFolder(MboxStore store, String name) {
- return new MboxFolder(store, name);
- }
-
- /*
- * Support routines for list().
- */
-
- /**
- * Return a canonicalized pattern given a reference name and a pattern.
- */
- private static String canonicalize(String ref, String pat) {
- if (ref == null)
- return pat;
- try {
- if (pat.length() == 0) {
- return ref;
- } else if (pat.charAt(0) == File.separatorChar) {
- return ref.substring(0, ref.indexOf(File.separatorChar)) + pat;
- } else {
- return ref + pat;
- }
- } catch (StringIndexOutOfBoundsException e) {
- return pat;
- }
- }
-
- /**
- * Return the first index of any of the characters in "any" in "s",
- * or -1 if none are found.
- *
- * This should be a method on String.
- */
- private static int indexOfAny(String s, String any) {
- try {
- int len = s.length();
- for (int i = 0; i < len; i++) {
- if (any.indexOf(s.charAt(i)) >= 0)
- return i;
- }
- return -1;
- } catch (StringIndexOutOfBoundsException e) {
- return -1;
- }
- }
-
- /**
- * The recursive part of generating the list of mailboxes.
- * realdir is the full pathname to the directory to search.
- * dir is the name the user uses, often a relative name that's
- * relative to the user's home directory. dir (if not null) always
- * has a trailing file separator character.
- *
- * @param realdir real pathname of directory to start looking in
- * @param dir user's name for realdir
- * @param pat pattern to match against
- * @param level level of the directory hierarchy we're in
- * @param flist list to which to add folder names that match
- */
- // Derived from the c-client listWork() function.
- private void listWork(String realdir, String dir, String pat,
- int level, List<String> flist) {
- String sl[];
- File fdir = new File(realdir);
- try {
- sl = fdir.list();
- } catch (SecurityException e) {
- return; // can't read it, ignore it
- }
-
- if (level == 0 && dir != null &&
- Match.path(dir, pat, File.separatorChar))
- flist.add(dir);
-
- if (sl == null)
- return; // nothing return, we're done
-
- if (realdir.charAt(realdir.length() - 1) != File.separatorChar)
- realdir += File.separator;
-
- for (int i = 0; i < sl.length; i++) {
- if (sl[i].charAt(0) == '.')
- continue; // ignore all "dot" files for now
- String md = realdir + sl[i];
- File mf = new File(md);
- if (!mf.exists())
- continue;
- String name;
- if (dir != null)
- name = dir + sl[i];
- else
- name = sl[i];
- if (mf.isDirectory()) {
- if (Match.path(name, pat, File.separatorChar)) {
- flist.add(name);
- name += File.separator;
- } else {
- name += File.separator;
- if (Match.path(name, pat, File.separatorChar))
- flist.add(name);
- }
- if (Match.dir(name, pat, File.separatorChar))
- listWork(md, name, pat, level + 1, flist);
- } else {
- if (Match.path(name, pat, File.separatorChar))
- flist.add(name);
- }
- }
- }
-}
-
-/**
- * Pattern matching support class for list().
- * Should probably be more public.
- */
-// Translated from the c-client functions pmatch_full() and dmatch().
-class Match {
- /**
- * Pathname pattern match
- *
- * @param s base string
- * @param pat pattern string
- * @param delim delimiter character
- * @return true if base matches pattern
- */
- static public boolean path(String s, String pat, char delim) {
- try {
- return path(s, 0, s.length(), pat, 0, pat.length(), delim);
- } catch (StringIndexOutOfBoundsException e) {
- return false;
- }
- }
-
- static private boolean path(String s, int s_index, int s_len,
- String pat, int p_index, int p_len, char delim)
- throws StringIndexOutOfBoundsException {
-
- while (p_index < p_len) {
- char c = pat.charAt(p_index);
- switch (c) {
- case '%':
- if (++p_index >= p_len) // % at end of pattern
- // ok if no delimiters
- return delim == 0 || s.indexOf(delim, s_index) < 0;
- // scan remainder until delimiter
- do {
- if (path(s, s_index, s_len, pat, p_index, p_len, delim))
- return true;
- } while (s.charAt(s_index) != delim && ++s_index < s_len);
- // ran into a delimiter or ran out of string without a match
- return false;
-
- case '*':
- if (++p_index >= p_len) // end of pattern?
- return true; // unconditional match
- do {
- if (path(s, s_index, s_len, pat, p_index, p_len, delim))
- return true;
- } while (++s_index < s_len);
- // ran out of string without a match
- return false;
-
- default:
- // if ran out of string or no match, fail
- if (s_index >= s_len || c != s.charAt(s_index))
- return false;
-
- // try the next string and pattern characters
- s_index++;
- p_index++;
- }
- }
- return s_index >= s_len;
- }
-
- /**
- * Directory pattern match
- *
- * @param s base string
- * @param pat pattern string
- * @return true if base is a matching directory of pattern
- */
- static public boolean dir(String s, String pat, char delim) {
- try {
- return dir(s, 0, s.length(), pat, 0, pat.length(), delim);
- } catch (StringIndexOutOfBoundsException e) {
- return false;
- }
- }
-
- static private boolean dir(String s, int s_index, int s_len,
- String pat, int p_index, int p_len, char delim)
- throws StringIndexOutOfBoundsException {
-
- while (p_index < p_len) {
- char c = pat.charAt(p_index);
- switch (c) {
- case '%':
- if (s_index >= s_len) // end of base?
- return true; // subset match
- if (++p_index >= p_len) // % at end of pattern?
- return false; // no inferiors permitted
- do {
- if (dir(s, s_index, s_len, pat, p_index, p_len, delim))
- return true;
- } while (s.charAt(s_index) != delim && ++s_index < s_len);
-
- if (s_index + 1 == s_len) // s ends with a delimiter
- return true; // must be a subset of pattern
- return dir(s, s_index, s_len, pat, p_index, p_len, delim);
-
- case '*':
- return true; // unconditional match
-
- default:
- if (s_index >= s_len) // end of base?
- return c == delim; // matched if at delimiter
-
- if (c != s.charAt(s_index))
- return false;
-
- // try the next string and pattern characters
- s_index++;
- p_index++;
- }
- }
- return s_index >= s_len;
- }
-}
-
-/**
- * A ByteArrayOutputStream that allows us to share the byte array
- * rather than copy it. Eventually could replace this with something
- * that doesn't require a single contiguous byte array.
- */
-class SharedByteArrayOutputStream extends ByteArrayOutputStream {
- public SharedByteArrayOutputStream(int size) {
- super(size);
- }
-
- public InputStream toStream() {
- return new SharedByteArrayInputStream(buf, 0, count);
- }
-}