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 java.util.EventListener;
import java.util.Objects;
import java.util.Vector;
import java.util.WeakHashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
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.
@ -33,16 +36,20 @@ import java.util.concurrent.LinkedBlockingQueue;
*/
class EventQueue implements Runnable {
private static final Logger logger = Logger.getLogger(EventQueue.class.getName());
private static WeakHashMap<ClassLoader, EventQueue> appq;
private volatile BlockingQueue<QueueElement> q;
private Executor executor;
private final Executor executor;
/**
* Construct an EventQueue using the specified Executor.
* If the Executor is null, threads will be created as needed.
*/
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.
*/
static synchronized EventQueue getApplicationEventQueue(Executor ex) {
Objects.requireNonNull(ex);
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (appq == null)
if (appq == null) {
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 (q == null) {
q = new LinkedBlockingQueue<>();
if (executor != null) {
executor.execute(this);
} else {
logger.log(Level.INFO, "starting event queue");
executor.execute(this);
/*else {
Thread qThread = new Thread(this, "Jakarta-Mail-EventQueue");
qThread.setDaemon(true); // not a user thread
qThread.start();
}
}*/
}
q.add(new QueueElement(event, vector));
}
@ -97,11 +101,11 @@ class EventQueue implements Runnable {
*/
@Override
public void run() {
BlockingQueue<QueueElement> bq = q;
if (bq == null)
return;
try {
logger.log(Level.INFO, "entering event queue");
loop:
for (; ; ) {
// block until an item is available
@ -148,7 +152,9 @@ class EventQueue implements Runnable {
* A "struct" to put on the queue.
*/
static class 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.Vector;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* Folder is an abstract class that represents a folder for mail
@ -151,21 +152,22 @@ public abstract class Folder implements AutoCloseable {
*/
protected Folder(Store store) {
this.store = store;
// create or choose the appropriate event queue
Session session = store.getSession();
String scope =
session.getProperties().getProperty("mail.event.scope", "folder");
Executor executor =
(Executor) session.getProperties().get("mail.event.executor");
if (scope.equalsIgnoreCase("application"))
String scope = session.getProperties().getProperty("mail.event.scope", "folder");
Executor executor = (Executor) session.getProperties().get("mail.event.executor");
if (executor == null) {
executor = Executors.newSingleThreadExecutor();
}
if (scope.equalsIgnoreCase("application")) {
q = EventQueue.getApplicationEventQueue(executor);
else if (scope.equalsIgnoreCase("session"))
} else if (scope.equalsIgnoreCase("session")) {
q = session.getEventQueue();
else if (scope.equalsIgnoreCase("store"))
} else if (scope.equalsIgnoreCase("store")) {
q = store.getEventQueue();
else // if (scope.equalsIgnoreCase("folder"))
} else {
q = new EventQueue(executor);
}
}
/**

View file

@ -24,6 +24,9 @@ import java.net.UnknownHostException;
import java.util.EventListener;
import java.util.Vector;
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
@ -40,6 +43,8 @@ import java.util.concurrent.Executor;
public abstract class Service implements AutoCloseable {
private static final Logger logger = Logger.getLogger(Service.class.getName());
/*
* connectionListeners is a Vector, initialized here,
* 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
* deadlocks when notifying listeners.)
*/
private final Vector<ConnectionListener> connectionListeners
= new Vector<>();
private final Vector<ConnectionListener> connectionListeners = new Vector<>();
/**
* The queue of events to be delivered.
*/
@ -121,21 +125,20 @@ public abstract class Service implements AutoCloseable {
if (user == null) {
user = System.getProperty("user.name");
}
url = new URLName(protocol, host, port, file, user, password);
// create or choose the appropriate event queue
String scope =
session.getProperties().getProperty("mail.event.scope", "folder");
Executor executor =
(Executor) session.getProperties().get("mail.event.executor");
if (scope.equalsIgnoreCase("application"))
String scope = session.getProperties().getProperty("mail.event.scope", "folder");
Executor executor = (Executor) session.getProperties().get("mail.event.executor");
if (executor == null) {
executor = Executors.newSingleThreadExecutor();
}
if (scope.equalsIgnoreCase("application")) {
q = EventQueue.getApplicationEventQueue(executor);
else if (scope.equalsIgnoreCase("session"))
} else if (scope.equalsIgnoreCase("session")) {
q = session.getEventQueue();
else // if (scope.equalsIgnoreCase("store") ||
// scope.equalsIgnoreCase("folder"))
} else {
q = new EventQueue(executor);
}
}
/**
@ -563,7 +566,7 @@ public abstract class Service implements AutoCloseable {
* Yes, listeners could be removed after checking, which
* just makes this an expensive no-op.
*/
if (connectionListeners.size() > 0) {
if (!connectionListeners.isEmpty()) {
ConnectionEvent e = new ConnectionEvent(this, type);
queueEvent(e, connectionListeners);
}
@ -578,8 +581,10 @@ public abstract class Service implements AutoCloseable {
* terminator event causes the event-dispatching thread to
* self destruct.
*/
if (type == ConnectionEvent.CLOSED)
if (type == ConnectionEvent.CLOSED) {
logger.log(Level.INFO, "sending terminator event");
q.terminateQueue();
}
}
/**
@ -601,8 +606,7 @@ public abstract class Service implements AutoCloseable {
* @param event the event
* @param vector the vector of listeners
*/
protected void queueEvent(MailEvent event,
Vector<? extends EventListener> vector) {
protected void queueEvent(MailEvent event, Vector<? extends EventListener> vector) {
/*
* Copy the vector in order to freeze the state of the set
* 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.StringTokenizer;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Level;
/**
@ -146,8 +147,8 @@ interface StreamLoader {
* Here's an example of <code>META-INF/javamail.default.providers</code>
* file contents:
* <pre>
* protocol=imap; type=store; class=com.sun.mail.imap.IMAPStore; vendor=Oracle;
* protocol=smtp; type=transport; class=com.sun.mail.smtp.SMTPTransport; 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=xbib;
* </pre><p>
* <p>
* The current implementation also supports configuring providers using
@ -211,7 +212,7 @@ public final class Session {
private static Session defaultSession = null;
static {
String dir = null;
String dir;
String home = System.getProperty("java.home");
String newdir = home + File.separator + "conf";
File conf = new File(newdir);
@ -261,7 +262,11 @@ public final class Session {
// load the resources
loadProviders(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 {
Class<?>[] c = {Session.class, URLName.class};
Constructor<?> cons = serviceClass.getConstructor(c);
Object[] o = {this, url};
return type.cast(cons.newInstance(o));
} 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,
Boolean isNamespace) {
super(store);
if (fullName == null)
if (fullName == null) {
throw new NullPointerException("Folder name is null");
}
this.fullName = fullName;
this.separator = separator;

View file

@ -1378,7 +1378,6 @@ public class IMAPStore extends Store
* Empty the connection pool.
*/
private void emptyConnectionPool(boolean force) {
synchronized (pool) {
for (int index = pool.authenticatedConnections.size() - 1;
index >= 0; --index) {
@ -1394,10 +1393,8 @@ public class IMAPStore extends Store
}
;
}
pool.authenticatedConnections.removeAllElements();
}
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=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;

View file

@ -19,7 +19,6 @@ package org.xbib.net.mail.test.imap;
import jakarta.mail.Session;
import jakarta.mail.Store;
import jakarta.mail.event.StoreEvent;
import jakarta.mail.event.StoreListener;
import org.junit.jupiter.api.Test;
import org.xbib.net.mail.test.test.TestServer;
@ -28,6 +27,9 @@ import java.io.OutputStream;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
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.fail;
@ -36,52 +38,48 @@ import static org.junit.jupiter.api.Assertions.fail;
*/
public final class IMAPAlertTest {
private static final Logger logger = Logger.getLogger(IMAPAlertTest.class.getName());
private volatile boolean gotAlert = false;
@Test
public void test() {
public void testImapAlert() {
TestServer server = null;
try {
final IMAPHandler handler = new IMAPHandlerAlert();
server = new TestServer(handler);
server.start();
final Properties properties = new Properties();
properties.setProperty("mail.imap.host", "localhost");
properties.setProperty("mail.imap.port", String.valueOf(server.getPort()));
final Session session = Session.getInstance(properties);
//session.setDebug(true);
final CountDownLatch latch = new CountDownLatch(1);
final Store store = session.getStore("imap");
store.addStoreListener(new StoreListener() {
@Override
public void notification(StoreEvent e) {
String s;
if (e.getMessageType() == StoreEvent.ALERT) {
s = "ALERT: ";
gotAlert = true;
latch.countDown();
} else
s = "NOTICE: ";
//System.out.println(s + e.getMessage());
}
store.addStoreListener(e -> {
String s;
if (e.getMessageType() == StoreEvent.ALERT) {
s = "ALERT: ";
gotAlert = true;
latch.countDown();
} else
s = "NOTICE: ";
//System.out.println(s + e.getMessage());
});
try {
store.connect("test", "test");
// time for event to be delivered
latch.await(5, TimeUnit.SECONDS);
assertTrue(gotAlert);
} catch (Exception ex) {
System.out.println(ex);
//ex.printStackTrace();
logger.log(Level.SEVERE, ex.getMessage(), ex);
fail(ex.toString());
} finally {
store.close();
}
} catch (final Exception e) {
//e.printStackTrace();
logger.log(Level.SEVERE, e.getMessage(), e);
fail(e.getMessage());
} finally {
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.fail;
/**
* Test IMAPFolder methods.
*/
@ -68,8 +67,7 @@ class IMAPFolderTest {
void testUtf7FolderNameCreate() {
testWithHandler(new IMAPTest() {
@Override
public void test(Store store, IMAPHandler handler)
throws MessagingException, IOException {
public void test(Store store, IMAPHandler handler) throws MessagingException {
Folder test = store.getFolder(utf8Folder);
assertTrue(test.create(Folder.HOLDS_MESSAGES));
}
@ -101,11 +99,9 @@ class IMAPFolderTest {
*/
@Test
void testUtf8FolderNameCreate() {
testWithHandler(
new IMAPTest() {
testWithHandler(new IMAPTest() {
@Override
public void test(Store store, IMAPHandler handler)
throws MessagingException, IOException {
public void test(Store store, IMAPHandler handler) throws MessagingException {
Folder test = store.getFolder(utf8Folder);
assertTrue(test.create(Folder.HOLDS_MESSAGES));
}
@ -137,11 +133,9 @@ class IMAPFolderTest {
*/
@Test
void testUtf7FolderNameDelete() {
testWithHandler(
new IMAPTest() {
testWithHandler(new IMAPTest() {
@Override
public void test(Store store, IMAPHandler handler)
throws MessagingException, IOException {
public void test(Store store, IMAPHandler handler) throws MessagingException {
Folder test = store.getFolder(utf8Folder);
assertTrue(test.delete(false));
}
@ -167,11 +161,9 @@ class IMAPFolderTest {
*/
@Test
void testUtf8FolderNameDelete() {
testWithHandler(
new IMAPTest() {
testWithHandler(new IMAPTest() {
@Override
public void test(Store store, IMAPHandler handler)
throws MessagingException, IOException {
public void test(Store store, IMAPHandler handler) throws MessagingException {
Folder test = store.getFolder(utf8Folder);
assertTrue(test.delete(false));
}
@ -198,11 +190,9 @@ class IMAPFolderTest {
*/
@Test
void testUtf7FolderNameSelect() {
testWithHandler(
new IMAPTest() {
testWithHandler(new IMAPTest() {
@Override
public void test(Store store, IMAPHandler handler)
throws MessagingException, IOException {
public void test(Store store, IMAPHandler handler) throws MessagingException {
Folder test = store.getFolder(utf8Folder);
test.open(Folder.READ_WRITE);
test.close(true);
@ -230,11 +220,9 @@ class IMAPFolderTest {
*/
@Test
void testUtf8FolderNameSelect() {
testWithHandler(
new IMAPTest() {
testWithHandler(new IMAPTest() {
@Override
public void test(Store store, IMAPHandler handler)
throws MessagingException, IOException {
public void test(Store store, IMAPHandler handler) throws MessagingException {
Folder test = store.getFolder(utf8Folder);
test.open(Folder.READ_WRITE);
test.close(true);
@ -262,11 +250,9 @@ class IMAPFolderTest {
*/
@Test
void testUtf7FolderNameExamine() {
testWithHandler(
new IMAPTest() {
testWithHandler(new IMAPTest() {
@Override
public void test(Store store, IMAPHandler handler)
throws MessagingException, IOException {
public void test(Store store, IMAPHandler handler) throws MessagingException {
Folder test = store.getFolder(utf8Folder);
test.open(Folder.READ_ONLY);
test.close(true);
@ -294,11 +280,9 @@ class IMAPFolderTest {
*/
@Test
void testUtf8FolderNameExamine() {
testWithHandler(
new IMAPTest() {
testWithHandler(new IMAPTest() {
@Override
public void test(Store store, IMAPHandler handler)
throws MessagingException, IOException {
public void test(Store store, IMAPHandler handler) throws MessagingException {
Folder test = store.getFolder(utf8Folder);
test.open(Folder.READ_ONLY);
test.close(true);
@ -326,11 +310,9 @@ class IMAPFolderTest {
*/
@Test
void testUtf7FolderNameStatus() {
testWithHandler(
new IMAPTest() {
testWithHandler(new IMAPTest() {
@Override
public void test(Store store, IMAPHandler handler)
throws MessagingException, IOException {
public void test(Store store, IMAPHandler handler) throws MessagingException {
Folder test = store.getFolder(utf8Folder);
assertEquals(123, ((UIDFolder) test).getUIDValidity());
}
@ -343,8 +325,7 @@ class IMAPFolderTest {
st.nextToken(); // skip "STATUS"
String name = st.nextToken();
if (name.equals(utf7Folder)) {
untagged(outputStream, "STATUS " + utf7Folder +
" (UIDVALIDITY 123)");
untagged(outputStream, "STATUS " + utf7Folder + " (UIDVALIDITY 123)");
ok(outputStream);
} else
no(outputStream, "wrong name");
@ -358,11 +339,9 @@ class IMAPFolderTest {
*/
@Test
void testUtf8FolderNameStatus() {
testWithHandler(
new IMAPTest() {
testWithHandler(new IMAPTest() {
@Override
public void test(Store store, IMAPHandler handler)
throws MessagingException, IOException {
public void test(Store store, IMAPHandler handler) throws MessagingException {
Folder test = store.getFolder(utf8Folder);
assertEquals(123, ((UIDFolder) test).getUIDValidity());
}
@ -375,8 +354,7 @@ class IMAPFolderTest {
st.nextToken(); // skip "STATUS"
String name = unquote(st.nextToken());
if (name.equals(utf8Folder)) {
untagged(outputStream, "STATUS \"" + utf8Folder +
"\" (UIDVALIDITY 123)");
untagged(outputStream, "STATUS \"" + utf8Folder + "\" (UIDVALIDITY 123)");
ok(outputStream);
} else
no(outputStream, "wrong name");
@ -389,17 +367,12 @@ class IMAPFolderTest {
*/
@Test
void testUidNotStickyFalse() {
testWithHandler(
new IMAPTest() {
testWithHandler(new IMAPTest() {
@Override
public void test(Store store, IMAPHandler handler)
throws MessagingException, IOException {
Folder test = store.getFolder("test");
try {
public void test(Store store, IMAPHandler handler) throws MessagingException {
try (Folder test = store.getFolder("test")) {
test.open(Folder.READ_WRITE);
assertFalse(((IMAPFolder) test).getUIDNotSticky());
} finally {
test.close();
}
}
},
@ -411,17 +384,12 @@ class IMAPFolderTest {
*/
@Test
void testUidNotStickyTrue() {
testWithHandler(
new IMAPTest() {
testWithHandler(new IMAPTest() {
@Override
public void test(Store store, IMAPHandler handler)
throws MessagingException, IOException {
Folder test = store.getFolder("test");
try {
public void test(Store store, IMAPHandler handler) throws MessagingException {
try (Folder test = store.getFolder("test")) {
test.open(Folder.READ_WRITE);
assertTrue(((IMAPFolder) test).getUIDNotSticky());
} finally {
test.close();
}
}
},
@ -440,11 +408,10 @@ class IMAPFolderTest {
*/
@Test
void testExpungeOutOfRange() {
testWithHandler(
new IMAPTest() {
testWithHandler(new IMAPTest() {
@Override
public void test(Store store, IMAPHandler handler)
throws MessagingException, IOException {
throws MessagingException {
Folder test = store.getFolder("test");
try {
test.open(Folder.READ_WRITE);
@ -471,27 +438,22 @@ class IMAPFolderTest {
try {
server = new TestServer(handler);
server.start();
final Properties properties = new Properties();
properties.setProperty("mail.imap.host", "localhost");
properties.setProperty("mail.imap.port", String.valueOf(server.getPort()));
test.init(properties);
final Session session = Session.getInstance(properties);
//session.setDebug(true);
final Store store = session.getStore("imap");
try {
try (Store store = session.getStore("imap")) {
store.connect("test", "test");
test.test(store, handler);
} catch (Exception ex) {
logger.log(Level.SEVERE, ex.getMessage(), ex);
fail(ex.toString());
} finally {
store.close();
fail();
}
} catch (final Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e);
fail(e.getMessage());
fail();
} finally {
if (server != null) {
server.quit();

View file

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

View file

@ -21,8 +21,11 @@ import org.xbib.net.mail.test.test.ProtocolHandler;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.Channels;
import java.nio.charset.StandardCharsets;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Handle connection.
@ -31,35 +34,36 @@ import java.util.logging.Level;
*/
public class POP3Handler extends ProtocolHandler {
/**
* Current line.
*/
private String currentLine;
private static final Logger logger = Logger.getLogger(POP3Handler.class.getName());
/**
* First test message.
*/
private String top1 =
"Mime-Version: 1.0\r\n" +
"From: joe@example.com\r\n" +
"To: bob@example.com\r\n" +
"Subject: Example\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n";
private String msg1 = top1 +
private final String top1 =
"""
Mime-Version: 1.0\r
From: joe@example.com\r
To: bob@example.com\r
Subject: Example\r
Content-Type: text/plain\r
\r
""";
private final String msg1 = top1 +
"plain text\r\n";
/**
* Second test message.
*/
private String top2 =
"Mime-Version: 1.0\r\n" +
"From: joe@example.com\r\n" +
"To: bob@example.com\r\n" +
"Subject: Multipart Example\r\n" +
"Content-Type: multipart/mixed; boundary=\"xxx\"\r\n" +
"\r\n";
private String msg2 = top2 +
private final String top2 =
"""
Mime-Version: 1.0\r
From: joe@example.com\r
To: bob@example.com\r
Subject: Multipart Example\r
Content-Type: multipart/mixed; boundary="xxx"\r
\r
""";
private final String msg2 = top2 +
"preamble\r\n" +
"--xxx\r\n" +
"\r\n" +
@ -71,9 +75,7 @@ public class POP3Handler extends ProtocolHandler {
"\r\n" +
"--xxx--\r\n";
public POP3Handler(InputStream inputStream,
OutputStream outputStream) {
super(inputStream, outputStream);
public POP3Handler() {
}
/**
@ -82,8 +84,8 @@ public class POP3Handler extends ProtocolHandler {
* @throws IOException unable to write to socket
*/
@Override
public void sendGreetings() throws IOException {
this.println("+OK POP3 CUSTOM");
public void sendGreetings(OutputStream outputStream) throws IOException {
this.println(outputStream, "+OK POP3 CUSTOM");
}
/**
@ -92,10 +94,9 @@ public class POP3Handler extends ProtocolHandler {
* @param str String to send
* @throws IOException unable to write to socket
*/
public void println(final String str) throws IOException {
this.writer.print(str);
this.writer.print("\r\n");
this.writer.flush();
public void println(OutputStream outputStream, final String str) throws IOException {
Channels.newChannel(outputStream).write(StandardCharsets.UTF_8.encode(str + "\r\n"));
outputStream.flush();
}
/**
@ -104,54 +105,36 @@ public class POP3Handler extends ProtocolHandler {
* @throws IOException unable to read/write to socket
*/
@Override
public void handleCommand() throws IOException {
this.currentLine = readLine();
public void handleCommand(InputStream inputStream, OutputStream outputStream) throws IOException {
String currentLine = readLine(inputStream);
if (this.currentLine == null) {
if (currentLine == null) {
// probably just EOF because the socket was closed
//LOGGER.severe("Current line is null!");
this.exit();
return;
}
final StringTokenizer st = new StringTokenizer(this.currentLine, " ");
final StringTokenizer st = new StringTokenizer(currentLine, " ");
final String commandName = st.nextToken().toUpperCase();
final String arg = st.hasMoreTokens() ? st.nextToken() : null;
if (commandName == null) {
logger.severe("Command name is empty!");
this.exit();
return;
}
if (commandName.equals("STAT")) {
this.stat();
} else if (commandName.equals("LIST")) {
this.list();
} else if (commandName.equals("RETR")) {
this.retr(arg);
} else if (commandName.equals("DELE")) {
this.dele();
} else if (commandName.equals("NOOP")) {
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);
this.println("-ERR unknown command");
switch (commandName) {
case "STAT" -> this.stat(outputStream);
case "LIST" -> this.list(outputStream);
case "RETR" -> this.retr(outputStream, arg);
case "DELE" -> this.dele(outputStream);
case "NOOP" -> this.noop(outputStream);
case "RSET" -> this.rset(outputStream);
case "QUIT" -> this.quit(outputStream);
case "TOP" -> this.top(outputStream, arg);
case "UIDL" -> this.uidl(outputStream);
case "USER" -> this.user(outputStream);
case "PASS" -> this.pass(outputStream);
case "CAPA" -> this.capa(outputStream);
case "AUTH" -> this.auth(outputStream);
default -> {
logger.log(Level.SEVERE, "ERROR command unknown: {0}", commandName);
this.println(outputStream, "-ERR unknown command");
}
}
}
@ -160,8 +143,8 @@ public class POP3Handler extends ProtocolHandler {
*
* @throws IOException unable to read/write to socket
*/
public void stat() throws IOException {
this.println("+OK 2 " + (msg1.length() + msg2.length()));
public void stat(OutputStream outputStream) throws IOException {
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
*/
public void list() throws IOException {
this.writer.println("+OK");
this.writer.println("1 " + msg1.length());
this.writer.println("2 " + msg2.length());
this.println(".");
public void list(OutputStream outputStream) throws IOException {
this.println(outputStream, "+OK\r\n1 " + msg1.length() + "\r\n2 " + msg2.length() + "\r\n.");
}
/**
@ -181,15 +161,14 @@ public class POP3Handler extends ProtocolHandler {
*
* @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;
if (arg.equals("1"))
if ("1".equals(arg))
msg = msg1;
else
msg = msg2;
this.println("+OK " + msg.length() + " octets");
this.writer.write(msg);
this.println(".");
this.println(outputStream, "+OK " + msg.length() + " octets");
this.println(outputStream, msg + ".");
}
/**
@ -197,8 +176,8 @@ public class POP3Handler extends ProtocolHandler {
*
* @throws IOException unable to read/write to socket
*/
public void dele() throws IOException {
this.println("-ERR DELE not supported");
public void dele(OutputStream outputStream) throws IOException {
this.println(outputStream, "-ERR DELE not supported");
}
/**
@ -206,8 +185,8 @@ public class POP3Handler extends ProtocolHandler {
*
* @throws IOException unable to read/write to socket
*/
public void noop() throws IOException {
this.println("+OK");
public void noop(OutputStream outputStream) throws IOException {
this.println(outputStream, "+OK");
}
/**
@ -215,8 +194,8 @@ public class POP3Handler extends ProtocolHandler {
*
* @throws IOException unable to read/write to socket
*/
public void rset() throws IOException {
this.println("+OK");
public void rset(OutputStream outputStream) throws IOException {
this.println(outputStream, "+OK");
}
/**
@ -224,8 +203,8 @@ public class POP3Handler extends ProtocolHandler {
*
* @throws IOException unable to read/write to socket
*/
public void quit() throws IOException {
this.println("+OK");
public void quit(OutputStream outputStream) throws IOException {
this.println(outputStream, "+OK");
this.exit();
}
@ -235,15 +214,14 @@ public class POP3Handler extends ProtocolHandler {
*
* @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;
if (arg.equals("1"))
if ("1".equals(arg))
top = top1;
else
top = top2;
this.println("+OK " + top.length() + " octets");
this.writer.write(top);
this.println(".");
this.println(outputStream, "+OK " + top.length() + " octets");
this.println(outputStream, top + ".");
}
/**
@ -251,11 +229,8 @@ public class POP3Handler extends ProtocolHandler {
*
* @throws IOException unable to read/write to socket
*/
public void uidl() throws IOException {
this.writer.println("+OK");
this.writer.println("1 1");
this.writer.println("2 2");
this.println(".");
public void uidl(OutputStream outputStream) throws IOException {
this.println(outputStream, "+OK\r\n1 1\r\n2 2\r\n.");
}
/**
@ -263,8 +238,8 @@ public class POP3Handler extends ProtocolHandler {
*
* @throws IOException unable to read/write to socket
*/
public void user() throws IOException {
this.println("+OK");
public void user(OutputStream outputStream) throws IOException {
this.println(outputStream, "+OK");
}
/**
@ -272,8 +247,8 @@ public class POP3Handler extends ProtocolHandler {
*
* @throws IOException unable to read/write to socket
*/
public void pass() throws IOException {
this.println("+OK");
public void pass(OutputStream outputStream) throws IOException {
this.println(outputStream, "+OK");
}
/**
@ -281,8 +256,8 @@ public class POP3Handler extends ProtocolHandler {
*
* @throws IOException unable to write to socket
*/
public void capa() throws IOException {
this.println("-ERR CAPA not supported");
public void capa(OutputStream outputStream) throws IOException {
this.println(outputStream, "-ERR CAPA not supported");
}
/**
@ -290,7 +265,7 @@ public class POP3Handler extends ProtocolHandler {
*
* @throws IOException unable to write to socket
*/
public void auth() throws IOException {
this.println("-ERR AUTH not supported");
public void auth(OutputStream outputStream) throws IOException {
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 java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -242,15 +244,13 @@ public final class POP3StoreTest {
*/
private static class POP3HandlerXOAUTH extends POP3Handler {
@Override
public void auth() throws IOException {
this.println("+OK POP3 server ready");
public void auth(OutputStream outputStream) throws IOException {
this.println(outputStream, "+OK POP3 server ready");
}
@Override
public void capa() throws IOException {
this.writer.println("+OK");
this.writer.println("SASL PLAIN XOAUTH2");
this.println(".");
public void capa(OutputStream outputStream) throws IOException {
this.println(outputStream, "+OK\r\nSASL PLAIN XOAUTH2\r\n.");
}
}
@ -265,8 +265,8 @@ public final class POP3StoreTest {
* {@inheritDoc}
*/
@Override
public void noop() throws IOException {
this.println("-ERR");
public void noop(OutputStream outputStream) throws IOException {
this.println(outputStream, "-ERR");
}
}
@ -281,8 +281,8 @@ public final class POP3StoreTest {
* {@inheritDoc}
*/
@Override
public void sendGreetings() throws IOException {
this.println("+OK");
public void sendGreetings(OutputStream outputStream) throws IOException {
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 java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.fail;
@ -86,9 +88,6 @@ public final class SMTPBdatTest {
} finally {
if (server != null) {
server.quit();
server.interrupt();
// wait for handler to exit
server.join();
}
}
}
@ -105,13 +104,13 @@ public final class SMTPBdatTest {
}
@Override
public void bdat(String line) throws IOException {
public void bdat(InputStream inputStream, OutputStream outputStream, String line) throws IOException {
String[] tok = line.split("\\s+");
int bytes = Integer.parseInt(tok[1]);
boolean last = tok.length > 2 &&
tok[2].equalsIgnoreCase("LAST");
readBdatMessage(bytes, last);
println("444 failed");
readBdatMessage(inputStream, bytes, last);
println(outputStream, "444 failed");
}
};
server = new TestServer(handler);
@ -144,9 +143,6 @@ public final class SMTPBdatTest {
} finally {
if (server != null) {
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.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.channels.Channels;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Handle connection.
@ -37,6 +39,8 @@ import java.util.logging.Level;
*/
public class SMTPHandler extends ProtocolHandler {
private static final Logger logger = Logger.getLogger(SMTPHandler.class.getName());
/**
* Current line.
*/
@ -59,7 +63,7 @@ public class SMTPHandler extends ProtocolHandler {
*/
@Override
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
*/
public void println(OutputStream outputStream, final String str) throws IOException {
writer.print(str);
writer.print("\r\n");
writer.flush();
Channels.newChannel(outputStream).write(StandardCharsets.UTF_8.encode(str + "\r\n"));
outputStream.flush();
}
/**
@ -80,52 +83,35 @@ public class SMTPHandler extends ProtocolHandler {
* @throws IOException unable to read/write to socket
*/
@Override
public void handleCommand() throws IOException {
currentLine = readLine();
if (currentLine == null)
return;
final StringTokenizer st = new StringTokenizer(currentLine, " ");
final String commandName = st.nextToken().toUpperCase();
if (commandName == null) {
logger.severe("Command name is empty!");
exit();
public void handleCommand(InputStream inputStream, OutputStream outputStream) throws IOException {
currentLine = readLine(inputStream);
if (currentLine == null) {
return;
}
if (commandName.equals("HELO")) {
helo();
} else if (commandName.equals("EHLO")) {
ehlo();
} else if (commandName.equals("MAIL")) {
mail(currentLine);
} 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);
println("-ERR unknown command");
final StringTokenizer st = new StringTokenizer(currentLine, " ");
final String commandName = st.nextToken().toUpperCase();
switch (commandName) {
case "HELO" -> helo(outputStream);
case "EHLO" -> ehlo(outputStream);
case "MAIL" -> mail(outputStream, currentLine);
case "RCPT" -> rcpt(outputStream, currentLine);
case "DATA" -> data(inputStream, outputStream);
case "BDAT" -> bdat(inputStream,outputStream, currentLine);
case "NOOP" -> noop(outputStream);
case "RSET" -> rset(outputStream);
case "QUIT" -> quit(outputStream);
case "AUTH" -> auth(inputStream, outputStream, currentLine);
default -> {
logger.log(Level.SEVERE, "ERROR command unknown: {0}", commandName);
println(outputStream, "-ERR unknown command");
}
}
}
protected String readLine() throws IOException {
currentLine = super.readLine();
@Override
protected String readLine(InputStream inputStream) throws IOException {
currentLine = super.readLine(inputStream);
if (currentLine == null) {
// XXX - often happens when shutting down
//LOGGER.severe("Current line is null!");
exit();
}
return currentLine;
@ -136,8 +122,8 @@ public class SMTPHandler extends ProtocolHandler {
*
* @throws IOException unable to read/write to socket
*/
public void helo() throws IOException {
println("220 Ok");
public void helo(OutputStream outputStream) throws IOException {
println(outputStream, "220 Ok");
}
/**
@ -145,11 +131,11 @@ public class SMTPHandler extends ProtocolHandler {
*
* @throws IOException unable to read/write to socket
*/
public void ehlo() throws IOException {
println("250-hello");
public void ehlo(OutputStream outputStream) throws IOException {
println(outputStream, "250-hello");
for (String ext : extensions)
println("250-" + ext);
println("250 AUTH PLAIN"); // PLAIN is simplest to fake
println(outputStream, "250-" + ext);
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
*/
public void mail(String line) throws IOException {
ok();
public void mail(OutputStream outputStream, String line) throws IOException {
ok(outputStream);
}
/**
@ -166,8 +152,8 @@ public class SMTPHandler extends ProtocolHandler {
*
* @throws IOException unable to read/write to socket
*/
public void rcpt(String line) throws IOException {
ok();
public void rcpt(OutputStream outputStream, String line) throws IOException {
ok(outputStream);
}
/**
@ -175,10 +161,10 @@ public class SMTPHandler extends ProtocolHandler {
*
* @throws IOException unable to read/write to socket
*/
public void data() throws IOException {
println("354 go ahead");
readMessage();
ok();
public void data(InputStream inputStream, OutputStream outputStream) throws IOException {
println(outputStream, "354 go ahead");
readMessage(inputStream);
ok(outputStream);
}
/**
@ -186,14 +172,14 @@ public class SMTPHandler extends ProtocolHandler {
*
* @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, " ");
String commandName = st.nextToken();
int bytes = Integer.parseInt(st.nextToken());
boolean last = st.hasMoreTokens() &&
st.nextToken().equalsIgnoreCase("LAST");
readBdatMessage(bytes, last);
ok();
readBdatMessage(inputStream, bytes, last);
ok(outputStream);
}
/**
@ -205,11 +191,11 @@ public class SMTPHandler extends ProtocolHandler {
/**
* Consume the message and save it.
*/
protected void readMessage() throws IOException {
protected void readMessage(InputStream inputStream) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
PrintWriter pw = new PrintWriter(new OutputStreamWriter(bos, StandardCharsets.UTF_8));
String line;
while ((line = super.readLine()) != null) {
while ((line = super.readLine(inputStream)) != null) {
if (line.equals("."))
break;
if (line.startsWith("."))
@ -225,7 +211,7 @@ public class SMTPHandler extends ProtocolHandler {
* Consume a chunk of the message and save it.
* 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];
int len = data.length;
int off = 0;
@ -248,8 +234,8 @@ public class SMTPHandler extends ProtocolHandler {
*
* @throws IOException unable to read/write to socket
*/
public void noop() throws IOException {
ok();
public void noop(OutputStream outputStream) throws IOException {
ok(outputStream);
}
/**
@ -257,8 +243,8 @@ public class SMTPHandler extends ProtocolHandler {
*
* @throws IOException unable to read/write to socket
*/
public void rset() throws IOException {
ok();
public void rset(OutputStream outputStream) throws IOException {
ok(outputStream);
}
/**
@ -266,8 +252,8 @@ public class SMTPHandler extends ProtocolHandler {
*
* @throws IOException unable to read/write to socket
*/
public void quit() throws IOException {
println("221 BYE");
public void quit(OutputStream outputStream) throws IOException {
println(outputStream, "221 BYE");
exit();
}
@ -276,11 +262,11 @@ public class SMTPHandler extends ProtocolHandler {
*
* @throws IOException unable to read/write to socket
*/
public void auth(String line) throws IOException {
println("235 Authorized");
public void auth(InputStream inputStream, OutputStream outputStream, String line) throws IOException {
println(outputStream, "235 Authorized");
}
protected void ok() throws IOException {
println("250 OK");
protected void ok(OutputStream outputStream) throws IOException {
println(outputStream, "250 OK");
}
}

View file

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

View file

@ -17,10 +17,13 @@
package org.xbib.net.mail.test.smtp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Handle connection with LOGIN or PLAIN authentication.
@ -28,6 +31,9 @@ import java.util.logging.Level;
* @author Bill Shannon
*/
public class SMTPLoginHandler extends SMTPHandler {
private static final Logger logger = Logger.getLogger(SMTPLoginHandler.class.getName());
protected String username = "test";
protected String password = "test";
@ -37,11 +43,11 @@ public class SMTPLoginHandler extends SMTPHandler {
* @throws IOException unable to read/write to socket
*/
@Override
public void ehlo() throws IOException {
println("250-hello");
println("250-SMTPUTF8");
println("250-8BITMIME");
println("250 AUTH PLAIN LOGIN");
public void ehlo(OutputStream outputStream) throws IOException {
println(outputStream, "250-hello");
println(outputStream, "250-SMTPUTF8");
println(outputStream, "250-8BITMIME");
println(outputStream, "250 AUTH PLAIN LOGIN");
}
/**
@ -50,7 +56,7 @@ public class SMTPLoginHandler extends SMTPHandler {
* @throws IOException unable to read/write to socket
*/
@Override
public void auth(String line) throws IOException {
public void auth(InputStream inputStream, OutputStream outputStream, String line) throws IOException {
StringTokenizer ct = new StringTokenizer(line, " ");
String commandName = ct.nextToken().toUpperCase();
String mech = ct.nextToken().toUpperCase();
@ -61,22 +67,22 @@ public class SMTPLoginHandler extends SMTPHandler {
if (logger.isLoggable(Level.FINE))
logger.fine(line);
if (mech.equalsIgnoreCase("PLAIN"))
plain(ir);
plain(outputStream, ir);
else if (mech.equalsIgnoreCase("LOGIN"))
login(ir);
login(inputStream, outputStream, ir);
else
println("501 bad AUTH mechanism");
println(outputStream, "501 bad AUTH mechanism");
}
/**
* AUTH LOGIN
*/
private void login(String ir) throws IOException {
println("334");
private void login(InputStream inputStream, OutputStream outputStream, String ir) throws IOException {
println(outputStream, "334");
// read user name
String resp = readLine();
String resp = readLine(inputStream);
if (!isBase64(resp)) {
println("501 response not base64");
println(outputStream, "501 response not base64");
return;
}
byte[] response = resp.getBytes(StandardCharsets.US_ASCII);
@ -84,12 +90,12 @@ public class SMTPLoginHandler extends SMTPHandler {
String u = new String(response, StandardCharsets.UTF_8);
if (logger.isLoggable(Level.FINE))
logger.fine("USER: " + u);
println("334");
println(outputStream, "334");
// read password
resp = readLine();
resp = readLine(inputStream);
if (!isBase64(resp)) {
println("501 response not base64");
println(outputStream, "501 response not base64");
return;
}
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);
if (!u.equals(username) || !p.equals(password)) {
println("535 authentication failed");
println(outputStream, "535 authentication failed");
return;
}
println("235 Authenticated");
println(outputStream, "235 Authenticated");
}
/**
* 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(
ir.getBytes(StandardCharsets.US_ASCII)),
StandardCharsets.UTF_8);
@ -119,10 +124,10 @@ public class SMTPLoginHandler extends SMTPHandler {
String p = ap[2];
//System.out.printf("USER: %s, PASSWORD: %s%n", u, p);
if (!u.equals(username) || !p.equals(password)) {
println("535 authentication failed");
println(outputStream, "535 authentication failed");
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.SaslServer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Base64;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Handle connection with SASL authentication.
@ -40,15 +43,17 @@ import java.util.logging.Level;
*/
public class SMTPSaslHandler extends SMTPHandler {
private static final Logger logger = Logger.getLogger(SMTPSaslHandler.class.getName());
/**
* EHLO command.
*
* @throws IOException unable to read/write to socket
*/
@Override
public void ehlo() throws IOException {
println("250-hello");
println("250 AUTH DIGEST-MD5");
public void ehlo(OutputStream outputStream) throws IOException {
println(outputStream, "250-hello");
println(outputStream, "250 AUTH DIGEST-MD5");
}
/**
@ -57,7 +62,7 @@ public class SMTPSaslHandler extends SMTPHandler {
* @throws IOException unable to read/write to socket
*/
@Override
public void auth(String line) throws IOException {
public void auth(InputStream inputStream, OutputStream outputStream, String line) throws IOException {
StringTokenizer ct = new StringTokenizer(line, " ");
String commandName = 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);
} catch (SaslException 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;
}
if (ss == null) {
logger.fine("No SASL support");
println("501 No SASL support");
println(outputStream, "501 No SASL support");
return;
}
if (logger.isLoggable(Level.FINE))
@ -141,20 +146,19 @@ public class SMTPSaslHandler extends SMTPHandler {
ASCIIUtility.toString(chal, 0, chal.length));
byte[] ba = Base64.getEncoder().encode(chal);
if (ba.length > 0)
println("334 " +
ASCIIUtility.toString(ba, 0, ba.length));
println(outputStream, "334 " + ASCIIUtility.toString(ba, 0, ba.length));
else
println("334");
println(outputStream, "334");
// read response
String resp = readLine();
String resp = readLine(inputStream);
if (!isBase64(resp)) {
println("501 response not base64");
println(outputStream, "501 response not base64");
break;
}
response = resp.getBytes();
response = Base64.getDecoder().decode(response);
} catch (SaslException ex) {
println("501 " + ex.toString());
println(outputStream, "501 " + ex.toString());
break;
}
}
@ -164,15 +168,13 @@ public class SMTPSaslHandler extends SMTPHandler {
if (qop != null && (qop.equalsIgnoreCase("auth-int") ||
qop.equalsIgnoreCase("auth-conf"))) {
// XXX - NOT SUPPORTED!!!
logger.fine(
"SASL Mechanism requires integrity or confidentiality");
println("501 " +
logger.fine("SASL Mechanism requires integrity or confidentiality");
println(outputStream, "501 " +
"SASL Mechanism requires integrity or confidentiality");
return;
}
}
println("235 Authenticated");
println(outputStream, "235 Authenticated");
}
/**

View file

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

View file

@ -24,6 +24,8 @@ import jakarta.mail.internet.MimeMessage;
import org.junit.jupiter.api.Test;
import org.xbib.net.mail.test.test.TestServer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;
import java.util.StringTokenizer;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -44,10 +46,10 @@ public class SMTPUtf8Test {
try {
server = new TestServer(new SMTPLoginHandler() {
@Override
public void auth(String line) throws IOException {
public void auth(InputStream inputStream, OutputStream outputStream, String line) throws IOException {
username = user;
password = user;
super.auth(line);
super.auth(inputStream, outputStream, line);
}
});
server.start();
@ -76,7 +78,6 @@ public class SMTPUtf8Test {
} finally {
if (server != null) {
server.quit();
server.interrupt();
}
}
}
@ -91,10 +92,10 @@ public class SMTPUtf8Test {
try {
server = new TestServer(new SMTPLoginHandler() {
@Override
public void auth(String line) throws IOException {
public void auth(InputStream inputStream, OutputStream outputStream, String line) throws IOException {
username = user;
password = user;
super.auth(line);
super.auth(inputStream, outputStream, line);
}
});
server.start();
@ -122,7 +123,6 @@ public class SMTPUtf8Test {
} finally {
if (server != null) {
server.quit();
server.interrupt();
}
}
}
@ -137,10 +137,10 @@ public class SMTPUtf8Test {
try {
server = new TestServer(new SMTPLoginHandler() {
@Override
public void auth(String line) throws IOException {
public void auth(InputStream inputStream, OutputStream outputStream, String line) throws IOException {
username = user;
password = user;
super.auth(line);
super.auth(inputStream, outputStream, line);
}
});
server.start();
@ -168,7 +168,6 @@ public class SMTPUtf8Test {
} finally {
if (server != null) {
server.quit();
server.interrupt();
}
}
}
@ -190,32 +189,32 @@ public class SMTPUtf8Test {
try {
server = new TestServer(new SMTPHandler() {
@Override
public void ehlo() throws IOException {
println("250-hello");
println("250-SMTPUTF8");
println("250 AUTH PLAIN");
public void ehlo(OutputStream outputStream) throws IOException {
println(outputStream, "250-hello");
println(outputStream, "250-SMTPUTF8");
println(outputStream, "250 AUTH PLAIN");
}
@Override
public void mail(String line) throws IOException {
public void mail(OutputStream outputStream, String line) throws IOException {
StringTokenizer st = new StringTokenizer(line);
st.nextToken(); // skip "MAIL"
env.from = st.nextToken().
replaceFirst("FROM:<(.*)>", "$1");
if (!st.hasMoreTokens() ||
!st.nextToken().equals("SMTPUTF8"))
println("500 fail");
println(outputStream, "500 fail");
else
ok();
ok(outputStream);
}
@Override
public void rcpt(String line) throws IOException {
public void rcpt(OutputStream outputStream, String line) throws IOException {
StringTokenizer st = new StringTokenizer(line);
st.nextToken(); // skip "RCPT"
env.to = st.nextToken().
replaceFirst("TO:<(.*)>", "$1");
ok();
ok(outputStream);
}
});
server.start();
@ -250,7 +249,6 @@ public class SMTPUtf8Test {
} finally {
if (server != null) {
server.quit();
server.interrupt();
}
}
// 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.xbib.net.mail.test.test.TestServer;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.fail;
@ -45,13 +46,13 @@ public final class SMTPWriteTimeoutTest {
try {
SMTPHandler handler = new SMTPHandler() {
@Override
public void readMessage() throws IOException {
public void readMessage(InputStream inputStream) throws IOException {
try {
// delay long enough to cause timeout
Thread.sleep(5 * TIMEOUT);
} catch (Exception ex) {
}
super.readMessage();
super.readMessage(inputStream);
}
};
server = new TestServer(handler);
@ -88,9 +89,6 @@ public final class SMTPWriteTimeoutTest {
} finally {
if (server != null) {
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.InputStream;
import java.io.OutputStream;
import java.net.SocketException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
@ -38,6 +39,7 @@ import java.security.KeyStore;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -61,7 +63,7 @@ public final class TestServer {
*/
private ServerSocket serverSocket;
private final ExecutorService executorService = Executors.newCachedThreadPool();
private final ExecutorService executorService;
/**
* Keep on?
@ -92,40 +94,27 @@ public final class TestServer {
public TestServer(final ProtocolHandler handler, final boolean isSSL)
throws IOException {
this.handler = handler;
/*
* 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 {
serverSocket = createServerSocket(port, isSSL);
return;
} catch (IOException ex) {
// ignore
} catch (UnrecoverableKeyException | CertificateException | KeyStoreException | NoSuchAlgorithmException |
KeyManagementException e) {
throw new RuntimeException(e);
}
this.executorService = Executors.newCachedThreadPool();
try {
this.serverSocket = createServerSocket(isSSL);
} catch (IOException ex) {
// ignore
} catch (UnrecoverableKeyException | CertificateException | KeyStoreException | NoSuchAlgorithmException | KeyManagementException 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 {
ServerSocket ss;
if (isSSL) {
SSLContext sslContext = createSSLContext();
SSLServerSocketFactory sf = sslContext.getServerSocketFactory();
ss = sf.createServerSocket(port);
} else
ss = new ServerSocket(port);
ss = sf.createServerSocket();
} else {
ss = new ServerSocket();
}
ss.bind(new InetSocketAddress(0));
return ss;
}
@ -159,22 +148,11 @@ public final class TestServer {
return serverSocket.getLocalPort();
}
/**
* Exit server.
*/
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() {
executorService.execute(this::run);
}
public void start() {
/*public void start() {
// don't return until server is really listening
// XXX - this might not be necessary
for (int tries = 0; tries < 10; tries++) {
@ -187,26 +165,26 @@ public final class TestServer {
}
}
throw new RuntimeException("Server isn't listening");
}
}*/
/**
* {@inheritDoc}
*/
public void run() {
private void run() {
try {
keepOn = true;
while (keepOn) {
try {
final Socket clientSocket = serverSocket.accept();
executorService.submit(new Callable<Object>() {
final InputStream inputStream = new BufferedInputStream(clientSocket.getInputStream());
executorService.submit(new Callable<>() {
final InputStream inputStream = new BufferedInputStream(clientSocket.getInputStream());
final OutputStream outputStream = clientSocket.getOutputStream();
@Override
public Object call() {
handler.handle(inputStream, outputStream);
return null;
}
});
} catch (SocketException e) {
// ignore socket close
} catch (Exception 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() {
synchronized (clients) {
// 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 {
Socket s = new Socket();
s.connect(new InetSocketAddress("localhost", port), 100);
@ -254,6 +253,6 @@ public final class TestServer {
//System.out.println(ex);
}
return false;
}
}*/
}

View file

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

View file

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