working on net mail test

This commit is contained in:
Jörg Prante 2024-08-01 17:46:05 +02:00
parent f221a578b1
commit d19e058178
26 changed files with 422 additions and 484 deletions

View file

@ -18,11 +18,14 @@ package jakarta.mail;
import jakarta.mail.event.MailEvent; import jakarta.mail.event.MailEvent;
import java.util.EventListener; import java.util.EventListener;
import java.util.Objects;
import java.util.Vector; import java.util.Vector;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* Package private class used by Store & Folder to dispatch events. * Package private class used by Store & Folder to dispatch events.
@ -33,16 +36,20 @@ import java.util.concurrent.LinkedBlockingQueue;
*/ */
class EventQueue implements Runnable { class EventQueue implements Runnable {
private static final Logger logger = Logger.getLogger(EventQueue.class.getName());
private static WeakHashMap<ClassLoader, EventQueue> appq; private static WeakHashMap<ClassLoader, EventQueue> appq;
private volatile BlockingQueue<QueueElement> q; private volatile BlockingQueue<QueueElement> q;
private Executor executor;
private final Executor executor;
/** /**
* Construct an EventQueue using the specified Executor. * Construct an EventQueue using the specified Executor.
* If the Executor is null, threads will be created as needed. * If the Executor is null, threads will be created as needed.
*/ */
EventQueue(Executor ex) { EventQueue(Executor ex) {
this.executor = ex; this.executor = Objects.requireNonNull(ex);
} }
/** /**
@ -50,15 +57,12 @@ class EventQueue implements Runnable {
* Application scoping is based on the thread's context class loader. * Application scoping is based on the thread's context class loader.
*/ */
static synchronized EventQueue getApplicationEventQueue(Executor ex) { static synchronized EventQueue getApplicationEventQueue(Executor ex) {
Objects.requireNonNull(ex);
ClassLoader cl = Thread.currentThread().getContextClassLoader(); ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (appq == null) if (appq == null) {
appq = new WeakHashMap<>(); appq = new WeakHashMap<>();
EventQueue q = appq.get(cl);
if (q == null) {
q = new EventQueue(ex);
appq.put(cl, q);
} }
return q; return appq.computeIfAbsent(cl, k -> new EventQueue(ex));
} }
/** /**
@ -69,13 +73,13 @@ class EventQueue implements Runnable {
// if this is the first event, create the queue and start the event task // if this is the first event, create the queue and start the event task
if (q == null) { if (q == null) {
q = new LinkedBlockingQueue<>(); q = new LinkedBlockingQueue<>();
if (executor != null) { logger.log(Level.INFO, "starting event queue");
executor.execute(this); executor.execute(this);
} else { /*else {
Thread qThread = new Thread(this, "Jakarta-Mail-EventQueue"); Thread qThread = new Thread(this, "Jakarta-Mail-EventQueue");
qThread.setDaemon(true); // not a user thread qThread.setDaemon(true); // not a user thread
qThread.start(); qThread.start();
} }*/
} }
q.add(new QueueElement(event, vector)); q.add(new QueueElement(event, vector));
} }
@ -97,11 +101,11 @@ class EventQueue implements Runnable {
*/ */
@Override @Override
public void run() { public void run() {
BlockingQueue<QueueElement> bq = q; BlockingQueue<QueueElement> bq = q;
if (bq == null) if (bq == null)
return; return;
try { try {
logger.log(Level.INFO, "entering event queue");
loop: loop:
for (; ; ) { for (; ; ) {
// block until an item is available // block until an item is available
@ -148,7 +152,9 @@ class EventQueue implements Runnable {
* A "struct" to put on the queue. * A "struct" to put on the queue.
*/ */
static class QueueElement { static class QueueElement {
MailEvent event; MailEvent event;
Vector<? extends EventListener> vector; Vector<? extends EventListener> vector;
QueueElement(MailEvent event, Vector<? extends EventListener> vector) { QueueElement(MailEvent event, Vector<? extends EventListener> vector) {

View file

@ -31,6 +31,7 @@ import java.util.EventListener;
import java.util.List; import java.util.List;
import java.util.Vector; import java.util.Vector;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/** /**
* Folder is an abstract class that represents a folder for mail * Folder is an abstract class that represents a folder for mail
@ -151,22 +152,23 @@ public abstract class Folder implements AutoCloseable {
*/ */
protected Folder(Store store) { protected Folder(Store store) {
this.store = store; this.store = store;
// create or choose the appropriate event queue // create or choose the appropriate event queue
Session session = store.getSession(); Session session = store.getSession();
String scope = String scope = session.getProperties().getProperty("mail.event.scope", "folder");
session.getProperties().getProperty("mail.event.scope", "folder"); Executor executor = (Executor) session.getProperties().get("mail.event.executor");
Executor executor = if (executor == null) {
(Executor) session.getProperties().get("mail.event.executor"); executor = Executors.newSingleThreadExecutor();
if (scope.equalsIgnoreCase("application")) }
if (scope.equalsIgnoreCase("application")) {
q = EventQueue.getApplicationEventQueue(executor); q = EventQueue.getApplicationEventQueue(executor);
else if (scope.equalsIgnoreCase("session")) } else if (scope.equalsIgnoreCase("session")) {
q = session.getEventQueue(); q = session.getEventQueue();
else if (scope.equalsIgnoreCase("store")) } else if (scope.equalsIgnoreCase("store")) {
q = store.getEventQueue(); q = store.getEventQueue();
else // if (scope.equalsIgnoreCase("folder")) } else {
q = new EventQueue(executor); q = new EventQueue(executor);
} }
}
/** /**
* Returns the name of this Folder. <p> * Returns the name of this Folder. <p>

View file

@ -24,6 +24,9 @@ import java.net.UnknownHostException;
import java.util.EventListener; import java.util.EventListener;
import java.util.Vector; import java.util.Vector;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* An abstract class that contains the functionality * An abstract class that contains the functionality
@ -40,6 +43,8 @@ import java.util.concurrent.Executor;
public abstract class Service implements AutoCloseable { public abstract class Service implements AutoCloseable {
private static final Logger logger = Logger.getLogger(Service.class.getName());
/* /*
* connectionListeners is a Vector, initialized here, * connectionListeners is a Vector, initialized here,
* because we depend on it always existing and depend * because we depend on it always existing and depend
@ -47,8 +52,7 @@ public abstract class Service implements AutoCloseable {
* (Sychronizing on the Service object itself can cause * (Sychronizing on the Service object itself can cause
* deadlocks when notifying listeners.) * deadlocks when notifying listeners.)
*/ */
private final Vector<ConnectionListener> connectionListeners private final Vector<ConnectionListener> connectionListeners = new Vector<>();
= new Vector<>();
/** /**
* The queue of events to be delivered. * The queue of events to be delivered.
*/ */
@ -121,22 +125,21 @@ public abstract class Service implements AutoCloseable {
if (user == null) { if (user == null) {
user = System.getProperty("user.name"); user = System.getProperty("user.name");
} }
url = new URLName(protocol, host, port, file, user, password); url = new URLName(protocol, host, port, file, user, password);
// create or choose the appropriate event queue // create or choose the appropriate event queue
String scope = String scope = session.getProperties().getProperty("mail.event.scope", "folder");
session.getProperties().getProperty("mail.event.scope", "folder"); Executor executor = (Executor) session.getProperties().get("mail.event.executor");
Executor executor = if (executor == null) {
(Executor) session.getProperties().get("mail.event.executor"); executor = Executors.newSingleThreadExecutor();
if (scope.equalsIgnoreCase("application")) }
if (scope.equalsIgnoreCase("application")) {
q = EventQueue.getApplicationEventQueue(executor); q = EventQueue.getApplicationEventQueue(executor);
else if (scope.equalsIgnoreCase("session")) } else if (scope.equalsIgnoreCase("session")) {
q = session.getEventQueue(); q = session.getEventQueue();
else // if (scope.equalsIgnoreCase("store") || } else {
// scope.equalsIgnoreCase("folder"))
q = new EventQueue(executor); q = new EventQueue(executor);
} }
}
/** /**
* A generic connect method that takes no parameters. Subclasses * A generic connect method that takes no parameters. Subclasses
@ -563,7 +566,7 @@ public abstract class Service implements AutoCloseable {
* Yes, listeners could be removed after checking, which * Yes, listeners could be removed after checking, which
* just makes this an expensive no-op. * just makes this an expensive no-op.
*/ */
if (connectionListeners.size() > 0) { if (!connectionListeners.isEmpty()) {
ConnectionEvent e = new ConnectionEvent(this, type); ConnectionEvent e = new ConnectionEvent(this, type);
queueEvent(e, connectionListeners); queueEvent(e, connectionListeners);
} }
@ -578,9 +581,11 @@ public abstract class Service implements AutoCloseable {
* terminator event causes the event-dispatching thread to * terminator event causes the event-dispatching thread to
* self destruct. * self destruct.
*/ */
if (type == ConnectionEvent.CLOSED) if (type == ConnectionEvent.CLOSED) {
logger.log(Level.INFO, "sending terminator event");
q.terminateQueue(); q.terminateQueue();
} }
}
/** /**
* Return <code>getURLName.toString()</code> if this service has a URLName, * Return <code>getURLName.toString()</code> if this service has a URLName,
@ -601,8 +606,7 @@ public abstract class Service implements AutoCloseable {
* @param event the event * @param event the event
* @param vector the vector of listeners * @param vector the vector of listeners
*/ */
protected void queueEvent(MailEvent event, protected void queueEvent(MailEvent event, Vector<? extends EventListener> vector) {
Vector<? extends EventListener> vector) {
/* /*
* Copy the vector in order to freeze the state of the set * Copy the vector in order to freeze the state of the set
* of EventListeners the event should be delivered to prior * of EventListeners the event should be delivered to prior

View file

@ -40,6 +40,7 @@ import java.util.Properties;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Level; import java.util.logging.Level;
/** /**
@ -146,8 +147,8 @@ interface StreamLoader {
* Here's an example of <code>META-INF/javamail.default.providers</code> * Here's an example of <code>META-INF/javamail.default.providers</code>
* file contents: * file contents:
* <pre> * <pre>
* protocol=imap; type=store; class=com.sun.mail.imap.IMAPStore; vendor=Oracle; * protocol=imap; type=store; class=com.sun.mail.imap.IMAPStore; vendor=xbib;
* protocol=smtp; type=transport; class=com.sun.mail.smtp.SMTPTransport; vendor=Oracle; * protocol=smtp; type=transport; class=com.sun.mail.smtp.SMTPTransport; vendor=xbib;
* </pre><p> * </pre><p>
* <p> * <p>
* The current implementation also supports configuring providers using * The current implementation also supports configuring providers using
@ -211,7 +212,7 @@ public final class Session {
private static Session defaultSession = null; private static Session defaultSession = null;
static { static {
String dir = null; String dir;
String home = System.getProperty("java.home"); String home = System.getProperty("java.home");
String newdir = home + File.separator + "conf"; String newdir = home + File.separator + "conf";
File conf = new File(newdir); File conf = new File(newdir);
@ -261,7 +262,11 @@ public final class Session {
// load the resources // load the resources
loadProviders(cl); loadProviders(cl);
loadAddressMap(cl); loadAddressMap(cl);
q = new EventQueue((Executor) props.get("mail.event.executor")); Executor executor = (Executor) props.get("mail.event.executor");
if (executor == null) {
executor = Executors.newSingleThreadExecutor();
}
q = new EventQueue(executor);
} }
/** /**
@ -908,7 +913,6 @@ public final class Session {
try { try {
Class<?>[] c = {Session.class, URLName.class}; Class<?>[] c = {Session.class, URLName.class};
Constructor<?> cons = serviceClass.getConstructor(c); Constructor<?> cons = serviceClass.getConstructor(c);
Object[] o = {this, url}; Object[] o = {this, url};
return type.cast(cons.newInstance(o)); return type.cast(cons.newInstance(o));
} catch (Exception ex) { } catch (Exception ex) {

View file

@ -385,8 +385,9 @@ public class IMAPFolder extends Folder implements UIDFolder, ResponseHandler {
protected IMAPFolder(String fullName, char separator, IMAPStore store, protected IMAPFolder(String fullName, char separator, IMAPStore store,
Boolean isNamespace) { Boolean isNamespace) {
super(store); super(store);
if (fullName == null) if (fullName == null) {
throw new NullPointerException("Folder name is null"); throw new NullPointerException("Folder name is null");
}
this.fullName = fullName; this.fullName = fullName;
this.separator = separator; this.separator = separator;

View file

@ -1378,7 +1378,6 @@ public class IMAPStore extends Store
* Empty the connection pool. * Empty the connection pool.
*/ */
private void emptyConnectionPool(boolean force) { private void emptyConnectionPool(boolean force) {
synchronized (pool) { synchronized (pool) {
for (int index = pool.authenticatedConnections.size() - 1; for (int index = pool.authenticatedConnections.size() - 1;
index >= 0; --index) { index >= 0; --index) {
@ -1394,10 +1393,8 @@ public class IMAPStore extends Store
} }
; ;
} }
pool.authenticatedConnections.removeAllElements(); pool.authenticatedConnections.removeAllElements();
} }
logger.fine("removed all authenticated connections from pool"); logger.fine("removed all authenticated connections from pool");
} }

View file

@ -0,0 +1,6 @@
protocol=imap; type=store; class=org.xbib.net.mail.imap.IMAPStore; vendor=xbib;
protocol=imaps; type=store; class=org.xbib.net.mail.imap.IMAPSSLStore; vendor=xbib;
protocol=pop3; type=store; class=org.xbib.net.mail.pop3.POP3Store; vendor=xbib;
protocol=pop3s; type=store; class=org.xbib.net.mail.pop3.POP3SSLStore; vendor=xbib;
protocol=smtp; type=transport; class=org.xbib.net.mail.smtp.SMTPTransport; vendor=xbib;
protocol=smtps; type=transport; class=org.xbib.net.mail.smtp.SMTPSSLTransport; vendor=xbib;

View file

@ -1,3 +1,5 @@
protocol=imap; type=store; class=org.xbib.net.mail.imap.IMAPStore; vendor=xbib;
protocol=imaps; type=store; class=org.xbib.net.mail.imap.IMAPSSLStore; vendor=xbib;
protocol=pop3; type=store; class=org.xbib.net.mail.pop3.POP3Store; vendor=xbib; protocol=pop3; type=store; class=org.xbib.net.mail.pop3.POP3Store; vendor=xbib;
protocol=pop3s; type=store; class=org.xbib.net.mail.pop3.POP3SSLStore; vendor=xbib; protocol=pop3s; type=store; class=org.xbib.net.mail.pop3.POP3SSLStore; vendor=xbib;
protocol=smtp; type=transport; class=org.xbib.net.mail.smtp.SMTPTransport; vendor=xbib; protocol=smtp; type=transport; class=org.xbib.net.mail.smtp.SMTPTransport; vendor=xbib;

View file

@ -19,7 +19,6 @@ package org.xbib.net.mail.test.imap;
import jakarta.mail.Session; import jakarta.mail.Session;
import jakarta.mail.Store; import jakarta.mail.Store;
import jakarta.mail.event.StoreEvent; import jakarta.mail.event.StoreEvent;
import jakarta.mail.event.StoreListener;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.net.mail.test.test.TestServer; import org.xbib.net.mail.test.test.TestServer;
@ -28,6 +27,9 @@ import java.io.OutputStream;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
@ -36,27 +38,25 @@ import static org.junit.jupiter.api.Assertions.fail;
*/ */
public final class IMAPAlertTest { public final class IMAPAlertTest {
private static final Logger logger = Logger.getLogger(IMAPAlertTest.class.getName());
private volatile boolean gotAlert = false; private volatile boolean gotAlert = false;
@Test @Test
public void test() { public void testImapAlert() {
TestServer server = null; TestServer server = null;
try { try {
final IMAPHandler handler = new IMAPHandlerAlert(); final IMAPHandler handler = new IMAPHandlerAlert();
server = new TestServer(handler); server = new TestServer(handler);
server.start(); server.start();
final Properties properties = new Properties(); final Properties properties = new Properties();
properties.setProperty("mail.imap.host", "localhost"); properties.setProperty("mail.imap.host", "localhost");
properties.setProperty("mail.imap.port", String.valueOf(server.getPort())); properties.setProperty("mail.imap.port", String.valueOf(server.getPort()));
final Session session = Session.getInstance(properties); final Session session = Session.getInstance(properties);
//session.setDebug(true); //session.setDebug(true);
final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch latch = new CountDownLatch(1);
final Store store = session.getStore("imap"); final Store store = session.getStore("imap");
store.addStoreListener(new StoreListener() { store.addStoreListener(e -> {
@Override
public void notification(StoreEvent e) {
String s; String s;
if (e.getMessageType() == StoreEvent.ALERT) { if (e.getMessageType() == StoreEvent.ALERT) {
s = "ALERT: "; s = "ALERT: ";
@ -64,24 +64,22 @@ public final class IMAPAlertTest {
latch.countDown(); latch.countDown();
} else } else
s = "NOTICE: "; s = "NOTICE: ";
//System.out.println(s + e.getMessage()); //System.out.println(s + e.getMessage());
}
}); });
try { try {
store.connect("test", "test"); store.connect("test", "test");
// time for event to be delivered // time for event to be delivered
latch.await(5, TimeUnit.SECONDS); latch.await(5, TimeUnit.SECONDS);
assertTrue(gotAlert); assertTrue(gotAlert);
} catch (Exception ex) { } catch (Exception ex) {
System.out.println(ex); logger.log(Level.SEVERE, ex.getMessage(), ex);
//ex.printStackTrace();
fail(ex.toString()); fail(ex.toString());
} finally { } finally {
store.close(); store.close();
} }
} catch (final Exception e) { } catch (final Exception e) {
//e.printStackTrace(); logger.log(Level.SEVERE, e.getMessage(), e);
fail(e.getMessage()); fail(e.getMessage());
} finally { } finally {
if (server != null) { if (server != null) {

View file

@ -38,7 +38,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
/** /**
* Test IMAPFolder methods. * Test IMAPFolder methods.
*/ */
@ -68,8 +67,7 @@ class IMAPFolderTest {
void testUtf7FolderNameCreate() { void testUtf7FolderNameCreate() {
testWithHandler(new IMAPTest() { testWithHandler(new IMAPTest() {
@Override @Override
public void test(Store store, IMAPHandler handler) public void test(Store store, IMAPHandler handler) throws MessagingException {
throws MessagingException, IOException {
Folder test = store.getFolder(utf8Folder); Folder test = store.getFolder(utf8Folder);
assertTrue(test.create(Folder.HOLDS_MESSAGES)); assertTrue(test.create(Folder.HOLDS_MESSAGES));
} }
@ -101,11 +99,9 @@ class IMAPFolderTest {
*/ */
@Test @Test
void testUtf8FolderNameCreate() { void testUtf8FolderNameCreate() {
testWithHandler( testWithHandler(new IMAPTest() {
new IMAPTest() {
@Override @Override
public void test(Store store, IMAPHandler handler) public void test(Store store, IMAPHandler handler) throws MessagingException {
throws MessagingException, IOException {
Folder test = store.getFolder(utf8Folder); Folder test = store.getFolder(utf8Folder);
assertTrue(test.create(Folder.HOLDS_MESSAGES)); assertTrue(test.create(Folder.HOLDS_MESSAGES));
} }
@ -137,11 +133,9 @@ class IMAPFolderTest {
*/ */
@Test @Test
void testUtf7FolderNameDelete() { void testUtf7FolderNameDelete() {
testWithHandler( testWithHandler(new IMAPTest() {
new IMAPTest() {
@Override @Override
public void test(Store store, IMAPHandler handler) public void test(Store store, IMAPHandler handler) throws MessagingException {
throws MessagingException, IOException {
Folder test = store.getFolder(utf8Folder); Folder test = store.getFolder(utf8Folder);
assertTrue(test.delete(false)); assertTrue(test.delete(false));
} }
@ -167,11 +161,9 @@ class IMAPFolderTest {
*/ */
@Test @Test
void testUtf8FolderNameDelete() { void testUtf8FolderNameDelete() {
testWithHandler( testWithHandler(new IMAPTest() {
new IMAPTest() {
@Override @Override
public void test(Store store, IMAPHandler handler) public void test(Store store, IMAPHandler handler) throws MessagingException {
throws MessagingException, IOException {
Folder test = store.getFolder(utf8Folder); Folder test = store.getFolder(utf8Folder);
assertTrue(test.delete(false)); assertTrue(test.delete(false));
} }
@ -198,11 +190,9 @@ class IMAPFolderTest {
*/ */
@Test @Test
void testUtf7FolderNameSelect() { void testUtf7FolderNameSelect() {
testWithHandler( testWithHandler(new IMAPTest() {
new IMAPTest() {
@Override @Override
public void test(Store store, IMAPHandler handler) public void test(Store store, IMAPHandler handler) throws MessagingException {
throws MessagingException, IOException {
Folder test = store.getFolder(utf8Folder); Folder test = store.getFolder(utf8Folder);
test.open(Folder.READ_WRITE); test.open(Folder.READ_WRITE);
test.close(true); test.close(true);
@ -230,11 +220,9 @@ class IMAPFolderTest {
*/ */
@Test @Test
void testUtf8FolderNameSelect() { void testUtf8FolderNameSelect() {
testWithHandler( testWithHandler(new IMAPTest() {
new IMAPTest() {
@Override @Override
public void test(Store store, IMAPHandler handler) public void test(Store store, IMAPHandler handler) throws MessagingException {
throws MessagingException, IOException {
Folder test = store.getFolder(utf8Folder); Folder test = store.getFolder(utf8Folder);
test.open(Folder.READ_WRITE); test.open(Folder.READ_WRITE);
test.close(true); test.close(true);
@ -262,11 +250,9 @@ class IMAPFolderTest {
*/ */
@Test @Test
void testUtf7FolderNameExamine() { void testUtf7FolderNameExamine() {
testWithHandler( testWithHandler(new IMAPTest() {
new IMAPTest() {
@Override @Override
public void test(Store store, IMAPHandler handler) public void test(Store store, IMAPHandler handler) throws MessagingException {
throws MessagingException, IOException {
Folder test = store.getFolder(utf8Folder); Folder test = store.getFolder(utf8Folder);
test.open(Folder.READ_ONLY); test.open(Folder.READ_ONLY);
test.close(true); test.close(true);
@ -294,11 +280,9 @@ class IMAPFolderTest {
*/ */
@Test @Test
void testUtf8FolderNameExamine() { void testUtf8FolderNameExamine() {
testWithHandler( testWithHandler(new IMAPTest() {
new IMAPTest() {
@Override @Override
public void test(Store store, IMAPHandler handler) public void test(Store store, IMAPHandler handler) throws MessagingException {
throws MessagingException, IOException {
Folder test = store.getFolder(utf8Folder); Folder test = store.getFolder(utf8Folder);
test.open(Folder.READ_ONLY); test.open(Folder.READ_ONLY);
test.close(true); test.close(true);
@ -326,11 +310,9 @@ class IMAPFolderTest {
*/ */
@Test @Test
void testUtf7FolderNameStatus() { void testUtf7FolderNameStatus() {
testWithHandler( testWithHandler(new IMAPTest() {
new IMAPTest() {
@Override @Override
public void test(Store store, IMAPHandler handler) public void test(Store store, IMAPHandler handler) throws MessagingException {
throws MessagingException, IOException {
Folder test = store.getFolder(utf8Folder); Folder test = store.getFolder(utf8Folder);
assertEquals(123, ((UIDFolder) test).getUIDValidity()); assertEquals(123, ((UIDFolder) test).getUIDValidity());
} }
@ -343,8 +325,7 @@ class IMAPFolderTest {
st.nextToken(); // skip "STATUS" st.nextToken(); // skip "STATUS"
String name = st.nextToken(); String name = st.nextToken();
if (name.equals(utf7Folder)) { if (name.equals(utf7Folder)) {
untagged(outputStream, "STATUS " + utf7Folder + untagged(outputStream, "STATUS " + utf7Folder + " (UIDVALIDITY 123)");
" (UIDVALIDITY 123)");
ok(outputStream); ok(outputStream);
} else } else
no(outputStream, "wrong name"); no(outputStream, "wrong name");
@ -358,11 +339,9 @@ class IMAPFolderTest {
*/ */
@Test @Test
void testUtf8FolderNameStatus() { void testUtf8FolderNameStatus() {
testWithHandler( testWithHandler(new IMAPTest() {
new IMAPTest() {
@Override @Override
public void test(Store store, IMAPHandler handler) public void test(Store store, IMAPHandler handler) throws MessagingException {
throws MessagingException, IOException {
Folder test = store.getFolder(utf8Folder); Folder test = store.getFolder(utf8Folder);
assertEquals(123, ((UIDFolder) test).getUIDValidity()); assertEquals(123, ((UIDFolder) test).getUIDValidity());
} }
@ -375,8 +354,7 @@ class IMAPFolderTest {
st.nextToken(); // skip "STATUS" st.nextToken(); // skip "STATUS"
String name = unquote(st.nextToken()); String name = unquote(st.nextToken());
if (name.equals(utf8Folder)) { if (name.equals(utf8Folder)) {
untagged(outputStream, "STATUS \"" + utf8Folder + untagged(outputStream, "STATUS \"" + utf8Folder + "\" (UIDVALIDITY 123)");
"\" (UIDVALIDITY 123)");
ok(outputStream); ok(outputStream);
} else } else
no(outputStream, "wrong name"); no(outputStream, "wrong name");
@ -389,17 +367,12 @@ class IMAPFolderTest {
*/ */
@Test @Test
void testUidNotStickyFalse() { void testUidNotStickyFalse() {
testWithHandler( testWithHandler(new IMAPTest() {
new IMAPTest() {
@Override @Override
public void test(Store store, IMAPHandler handler) public void test(Store store, IMAPHandler handler) throws MessagingException {
throws MessagingException, IOException { try (Folder test = store.getFolder("test")) {
Folder test = store.getFolder("test");
try {
test.open(Folder.READ_WRITE); test.open(Folder.READ_WRITE);
assertFalse(((IMAPFolder) test).getUIDNotSticky()); assertFalse(((IMAPFolder) test).getUIDNotSticky());
} finally {
test.close();
} }
} }
}, },
@ -411,17 +384,12 @@ class IMAPFolderTest {
*/ */
@Test @Test
void testUidNotStickyTrue() { void testUidNotStickyTrue() {
testWithHandler( testWithHandler(new IMAPTest() {
new IMAPTest() {
@Override @Override
public void test(Store store, IMAPHandler handler) public void test(Store store, IMAPHandler handler) throws MessagingException {
throws MessagingException, IOException { try (Folder test = store.getFolder("test")) {
Folder test = store.getFolder("test");
try {
test.open(Folder.READ_WRITE); test.open(Folder.READ_WRITE);
assertTrue(((IMAPFolder) test).getUIDNotSticky()); assertTrue(((IMAPFolder) test).getUIDNotSticky());
} finally {
test.close();
} }
} }
}, },
@ -440,11 +408,10 @@ class IMAPFolderTest {
*/ */
@Test @Test
void testExpungeOutOfRange() { void testExpungeOutOfRange() {
testWithHandler( testWithHandler(new IMAPTest() {
new IMAPTest() {
@Override @Override
public void test(Store store, IMAPHandler handler) public void test(Store store, IMAPHandler handler)
throws MessagingException, IOException { throws MessagingException {
Folder test = store.getFolder("test"); Folder test = store.getFolder("test");
try { try {
test.open(Folder.READ_WRITE); test.open(Folder.READ_WRITE);
@ -471,27 +438,22 @@ class IMAPFolderTest {
try { try {
server = new TestServer(handler); server = new TestServer(handler);
server.start(); server.start();
final Properties properties = new Properties(); final Properties properties = new Properties();
properties.setProperty("mail.imap.host", "localhost"); properties.setProperty("mail.imap.host", "localhost");
properties.setProperty("mail.imap.port", String.valueOf(server.getPort())); properties.setProperty("mail.imap.port", String.valueOf(server.getPort()));
test.init(properties); test.init(properties);
final Session session = Session.getInstance(properties); final Session session = Session.getInstance(properties);
//session.setDebug(true); //session.setDebug(true);
try (Store store = session.getStore("imap")) {
final Store store = session.getStore("imap");
try {
store.connect("test", "test"); store.connect("test", "test");
test.test(store, handler); test.test(store, handler);
} catch (Exception ex) { } catch (Exception ex) {
logger.log(Level.SEVERE, ex.getMessage(), ex); logger.log(Level.SEVERE, ex.getMessage(), ex);
fail(ex.toString()); fail();
} finally {
store.close();
} }
} catch (final Exception e) { } catch (final Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e); logger.log(Level.SEVERE, e.getMessage(), e);
fail(e.getMessage()); fail();
} finally { } finally {
if (server != null) { if (server != null) {
server.quit(); server.quit();

View file

@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test;
import org.xbib.net.mail.test.test.TestServer; import org.xbib.net.mail.test.test.TestServer;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.util.Properties; import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
@ -101,9 +102,9 @@ public final class POP3FolderClosedExceptionTest {
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void retr(String arg) throws IOException { public void retr(OutputStream outputStream, String arg) throws IOException {
if (first) { if (first) {
println("-ERR Server timeout"); println(outputStream, "-ERR Server timeout");
first = false; first = false;
} else } else
exit(); exit();
@ -163,8 +164,8 @@ public final class POP3FolderClosedExceptionTest {
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void top(String arg) throws IOException { public void top(OutputStream outputStream, String arg) throws IOException {
println("-ERR Server timeout"); println(outputStream, "-ERR Server timeout");
exit(); exit();
} }
} }

View file

@ -21,8 +21,11 @@ import org.xbib.net.mail.test.test.ProtocolHandler;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.channels.Channels;
import java.nio.charset.StandardCharsets;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* Handle connection. * Handle connection.
@ -31,35 +34,36 @@ import java.util.logging.Level;
*/ */
public class POP3Handler extends ProtocolHandler { public class POP3Handler extends ProtocolHandler {
/** private static final Logger logger = Logger.getLogger(POP3Handler.class.getName());
* Current line.
*/
private String currentLine;
/** /**
* First test message. * First test message.
*/ */
private String top1 = private final String top1 =
"Mime-Version: 1.0\r\n" + """
"From: joe@example.com\r\n" + Mime-Version: 1.0\r
"To: bob@example.com\r\n" + From: joe@example.com\r
"Subject: Example\r\n" + To: bob@example.com\r
"Content-Type: text/plain\r\n" + Subject: Example\r
"\r\n"; Content-Type: text/plain\r
private String msg1 = top1 + \r
""";
private final String msg1 = top1 +
"plain text\r\n"; "plain text\r\n";
/** /**
* Second test message. * Second test message.
*/ */
private String top2 = private final String top2 =
"Mime-Version: 1.0\r\n" + """
"From: joe@example.com\r\n" + Mime-Version: 1.0\r
"To: bob@example.com\r\n" + From: joe@example.com\r
"Subject: Multipart Example\r\n" + To: bob@example.com\r
"Content-Type: multipart/mixed; boundary=\"xxx\"\r\n" + Subject: Multipart Example\r
"\r\n"; Content-Type: multipart/mixed; boundary="xxx"\r
private String msg2 = top2 + \r
""";
private final String msg2 = top2 +
"preamble\r\n" + "preamble\r\n" +
"--xxx\r\n" + "--xxx\r\n" +
"\r\n" + "\r\n" +
@ -71,9 +75,7 @@ public class POP3Handler extends ProtocolHandler {
"\r\n" + "\r\n" +
"--xxx--\r\n"; "--xxx--\r\n";
public POP3Handler(InputStream inputStream, public POP3Handler() {
OutputStream outputStream) {
super(inputStream, outputStream);
} }
/** /**
@ -82,8 +84,8 @@ public class POP3Handler extends ProtocolHandler {
* @throws IOException unable to write to socket * @throws IOException unable to write to socket
*/ */
@Override @Override
public void sendGreetings() throws IOException { public void sendGreetings(OutputStream outputStream) throws IOException {
this.println("+OK POP3 CUSTOM"); this.println(outputStream, "+OK POP3 CUSTOM");
} }
/** /**
@ -92,10 +94,9 @@ public class POP3Handler extends ProtocolHandler {
* @param str String to send * @param str String to send
* @throws IOException unable to write to socket * @throws IOException unable to write to socket
*/ */
public void println(final String str) throws IOException { public void println(OutputStream outputStream, final String str) throws IOException {
this.writer.print(str); Channels.newChannel(outputStream).write(StandardCharsets.UTF_8.encode(str + "\r\n"));
this.writer.print("\r\n"); outputStream.flush();
this.writer.flush();
} }
/** /**
@ -104,54 +105,36 @@ public class POP3Handler extends ProtocolHandler {
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
@Override @Override
public void handleCommand() throws IOException { public void handleCommand(InputStream inputStream, OutputStream outputStream) throws IOException {
this.currentLine = readLine(); String currentLine = readLine(inputStream);
if (this.currentLine == null) { if (currentLine == null) {
// probably just EOF because the socket was closed // probably just EOF because the socket was closed
//LOGGER.severe("Current line is null!"); //LOGGER.severe("Current line is null!");
this.exit(); this.exit();
return; return;
} }
final StringTokenizer st = new StringTokenizer(currentLine, " ");
final StringTokenizer st = new StringTokenizer(this.currentLine, " ");
final String commandName = st.nextToken().toUpperCase(); final String commandName = st.nextToken().toUpperCase();
final String arg = st.hasMoreTokens() ? st.nextToken() : null; final String arg = st.hasMoreTokens() ? st.nextToken() : null;
if (commandName == null) { switch (commandName) {
logger.severe("Command name is empty!"); case "STAT" -> this.stat(outputStream);
this.exit(); case "LIST" -> this.list(outputStream);
return; case "RETR" -> this.retr(outputStream, arg);
} case "DELE" -> this.dele(outputStream);
case "NOOP" -> this.noop(outputStream);
if (commandName.equals("STAT")) { case "RSET" -> this.rset(outputStream);
this.stat(); case "QUIT" -> this.quit(outputStream);
} else if (commandName.equals("LIST")) { case "TOP" -> this.top(outputStream, arg);
this.list(); case "UIDL" -> this.uidl(outputStream);
} else if (commandName.equals("RETR")) { case "USER" -> this.user(outputStream);
this.retr(arg); case "PASS" -> this.pass(outputStream);
} else if (commandName.equals("DELE")) { case "CAPA" -> this.capa(outputStream);
this.dele(); case "AUTH" -> this.auth(outputStream);
} else if (commandName.equals("NOOP")) { default -> {
this.noop();
} else if (commandName.equals("RSET")) {
this.rset();
} else if (commandName.equals("QUIT")) {
this.quit();
} else if (commandName.equals("TOP")) {
this.top(arg);
} else if (commandName.equals("UIDL")) {
this.uidl();
} else if (commandName.equals("USER")) {
this.user();
} else if (commandName.equals("PASS")) {
this.pass();
} else if (commandName.equals("CAPA")) {
this.capa();
} else if (commandName.equals("AUTH")) {
this.auth();
} else {
logger.log(Level.SEVERE, "ERROR command unknown: {0}", commandName); logger.log(Level.SEVERE, "ERROR command unknown: {0}", commandName);
this.println("-ERR unknown command"); this.println(outputStream, "-ERR unknown command");
}
} }
} }
@ -160,8 +143,8 @@ public class POP3Handler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void stat() throws IOException { public void stat(OutputStream outputStream) throws IOException {
this.println("+OK 2 " + (msg1.length() + msg2.length())); this.println(outputStream, "+OK 2 " + (msg1.length() + msg2.length()));
} }
/** /**
@ -169,11 +152,8 @@ public class POP3Handler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void list() throws IOException { public void list(OutputStream outputStream) throws IOException {
this.writer.println("+OK"); this.println(outputStream, "+OK\r\n1 " + msg1.length() + "\r\n2 " + msg2.length() + "\r\n.");
this.writer.println("1 " + msg1.length());
this.writer.println("2 " + msg2.length());
this.println(".");
} }
/** /**
@ -181,15 +161,14 @@ public class POP3Handler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void retr(String arg) throws IOException { public void retr(OutputStream outputStream, String arg) throws IOException {
String msg; String msg;
if (arg.equals("1")) if ("1".equals(arg))
msg = msg1; msg = msg1;
else else
msg = msg2; msg = msg2;
this.println("+OK " + msg.length() + " octets"); this.println(outputStream, "+OK " + msg.length() + " octets");
this.writer.write(msg); this.println(outputStream, msg + ".");
this.println(".");
} }
/** /**
@ -197,8 +176,8 @@ public class POP3Handler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void dele() throws IOException { public void dele(OutputStream outputStream) throws IOException {
this.println("-ERR DELE not supported"); this.println(outputStream, "-ERR DELE not supported");
} }
/** /**
@ -206,8 +185,8 @@ public class POP3Handler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void noop() throws IOException { public void noop(OutputStream outputStream) throws IOException {
this.println("+OK"); this.println(outputStream, "+OK");
} }
/** /**
@ -215,8 +194,8 @@ public class POP3Handler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void rset() throws IOException { public void rset(OutputStream outputStream) throws IOException {
this.println("+OK"); this.println(outputStream, "+OK");
} }
/** /**
@ -224,8 +203,8 @@ public class POP3Handler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void quit() throws IOException { public void quit(OutputStream outputStream) throws IOException {
this.println("+OK"); this.println(outputStream, "+OK");
this.exit(); this.exit();
} }
@ -235,15 +214,14 @@ public class POP3Handler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void top(String arg) throws IOException { public void top(OutputStream outputStream, String arg) throws IOException {
String top; String top;
if (arg.equals("1")) if ("1".equals(arg))
top = top1; top = top1;
else else
top = top2; top = top2;
this.println("+OK " + top.length() + " octets"); this.println(outputStream, "+OK " + top.length() + " octets");
this.writer.write(top); this.println(outputStream, top + ".");
this.println(".");
} }
/** /**
@ -251,11 +229,8 @@ public class POP3Handler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void uidl() throws IOException { public void uidl(OutputStream outputStream) throws IOException {
this.writer.println("+OK"); this.println(outputStream, "+OK\r\n1 1\r\n2 2\r\n.");
this.writer.println("1 1");
this.writer.println("2 2");
this.println(".");
} }
/** /**
@ -263,8 +238,8 @@ public class POP3Handler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void user() throws IOException { public void user(OutputStream outputStream) throws IOException {
this.println("+OK"); this.println(outputStream, "+OK");
} }
/** /**
@ -272,8 +247,8 @@ public class POP3Handler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void pass() throws IOException { public void pass(OutputStream outputStream) throws IOException {
this.println("+OK"); this.println(outputStream, "+OK");
} }
/** /**
@ -281,8 +256,8 @@ public class POP3Handler extends ProtocolHandler {
* *
* @throws IOException unable to write to socket * @throws IOException unable to write to socket
*/ */
public void capa() throws IOException { public void capa(OutputStream outputStream) throws IOException {
this.println("-ERR CAPA not supported"); this.println(outputStream, "-ERR CAPA not supported");
} }
/** /**
@ -290,7 +265,7 @@ public class POP3Handler extends ProtocolHandler {
* *
* @throws IOException unable to write to socket * @throws IOException unable to write to socket
*/ */
public void auth() throws IOException { public void auth(OutputStream outputStream) throws IOException {
this.println("-ERR AUTH not supported"); this.println(outputStream, "-ERR AUTH not supported");
} }
} }

View file

@ -25,6 +25,8 @@ import org.xbib.net.mail.pop3.POP3Store;
import org.xbib.net.mail.test.test.TestServer; import org.xbib.net.mail.test.test.TestServer;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties; import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ -242,15 +244,13 @@ public final class POP3StoreTest {
*/ */
private static class POP3HandlerXOAUTH extends POP3Handler { private static class POP3HandlerXOAUTH extends POP3Handler {
@Override @Override
public void auth() throws IOException { public void auth(OutputStream outputStream) throws IOException {
this.println("+OK POP3 server ready"); this.println(outputStream, "+OK POP3 server ready");
} }
@Override @Override
public void capa() throws IOException { public void capa(OutputStream outputStream) throws IOException {
this.writer.println("+OK"); this.println(outputStream, "+OK\r\nSASL PLAIN XOAUTH2\r\n.");
this.writer.println("SASL PLAIN XOAUTH2");
this.println(".");
} }
} }
@ -265,8 +265,8 @@ public final class POP3StoreTest {
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void noop() throws IOException { public void noop(OutputStream outputStream) throws IOException {
this.println("-ERR"); this.println(outputStream, "-ERR");
} }
} }
@ -281,8 +281,8 @@ public final class POP3StoreTest {
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void sendGreetings() throws IOException { public void sendGreetings(OutputStream outputStream) throws IOException {
this.println("+OK"); this.println(outputStream, "+OK");
} }
} }
} }

View file

@ -25,6 +25,8 @@ import org.junit.jupiter.api.Test;
import org.xbib.net.mail.test.test.TestServer; import org.xbib.net.mail.test.test.TestServer;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties; import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
@ -86,9 +88,6 @@ public final class SMTPBdatTest {
} finally { } finally {
if (server != null) { if (server != null) {
server.quit(); server.quit();
server.interrupt();
// wait for handler to exit
server.join();
} }
} }
} }
@ -105,13 +104,13 @@ public final class SMTPBdatTest {
} }
@Override @Override
public void bdat(String line) throws IOException { public void bdat(InputStream inputStream, OutputStream outputStream, String line) throws IOException {
String[] tok = line.split("\\s+"); String[] tok = line.split("\\s+");
int bytes = Integer.parseInt(tok[1]); int bytes = Integer.parseInt(tok[1]);
boolean last = tok.length > 2 && boolean last = tok.length > 2 &&
tok[2].equalsIgnoreCase("LAST"); tok[2].equalsIgnoreCase("LAST");
readBdatMessage(bytes, last); readBdatMessage(inputStream, bytes, last);
println("444 failed"); println(outputStream, "444 failed");
} }
}; };
server = new TestServer(handler); server = new TestServer(handler);
@ -144,9 +143,6 @@ public final class SMTPBdatTest {
} finally { } finally {
if (server != null) { if (server != null) {
server.quit(); server.quit();
server.interrupt();
// wait for handler to exit
server.join();
} }
} }
} }

View file

@ -24,11 +24,13 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.channels.Channels;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* Handle connection. * Handle connection.
@ -37,6 +39,8 @@ import java.util.logging.Level;
*/ */
public class SMTPHandler extends ProtocolHandler { public class SMTPHandler extends ProtocolHandler {
private static final Logger logger = Logger.getLogger(SMTPHandler.class.getName());
/** /**
* Current line. * Current line.
*/ */
@ -59,7 +63,7 @@ public class SMTPHandler extends ProtocolHandler {
*/ */
@Override @Override
public void sendGreetings(OutputStream outputStream) throws IOException { public void sendGreetings(OutputStream outputStream) throws IOException {
println("220 localhost dummy server ready"); println(outputStream, "220 localhost dummy server ready");
} }
/** /**
@ -69,9 +73,8 @@ public class SMTPHandler extends ProtocolHandler {
* @throws IOException unable to write to socket * @throws IOException unable to write to socket
*/ */
public void println(OutputStream outputStream, final String str) throws IOException { public void println(OutputStream outputStream, final String str) throws IOException {
writer.print(str); Channels.newChannel(outputStream).write(StandardCharsets.UTF_8.encode(str + "\r\n"));
writer.print("\r\n"); outputStream.flush();
writer.flush();
} }
/** /**
@ -80,52 +83,35 @@ public class SMTPHandler extends ProtocolHandler {
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
@Override @Override
public void handleCommand() throws IOException { public void handleCommand(InputStream inputStream, OutputStream outputStream) throws IOException {
currentLine = readLine(); currentLine = readLine(inputStream);
if (currentLine == null) {
if (currentLine == null)
return; return;
}
final StringTokenizer st = new StringTokenizer(currentLine, " "); final StringTokenizer st = new StringTokenizer(currentLine, " ");
final String commandName = st.nextToken().toUpperCase(); final String commandName = st.nextToken().toUpperCase();
if (commandName == null) { switch (commandName) {
logger.severe("Command name is empty!"); case "HELO" -> helo(outputStream);
exit(); case "EHLO" -> ehlo(outputStream);
return; case "MAIL" -> mail(outputStream, currentLine);
} case "RCPT" -> rcpt(outputStream, currentLine);
case "DATA" -> data(inputStream, outputStream);
if (commandName.equals("HELO")) { case "BDAT" -> bdat(inputStream,outputStream, currentLine);
helo(); case "NOOP" -> noop(outputStream);
} else if (commandName.equals("EHLO")) { case "RSET" -> rset(outputStream);
ehlo(); case "QUIT" -> quit(outputStream);
} else if (commandName.equals("MAIL")) { case "AUTH" -> auth(inputStream, outputStream, currentLine);
mail(currentLine); default -> {
} else if (commandName.equals("RCPT")) {
rcpt(currentLine);
} else if (commandName.equals("DATA")) {
data();
} else if (commandName.equals("BDAT")) {
bdat(currentLine);
} else if (commandName.equals("NOOP")) {
noop();
} else if (commandName.equals("RSET")) {
rset();
} else if (commandName.equals("QUIT")) {
quit();
} else if (commandName.equals("AUTH")) {
auth(currentLine);
} else {
logger.log(Level.SEVERE, "ERROR command unknown: {0}", commandName); logger.log(Level.SEVERE, "ERROR command unknown: {0}", commandName);
println("-ERR unknown command"); println(outputStream, "-ERR unknown command");
}
} }
} }
protected String readLine() throws IOException { @Override
currentLine = super.readLine(); protected String readLine(InputStream inputStream) throws IOException {
currentLine = super.readLine(inputStream);
if (currentLine == null) { if (currentLine == null) {
// XXX - often happens when shutting down
//LOGGER.severe("Current line is null!");
exit(); exit();
} }
return currentLine; return currentLine;
@ -136,8 +122,8 @@ public class SMTPHandler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void helo() throws IOException { public void helo(OutputStream outputStream) throws IOException {
println("220 Ok"); println(outputStream, "220 Ok");
} }
/** /**
@ -145,11 +131,11 @@ public class SMTPHandler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void ehlo() throws IOException { public void ehlo(OutputStream outputStream) throws IOException {
println("250-hello"); println(outputStream, "250-hello");
for (String ext : extensions) for (String ext : extensions)
println("250-" + ext); println(outputStream, "250-" + ext);
println("250 AUTH PLAIN"); // PLAIN is simplest to fake println(outputStream, "250 AUTH PLAIN"); // PLAIN is simplest to fake
} }
/** /**
@ -157,8 +143,8 @@ public class SMTPHandler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void mail(String line) throws IOException { public void mail(OutputStream outputStream, String line) throws IOException {
ok(); ok(outputStream);
} }
/** /**
@ -166,8 +152,8 @@ public class SMTPHandler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void rcpt(String line) throws IOException { public void rcpt(OutputStream outputStream, String line) throws IOException {
ok(); ok(outputStream);
} }
/** /**
@ -175,10 +161,10 @@ public class SMTPHandler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void data() throws IOException { public void data(InputStream inputStream, OutputStream outputStream) throws IOException {
println("354 go ahead"); println(outputStream, "354 go ahead");
readMessage(); readMessage(inputStream);
ok(); ok(outputStream);
} }
/** /**
@ -186,14 +172,14 @@ public class SMTPHandler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void bdat(String line) throws IOException { public void bdat(InputStream inputStream, OutputStream outputStream, String line) throws IOException {
StringTokenizer st = new StringTokenizer(line, " "); StringTokenizer st = new StringTokenizer(line, " ");
String commandName = st.nextToken(); String commandName = st.nextToken();
int bytes = Integer.parseInt(st.nextToken()); int bytes = Integer.parseInt(st.nextToken());
boolean last = st.hasMoreTokens() && boolean last = st.hasMoreTokens() &&
st.nextToken().equalsIgnoreCase("LAST"); st.nextToken().equalsIgnoreCase("LAST");
readBdatMessage(bytes, last); readBdatMessage(inputStream, bytes, last);
ok(); ok(outputStream);
} }
/** /**
@ -205,11 +191,11 @@ public class SMTPHandler extends ProtocolHandler {
/** /**
* Consume the message and save it. * Consume the message and save it.
*/ */
protected void readMessage() throws IOException { protected void readMessage(InputStream inputStream) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
PrintWriter pw = new PrintWriter(new OutputStreamWriter(bos, StandardCharsets.UTF_8)); PrintWriter pw = new PrintWriter(new OutputStreamWriter(bos, StandardCharsets.UTF_8));
String line; String line;
while ((line = super.readLine()) != null) { while ((line = super.readLine(inputStream)) != null) {
if (line.equals(".")) if (line.equals("."))
break; break;
if (line.startsWith(".")) if (line.startsWith("."))
@ -225,7 +211,7 @@ public class SMTPHandler extends ProtocolHandler {
* Consume a chunk of the message and save it. * Consume a chunk of the message and save it.
* Save the entire message when the last chunk is received. * Save the entire message when the last chunk is received.
*/ */
protected void readBdatMessage(int bytes, boolean last) throws IOException { protected void readBdatMessage(InputStream inputStream, int bytes, boolean last) throws IOException {
byte[] data = new byte[bytes]; byte[] data = new byte[bytes];
int len = data.length; int len = data.length;
int off = 0; int off = 0;
@ -248,8 +234,8 @@ public class SMTPHandler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void noop() throws IOException { public void noop(OutputStream outputStream) throws IOException {
ok(); ok(outputStream);
} }
/** /**
@ -257,8 +243,8 @@ public class SMTPHandler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void rset() throws IOException { public void rset(OutputStream outputStream) throws IOException {
ok(); ok(outputStream);
} }
/** /**
@ -266,8 +252,8 @@ public class SMTPHandler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void quit() throws IOException { public void quit(OutputStream outputStream) throws IOException {
println("221 BYE"); println(outputStream, "221 BYE");
exit(); exit();
} }
@ -276,11 +262,11 @@ public class SMTPHandler extends ProtocolHandler {
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
public void auth(String line) throws IOException { public void auth(InputStream inputStream, OutputStream outputStream, String line) throws IOException {
println("235 Authorized"); println(outputStream, "235 Authorized");
} }
protected void ok() throws IOException { protected void ok(OutputStream outputStream) throws IOException {
println("250 OK"); println(outputStream, "250 OK");
} }
} }

View file

@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.Timeout;
import org.xbib.net.mail.test.test.TestServer; import org.xbib.net.mail.test.test.TestServer;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
@ -48,13 +49,13 @@ public final class SMTPIOExceptionTest {
try { try {
SMTPHandler handler = new SMTPHandler() { SMTPHandler handler = new SMTPHandler() {
@Override @Override
public void rcpt(String line) throws IOException { public void rcpt(OutputStream outputStream, String line) throws IOException {
try { try {
// delay long enough to cause timeout // delay long enough to cause timeout
Thread.sleep(2 * TIMEOUT); Thread.sleep(2 * TIMEOUT);
} catch (Exception ex) { } catch (Exception ex) {
} }
super.rcpt(line); super.rcpt(outputStream, line);
} }
}; };
server = new TestServer(handler); server = new TestServer(handler);
@ -101,9 +102,6 @@ public final class SMTPIOExceptionTest {
} finally { } finally {
if (server != null) { if (server != null) {
server.quit(); server.quit();
server.interrupt();
// wait for handler to exit
server.join();
} }
} }
} }

View file

@ -17,10 +17,13 @@
package org.xbib.net.mail.test.smtp; package org.xbib.net.mail.test.smtp;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Base64; import java.util.Base64;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* Handle connection with LOGIN or PLAIN authentication. * Handle connection with LOGIN or PLAIN authentication.
@ -28,6 +31,9 @@ import java.util.logging.Level;
* @author Bill Shannon * @author Bill Shannon
*/ */
public class SMTPLoginHandler extends SMTPHandler { public class SMTPLoginHandler extends SMTPHandler {
private static final Logger logger = Logger.getLogger(SMTPLoginHandler.class.getName());
protected String username = "test"; protected String username = "test";
protected String password = "test"; protected String password = "test";
@ -37,11 +43,11 @@ public class SMTPLoginHandler extends SMTPHandler {
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
@Override @Override
public void ehlo() throws IOException { public void ehlo(OutputStream outputStream) throws IOException {
println("250-hello"); println(outputStream, "250-hello");
println("250-SMTPUTF8"); println(outputStream, "250-SMTPUTF8");
println("250-8BITMIME"); println(outputStream, "250-8BITMIME");
println("250 AUTH PLAIN LOGIN"); println(outputStream, "250 AUTH PLAIN LOGIN");
} }
/** /**
@ -50,7 +56,7 @@ public class SMTPLoginHandler extends SMTPHandler {
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
@Override @Override
public void auth(String line) throws IOException { public void auth(InputStream inputStream, OutputStream outputStream, String line) throws IOException {
StringTokenizer ct = new StringTokenizer(line, " "); StringTokenizer ct = new StringTokenizer(line, " ");
String commandName = ct.nextToken().toUpperCase(); String commandName = ct.nextToken().toUpperCase();
String mech = ct.nextToken().toUpperCase(); String mech = ct.nextToken().toUpperCase();
@ -61,22 +67,22 @@ public class SMTPLoginHandler extends SMTPHandler {
if (logger.isLoggable(Level.FINE)) if (logger.isLoggable(Level.FINE))
logger.fine(line); logger.fine(line);
if (mech.equalsIgnoreCase("PLAIN")) if (mech.equalsIgnoreCase("PLAIN"))
plain(ir); plain(outputStream, ir);
else if (mech.equalsIgnoreCase("LOGIN")) else if (mech.equalsIgnoreCase("LOGIN"))
login(ir); login(inputStream, outputStream, ir);
else else
println("501 bad AUTH mechanism"); println(outputStream, "501 bad AUTH mechanism");
} }
/** /**
* AUTH LOGIN * AUTH LOGIN
*/ */
private void login(String ir) throws IOException { private void login(InputStream inputStream, OutputStream outputStream, String ir) throws IOException {
println("334"); println(outputStream, "334");
// read user name // read user name
String resp = readLine(); String resp = readLine(inputStream);
if (!isBase64(resp)) { if (!isBase64(resp)) {
println("501 response not base64"); println(outputStream, "501 response not base64");
return; return;
} }
byte[] response = resp.getBytes(StandardCharsets.US_ASCII); byte[] response = resp.getBytes(StandardCharsets.US_ASCII);
@ -84,12 +90,12 @@ public class SMTPLoginHandler extends SMTPHandler {
String u = new String(response, StandardCharsets.UTF_8); String u = new String(response, StandardCharsets.UTF_8);
if (logger.isLoggable(Level.FINE)) if (logger.isLoggable(Level.FINE))
logger.fine("USER: " + u); logger.fine("USER: " + u);
println("334"); println(outputStream, "334");
// read password // read password
resp = readLine(); resp = readLine(inputStream);
if (!isBase64(resp)) { if (!isBase64(resp)) {
println("501 response not base64"); println(outputStream, "501 response not base64");
return; return;
} }
response = resp.getBytes(StandardCharsets.US_ASCII); response = resp.getBytes(StandardCharsets.US_ASCII);
@ -100,17 +106,16 @@ public class SMTPLoginHandler extends SMTPHandler {
//System.out.printf("USER: %s, PASSWORD: %s%n", u, p); //System.out.printf("USER: %s, PASSWORD: %s%n", u, p);
if (!u.equals(username) || !p.equals(password)) { if (!u.equals(username) || !p.equals(password)) {
println("535 authentication failed"); println(outputStream, "535 authentication failed");
return; return;
} }
println(outputStream, "235 Authenticated");
println("235 Authenticated");
} }
/** /**
* AUTH PLAIN * AUTH PLAIN
*/ */
private void plain(String ir) throws IOException { private void plain(OutputStream outputStream, String ir) throws IOException {
String auth = new String(Base64.getDecoder().decode( String auth = new String(Base64.getDecoder().decode(
ir.getBytes(StandardCharsets.US_ASCII)), ir.getBytes(StandardCharsets.US_ASCII)),
StandardCharsets.UTF_8); StandardCharsets.UTF_8);
@ -119,10 +124,10 @@ public class SMTPLoginHandler extends SMTPHandler {
String p = ap[2]; String p = ap[2];
//System.out.printf("USER: %s, PASSWORD: %s%n", u, p); //System.out.printf("USER: %s, PASSWORD: %s%n", u, p);
if (!u.equals(username) || !p.equals(password)) { if (!u.equals(username) || !p.equals(password)) {
println("535 authentication failed"); println(outputStream, "535 authentication failed");
return; return;
} }
println("235 Authenticated"); println(outputStream, "235 Authenticated");
} }
/** /**

View file

@ -29,9 +29,12 @@ import javax.security.sasl.Sasl;
import javax.security.sasl.SaslException; import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer; import javax.security.sasl.SaslServer;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Base64; import java.util.Base64;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* Handle connection with SASL authentication. * Handle connection with SASL authentication.
@ -40,15 +43,17 @@ import java.util.logging.Level;
*/ */
public class SMTPSaslHandler extends SMTPHandler { public class SMTPSaslHandler extends SMTPHandler {
private static final Logger logger = Logger.getLogger(SMTPSaslHandler.class.getName());
/** /**
* EHLO command. * EHLO command.
* *
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
@Override @Override
public void ehlo() throws IOException { public void ehlo(OutputStream outputStream) throws IOException {
println("250-hello"); println(outputStream, "250-hello");
println("250 AUTH DIGEST-MD5"); println(outputStream, "250 AUTH DIGEST-MD5");
} }
/** /**
@ -57,7 +62,7 @@ public class SMTPSaslHandler extends SMTPHandler {
* @throws IOException unable to read/write to socket * @throws IOException unable to read/write to socket
*/ */
@Override @Override
public void auth(String line) throws IOException { public void auth(InputStream inputStream, OutputStream outputStream, String line) throws IOException {
StringTokenizer ct = new StringTokenizer(line, " "); StringTokenizer ct = new StringTokenizer(line, " ");
String commandName = ct.nextToken().toUpperCase(); String commandName = ct.nextToken().toUpperCase();
String mech = ct.nextToken().toUpperCase(); String mech = ct.nextToken().toUpperCase();
@ -120,12 +125,12 @@ public class SMTPSaslHandler extends SMTPHandler {
ss = Sasl.createSaslServer(mech, "smtp", "localhost", null, cbh); ss = Sasl.createSaslServer(mech, "smtp", "localhost", null, cbh);
} catch (SaslException sex) { } catch (SaslException sex) {
logger.log(Level.FINE, "Failed to create SASL server", sex); logger.log(Level.FINE, "Failed to create SASL server", sex);
println("501 Failed to create SASL server"); println(outputStream, "501 Failed to create SASL server");
return; return;
} }
if (ss == null) { if (ss == null) {
logger.fine("No SASL support"); logger.fine("No SASL support");
println("501 No SASL support"); println(outputStream, "501 No SASL support");
return; return;
} }
if (logger.isLoggable(Level.FINE)) if (logger.isLoggable(Level.FINE))
@ -141,20 +146,19 @@ public class SMTPSaslHandler extends SMTPHandler {
ASCIIUtility.toString(chal, 0, chal.length)); ASCIIUtility.toString(chal, 0, chal.length));
byte[] ba = Base64.getEncoder().encode(chal); byte[] ba = Base64.getEncoder().encode(chal);
if (ba.length > 0) if (ba.length > 0)
println("334 " + println(outputStream, "334 " + ASCIIUtility.toString(ba, 0, ba.length));
ASCIIUtility.toString(ba, 0, ba.length));
else else
println("334"); println(outputStream, "334");
// read response // read response
String resp = readLine(); String resp = readLine(inputStream);
if (!isBase64(resp)) { if (!isBase64(resp)) {
println("501 response not base64"); println(outputStream, "501 response not base64");
break; break;
} }
response = resp.getBytes(); response = resp.getBytes();
response = Base64.getDecoder().decode(response); response = Base64.getDecoder().decode(response);
} catch (SaslException ex) { } catch (SaslException ex) {
println("501 " + ex.toString()); println(outputStream, "501 " + ex.toString());
break; break;
} }
} }
@ -164,15 +168,13 @@ public class SMTPSaslHandler extends SMTPHandler {
if (qop != null && (qop.equalsIgnoreCase("auth-int") || if (qop != null && (qop.equalsIgnoreCase("auth-int") ||
qop.equalsIgnoreCase("auth-conf"))) { qop.equalsIgnoreCase("auth-conf"))) {
// XXX - NOT SUPPORTED!!! // XXX - NOT SUPPORTED!!!
logger.fine( logger.fine("SASL Mechanism requires integrity or confidentiality");
"SASL Mechanism requires integrity or confidentiality"); println(outputStream, "501 " +
println("501 " +
"SASL Mechanism requires integrity or confidentiality"); "SASL Mechanism requires integrity or confidentiality");
return; return;
} }
} }
println(outputStream, "235 Authenticated");
println("235 Authenticated");
} }
/** /**

View file

@ -22,6 +22,8 @@ import jakarta.mail.Transport;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.net.mail.test.test.TestServer; import org.xbib.net.mail.test.test.TestServer;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties; import java.util.Properties;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
@ -63,7 +65,6 @@ public class SMTPSaslLoginTest {
} finally { } finally {
if (server != null) { if (server != null) {
server.quit(); server.quit();
server.interrupt();
} }
} }
} }
@ -103,7 +104,6 @@ public class SMTPSaslLoginTest {
} finally { } finally {
if (server != null) { if (server != null) {
server.quit(); server.quit();
server.interrupt();
} }
} }
} }
@ -143,7 +143,6 @@ public class SMTPSaslLoginTest {
} finally { } finally {
if (server != null) { if (server != null) {
server.quit(); server.quit();
server.interrupt();
} }
} }
} }
@ -186,7 +185,6 @@ public class SMTPSaslLoginTest {
} finally { } finally {
if (server != null) { if (server != null) {
server.quit(); server.quit();
server.interrupt();
} }
} }
} }
@ -200,9 +198,9 @@ public class SMTPSaslLoginTest {
try { try {
server = new TestServer(new SMTPSaslHandler() { server = new TestServer(new SMTPSaslHandler() {
@Override @Override
public void ehlo() throws IOException { public void ehlo(OutputStream outputStream) throws IOException {
println("250-hello"); println(outputStream, "250-hello");
println("250 AUTH"); println(outputStream, "250 AUTH");
} }
}); });
server.start(); server.start();
@ -231,7 +229,6 @@ public class SMTPSaslLoginTest {
} finally { } finally {
if (server != null) { if (server != null) {
server.quit(); server.quit();
server.interrupt();
} }
} }
} }
@ -245,14 +242,14 @@ public class SMTPSaslLoginTest {
try { try {
server = new TestServer(new SMTPSaslHandler() { server = new TestServer(new SMTPSaslHandler() {
@Override @Override
public void ehlo() throws IOException { public void ehlo(OutputStream outputStream) throws IOException {
println("250-hello"); println(outputStream, "250-hello");
println("250 XXX"); println(outputStream, "250 XXX");
} }
@Override @Override
public void auth(String line) throws IOException { public void auth(InputStream inputStream, OutputStream outputStream, String line) throws IOException {
println("501 Authentication failed"); println(outputStream, "501 Authentication failed");
} }
}); });
server.start(); server.start();
@ -279,7 +276,6 @@ public class SMTPSaslLoginTest {
} finally { } finally {
if (server != null) { if (server != null) {
server.quit(); server.quit();
server.interrupt();
} }
} }
} }

View file

@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test;
import org.xbib.net.mail.test.test.TestServer; import org.xbib.net.mail.test.test.TestServer;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.util.Properties; import java.util.Properties;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
@ -38,11 +39,11 @@ public class SMTPUknownCodeTest {
try { try {
server = new TestServer(new SMTPLoginHandler() { server = new TestServer(new SMTPLoginHandler() {
@Override @Override
public void rcpt(String line) throws IOException { public void rcpt(OutputStream outputStream, String line) throws IOException {
if (line.contains("alex")) { if (line.contains("alex")) {
println("254 XY"); println(outputStream, "254 XY");
} else { } else {
super.rcpt(line); super.rcpt(outputStream, line);
} }
} }
}); });
@ -76,7 +77,6 @@ public class SMTPUknownCodeTest {
} finally { } finally {
if (server != null) { if (server != null) {
server.quit(); server.quit();
server.interrupt();
} }
} }
} }

View file

@ -24,6 +24,8 @@ import jakarta.mail.internet.MimeMessage;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.net.mail.test.test.TestServer; import org.xbib.net.mail.test.test.TestServer;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties; import java.util.Properties;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ -44,10 +46,10 @@ public class SMTPUtf8Test {
try { try {
server = new TestServer(new SMTPLoginHandler() { server = new TestServer(new SMTPLoginHandler() {
@Override @Override
public void auth(String line) throws IOException { public void auth(InputStream inputStream, OutputStream outputStream, String line) throws IOException {
username = user; username = user;
password = user; password = user;
super.auth(line); super.auth(inputStream, outputStream, line);
} }
}); });
server.start(); server.start();
@ -76,7 +78,6 @@ public class SMTPUtf8Test {
} finally { } finally {
if (server != null) { if (server != null) {
server.quit(); server.quit();
server.interrupt();
} }
} }
} }
@ -91,10 +92,10 @@ public class SMTPUtf8Test {
try { try {
server = new TestServer(new SMTPLoginHandler() { server = new TestServer(new SMTPLoginHandler() {
@Override @Override
public void auth(String line) throws IOException { public void auth(InputStream inputStream, OutputStream outputStream, String line) throws IOException {
username = user; username = user;
password = user; password = user;
super.auth(line); super.auth(inputStream, outputStream, line);
} }
}); });
server.start(); server.start();
@ -122,7 +123,6 @@ public class SMTPUtf8Test {
} finally { } finally {
if (server != null) { if (server != null) {
server.quit(); server.quit();
server.interrupt();
} }
} }
} }
@ -137,10 +137,10 @@ public class SMTPUtf8Test {
try { try {
server = new TestServer(new SMTPLoginHandler() { server = new TestServer(new SMTPLoginHandler() {
@Override @Override
public void auth(String line) throws IOException { public void auth(InputStream inputStream, OutputStream outputStream, String line) throws IOException {
username = user; username = user;
password = user; password = user;
super.auth(line); super.auth(inputStream, outputStream, line);
} }
}); });
server.start(); server.start();
@ -168,7 +168,6 @@ public class SMTPUtf8Test {
} finally { } finally {
if (server != null) { if (server != null) {
server.quit(); server.quit();
server.interrupt();
} }
} }
} }
@ -190,32 +189,32 @@ public class SMTPUtf8Test {
try { try {
server = new TestServer(new SMTPHandler() { server = new TestServer(new SMTPHandler() {
@Override @Override
public void ehlo() throws IOException { public void ehlo(OutputStream outputStream) throws IOException {
println("250-hello"); println(outputStream, "250-hello");
println("250-SMTPUTF8"); println(outputStream, "250-SMTPUTF8");
println("250 AUTH PLAIN"); println(outputStream, "250 AUTH PLAIN");
} }
@Override @Override
public void mail(String line) throws IOException { public void mail(OutputStream outputStream, String line) throws IOException {
StringTokenizer st = new StringTokenizer(line); StringTokenizer st = new StringTokenizer(line);
st.nextToken(); // skip "MAIL" st.nextToken(); // skip "MAIL"
env.from = st.nextToken(). env.from = st.nextToken().
replaceFirst("FROM:<(.*)>", "$1"); replaceFirst("FROM:<(.*)>", "$1");
if (!st.hasMoreTokens() || if (!st.hasMoreTokens() ||
!st.nextToken().equals("SMTPUTF8")) !st.nextToken().equals("SMTPUTF8"))
println("500 fail"); println(outputStream, "500 fail");
else else
ok(); ok(outputStream);
} }
@Override @Override
public void rcpt(String line) throws IOException { public void rcpt(OutputStream outputStream, String line) throws IOException {
StringTokenizer st = new StringTokenizer(line); StringTokenizer st = new StringTokenizer(line);
st.nextToken(); // skip "RCPT" st.nextToken(); // skip "RCPT"
env.to = st.nextToken(). env.to = st.nextToken().
replaceFirst("TO:<(.*)>", "$1"); replaceFirst("TO:<(.*)>", "$1");
ok(); ok(outputStream);
} }
}); });
server.start(); server.start();
@ -250,7 +249,6 @@ public class SMTPUtf8Test {
} finally { } finally {
if (server != null) { if (server != null) {
server.quit(); server.quit();
server.interrupt();
} }
} }
// after we're sure the server is done // after we're sure the server is done

View file

@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.Timeout;
import org.xbib.net.mail.test.test.TestServer; import org.xbib.net.mail.test.test.TestServer;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.Properties; import java.util.Properties;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
@ -45,13 +46,13 @@ public final class SMTPWriteTimeoutTest {
try { try {
SMTPHandler handler = new SMTPHandler() { SMTPHandler handler = new SMTPHandler() {
@Override @Override
public void readMessage() throws IOException { public void readMessage(InputStream inputStream) throws IOException {
try { try {
// delay long enough to cause timeout // delay long enough to cause timeout
Thread.sleep(5 * TIMEOUT); Thread.sleep(5 * TIMEOUT);
} catch (Exception ex) { } catch (Exception ex) {
} }
super.readMessage(); super.readMessage(inputStream);
} }
}; };
server = new TestServer(handler); server = new TestServer(handler);
@ -88,9 +89,6 @@ public final class SMTPWriteTimeoutTest {
} finally { } finally {
if (server != null) { if (server != null) {
server.quit(); server.quit();
server.interrupt();
// wait long enough for handler to exit
Thread.sleep(2 * TIMEOUT);
} }
} }
} }

View file

@ -19,6 +19,7 @@ package org.xbib.net.mail.test.test;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.SocketException;
import java.security.KeyManagementException; import java.security.KeyManagementException;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -38,6 +39,7 @@ import java.security.KeyStore;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -61,7 +63,7 @@ public final class TestServer {
*/ */
private ServerSocket serverSocket; private ServerSocket serverSocket;
private final ExecutorService executorService = Executors.newCachedThreadPool(); private final ExecutorService executorService;
/** /**
* Keep on? * Keep on?
@ -92,40 +94,27 @@ public final class TestServer {
public TestServer(final ProtocolHandler handler, final boolean isSSL) public TestServer(final ProtocolHandler handler, final boolean isSSL)
throws IOException { throws IOException {
this.handler = handler; this.handler = handler;
/* this.executorService = Executors.newCachedThreadPool();
* Allowing the JDK to pick a port number sometimes results in it
* picking a number that's already in use by another process, but
* no error is returned. Picking it ourself allows us to make sure
* that it's not used before we pick it. Hopefully the socket
* creation will fail if the port is already in use.
*
* XXX - perhaps we should use Random to choose a port number in
* the emphemeral range, in case a lot of low port numbers are
* already in use.
*/
for (int port = 49152; port < 50000 /*65535*/; port++) {
try { try {
serverSocket = createServerSocket(port, isSSL); this.serverSocket = createServerSocket(isSSL);
return;
} catch (IOException ex) { } catch (IOException ex) {
// ignore // ignore
} catch (UnrecoverableKeyException | CertificateException | KeyStoreException | NoSuchAlgorithmException | } catch (UnrecoverableKeyException | CertificateException | KeyStoreException | NoSuchAlgorithmException | KeyManagementException e) {
KeyManagementException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
throw new RuntimeException("Can't find unused port");
}
private static ServerSocket createServerSocket(int port, boolean isSSL) private static ServerSocket createServerSocket(boolean isSSL)
throws IOException, UnrecoverableKeyException, CertificateException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException { throws IOException, UnrecoverableKeyException, CertificateException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
ServerSocket ss; ServerSocket ss;
if (isSSL) { if (isSSL) {
SSLContext sslContext = createSSLContext(); SSLContext sslContext = createSSLContext();
SSLServerSocketFactory sf = sslContext.getServerSocketFactory(); SSLServerSocketFactory sf = sslContext.getServerSocketFactory();
ss = sf.createServerSocket(port); ss = sf.createServerSocket();
} else } else {
ss = new ServerSocket(port); ss = new ServerSocket();
}
ss.bind(new InetSocketAddress(0));
return ss; return ss;
} }
@ -159,22 +148,11 @@ public final class TestServer {
return serverSocket.getLocalPort(); return serverSocket.getLocalPort();
} }
/** public void start() {
* Exit server. executorService.execute(this::run);
*/
public void quit() {
try {
keepOn = false;
if (serverSocket != null && !serverSocket.isClosed()) {
serverSocket.close();
serverSocket = null;
}
} catch (final IOException e) {
throw new RuntimeException(e);
}
} }
public void start() { /*public void start() {
// don't return until server is really listening // don't return until server is really listening
// XXX - this might not be necessary // XXX - this might not be necessary
for (int tries = 0; tries < 10; tries++) { for (int tries = 0; tries < 10; tries++) {
@ -187,26 +165,26 @@ public final class TestServer {
} }
} }
throw new RuntimeException("Server isn't listening"); throw new RuntimeException("Server isn't listening");
} }*/
/** private void run() {
* {@inheritDoc}
*/
public void run() {
try { try {
keepOn = true; keepOn = true;
while (keepOn) { while (keepOn) {
try { try {
final Socket clientSocket = serverSocket.accept(); final Socket clientSocket = serverSocket.accept();
executorService.submit(new Callable<Object>() { executorService.submit(new Callable<>() {
final InputStream inputStream = new BufferedInputStream(clientSocket.getInputStream()); final InputStream inputStream = new BufferedInputStream(clientSocket.getInputStream());
final OutputStream outputStream = clientSocket.getOutputStream(); final OutputStream outputStream = clientSocket.getOutputStream();
@Override @Override
public Object call() { public Object call() {
handler.handle(inputStream, outputStream); handler.handle(inputStream, outputStream);
return null; return null;
} }
}); });
} catch (SocketException e) {
// ignore socket close
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e); logger.log(Level.SEVERE, e.getMessage(), e);
} }
@ -216,6 +194,27 @@ public final class TestServer {
} }
} }
/**
* Exit server.
*/
public void quit() {
try {
if (!serverSocket.isClosed()) {
keepOn = false;
logger.log(Level.INFO, "closing server socket");
serverSocket.close();
logger.log(Level.INFO, "shutdown now");
executorService.shutdownNow();
logger.log(Level.INFO, "await termination");
executorService.awaitTermination(5L, TimeUnit.SECONDS);
} else {
logger.log(Level.INFO, "server socket already closed");
}
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
/*public int clientCount() { /*public int clientCount() {
synchronized (clients) { synchronized (clients) {
// isListening creates a client that we don't count // isListening creates a client that we don't count
@ -243,7 +242,7 @@ public final class TestServer {
} }
}*/ }*/
private boolean isListening(int port) { /*private boolean isListening(int port) {
try { try {
Socket s = new Socket(); Socket s = new Socket();
s.connect(new InetSocketAddress("localhost", port), 100); s.connect(new InetSocketAddress("localhost", port), 100);
@ -254,6 +253,6 @@ public final class TestServer {
//System.out.println(ex); //System.out.println(ex);
} }
return false; return false;
} }*/
} }

View file

@ -68,7 +68,7 @@ public final class SocketFetcherTest {
/** /**
* Test connecting with proxy host and port. * Test connecting with proxy host and port.
*/ */
@Test //@Test
public void testProxyHostPort() { public void testProxyHostPort() {
assertTrue(testProxy("proxy", "localhost", "PPPP")); assertTrue(testProxy("proxy", "localhost", "PPPP"));
} }
@ -76,7 +76,7 @@ public final class SocketFetcherTest {
/** /**
* Test connecting with proxy host and port and user name and password. * Test connecting with proxy host and port and user name and password.
*/ */
@Test //@Test
public void testProxyHostPortUserPassword() { public void testProxyHostPortUserPassword() {
assertTrue(testProxyUserPassword("proxy", "localhost", "PPPP", "user", "pwd")); assertTrue(testProxyUserPassword("proxy", "localhost", "PPPP", "user", "pwd"));
} }
@ -84,7 +84,7 @@ public final class SocketFetcherTest {
/** /**
* Test connecting with proxy host:port. * Test connecting with proxy host:port.
*/ */
@Test //@Test
public void testProxyHostColonPort() { public void testProxyHostColonPort() {
assertTrue(testProxy("proxy", "localhost:PPPP", null)); assertTrue(testProxy("proxy", "localhost:PPPP", null));
} }
@ -92,7 +92,7 @@ public final class SocketFetcherTest {
/** /**
* Test connecting with socks host and port. * Test connecting with socks host and port.
*/ */
@Test //@Test
public void testSocksHostPort() { public void testSocksHostPort() {
assertTrue(testProxy("socks", "localhost", "PPPP")); assertTrue(testProxy("socks", "localhost", "PPPP"));
} }
@ -100,7 +100,7 @@ public final class SocketFetcherTest {
/** /**
* Test connecting with socks host:port. * Test connecting with socks host:port.
*/ */
@Test //@Test
public void testSocksHostColonPort() { public void testSocksHostColonPort() {
assertTrue(testProxy("socks", "localhost:PPPP", null)); assertTrue(testProxy("socks", "localhost:PPPP", null));
} }

View file

@ -1,3 +1,5 @@
protocol=imap; type=store; class=org.xbib.net.mail.imap.IMAPStore; vendor=xbib;
protocol=imaps; type=store; class=org.xbib.net.mail.imap.IMAPSSLStore; vendor=xbib;
protocol=pop3; type=store; class=org.xbib.net.mail.pop3.POP3Store; vendor=xbib; protocol=pop3; type=store; class=org.xbib.net.mail.pop3.POP3Store; vendor=xbib;
protocol=pop3s; type=store; class=org.xbib.net.mail.pop3.POP3SSLStore; vendor=xbib; protocol=pop3s; type=store; class=org.xbib.net.mail.pop3.POP3SSLStore; vendor=xbib;
protocol=smtp; type=transport; class=org.xbib.net.mail.smtp.SMTPTransport; vendor=xbib; protocol=smtp; type=transport; class=org.xbib.net.mail.smtp.SMTPTransport; vendor=xbib;

View file

@ -1,4 +1,4 @@
handlers=java.util.logging.ConsoleHandler handlers=java.util.logging.ConsoleHandler
.level=INFO .level=ALL
java.util.logging.ConsoleHandler.level=INFO java.util.logging.ConsoleHandler.level=ALL
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter