From: Mathieu Baudier Date: Sat, 7 May 2022 09:23:25 +0000 (+0200) Subject: Introduce mbox support X-Git-Tag: v2.3.5~62 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=04b3a2625bc79af749a638ad2a97c3f36482f8aa;p=gpl%2Fargeo-slc.git Introduce mbox support --- diff --git a/Makefile-ext.mk b/Makefile-ext.mk index 11868d49f..f671f2822 100644 --- a/Makefile-ext.mk +++ b/Makefile-ext.mk @@ -7,6 +7,7 @@ A2_CATEGORY = org.argeo.tp BUNDLES = \ ext/org.argeo.ext.slf4j \ +ext/javax.mail.mbox \ clean: rm -rf $(BUILD_BASE) diff --git a/ext/javax.mail.mbox/.classpath b/ext/javax.mail.mbox/.classpath new file mode 100644 index 000000000..e801ebfb4 --- /dev/null +++ b/ext/javax.mail.mbox/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/ext/javax.mail.mbox/.project b/ext/javax.mail.mbox/.project new file mode 100644 index 000000000..b037595ff --- /dev/null +++ b/ext/javax.mail.mbox/.project @@ -0,0 +1,28 @@ + + + javax.mail.mbox + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/ext/javax.mail.mbox/bnd.bnd b/ext/javax.mail.mbox/bnd.bnd new file mode 100644 index 000000000..496cdb134 --- /dev/null +++ b/ext/javax.mail.mbox/bnd.bnd @@ -0,0 +1,7 @@ +Export-Package: com.sun.mail.*;version="1.6.7" + +Import-Package: \ +javax.mail.event,\ +* + +Bundle-Version : 1.6.7 diff --git a/ext/javax.mail.mbox/build.properties b/ext/javax.mail.mbox/build.properties new file mode 100644 index 000000000..34d2e4d2d --- /dev/null +++ b/ext/javax.mail.mbox/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/ContentLengthCounter.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/ContentLengthCounter.java new file mode 100644 index 000000000..f01aa592d --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/ContentLengthCounter.java @@ -0,0 +1,75 @@ +/* + * 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 java.io.*; + +/** + * Count the number of bytes in the body of the message written to the stream. + */ +class ContentLengthCounter extends OutputStream { + private long size = 0; + private boolean inHeader = true; + private int lastb1 = -1, lastb2 = -1; + + public void write(int b) throws IOException { + if (inHeader) { + // if line terminator is CR + if (b == '\r' && lastb1 == '\r') + inHeader = false; + else if (b == '\n') { + // if line terminator is \n + if (lastb1 == '\n') + inHeader = false; + // if line terminator is CRLF + else if (lastb1 == '\r' && lastb2 == '\n') + inHeader = false; + } + lastb2 = lastb1; + lastb1 = b; + } else + size++; + } + + public void write(byte[] b) throws IOException { + if (inHeader) + super.write(b); + else + size += b.length; + } + + public void write(byte[] b, int off, int len) throws IOException { + if (inHeader) + super.write(b, off, len); + else + size += len; + } + + public long getSize() { + return size; + } + + /* + public static void main(String argv[]) throws Exception { + int b; + ContentLengthCounter os = new ContentLengthCounter(); + while ((b = System.in.read()) >= 0) + os.write(b); + System.out.println("size " + os.getSize()); + } + */ +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/ContentLengthUpdater.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/ContentLengthUpdater.java new file mode 100644 index 000000000..b6d4408f2 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/ContentLengthUpdater.java @@ -0,0 +1,116 @@ +/* + * 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 java.io.*; + +/** + * Update the Content-Length header in the message written to the stream. + */ +class ContentLengthUpdater extends FilterOutputStream { + private String contentLength; + private boolean inHeader = true; + private boolean sawContentLength = false; + private int lastb1 = -1, lastb2 = -1; + private StringBuilder line = new StringBuilder(); + + public ContentLengthUpdater(OutputStream os, long contentLength) { + super(os); + this.contentLength = "Content-Length: " + contentLength; + } + + public void write(int b) throws IOException { + if (inHeader) { + String eol = "\n"; + // First, determine if we're still in the header. + if (b == '\r') { + // if line terminator is CR + if (lastb1 == '\r') { + inHeader = false; + eol = "\r"; + // else, if line terminator is CRLF + } else if (lastb1 == '\n' && lastb2 == '\r') { + inHeader = false; + eol = "\r\n"; + } + // else, if line terminator is \n + } else if (b == '\n') { + if (lastb1 == '\n') { + inHeader = false; + eol = "\n"; + } + } + + // If we're no longer in the header, and we haven't seen + // a Content-Length header yet, it's time to put one out. + if (!inHeader && !sawContentLength) { + out.write(contentLength.getBytes("iso-8859-1")); + out.write(eol.getBytes("iso-8859-1")); + } + + // If we have a full line, see if it's a Content-Length header. + if (b == '\r' || (b == '\n' && lastb1 != '\r')) { + if (line.toString().regionMatches(true, 0, + "content-length:", 0, 15)) { + // yup, got it + sawContentLength = true; + // put out the new version + out.write(contentLength.getBytes("iso-8859-1")); + } else { + // not a Content-Length header, just write it out + out.write(line.toString().getBytes("iso-8859-1")); + } + line.setLength(0); // clear buffer for next line + } + if (b == '\r' || b == '\n') + out.write(b); // write out line terminator immediately + else + line.append((char)b); // accumulate characters of the line + + // rotate saved characters for next time through loop + lastb2 = lastb1; + lastb1 = b; + } else + out.write(b); // not in the header, just write it out + } + + public void write(byte[] b) throws IOException { + if (inHeader) + write(b, 0, b.length); + else + out.write(b); + } + + public void write(byte[] b, int off, int len) throws IOException { + if (inHeader) { + for (int i = 0 ; i < len ; i++) { + write(b[off + i]); + } + } else + out.write(b, off, len); + } + + // for testing + public static void main(String argv[]) throws Exception { + int b; + ContentLengthUpdater os = + new ContentLengthUpdater(System.out, Long.parseLong(argv[0])); + while ((b = System.in.read()) >= 0) + os.write(b); + os.flush(); + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/DefaultMailbox.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/DefaultMailbox.java new file mode 100644 index 000000000..bac0b0576 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/DefaultMailbox.java @@ -0,0 +1,106 @@ +/* + * 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 java.io.*; + +public class DefaultMailbox extends Mailbox { + private final String home; + + private static final boolean homeRelative = + Boolean.getBoolean("mail.mbox.homerelative"); + + public DefaultMailbox() { + home = System.getProperty("user.home"); + } + + public MailFile getMailFile(String user, String folder) { + return new DefaultMailFile(filename(user, folder)); + } + + public String filename(String user, String folder) { + try { + char c = folder.charAt(0); + if (c == File.separatorChar) { + return folder; + } else if (c == '~') { + int i = folder.indexOf(File.separatorChar); + String tail = ""; + if (i > 0) { + tail = folder.substring(i); + folder = folder.substring(0, i); + } + return home + tail; + } else { + if (folder.equalsIgnoreCase("INBOX")) + folder = "INBOX"; + if (homeRelative) + return home + File.separator + folder; + else + return folder; + } + } catch (StringIndexOutOfBoundsException e) { + return folder; + } + } +} + +class DefaultMailFile extends File implements MailFile { + protected transient RandomAccessFile file; + + private static final long serialVersionUID = 3713116697523761684L; + + DefaultMailFile(String name) { + super(name); + } + + public boolean lock(String mode) { + try { + file = new RandomAccessFile(this, mode); + return true; + } catch (FileNotFoundException fe) { + return false; + } catch (IOException ie) { + file = null; + return false; + } + } + + public void unlock() { + if (file != null) { + try { + file.close(); + } catch (IOException e) { + // ignore it + } + file = null; + } + } + + public void touchlock() { + } + + public FileDescriptor getFD() { + if (file == null) + return null; + try { + return file.getFD(); + } catch (IOException e) { + return null; + } + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/FileInterface.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/FileInterface.java new file mode 100644 index 000000000..891f0d551 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/FileInterface.java @@ -0,0 +1,143 @@ +/* + * 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 java.io.File; +import java.io.FilenameFilter; + +public interface FileInterface { + /** + * Gets the name of the file. This method does not include the + * directory. + * @return the file name. + */ + public String getName(); + + /** + * Gets the path of the file. + * @return the file path. + */ + public String getPath(); + + /** + * Gets the absolute path of the file. + * @return the absolute file path. + */ + public String getAbsolutePath(); + + /** + * Gets the official, canonical path of the File. + * @return canonical path + */ + // XXX - JDK1.1 + // public String getCanonicalPath(); + + /** + * Gets the name of the parent directory. + * @return the parent directory, or null if one is not found. + */ + public String getParent(); + + /** + * Returns a boolean indicating whether or not a file exists. + */ + public boolean exists(); + + /** + * Returns a boolean indicating whether or not a writable file + * exists. + */ + public boolean canWrite(); + + /** + * Returns a boolean indicating whether or not a readable file + * exists. + */ + public boolean canRead(); + + /** + * Returns a boolean indicating whether or not a normal file + * exists. + */ + public boolean isFile(); + + /** + * Returns a boolean indicating whether or not a directory file + * exists. + */ + public boolean isDirectory(); + + /** + * Returns a boolean indicating whether the file name is absolute. + */ + public boolean isAbsolute(); + + /** + * Returns the last modification time. The return value should + * only be used to compare modification dates. It is meaningless + * as an absolute time. + */ + public long lastModified(); + + /** + * Returns the length of the file. + */ + public long length(); + + /** + * Creates a directory and returns a boolean indicating the + * success of the creation. Will return false if the directory already + * exists. + */ + public boolean mkdir(); + + /** + * Renames a file and returns a boolean indicating whether + * or not this method was successful. + * @param dest the new file name + */ + public boolean renameTo(File dest); + + /** + * Creates all directories in this path. This method + * returns true if the target (deepest) directory was created, + * false if the target directory was not created (e.g., if it + * existed previously). + */ + public boolean mkdirs(); + + /** + * Lists the files in a directory. Works only on directories. + * @return an array of file names. This list will include all + * files in the directory except the equivalent of "." and ".." . + */ + public String[] list(); + + /** + * Uses the specified filter to list files in a directory. + * @param filter the filter used to select file names + * @return the filter selected files in this directory. + * @see FilenameFilter + */ + public String[] list(FilenameFilter filter); + + /** + * Deletes the specified file. Returns true + * if the file could be deleted. + */ + public boolean delete(); +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/InboxFile.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/InboxFile.java new file mode 100644 index 000000000..e4fffd5f6 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/InboxFile.java @@ -0,0 +1,22 @@ +/* + * 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; + +public interface InboxFile extends MailFile { + public boolean openLock(String mode); + public void closeLock(); +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/LineCounter.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/LineCounter.java new file mode 100644 index 000000000..1ae4084f9 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/LineCounter.java @@ -0,0 +1,64 @@ +/* + * 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 java.io.*; + +/** + * Count number of lines output. + */ +class LineCounter extends FilterOutputStream { + private int lastb = -1; + protected int lineCount; + + public LineCounter(OutputStream os) { + super(os); + } + + public void write(int b) throws IOException { + // If we have a full line, count it. + if (b == '\r' || (b == '\n' && lastb != '\r')) + lineCount++; + out.write(b); + lastb = b; + } + + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + public void write(byte[] b, int off, int len) throws IOException { + for (int i = 0 ; i < len ; i++) { + write(b[off + i]); + } + } + + public int getLineCount() { + return lineCount; + } + + // for testing + public static void main(String argv[]) throws Exception { + int b; + LineCounter os = + new LineCounter(System.out); + while ((b = System.in.read()) >= 0) + os.write(b); + os.flush(); + System.out.println(os.getLineCount()); + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/MailFile.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/MailFile.java new file mode 100644 index 000000000..f790fced8 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/MailFile.java @@ -0,0 +1,26 @@ +/* + * 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 java.io.FileDescriptor; + +public interface MailFile extends FileInterface { + public boolean lock(String mode); + public void unlock(); + public void touchlock(); + public FileDescriptor getFD(); +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/Mailbox.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/Mailbox.java new file mode 100644 index 000000000..b902121c9 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/Mailbox.java @@ -0,0 +1,29 @@ +/* + * 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; + +public abstract class Mailbox { + /** + * Return a MailFile object for the specified user's folder. + */ + public abstract MailFile getMailFile(String user, String folder); + + /** + * Return the file name corresponding to a folder with the given name. + */ + public abstract String filename(String user, String folder); +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/MboxFolder.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/MboxFolder.java new file mode 100644 index 000000000..340b84ff0 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/MboxFolder.java @@ -0,0 +1,1164 @@ +/* + * 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 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.

+ * + * 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 flist = new ArrayList(); + 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(); + 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.

+ * + * 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 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); + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/MboxMessage.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/MboxMessage.java new file mode 100644 index 000000000..db57a2652 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/MboxMessage.java @@ -0,0 +1,532 @@ +/* + * 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 java.io.*; +import java.util.StringTokenizer; +import java.util.Date; +import java.text.SimpleDateFormat; +import javax.activation.*; +import javax.mail.*; +import javax.mail.internet.*; +import javax.mail.event.MessageChangedEvent; +import com.sun.mail.util.LineInputStream; + +/** + * This class represents an RFC822 style email message that resides in a file. + * + * @author Bill Shannon + */ + +public class MboxMessage extends MimeMessage { + + boolean writable = false; + // original msg flags, used by MboxFolder to detect modification + Flags origFlags; + /* + * A UNIX From line looks like: + * From address Day Mon DD HH:MM:SS YYYY + */ + String unix_from; + InternetAddress unix_from_user; + Date rcvDate; + int lineCount = -1; + private static OutputStream nullOutputStream = new OutputStream() { + public void write(int b) { } + public void write(byte[] b, int off, int len) { } + }; + + /** + * Construct an MboxMessage from the InputStream. + */ + public MboxMessage(Session session, InputStream is) + throws MessagingException, IOException { + super(session); + BufferedInputStream bis; + if (is instanceof BufferedInputStream) + bis = (BufferedInputStream)is; + else + bis = new BufferedInputStream(is); + LineInputStream dis = new LineInputStream(bis); + bis.mark(1024); + String line = dis.readLine(); + if (line != null && line.startsWith("From ")) + this.unix_from = line; + else + bis.reset(); + parse(bis); + saved = true; + } + + /** + * Construct an MboxMessage using the given InternetHeaders object + * and content from an InputStream. + */ + public MboxMessage(MboxFolder folder, InternetHeaders hdrs, InputStream is, + int msgno, String unix_from, boolean writable) + throws MessagingException { + super(folder, hdrs, null, msgno); + setFlagsFromHeaders(); + origFlags = getFlags(); + this.unix_from = unix_from; + this.writable = writable; + this.contentStream = is; + } + + /** + * Returns the "From" attribute. The "From" attribute contains + * the identity of the person(s) who wished this message to + * be sent.

+ * + * If our superclass doesn't have a value, we return the address + * from the UNIX From line. + * + * @return array of Address objects + * @exception MessagingException + */ + public Address[] getFrom() throws MessagingException { + Address[] ret = super.getFrom(); + if (ret == null) { + InternetAddress ia = getUnixFrom(); + if (ia != null) + ret = new InternetAddress[] { ia }; + } + return ret; + } + + /** + * Returns the address from the UNIX "From" line. + * + * @return UNIX From address + * @exception MessagingException + */ + public synchronized InternetAddress getUnixFrom() + throws MessagingException { + if (unix_from_user == null && unix_from != null) { + int i; + // find the space after the address, before the date + i = unix_from.indexOf(' ', 5); + if (i > 5) { + try { + unix_from_user = + new InternetAddress(unix_from.substring(5, i)); + } catch (AddressException e) { + // ignore it + } + } + } + return unix_from_user != null ? + (InternetAddress)unix_from_user.clone() : null; + } + + private String getUnixFromLine() { + if (unix_from != null) + return unix_from; + String from = "unknown"; + try { + Address[] froma = getFrom(); + if (froma != null && froma.length > 0 && + froma[0] instanceof InternetAddress) + from = ((InternetAddress)froma[0]).getAddress(); + } catch (MessagingException ex) { } + Date d = null; + try { + d = getSentDate(); + } catch (MessagingException ex) { + // ignore + } + if (d == null) + d = new Date(); + // From shannon Mon Jun 10 12:06:52 2002 + SimpleDateFormat fmt = new SimpleDateFormat("EEE LLL dd HH:mm:ss yyyy"); + return "From " + from + " " + fmt.format(d); + } + + /** + * Get the date this message was received, from the UNIX From line. + * + * @return the date this message was received + * @exception MessagingException + */ + @SuppressWarnings("deprecation") // for Date constructor + public Date getReceivedDate() throws MessagingException { + if (rcvDate == null && unix_from != null) { + int i; + // find the space after the address, before the date + i = unix_from.indexOf(' ', 5); + if (i > 5) { + try { + rcvDate = new Date(unix_from.substring(i)); + } catch (IllegalArgumentException iae) { + // ignore it + } + } + } + return rcvDate == null ? null : new Date(rcvDate.getTime()); + } + + /** + * Return the number of lines for the content of this message. + * Return -1 if this number cannot be determined.

+ * + * Note that this number may not be an exact measure of the + * content length and may or may not account for any transfer + * encoding of the content.

+ * + * This implementation returns -1. + * + * @return number of lines in the content. + * @exception MessagingException + */ + public int getLineCount() throws MessagingException { + if (lineCount < 0 && isMimeType("text/plain")) { + LineCounter lc = null; + // writeTo will set the SEEN flag, remember the original state + boolean seen = isSet(Flags.Flag.SEEN); + try { + lc = new LineCounter(nullOutputStream); + getDataHandler().writeTo(lc); + lineCount = lc.getLineCount(); + } catch (IOException ex) { + // ignore it, can't happen + } finally { + try { + if (lc != null) + lc.close(); + } catch (IOException ex) { + // can't happen + } + } + if (!seen) + setFlag(Flags.Flag.SEEN, false); + } + return lineCount; + } + + /** + * Set the specified flags on this message to the specified value. + * + * @param flags the flags to be set + * @param set the value to be set + */ + public void setFlags(Flags newFlags, boolean set) + throws MessagingException { + Flags oldFlags = (Flags)flags.clone(); + super.setFlags(newFlags, set); + if (!flags.equals(oldFlags)) { + setHeadersFromFlags(this); + if (folder != null) + ((MboxFolder)folder).notifyMessageChangedListeners( + MessageChangedEvent.FLAGS_CHANGED, this); + } + } + + /** + * Return the content type, mapping from SunV3 types to MIME types + * as necessary. + */ + public String getContentType() throws MessagingException { + String ct = super.getContentType(); + if (ct.indexOf('/') < 0) + ct = SunV3BodyPart.MimeV3Map.toMime(ct); + return ct; + } + + /** + * Produce the raw bytes of the content. This method is used during + * parsing, to create a DataHandler object for the content. Subclasses + * that can provide a separate input stream for just the message + * content might want to override this method.

+ * + * This implementation just returns a ByteArrayInputStream constructed + * out of the content byte array. + * + * @see #content + */ + protected InputStream getContentStream() throws MessagingException { + if (folder != null) + ((MboxFolder)folder).checkOpen(); + if (isExpunged()) + throw new MessageRemovedException("mbox message expunged"); + if (!isSet(Flags.Flag.SEEN)) + setFlag(Flags.Flag.SEEN, true); + return super.getContentStream(); + } + + /** + * Return a DataHandler for this Message's content. + * If this is a SunV3 multipart message, handle it specially. + * + * @exception MessagingException + */ + public synchronized DataHandler getDataHandler() + throws MessagingException { + if (dh == null) { + // XXX - Following is a kludge to avoid having to register + // the "multipart/x-sun-attachment" data type with the JAF. + String ct = getContentType(); + if (ct.equalsIgnoreCase("multipart/x-sun-attachment")) + dh = new DataHandler( + new SunV3Multipart(new MimePartDataSource(this)), ct); + else + return super.getDataHandler(); // will set "dh" + } + return dh; + } + + // here only to allow package private access from MboxFolder + protected void setMessageNumber(int msgno) { + super.setMessageNumber(msgno); + } + + // here to synchronize access to expunged field + public synchronized boolean isExpunged() { + return super.isExpunged(); + } + + // here to synchronize and to allow access from MboxFolder + protected synchronized void setExpunged(boolean expunged) { + super.setExpunged(expunged); + } + + // XXX - We assume that only body parts that are part of a SunV3 + // multipart will use the SunV3 headers (X-Sun-Content-Length, + // X-Sun-Content-Lines, X-Sun-Data-Type, X-Sun-Encoding-Info, + // X-Sun-Data-Description, X-Sun-Data-Name) so we don't handle + // them here. + + /** + * Set the flags for this message based on the Status, + * X-Status, and X-Dt-Delete-Time headers. + * + * SIMS 2.0: + * "X-Status: DFAT", deleted, flagged, answered, draft. + * Unset flags represented as "$". + * User flags not supported. + * + * University of Washington IMAP server: + * "X-Status: DFAT", deleted, flagged, answered, draft. + * Unset flags not present. + * "X-Keywords: userflag1 userflag2" + */ + private synchronized void setFlagsFromHeaders() { + flags = new Flags(Flags.Flag.RECENT); + try { + String s = getHeader("Status", null); + if (s != null) { + if (s.indexOf('R') >= 0) + flags.add(Flags.Flag.SEEN); + if (s.indexOf('O') >= 0) + flags.remove(Flags.Flag.RECENT); + } + s = getHeader("X-Dt-Delete-Time", null); // set by dtmail + if (s != null) + flags.add(Flags.Flag.DELETED); + s = getHeader("X-Status", null); // set by IMAP server + if (s != null) { + if (s.indexOf('D') >= 0) + flags.add(Flags.Flag.DELETED); + if (s.indexOf('F') >= 0) + flags.add(Flags.Flag.FLAGGED); + if (s.indexOf('A') >= 0) + flags.add(Flags.Flag.ANSWERED); + if (s.indexOf('T') >= 0) + flags.add(Flags.Flag.DRAFT); + } + s = getHeader("X-Keywords", null); // set by IMAP server + if (s != null) { + StringTokenizer st = new StringTokenizer(s); + while (st.hasMoreTokens()) + flags.add(st.nextToken()); + } + } catch (MessagingException e) { + // ignore it + } + } + + /** + * Set the various header fields that represent the message flags. + */ + static void setHeadersFromFlags(MimeMessage msg) { + try { + Flags flags = msg.getFlags(); + StringBuilder status = new StringBuilder(); + if (flags.contains(Flags.Flag.SEEN)) + status.append('R'); + if (!flags.contains(Flags.Flag.RECENT)) + status.append('O'); + if (status.length() > 0) + msg.setHeader("Status", status.toString()); + else + msg.removeHeader("Status"); + + boolean sims = false; + String s = msg.getHeader("X-Status", null); + // is it a SIMS 2.0 format X-Status header? + sims = s != null && s.length() == 4 && s.indexOf('$') >= 0; + status.setLength(0); + if (flags.contains(Flags.Flag.DELETED)) + status.append('D'); + else if (sims) + status.append('$'); + if (flags.contains(Flags.Flag.FLAGGED)) + status.append('F'); + else if (sims) + status.append('$'); + if (flags.contains(Flags.Flag.ANSWERED)) + status.append('A'); + else if (sims) + status.append('$'); + if (flags.contains(Flags.Flag.DRAFT)) + status.append('T'); + else if (sims) + status.append('$'); + if (status.length() > 0) + msg.setHeader("X-Status", status.toString()); + else + msg.removeHeader("X-Status"); + + String[] userFlags = flags.getUserFlags(); + if (userFlags.length > 0) { + status.setLength(0); + for (int i = 0; i < userFlags.length; i++) + status.append(userFlags[i]).append(' '); + status.setLength(status.length() - 1); // smash trailing space + msg.setHeader("X-Keywords", status.toString()); + } + if (flags.contains(Flags.Flag.DELETED)) { + s = msg.getHeader("X-Dt-Delete-Time", null); + if (s == null) + // XXX - should be time + msg.setHeader("X-Dt-Delete-Time", "1"); + } + } catch (MessagingException e) { + // ignore it + } + } + + protected void updateHeaders() throws MessagingException { + super.updateHeaders(); + setHeadersFromFlags(this); + } + + /** + * Save any changes made to this message. + */ + public void saveChanges() throws MessagingException { + if (folder != null) + ((MboxFolder)folder).checkOpen(); + if (isExpunged()) + throw new MessageRemovedException("mbox message expunged"); + if (!writable) + throw new MessagingException("Message is read-only"); + + super.saveChanges(); + + try { + /* + * Count the size of the body, in order to set the Content-Length + * header. (Should we only do this to update an existing + * Content-Length header?) + * XXX - We could cache the content bytes here, for use later + * in writeTo. + */ + ContentLengthCounter cos = new ContentLengthCounter(); + OutputStream os = new NewlineOutputStream(cos); + super.writeTo(os); + os.flush(); + setHeader("Content-Length", String.valueOf(cos.getSize())); + // setContentSize((int)cos.getSize()); + } catch (MessagingException e) { + throw e; + } catch (Exception e) { + throw new MessagingException("unexpected exception " + e); + } + } + + /** + * Expose modified flag to MboxFolder. + */ + boolean isModified() { + return modified; + } + + /** + * Put out a byte stream suitable for saving to a file. + * XXX - ultimately implement "ignore headers" here? + */ + public void writeToFile(OutputStream os) throws IOException { + try { + if (getHeader("Content-Length") == null) { + /* + * Count the size of the body, in order to set the + * Content-Length header. + */ + ContentLengthCounter cos = new ContentLengthCounter(); + OutputStream oos = new NewlineOutputStream(cos); + super.writeTo(oos, null); + oos.flush(); + setHeader("Content-Length", String.valueOf(cos.getSize())); + // setContentSize((int)cos.getSize()); + } + + os = new NewlineOutputStream(os, true); + PrintStream pos = new PrintStream(os, false, "iso-8859-1"); + + pos.println(getUnixFromLine()); + super.writeTo(pos, null); + pos.flush(); + } catch (MessagingException e) { + throw new IOException("unexpected exception " + e); + } + } + + public void writeTo(OutputStream os, String[] ignoreList) + throws IOException, MessagingException { + // set the SEEN flag now, which will normally be set by + // getContentStream, so it will show up in our headers + if (!isSet(Flags.Flag.SEEN)) + setFlag(Flags.Flag.SEEN, true); + super.writeTo(os, ignoreList); + } + + /** + * Interpose on superclass method to make sure folder is still open + * and message hasn't been expunged. + */ + public String[] getHeader(String name) + throws MessagingException { + if (folder != null) + ((MboxFolder)folder).checkOpen(); + if (isExpunged()) + throw new MessageRemovedException("mbox message expunged"); + return super.getHeader(name); + } + + /** + * Interpose on superclass method to make sure folder is still open + * and message hasn't been expunged. + */ + public String getHeader(String name, String delimiter) + throws MessagingException { + if (folder != null) + ((MboxFolder)folder).checkOpen(); + if (isExpunged()) + throw new MessageRemovedException("mbox message expunged"); + return super.getHeader(name, delimiter); + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/MboxProvider.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/MboxProvider.java new file mode 100644 index 000000000..d0b056d1a --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/MboxProvider.java @@ -0,0 +1,29 @@ +/* + * 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.Provider; + +/** + * The Mbox protocol provider. + */ +public class MboxProvider extends Provider { + public MboxProvider() { + super(Provider.Type.STORE, "mbox", MboxStore.class.getName(), + "Oracle", null); + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/MboxStore.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/MboxStore.java new file mode 100644 index 000000000..1ce39bc9b --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/MboxStore.java @@ -0,0 +1,113 @@ +/* + * 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 java.io.*; +import javax.mail.*; + +public class MboxStore extends Store { + + String user; + String home; + Mailbox mb; + static Flags permFlags; + + static { + // we support all flags + permFlags = new Flags(); + permFlags.add(Flags.Flag.SEEN); + permFlags.add(Flags.Flag.RECENT); + permFlags.add(Flags.Flag.DELETED); + permFlags.add(Flags.Flag.FLAGGED); + permFlags.add(Flags.Flag.ANSWERED); + permFlags.add(Flags.Flag.DRAFT); + permFlags.add(Flags.Flag.USER); + } + + public MboxStore(Session session, URLName url) { + super(session, url); + + // XXX - handle security exception + user = System.getProperty("user.name"); + home = System.getProperty("user.home"); + String os = System.getProperty("os.name"); + try { + String cl = "com.sun.mail.mbox." + os + "Mailbox"; + mb = (Mailbox)Class.forName(cl). + getDeclaredConstructor().newInstance(); + } catch (Exception e) { + mb = new DefaultMailbox(); + } + } + + /** + * Since we do not have any authentication + * to do and we do not want a dialog put up asking the user for a + * password we always succeed in connecting. + * But if we're given a password, that means the user is + * doing something wrong so fail the request. + */ + protected boolean protocolConnect(String host, int port, String user, + String passwd) throws MessagingException { + + if (passwd != null) + throw new AuthenticationFailedException( + "mbox does not allow passwords"); + // XXX - should we use the user? + return true; + } + + protected void setURLName(URLName url) { + // host, user, password, and file don't matter so we strip them out + if (url != null && (url.getUsername() != null || + url.getHost() != null || + url.getFile() != null)) + url = new URLName(url.getProtocol(), null, -1, null, null, null); + super.setURLName(url); + } + + + public Folder getDefaultFolder() throws MessagingException { + checkConnected(); + + return new MboxFolder(this, null); + } + + public Folder getFolder(String name) throws MessagingException { + checkConnected(); + + return new MboxFolder(this, name); + } + + public Folder getFolder(URLName url) throws MessagingException { + checkConnected(); + return getFolder(url.getFile()); + } + + private void checkConnected() throws MessagingException { + if (!isConnected()) + throw new MessagingException("Not connected"); + } + + MailFile getMailFile(String folder) { + return mb.getMailFile(user, folder); + } + + Session getSession() { + return session; + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/MessageLoader.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/MessageLoader.java new file mode 100644 index 000000000..b5ecaaadd --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/MessageLoader.java @@ -0,0 +1,285 @@ +/* + * 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 java.io.*; +import java.util.*; + +/** + * A support class that contains the state and logic needed when + * loading messages from a folder. + */ +final class MessageLoader { + private final TempFile temp; + private FileInputStream fis = null; + private OutputStream fos = null; + private int pos, len; // position in and length of buffer + private long off; // current offset in temp file + private long prevend; // the end of the previous message in temp file + private MboxFolder.MessageMetadata md; + private byte[] buf = null; + // the length of the longest header we'll need to look at + private static final int LINELEN = "Content-Length: XXXXXXXXXX".length(); + private char[] line; + + public MessageLoader(TempFile temp) { + this.temp = temp; + } + + /** + * Load messages from the given file descriptor, starting at the + * specified offset, adding the MessageMetadata to the list.

+ * + * The data is assumed to be in UNIX mbox format, with newlines + * only as the line terminators. + */ + public int load(FileDescriptor fd, long offset, + List msgs) + throws IOException { + // XXX - could allocate and deallocate buffers here + int loaded = 0; + try { + fis = new FileInputStream(fd); + if (fis.skip(offset) != offset) + throw new EOFException("Failed to skip to offset " + offset); + this.off = prevend = temp.length(); + pos = len = 0; + line = new char[LINELEN]; + buf = new byte[64 * 1024]; + fos = temp.getAppendStream(); + int n; + // keep loading messages as long as we have headers + while ((n = skipHeader(loaded == 0)) >= 0) { + long start; + if (n == 0) { + // didn't find a Content-Length, skip the body + start = skipBody(); + if (start < 0) { + // md.end = -1; + md.dataend = -1; + msgs.add(md); + loaded++; + break; + } + md.dataend = start; + } else { + // skip over the body + skip(n); + md.dataend = off; + int b; + // skip any blank lines after the body + while ((b = get()) >= 0) { + if (b != '\n') + break; + } + start = off; + if (b >= 0) + start--; // back up one byte if not at EOF + } + // md.end = start; + prevend = start; + msgs.add(md); + loaded++; + } + } finally { + try { + fis.close(); + } catch (IOException ex) { + // ignore + } + try { + fos.close(); + } catch (IOException ex) { + // ignore + } + line = null; + buf = null; + } + return loaded; + } + + /** + * Skip over the message header, returning the content length + * of the body, or 0 if no Content-Length header was seen. + * Update the MessageMetadata based on the headers seen. + * return -1 on EOF. + */ + private int skipHeader(boolean first) throws IOException { + int clen = 0; + boolean bol = true; + int lpos = -1; + int b; + boolean saw_unix_from = false; + int lineno = 0; + md = new MboxFolder.MessageMetadata(); + md.start = prevend; + md.recent = true; + while ((b = get()) >= 0) { + if (bol) { + if (b == '\n') + break; + lpos = 0; + } + if (b == '\n') { + bol = true; + lineno++; + // newline at end of line, was the line one of the headers + // we're looking for? + if (lpos > 7) { + // XXX - make this more efficient? + String s = new String(line, 0, lpos); + // fast check for Content-Length header + if (lineno == 1 && line[0] == 'F' && isPrefix(s, "From ")) { + saw_unix_from = true; + } else if (line[7] == '-' && + isPrefix(s, "Content-Length:")) { + s = s.substring(15).trim(); + try { + clen = Integer.parseInt(s); + } catch (NumberFormatException ex) { + // ignore it + } + // fast check for Status header + } else if ((line[1] == 't' || line[1] == 'T') && + isPrefix(s, "Status:")) { + if (s.indexOf('O') >= 0) + md.recent = false; + // fast check for X-Status header + } else if ((line[3] == 't' || line[3] == 'T') && + isPrefix(s, "X-Status:")) { + if (s.indexOf('D') >= 0) + md.deleted = true; + // fast check for X-Dt-Delete-Time header + } else if (line[4] == '-' && + isPrefix(s, "X-Dt-Delete-Time:")) { + md.deleted = true; + // fast check for X-IMAP header + } else if (line[5] == 'P' && s.startsWith("X-IMAP:")) { + md.imap = true; + } + } + } else { + // accumlate data in line buffer + bol = false; + if (lpos < 0) // ignoring this line + continue; + if (lpos == 0 && (b == ' ' || b == '\t')) + lpos = -1; // ignore continuation lines + else if (lpos < line.length) + line[lpos++] = (char)b; + } + } + + // if we hit EOF, or this is the first message we're loading and + // it doesn't have a UNIX From line, return EOF. + // (After the first message, UNIX From lines are seen by skipBody + // to terminate the message.) + if (b < 0 || (first && !saw_unix_from)) + return -1; + else + return clen; + } + + /** + * Does "s" start with "pre", ignoring case? + */ + private static final boolean isPrefix(String s, String pre) { + return s.regionMatches(true, 0, pre, 0, pre.length()); + } + + /** + * Skip over the body of the message looking for a line that starts + * with "From ". If found, return the offset of the beginning of + * that line. Return -1 on EOF. + */ + private long skipBody() throws IOException { + boolean bol = true; + int lpos = -1; + long loff = off; + int b; + while ((b = get()) >= 0) { + if (bol) { + lpos = 0; + loff = off - 1; + } + if (b == '\n') { + bol = true; + if (lpos >= 5) { // have enough data to test? + if (line[0] == 'F' && line[1] == 'r' && line[2] == 'o' && + line[3] == 'm' && line[4] == ' ') + return loff; + } + } else { + bol = false; + if (lpos < 0) + continue; + if (lpos == 0 && b != 'F') + lpos = -1; // ignore lines that don't start with F + else if (lpos < 5) // only need first 5 chars to test + line[lpos++] = (char)b; + } + } + return -1; + } + + /** + * Skip "n" bytes, returning how much we were able to skip. + */ + private final int skip(int n) throws IOException { + int n0 = n; + if (pos + n < len) { + pos += n; // can do it all within this buffer + off += n; + } else { + do { + n -= (len - pos); // skip rest of this buffer + off += (len - pos); + fill(); + if (len <= 0) // ran out of data + return n0 - n; + } while (n > len); + pos += n; + off += n; + } + return n0; + } + + /** + * Return the next byte. + */ + private final int get() throws IOException { + if (pos >= len) + fill(); + if (pos >= len) + return -1; + else { + off++; + return buf[pos++] & 0xff; + } + } + + /** + * Fill our buffer with more data. + * Every buffer we read is also written to the temp file. + */ + private final void fill() throws IOException { + len = fis.read(buf); + pos = 0; + if (len > 0) + fos.write(buf, 0, len); + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/NewlineOutputStream.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/NewlineOutputStream.java new file mode 100644 index 000000000..f4a31cba0 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/NewlineOutputStream.java @@ -0,0 +1,94 @@ +/* + * 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 java.io.*; +import java.nio.charset.StandardCharsets; + +/** + * Convert the various newline conventions to the local platform's + * newline convention. Optionally, make sure the output ends with + * a blank line. + */ +public class NewlineOutputStream extends FilterOutputStream { + private int lastb = -1; + private int bol = 1; // number of times in a row we're at beginning of line + private final boolean endWithBlankLine; + private static final byte[] newline; + + static { + String s = null; + try { + s = System.lineSeparator(); + } catch (SecurityException sex) { + // ignore, should never happen + } + if (s == null || s.length() <= 0) + s = "\n"; + newline = s.getBytes(StandardCharsets.ISO_8859_1); + } + + public NewlineOutputStream(OutputStream os) { + this(os, false); + } + + public NewlineOutputStream(OutputStream os, boolean endWithBlankLine) { + super(os); + this.endWithBlankLine = endWithBlankLine; + } + + public void write(int b) throws IOException { + if (b == '\r') { + out.write(newline); + bol++; + } else if (b == '\n') { + if (lastb != '\r') { + out.write(newline); + bol++; + } + } else { + out.write(b); + bol = 0; // no longer at beginning of line + } + lastb = b; + } + + public void write(byte b[]) throws IOException { + write(b, 0, b.length); + } + + public void write(byte b[], int off, int len) throws IOException { + for (int i = 0 ; i < len ; i++) { + write(b[off + i]); + } + } + + public void flush() throws IOException { + if (endWithBlankLine) { + if (bol == 0) { + // not at bol, return to bol and add a blank line + out.write(newline); + out.write(newline); + } else if (bol == 1) { + // at bol, add a blank line + out.write(newline); + } + } + bol = 2; + out.flush(); + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/SolarisMailbox.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/SolarisMailbox.java new file mode 100644 index 000000000..84f3abcc9 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/SolarisMailbox.java @@ -0,0 +1,81 @@ +/* + * 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 java.io.File; + +public class SolarisMailbox extends Mailbox { + private final String home; + private final String user; + + private static final boolean homeRelative = + Boolean.getBoolean("mail.mbox.homerelative"); + + public SolarisMailbox() { + String h = System.getenv("HOME"); + if (h == null) + h = System.getProperty("user.home"); + home = h; + user = System.getProperty("user.name"); + } + + public MailFile getMailFile(String user, String folder) { + if (folder.equalsIgnoreCase("INBOX")) + return new UNIXInbox(user, filename(user, folder)); + else + return new UNIXFolder(filename(user, folder)); + } + + /** + * Given a name of a mailbox folder, expand it to a full path name. + */ + public String filename(String user, String folder) { + try { + switch (folder.charAt(0)) { + case '/': + return folder; + case '~': + int i = folder.indexOf(File.separatorChar); + String tail = ""; + if (i > 0) { + tail = folder.substring(i); + folder = folder.substring(0, i); + } + if (folder.length() == 1) + return home + tail; + else + return "/home/" + folder.substring(1) + tail; // XXX + default: + if (folder.equalsIgnoreCase("INBOX")) { + if (user == null) // XXX - should never happen + user = this.user; + String inbox = System.getenv("MAIL"); + if (inbox == null) + inbox = "/var/mail/" + user; + return inbox; + } else { + if (homeRelative) + return home + File.separator + folder; + else + return folder; + } + } + } catch (StringIndexOutOfBoundsException e) { + return folder; + } + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/SunOSMailbox.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/SunOSMailbox.java new file mode 100644 index 000000000..55230d1e1 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/SunOSMailbox.java @@ -0,0 +1,20 @@ +/* + * 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; + +public class SunOSMailbox extends SolarisMailbox { +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/SunV3BodyPart.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/SunV3BodyPart.java new file mode 100644 index 000000000..189ff85a2 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/SunV3BodyPart.java @@ -0,0 +1,314 @@ +/* + * 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.internet.*; +import javax.activation.*; +import java.io.*; + +/** + * This class represents a SunV3 BodyPart. + * + * @author Bill Shannon + * @see javax.mail.Part + * @see javax.mail.internet.MimePart + * @see javax.mail.internet.MimeBodyPart + */ + +public class SunV3BodyPart extends MimeBodyPart { + /** + * Constructs a SunV3BodyPart using the given header and + * content bytes.

+ * + * Used by providers. + * + * @param headers The header of this part + * @param content bytes representing the body of this part. + */ + public SunV3BodyPart(InternetHeaders headers, byte[] content) + throws MessagingException { + super(headers, content); + } + + /** + * Return the size of the content of this BodyPart in bytes. + * Return -1 if the size cannot be determined.

+ * + * Note that this number may not be an exact measure of the + * content size and may or may not account for any transfer + * encoding of the content.

+ * + * @return size in bytes + */ + public int getSize() throws MessagingException { + String s = getHeader("X-Sun-Content-Length", null); + try { + return Integer.parseInt(s); + } catch (NumberFormatException ex) { + return -1; + } + } + + /** + * Return the number of lines for the content of this Part. + * Return -1 if this number cannot be determined.

+ * + * Note that this number may not be an exact measure of the + * content length and may or may not account for any transfer + * encoding of the content. + */ + public int getLineCount() throws MessagingException { + String s = getHeader("X-Sun-Content-Lines", null); + try { + return Integer.parseInt(s); + } catch (NumberFormatException ex) { + return -1; + } + } + + /* + * This is just enough to get us going. + * + * For more complete transformation from V3 to MIME, refer to + * sun_att.c from the Sun IMAP server code. + */ + static class MimeV3Map { + String mime; + String v3; + + MimeV3Map(String mime, String v3) { + this.mime = mime; + this.v3 = v3; + } + + private static MimeV3Map[] mimeV3Table = new MimeV3Map[] { + new MimeV3Map("text/plain", "text"), + new MimeV3Map("text/plain", "default"), + new MimeV3Map("multipart/x-sun-attachment", "X-sun-attachment"), + new MimeV3Map("application/postscript", "postscript-file"), + new MimeV3Map("image/gif", "gif-file") + // audio-file + // cshell-script + }; + + // V3 Content-Type to MIME Content-Type + static String toMime(String s) { + for (int i = 0; i < mimeV3Table.length; i++) { + if (mimeV3Table[i].v3.equalsIgnoreCase(s)) + return mimeV3Table[i].mime; + } + return "application/x-" + s; + } + + // MIME Content-Type to V3 Content-Type + static String toV3(String s) { + for (int i = 0; i < mimeV3Table.length; i++) { + if (mimeV3Table[i].mime.equalsIgnoreCase(s)) + return mimeV3Table[i].v3; + } + return s; + } + } + + /** + * Returns the value of the RFC822 "Content-Type" header field. + * This represents the content-type of the content of this + * BodyPart. This value must not be null. If this field is + * unavailable, "text/plain" should be returned.

+ * + * This implementation uses getHeader(name) + * to obtain the requisite header field. + * + * @return Content-Type of this BodyPart + */ + public String getContentType() throws MessagingException { + String ct = getHeader("Content-Type", null); + if (ct == null) + ct = getHeader("X-Sun-Data-Type", null); + if (ct == null) + ct = "text/plain"; + else if (ct.indexOf('/') < 0) + ct = MimeV3Map.toMime(ct); + return ct; + } + + /** + * Returns the value of the "Content-Transfer-Encoding" header + * field. Returns null if the header is unavailable + * or its value is absent.

+ * + * This implementation uses getHeader(name) + * to obtain the requisite header field. + * + * @see #headers + */ + public String getEncoding() throws MessagingException { + String enc = super.getEncoding(); + if (enc == null) + enc = getHeader("X-Sun-Encoding-Info", null); + return enc; + } + + /** + * Returns the "Content-Description" header field of this BodyPart. + * This typically associates some descriptive information with + * this part. Returns null if this field is unavailable or its + * value is absent.

+ * + * If the Content-Description field is encoded as per RFC 2047, + * it is decoded and converted into Unicode. If the decoding or + * conversion fails, the raw data is returned as-is

+ * + * This implementation uses getHeader(name) + * to obtain the requisite header field. + * + * @return content-description + */ + public String getDescription() throws MessagingException { + String desc = super.getDescription(); + if (desc == null) + desc = getHeader("X-Sun-Data-Description", null); + return desc; + } + + /** + * Set the "Content-Description" header field for this BodyPart. + * If the description parameter is null, then any + * existing "Content-Description" fields are removed.

+ * + * If the description contains non US-ASCII characters, it will + * be encoded using the platform's default charset. If the + * description contains only US-ASCII characters, no encoding + * is done and it is used as-is. + * + * @param description content-description + * @exception IllegalWriteException if the underlying + * implementation does not support modification + * @exception IllegalStateException if this BodyPart is + * obtained from a READ_ONLY folder. + */ + public void setDescription(String description) throws MessagingException { + throw new MethodNotSupportedException("SunV3BodyPart not writable"); + } + + /** + * Set the "Content-Description" header field for this BodyPart. + * If the description parameter is null, then any + * existing "Content-Description" fields are removed.

+ * + * If the description contains non US-ASCII characters, it will + * be encoded using the specified charset. If the description + * contains only US-ASCII characters, no encoding is done and + * it is used as-is + * + * @param description Description + * @param charset Charset for encoding + * @exception IllegalWriteException if the underlying + * implementation does not support modification + * @exception IllegalStateException if this BodyPart is + * obtained from a READ_ONLY folder. + */ + public void setDescription(String description, String charset) + throws MessagingException { + throw new MethodNotSupportedException("SunV3BodyPart not writable"); + } + + /** + * Get the filename associated with this BodyPart.

+ * + * Returns the value of the "filename" parameter from the + * "Content-Disposition" header field of this BodyPart. If its + * not available, returns the value of the "name" parameter from + * the "Content-Type" header field of this BodyPart. + * Returns null if both are absent. + * + * @return filename + */ + public String getFileName() throws MessagingException { + String name = super.getFileName(); + if (name == null) + name = getHeader("X-Sun-Data-Name", null); + return name; + } + + /** + * Set the filename associated with this BodyPart, if possible.

+ * + * Sets the "filename" parameter of the "Content-Disposition" + * header field of this BodyPart. + * + * @exception IllegalWriteException if the underlying + * implementation does not support modification + * @exception IllegalStateException if this BodyPart is + * obtained from a READ_ONLY folder. + */ + public void setFileName(String filename) throws MessagingException { + throw new MethodNotSupportedException("SunV3BodyPart not writable"); + } + + /** + * This method provides the mechanism to set this BodyPart's content. + * The given DataHandler object should wrap the actual content. + * + * @param dh The DataHandler for the content + * @exception IllegalWriteException if the underlying + * implementation does not support modification + * @exception IllegalStateException if this BodyPart is + * obtained from a READ_ONLY folder. + */ + public void setDataHandler(DataHandler dh) + throws MessagingException { + throw new MethodNotSupportedException("SunV3BodyPart not writable"); + } + + /** + * Output the BodyPart as a RFC822 format stream. + * + * @exception MessagingException + * @exception IOException if an error occurs writing to the + * stream or if an error is generated + * by the javax.activation layer. + * @see javax.activation.DataHandler#writeTo() + */ + public void writeTo(OutputStream os) + throws IOException, MessagingException { + throw new MethodNotSupportedException("SunV3BodyPart writeTo"); + } + + /** + * This is the method that has the 'smarts' to query the 'content' + * and update the appropriate headers. Typical headers that get + * set here are: Content-Type, Content-Encoding, boundary (for + * multipart). Now, the tricky part here is when to actually + * activate this method: + * + * - A Message being crafted by a mail-application will certainly + * need to activate this method at some point to fill up its internal + * headers. Typically this is triggered off by our writeTo() method. + * + * - A message read-in from a MessageStore will have obtained + * all its headers from the store, and so does'nt need this. + * However, if this message is editable and if any edits have + * been made to either the content or message-structure, we might + * need to resync our headers. Typically this is triggered off by + * the Message.saveChanges() methods. + */ + protected void updateHeaders() throws MessagingException { + throw new MethodNotSupportedException("SunV3BodyPart updateHeaders"); + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/SunV3Multipart.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/SunV3Multipart.java new file mode 100644 index 000000000..8a642f1fb --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/SunV3Multipart.java @@ -0,0 +1,201 @@ +/* + * 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.internet.*; +import javax.activation.*; +import java.util.*; +import java.io.*; +import java.nio.charset.StandardCharsets; +import com.sun.mail.util.LineInputStream; + +/** + * The SunV3Multipart class is an implementation of the abstract Multipart + * class that uses SunV3 conventions for the multipart data.

+ * + * @author Bill Shannon + */ + +public class SunV3Multipart extends MimeMultipart { + private boolean parsing; + + /** + * Constructs a SunV3Multipart object and its bodyparts from the + * given DataSource.

+ * + * @param ds DataSource, can be a MultipartDataSource + */ + public SunV3Multipart(DataSource ds) throws MessagingException { + super(ds); + } + + /** + * Set the subtype. Throws MethodNotSupportedException. + * + * @param subtype Subtype + */ + public void setSubType(String subtype) throws MessagingException { + throw new MethodNotSupportedException( + "can't change SunV3Multipart subtype"); + } + + /** + * Get the BodyPart referred to by the given ContentID (CID). + * Throws MethodNotSupportException. + */ + public synchronized BodyPart getBodyPart(String CID) + throws MessagingException { + throw new MethodNotSupportedException( + "SunV3Multipart doesn't support Content-ID"); + } + + /** + * Update headers. Throws MethodNotSupportException. + */ + protected void updateHeaders() throws MessagingException { + throw new MethodNotSupportedException("SunV3Multipart not writable"); + } + + /** + * Iterates through all the parts and outputs each SunV3 part + * separated by a boundary. + */ + public void writeTo(OutputStream os) + throws IOException, MessagingException { + throw new MethodNotSupportedException( + "SunV3Multipart writeTo not supported"); + } + + private static final String boundary = "----------"; + + /* + * Parse the contents of this multipart message and create the + * child body parts. + */ + protected synchronized void parse() throws MessagingException { + /* + * If the data has already been parsed, or we're in the middle of + * parsing it, there's nothing to do. The latter will occur when + * we call addBodyPart, which will call parse again. We really + * want to be able to call super.super.addBodyPart. + */ + if (parsed || parsing) + return; + + InputStream in = null; + + try { + in = ds.getInputStream(); + if (!(in instanceof ByteArrayInputStream) && + !(in instanceof BufferedInputStream)) + in = new BufferedInputStream(in); + } catch (IOException ex) { + throw new MessagingException("No inputstream from datasource"); + } catch (RuntimeException ex) { + throw new MessagingException("No inputstream from datasource"); + } + + byte[] bndbytes = boundary.getBytes(StandardCharsets.ISO_8859_1); + int bl = bndbytes.length; + + String line; + parsing = true; + try { + /* + * Skip any kind of junk until we get to the first + * boundary line. + */ + LineInputStream lin = new LineInputStream(in); + while ((line = lin.readLine()) != null) { + if (line.trim().equals(boundary)) + break; + } + if (line == null) + throw new MessagingException("Missing start boundary"); + + /* + * Read and process body parts until we see the + * terminating boundary line (or EOF). + */ + for (;;) { + /* + * Collect the headers for this body part. + */ + InternetHeaders headers = new InternetHeaders(in); + + if (!in.markSupported()) + throw new MessagingException("Stream doesn't support mark"); + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + int b; + + /* + * Read and save the content bytes in buf. + */ + while ((b = in.read()) >= 0) { + if (b == '\r' || b == '\n') { + /* + * Found the end of a line, check whether the + * next line is a boundary. + */ + int i; + in.mark(bl + 4 + 1); // "4" for possible "--\r\n" + if (b == '\r' && in.read() != '\n') { + in.reset(); + in.mark(bl + 4); + } + // read bytes, matching against the boundary + for (i = 0; i < bl; i++) + if (in.read() != bndbytes[i]) + break; + if (i == bl) { + int b2 = in.read(); + // check for end of line + if (b2 == '\n') + break; // got it! break out of the while loop + if (b2 == '\r') { + in.mark(1); + if (in.read() != '\n') + in.reset(); + break; // got it! break out of the while loop + } + } + // failed to match, reset and proceed normally + in.reset(); + } + buf.write(b); + } + + /* + * Create a SunV3BodyPart to represent this body part. + */ + SunV3BodyPart body = + new SunV3BodyPart(headers, buf.toByteArray()); + addBodyPart(body); + if (b < 0) + break; + } + } catch (IOException e) { + throw new MessagingException("IO Error"); // XXX + } finally { + parsing = false; + } + + parsed = true; + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/TempFile.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/TempFile.java new file mode 100644 index 000000000..25f4bd8d8 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/TempFile.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2010, 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 java.util.*; +import java.net.*; +import java.io.*; +import java.security.*; + +import com.sun.mail.util.PropUtil; +import javax.mail.util.SharedFileInputStream; + +/** + * A temporary file used to cache messages. + */ +class TempFile { + + private File file; // the temp file name + private WritableSharedFile sf; + + /** + * Create a temp file in the specified directory (if not null). + * The file will be deleted when the JVM exits. + */ + public TempFile(File dir) throws IOException { + file = File.createTempFile("mbox.", ".mbox", dir); + // XXX - need JDK 6 to set permissions on the file to owner-only + file.deleteOnExit(); + sf = new WritableSharedFile(file); + } + + /** + * Return a stream for appending to the temp file. + */ + public AppendStream getAppendStream() throws IOException { + return sf.getAppendStream(); + } + + /** + * Return a stream for reading from part of the file. + */ + public InputStream newStream(long start, long end) { + return sf.newStream(start, end); + } + + public long length() { + return file.length(); + } + + /** + * Close and remove this temp file. + */ + public void close() { + try { + sf.close(); + } catch (IOException ex) { + // ignore it + } + file.delete(); + } + + protected void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } +} + +/** + * A subclass of SharedFileInputStream that also allows writing. + */ +class WritableSharedFile extends SharedFileInputStream { + private RandomAccessFile raf; + private AppendStream af; + + public WritableSharedFile(File file) throws IOException { + super(file); + try { + raf = new RandomAccessFile(file, "rw"); + } catch (IOException ex) { + // if anything goes wrong opening the writable file, + // close the readable file too + super.close(); + } + } + + /** + * Return the writable version of this file. + */ + public RandomAccessFile getWritableFile() { + return raf; + } + + /** + * Close the readable and writable files. + */ + public void close() throws IOException { + try { + super.close(); + } finally { + raf.close(); + } + } + + /** + * Update the size of the readable file after writing + * to the file. Updates the length to be the current + * size of the file. + */ + synchronized long updateLength() throws IOException { + datalen = in.length(); + af = null; + return datalen; + } + + /** + * Return a new AppendStream, but only if one isn't in active use. + */ + public synchronized AppendStream getAppendStream() throws IOException { + if (af != null) + throw new IOException( + "file cache only supports single threaded access"); + af = new AppendStream(this); + return af; + } +} + +/** + * A stream for writing to the temp file, and when done + * can return a stream for reading the data just written. + * NOTE: We assume that only one thread is writing to the + * file at a time. + */ +class AppendStream extends OutputStream { + private final WritableSharedFile tf; + private RandomAccessFile raf; + private final long start; + private long end; + + public AppendStream(WritableSharedFile tf) throws IOException { + this.tf = tf; + raf = tf.getWritableFile(); + start = raf.length(); + raf.seek(start); + } + + public void write(int b) throws IOException { + raf.write(b); + } + + public void write(byte[] b) throws IOException { + raf.write(b); + } + + public void write(byte[] b, int off, int len) throws IOException { + raf.write(b, off, len); + } + + public synchronized void close() throws IOException { + end = tf.updateLength(); + raf = null; // no more writing allowed + } + + public synchronized InputStream getInputStream() throws IOException { + return tf.newStream(start, end); + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/UNIXFile.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/UNIXFile.java new file mode 100644 index 000000000..ab1579e18 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/UNIXFile.java @@ -0,0 +1,132 @@ +/* + * 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 java.io.File; +import java.io.FileDescriptor; +import java.util.StringTokenizer; + +public class UNIXFile extends File { + protected static final boolean loaded; + protected static final int lockType; + + private static final long serialVersionUID = -7972156315284146651L; + + public UNIXFile(String name) { + super(name); + } + + // lock type enum + protected static final int NONE = 0; + protected static final int NATIVE = 1; + protected static final int JAVA = 2; + + static { + String lt = System.getProperty("mail.mbox.locktype", "native"); + int type = NATIVE; + if (lt.equalsIgnoreCase("none")) + type = NONE; + else if (lt.equalsIgnoreCase("java")) + type = JAVA; + lockType = type; + + boolean lloaded = false; + if (lockType == NATIVE) { + try { + System.loadLibrary("mbox"); + lloaded = true; + } catch (UnsatisfiedLinkError e) { + String classpath = System.getProperty("java.class.path"); + String sep = System.getProperty("path.separator"); + String arch = System.getProperty("os.arch"); + StringTokenizer st = new StringTokenizer(classpath, sep); + while (st.hasMoreTokens()) { + String path = st.nextToken(); + if (path.endsWith("/classes") || + path.endsWith("/mail.jar") || + path.endsWith("/javax.mail.jar")) { + int i = path.lastIndexOf('/'); + String libdir = path.substring(0, i + 1) + "lib/"; + String lib = libdir + arch + "/libmbox.so"; + try { + System.load(lib); + lloaded = true; + break; + } catch (UnsatisfiedLinkError e2) { + lib = libdir + "libmbox.so"; + try { + System.load(lib); + lloaded = true; + break; + } catch (UnsatisfiedLinkError e3) { + continue; + } + } + } + } + } + } + loaded = lloaded; + if (loaded) + initIDs(FileDescriptor.class, FileDescriptor.in); + } + + /** + * Return the access time of the file. + */ + public static long lastAccessed(File file) { + return lastAccessed0(file.getPath()); + } + + public long lastAccessed() { + return lastAccessed0(getPath()); + } + + private static native void initIDs(Class fdClass, + FileDescriptor stdin); + + /** + * Lock the file referred to by fd. The string mode is "r" + * for a read lock or "rw" for a write lock. Don't block + * if lock can't be acquired. + */ + public static boolean lock(FileDescriptor fd, String mode) { + return lock(fd, mode, false); + } + + /** + * Lock the file referred to by fd. The string mode is "r" + * for a read lock or "rw" for a write lock. If block is set, + * block waiting for the lock if necessary. + */ + private static boolean lock(FileDescriptor fd, String mode, boolean block) { + //return loaded && lock0(fd, mode); + if (loaded) { + boolean ret; + //System.out.println("UNIXFile.lock(" + fd + ", " + mode + ")"); + ret = lock0(fd, mode, block); + //System.out.println("UNIXFile.lock returns " + ret); + return ret; + } + return false; + } + + private static native boolean lock0(FileDescriptor fd, String mode, + boolean block); + + public static native long lastAccessed0(String name); +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/UNIXFolder.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/UNIXFolder.java new file mode 100644 index 000000000..13dc30ad0 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/UNIXFolder.java @@ -0,0 +1,74 @@ +/* + * 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 java.io.*; + +public class UNIXFolder extends UNIXFile implements MailFile { + protected transient RandomAccessFile file; + + private static final long serialVersionUID = -254578891263785591L; + + public UNIXFolder(String name) { + super(name); + } + + public boolean lock(String mode) { + try { + file = new RandomAccessFile(this, mode); + switch (lockType) { + case NONE: + return true; + case NATIVE: + default: + return UNIXFile.lock(file.getFD(), mode); + case JAVA: + return file.getChannel(). + tryLock(0L, Long.MAX_VALUE, !mode.equals("rw")) != null; + } + } catch (FileNotFoundException fe) { + return false; + } catch (IOException ie) { + file = null; + return false; + } + } + + public void unlock() { + if (file != null) { + try { + file.close(); + } catch (IOException e) { + // ignore it + } + file = null; + } + } + + public void touchlock() { + } + + public FileDescriptor getFD() { + if (file == null) + return null; + try { + return file.getFD(); + } catch (IOException e) { + return null; + } + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/mbox/UNIXInbox.java b/ext/javax.mail.mbox/src/com/sun/mail/mbox/UNIXInbox.java new file mode 100644 index 000000000..70ce86541 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/mbox/UNIXInbox.java @@ -0,0 +1,123 @@ +/* + * 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 java.io.*; + +public class UNIXInbox extends UNIXFolder implements InboxFile { + private final String user; + + private static final long serialVersionUID = 651261842162777620L; + + /* + * Superclass UNIXFile loads the library containing all the + * native code and sets the "loaded" flag if successful. + */ + + public UNIXInbox(String user, String name) { + super(name); + this.user = user; + if (user == null) + throw new NullPointerException("user name is null in UNIXInbox"); + } + + public boolean lock(String mode) { + if (lockType == NATIVE) { + if (!loaded) + return false; + if (!maillock(user, 5)) + return false; + } + if (!super.lock(mode)) { + if (loaded) + mailunlock(); + return false; + } + return true; + } + + public void unlock() { + super.unlock(); + if (loaded) + mailunlock(); + } + + public void touchlock() { + if (loaded) + touchlock0(); + } + + private transient RandomAccessFile lockfile; // the user's ~/.Maillock file + private transient String lockfileName; // its name + + public boolean openLock(String mode) { + if (mode.equals("r")) + return true; + if (lockfileName == null) { + String home = System.getProperty("user.home"); + lockfileName = home + File.separator + ".Maillock"; + } + try { + lockfile = new RandomAccessFile(lockfileName, mode); + boolean ret; + switch (lockType) { + case NONE: + ret = true; + break; + case NATIVE: + default: + ret = UNIXFile.lock(lockfile.getFD(), mode); + break; + case JAVA: + ret = lockfile.getChannel(). + tryLock(0L, Long.MAX_VALUE, !mode.equals("rw")) != null; + break; + } + if (!ret) + closeLock(); + return ret; + } catch (IOException ex) { + } + return false; + } + + public void closeLock() { + if (lockfile == null) + return; + try { + lockfile.close(); + } catch (IOException ex) { + } finally { + lockfile = null; + } + } + + public boolean equals(Object o) { + if (!(o instanceof UNIXInbox)) + return false; + UNIXInbox other = (UNIXInbox)o; + return user.equals(other.user) && super.equals(other); + } + + public int hashCode() { + return super.hashCode() + user.hashCode(); + } + + private native boolean maillock(String user, int retryCount); + private native void mailunlock(); + private native void touchlock0(); +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/remote/POP3RemoteProvider.java b/ext/javax.mail.mbox/src/com/sun/mail/remote/POP3RemoteProvider.java new file mode 100644 index 000000000..e5e3987db --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/remote/POP3RemoteProvider.java @@ -0,0 +1,29 @@ +/* + * 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.remote; + +import javax.mail.Provider; + +/** + * The POP3 remote protocol provider. + */ +public class POP3RemoteProvider extends Provider { + public POP3RemoteProvider() { + super(Provider.Type.STORE, "pop3remote", + POP3RemoteStore.class.getName(), "Oracle", null); + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/remote/POP3RemoteStore.java b/ext/javax.mail.mbox/src/com/sun/mail/remote/POP3RemoteStore.java new file mode 100644 index 000000000..fa6b1d465 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/remote/POP3RemoteStore.java @@ -0,0 +1,36 @@ +/* + * 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.remote; + +import javax.mail.*; +import com.sun.mail.pop3.POP3Store; + +/** + * A local store that uses POP3 to populate the INBOX. + * + * @author Bill Shannon + */ +public class POP3RemoteStore extends RemoteStore { + + public POP3RemoteStore(Session session, URLName url) { + super(session, url); + } + + protected Store getRemoteStore(Session session, URLName url) { + return new POP3Store(session, url); + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/remote/RemoteDefaultFolder.java b/ext/javax.mail.mbox/src/com/sun/mail/remote/RemoteDefaultFolder.java new file mode 100644 index 000000000..81c0f9095 --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/remote/RemoteDefaultFolder.java @@ -0,0 +1,50 @@ +/* + * 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.remote; + +import javax.mail.*; +import com.sun.mail.mbox.*; + +/** + * The default folder for the "remote" protocol. + * + * @author Bill Shannon + */ +public class RemoteDefaultFolder extends MboxFolder { + + protected RemoteDefaultFolder(RemoteStore store, String name) { + super(store, name); + } + + /** + * Depending on the name of the requested folder, create an + * appropriate Folder subclass. If the name is + * null, create a RemoteDefaultFolder. + * If the name is "INBOX" (ignoring case), create a + * RemoteInbox. Otherwise, create an MboxFolder. + * + * @return the new Folder + */ + protected Folder createFolder(Store store, String name) { + if (name == null) + return new RemoteDefaultFolder((RemoteStore)store, null); + else if (name.equalsIgnoreCase("INBOX")) + return new RemoteInbox((RemoteStore)store, name); + else + return new MboxFolder((MboxStore)store, name); + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/remote/RemoteInbox.java b/ext/javax.mail.mbox/src/com/sun/mail/remote/RemoteInbox.java new file mode 100644 index 000000000..5bfd232cd --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/remote/RemoteInbox.java @@ -0,0 +1,68 @@ +/* + * 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.remote; + +import javax.mail.*; +import com.sun.mail.mbox.*; + +/** + * A remote Inbox folder. The data is actually managed by our subclass + * (MboxFolder). We fetch data from the remote Inbox and + * add it to the local Inbox. + * + * @author Bill Shannon + */ + +public class RemoteInbox extends MboxFolder { + + private RemoteStore mstore; + + protected RemoteInbox(RemoteStore store, String name) { + super(store, name); + this.mstore = store; + } + + /** + * Poll the remote store for any new messages. + */ + public synchronized boolean hasNewMessages() { + try { + mstore.updateInbox(); + } catch (MessagingException ex) { + // ignore it + } + return super.hasNewMessages(); + } + + /** + * Open the folder in the specified mode. + * Poll the remote store for any new messages first. + */ + public synchronized void open(int mode) throws MessagingException { + mstore.updateInbox(); + super.open(mode); + } + + /** + * Return the number of messages in this folder. + * Poll the remote store for any new messages first. + */ + public synchronized int getMessageCount() throws MessagingException { + mstore.updateInbox(); + return super.getMessageCount(); + } +} diff --git a/ext/javax.mail.mbox/src/com/sun/mail/remote/RemoteStore.java b/ext/javax.mail.mbox/src/com/sun/mail/remote/RemoteStore.java new file mode 100644 index 000000000..faf9dd4cd --- /dev/null +++ b/ext/javax.mail.mbox/src/com/sun/mail/remote/RemoteStore.java @@ -0,0 +1,127 @@ +/* + * 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.remote; + +import java.io.*; +import javax.mail.*; +import com.sun.mail.mbox.*; + +/** + * A wrapper around a local MboxStore that fetches data + * from the Inbox in a remote store and adds it to our local Inbox. + */ +public abstract class RemoteStore extends MboxStore { + + protected Store remoteStore; + protected Folder remoteInbox; + protected Folder inbox; + protected String host, user, password; + protected int port; + protected long lastUpdate = 0; + + public RemoteStore(Session session, URLName url) { + super(session, url); + remoteStore = getRemoteStore(session, url); + } + + /** + * Subclasses override this method to return the appropriate + * Store object. This method will be called by + * the RemoteStore constructor. + */ + protected abstract Store getRemoteStore(Session session, URLName url); + + /** + * Connect to the store. + */ + public void connect(String host, int port, String user, String password) + throws MessagingException { + this.host = host; + this.port = port; + this.user = user; + this.password = password; + updateInbox(); + } + + /** + * Fetch any new mail in the remote INBOX and add it to the local INBOX. + */ + protected void updateInbox() throws MessagingException { + // is it time to do an update yet? + // XXX - polling frequency, rules, etc. should be in properties + if (System.currentTimeMillis() < lastUpdate + (5 * 1000)) + return; + try { + /* + * Connect to the remote store, using the saved + * authentication information. + */ + remoteStore.connect(host, port, user, password); + + /* + * If this store isn't connected yet, do it now, because + * it needs to be connected to get the INBOX folder. + */ + if (!isConnected()) + super.connect(host, port, user, password); + if (remoteInbox == null) + remoteInbox = remoteStore.getFolder("INBOX"); + if (inbox == null) + inbox = getFolder("INBOX"); + remoteInbox.open(Folder.READ_WRITE); + Message[] msgs = remoteInbox.getMessages(); + inbox.appendMessages(msgs); + remoteInbox.setFlags(msgs, new Flags(Flags.Flag.DELETED), true); + remoteInbox.close(true); + remoteStore.close(); + } catch (MessagingException ex) { + try { + if (remoteInbox != null && remoteInbox.isOpen()) + remoteInbox.close(false); + } finally { + if (remoteStore != null && remoteStore.isConnected()) + remoteStore.close(); + } + throw ex; + } + } + + public Folder getDefaultFolder() throws MessagingException { + checkConnected(); + + return new RemoteDefaultFolder(this, null); + } + + public Folder getFolder(String name) throws MessagingException { + checkConnected(); + + if (name.equalsIgnoreCase("INBOX")) + return new RemoteInbox(this, name); + else + return super.getFolder(name); + } + + public Folder getFolder(URLName url) throws MessagingException { + checkConnected(); + return getFolder(url.getFile()); + } + + private void checkConnected() throws MessagingException { + if (!isConnected()) + throw new MessagingException("Not connected"); + } +}