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