forked from I2P_Developers/i2p.i2p
SusiMail:
- CSS padding in inputs
- Flush writes in SMTP
- Don't wait for SMTP response after QUIT
- Translate the "login failed" message
- Show "no messages" in folder view if none
- Message view attachment cleanups
- Fix the message view layout in CSS
- Pipeline USER and PASS to save a round-trip at startup
- Better synchronization in POP3
- Properly de-byte-stuff in POP3
- Flush writes in POP3 for speedup
- Remove unnecessary caching in POP3, this is handled in MailCache
- More efficient handling of POP3 server responses
- Remove 60s timeout for fetching a message,
so retrieval of large messages doesn't fail
- Don't allow line breaks in date/time or size in folder view
- Use DataHelper.formatSize2() for message size
- Identify susimail log messages in wrapper
- Debug log tweaks
This commit is contained in:
@@ -42,7 +42,7 @@ public class Debug {
|
||||
public static void debug( int msgLevel, String msg )
|
||||
{
|
||||
if( msgLevel <= level )
|
||||
System.err.println( msg );
|
||||
System.err.println("SusiMail: " + msg);
|
||||
if (msgLevel <= ERROR)
|
||||
I2PAppContext.getGlobalContext().logManager().getLog(Debug.class).error(msg);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ import java.util.Iterator;
|
||||
* and then fetch the content of the current page with
|
||||
* currentPageIterator().
|
||||
*
|
||||
* Warning - unsynchronized - not thread safe
|
||||
*
|
||||
* @author susi
|
||||
*/
|
||||
public class Folder<O extends Object> {
|
||||
|
||||
@@ -61,6 +61,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/**
|
||||
* @author susi23
|
||||
@@ -182,7 +183,7 @@ public class WebMail extends HttpServlet
|
||||
* @author susi
|
||||
*/
|
||||
private static class IDSorter implements Comparator<String> {
|
||||
private MailCache mailCache;
|
||||
private final MailCache mailCache;
|
||||
|
||||
/**
|
||||
* Set MailCache object, where to get Mails from
|
||||
@@ -209,7 +210,7 @@ public class WebMail extends HttpServlet
|
||||
* @author susi
|
||||
*/
|
||||
private static class SenderSorter implements Comparator<String> {
|
||||
private MailCache mailCache;
|
||||
private final MailCache mailCache;
|
||||
|
||||
/**
|
||||
* Set MailCache object, where to get Mails from
|
||||
@@ -236,7 +237,7 @@ public class WebMail extends HttpServlet
|
||||
*/
|
||||
private static class SubjectSorter implements Comparator<String> {
|
||||
|
||||
private MailCache mailCache;
|
||||
private final MailCache mailCache;
|
||||
/**
|
||||
* Set MailCache object, where to get Mails from
|
||||
* @param mailCache
|
||||
@@ -262,7 +263,7 @@ public class WebMail extends HttpServlet
|
||||
*/
|
||||
private static class DateSorter implements Comparator<String> {
|
||||
|
||||
private MailCache mailCache;
|
||||
private final MailCache mailCache;
|
||||
/**
|
||||
* Set MailCache object, where to get Mails from
|
||||
* @param mailCache
|
||||
@@ -287,7 +288,7 @@ public class WebMail extends HttpServlet
|
||||
*/
|
||||
private static class SizeSorter implements Comparator<String> {
|
||||
|
||||
private MailCache mailCache;
|
||||
private final MailCache mailCache;
|
||||
/**
|
||||
* Set MailCache object, where to get Mails from
|
||||
* @param mailCache
|
||||
@@ -444,7 +445,7 @@ public class WebMail extends HttpServlet
|
||||
( mailPart.description != null ? mailPart.description + ", " : "" ) +
|
||||
( mailPart.filename != null ? mailPart.filename + ", " : "" ) +
|
||||
( mailPart.name != null ? mailPart.name + ", " : "" ) +
|
||||
( mailPart.type != null ? mailPart.type : _("unknown") ) );
|
||||
( mailPart.type != null ? '(' + mailPart.type + ')' : _("unknown") ) );
|
||||
|
||||
if( level == 0 && mailPart.version == null ) {
|
||||
/*
|
||||
@@ -514,8 +515,10 @@ public class WebMail extends HttpServlet
|
||||
}
|
||||
if( prepareAttachment ) {
|
||||
if( html ) {
|
||||
out.println( "<p class=\"mailbody\">" );
|
||||
out.println( "<a target=\"_blank\" href=\"" + myself + "?" + DOWNLOAD + "=" + mailPart.hashCode() + "\">" + _("Download") + "</a> " + _("attachment ({0}).", ident) + " " + _("File is packed into a zipfile for security reasons.") );
|
||||
out.println( "<hr><p class=\"mailbody\">" );
|
||||
out.println( "<a target=\"_blank\" href=\"" + myself + "?" + DOWNLOAD + "=" +
|
||||
mailPart.hashCode() + "\">" + _("Download attachment {0}", ident) + "</a>" +
|
||||
" (" + _("File is packed into a zipfile for security reasons.") + ')');
|
||||
out.println( "</p>" );
|
||||
}
|
||||
else {
|
||||
@@ -635,6 +638,7 @@ public class WebMail extends HttpServlet
|
||||
sessionObject.folder.addSorter( SORT_DATE, new DateSorter( sessionObject.mailCache ) );
|
||||
sessionObject.folder.addSorter( SORT_SIZE, new SizeSorter( sessionObject.mailCache ) );
|
||||
sessionObject.folder.setSortingDirection( Folder.DOWN );
|
||||
sessionObject.folder.sortBy(SORT_DATE);
|
||||
sessionObject.reallyDelete = false;
|
||||
Debug.debug(Debug.DEBUG, "CONNECTED, YAY");
|
||||
}
|
||||
@@ -1077,7 +1081,8 @@ public class WebMail extends HttpServlet
|
||||
String str = request.getParameter( PAGESIZE );
|
||||
if( str != null && str.length() > 0 ) {
|
||||
try {
|
||||
int pageSize = Integer.parseInt( str );
|
||||
// limit max to 100 as it makes the startup really slow
|
||||
int pageSize = Math.min(100, Math.max(5, Integer.parseInt( str )));
|
||||
int oldPageSize = sessionObject.folder.getPageSize();
|
||||
if( pageSize != oldPageSize )
|
||||
sessionObject.folder.setPageSize( pageSize );
|
||||
@@ -1357,6 +1362,7 @@ public class WebMail extends HttpServlet
|
||||
out.println( "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=2.0, user-scalable=yes\" />\n" +
|
||||
"<link rel=\"stylesheet\" type=\"text/css\" href=\"" + sessionObject.themePath + "mobile.css\" />\n" );
|
||||
}
|
||||
// TODO javascript here
|
||||
out.println( "</head>\n<body>\n" +
|
||||
"<div class=\"page\"><p><img src=\"" + sessionObject.imgPath + "susimail.png\" alt=\"Susimail\"><br> </p>\n" +
|
||||
"<form method=\"POST\" enctype=\"multipart/form-data\" action=\"" + myself + "\" accept-charset=\"UTF-8\">" );
|
||||
@@ -1603,7 +1609,7 @@ public class WebMail extends HttpServlet
|
||||
*/
|
||||
private static void showCompose( PrintWriter out, SessionObject sessionObject, RequestWrapper request )
|
||||
{
|
||||
out.println( button( SEND, _("Send") ) +
|
||||
out.println( button( SEND, _("Send") ) + spacer +
|
||||
button( CANCEL, _("Cancel") ) + spacer +
|
||||
(sessionObject.attachments != null && (!sessionObject.attachments.isEmpty()) ? button( DELETE_ATTACHMENT, _("Delete Attachment") ) : button2( DELETE_ATTACHMENT, _("Delete Attachment") ) ) + spacer);
|
||||
//if (Config.hasConfigFile())
|
||||
@@ -1645,10 +1651,12 @@ public class WebMail extends HttpServlet
|
||||
boolean wroteHeader = false;
|
||||
for( Attachment attachment : sessionObject.attachments ) {
|
||||
if( !wroteHeader ) {
|
||||
out.println( "<tr><td colspan=\"2\" align=\"center\">" + _("Attachments:") + "</td></tr>" );
|
||||
out.println("<tr><td align=\"right\">" + _("Attachments:") + "</td>");
|
||||
wroteHeader = true;
|
||||
} else {
|
||||
out.println("<tr><td align=\"right\"> </td>");
|
||||
}
|
||||
out.println( "<tr><td colspan=\"2\" align=\"center\"><input type=\"checkbox\" class=\"optbox\" name=\"check" + attachment.hashCode() + "\" value=\"1\"> " + attachment.getFileName() + "</td></tr>");
|
||||
out.println("<td align=\"left\"><input type=\"checkbox\" class=\"optbox\" name=\"check" + attachment.hashCode() + "\" value=\"1\"> " + attachment.getFileName() + "</td></tr>");
|
||||
}
|
||||
}
|
||||
out.println( "</table>" );
|
||||
@@ -1756,18 +1764,25 @@ public class WebMail extends HttpServlet
|
||||
out.println( "<tr class=\"list" + bg + "\"><td><input type=\"checkbox\" class=\"optbox\" name=\"check" + i + "\" value=\"1\"" +
|
||||
( idChecked ? "checked" : "" ) + ">" + "</td><td>" +
|
||||
link + mail.shortSender + "</a></td><td> </td><td>" + link + mail.shortSubject + "</a></td><td> </td><td>" +
|
||||
mail.localFormattedDate + "</td><td> </td><td>" + ngettext("1 Byte", "{0} Bytes", mail.size) + "</td></tr>" );
|
||||
// don't let date get split across lines
|
||||
mail.localFormattedDate.replace(" ", " ") + "</td><td> </td><td>" +
|
||||
DataHelper.formatSize2(mail.size) + "B</td></tr>" );
|
||||
bg = 1 - bg;
|
||||
i++;
|
||||
}
|
||||
out.println( "<tr><td colspan=\"8\"><hr></td></tr>\n</table>" +
|
||||
if (i == 0)
|
||||
out.println("<tr><td colspan=\"8\" align=\"center\"><i>" + _("No messages") + "</i></td></tr>\n</table>");
|
||||
out.println( "<tr><td colspan=\"8\"><hr></td></tr>\n</table>");
|
||||
if (i > 0) {
|
||||
out.println(
|
||||
button( MARKALL, _("Mark All") ) +
|
||||
button( INVERT, _("Invert Selection") ) +
|
||||
button( CLEAR, _("Clear") ) +
|
||||
"<br>");
|
||||
out.println(
|
||||
out.println(
|
||||
_("Page Size:") + " <input type=\"text\" style=\"text-align: right;\" name=\"" + PAGESIZE + "\" size=\"4\" value=\"" + sessionObject.folder.getPageSize() + "\">" +
|
||||
button( SETPAGESIZE, _("Set") ) );
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
|
||||
@@ -27,39 +27,38 @@ import i2p.susi.debug.Debug;
|
||||
import i2p.susi.webmail.Messages;
|
||||
import i2p.susi.util.ReadBuffer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Hashtable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/**
|
||||
* @author susi23
|
||||
*/
|
||||
public class POP3MailBox {
|
||||
|
||||
private static final int DEFAULT_BUFSIZE = 4096;
|
||||
|
||||
private final String host, user, pass;
|
||||
|
||||
private static final String ERR = "-ERR ";
|
||||
private String lastLine, lastError;
|
||||
|
||||
private final int port;
|
||||
private int mails, read;
|
||||
private int mails;
|
||||
|
||||
private boolean connected;
|
||||
|
||||
private final Hashtable<Integer, ReadBuffer> headerList, bodyList;
|
||||
private Hashtable<Integer, Integer> sizes;
|
||||
private final Hashtable<String, Integer> uidlToID;
|
||||
private final ArrayList<String> uidlList;
|
||||
/** ID to size */
|
||||
private final HashMap<Integer, Integer> sizes;
|
||||
/** UIDL to ID */
|
||||
private final HashMap<String, Integer> uidlToID;
|
||||
|
||||
private Socket socket;
|
||||
|
||||
private final byte[] buffer = new byte[DEFAULT_BUFSIZE];
|
||||
|
||||
private final Object synchronizer;
|
||||
|
||||
/**
|
||||
@@ -76,109 +75,94 @@ public class POP3MailBox {
|
||||
this.port = port;
|
||||
this.user = user;
|
||||
this.pass = pass;
|
||||
headerList = new Hashtable<Integer, ReadBuffer>();
|
||||
bodyList = new Hashtable<Integer, ReadBuffer>();
|
||||
uidlList = new ArrayList<String>();
|
||||
uidlToID = new Hashtable<String, Integer>();
|
||||
sizes = new Hashtable<Integer, Integer>();
|
||||
uidlToID = new HashMap<String, Integer>();
|
||||
sizes = new HashMap<Integer, Integer>();
|
||||
synchronizer = new Object();
|
||||
// this appears in the UI so translate
|
||||
lastLine = ERR + _("No response from server");
|
||||
lastLine = _("No response from server");
|
||||
connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the header. Does not cache.
|
||||
*
|
||||
* @param uidl
|
||||
* @return Byte buffer containing header data.
|
||||
* @return Byte buffer containing header data or null
|
||||
*/
|
||||
public ReadBuffer getHeader( String uidl ) {
|
||||
synchronized( synchronizer ) {
|
||||
return getHeader( getIDfromUIDL( uidl ) );
|
||||
int id = getIDfromUIDL(uidl);
|
||||
if (id < 0)
|
||||
return null;
|
||||
return getHeader(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieves header from pop3 server (with TOP command and RETR as fallback)
|
||||
* Caller must sync.
|
||||
*
|
||||
* @param id message id
|
||||
* @return Byte buffer containing header data.
|
||||
* @return Byte buffer containing header data or null
|
||||
*/
|
||||
private ReadBuffer getHeader( int id ) {
|
||||
synchronized( synchronizer ) {
|
||||
Debug.debug(Debug.DEBUG, "getHeader(" + id + ")");
|
||||
Integer idObj = Integer.valueOf(id);
|
||||
ReadBuffer header = null;
|
||||
if (id >= 1 && id <= mails) {
|
||||
/*
|
||||
* is data already cached?
|
||||
* try 'TOP n 0' command
|
||||
*/
|
||||
header = (ReadBuffer)headerList.get(idObj);
|
||||
if (header == null) {
|
||||
/*
|
||||
* try 'TOP n 0' command
|
||||
*/
|
||||
header = sendCmdN("TOP " + id + " 0" );
|
||||
}
|
||||
header = sendCmdN("TOP " + id + " 0" );
|
||||
if( header == null) {
|
||||
/*
|
||||
* try 'RETR n' command
|
||||
*/
|
||||
header = sendCmdN("RETR " + id );
|
||||
if (header == null)
|
||||
Debug.debug( Debug.DEBUG, "RETR returned null" );
|
||||
}
|
||||
if( header != null ) {
|
||||
/*
|
||||
* store result in hashtable
|
||||
*/
|
||||
headerList.put(idObj, header);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
lastError = "Message id out of range.";
|
||||
}
|
||||
return header;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the body. Does not cache.
|
||||
*
|
||||
* @param uidl
|
||||
* @return Byte buffer containing body data.
|
||||
* @return Byte buffer containing body data or null
|
||||
*/
|
||||
public ReadBuffer getBody( String uidl ) {
|
||||
synchronized( synchronizer ) {
|
||||
return getBody( getIDfromUIDL( uidl ) );
|
||||
int id = getIDfromUIDL(uidl);
|
||||
if (id < 0)
|
||||
return null;
|
||||
return getBody(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve message body from pop3 server (via RETR command)
|
||||
* Caller must sync.
|
||||
*
|
||||
* @param id message id
|
||||
* @return Byte buffer containing body data.
|
||||
* @return Byte buffer containing body data or null
|
||||
*/
|
||||
private ReadBuffer getBody(int id) {
|
||||
synchronized( synchronizer ) {
|
||||
Debug.debug(Debug.DEBUG, "getBody(" + id + ")");
|
||||
Integer idObj = Integer.valueOf(id);
|
||||
ReadBuffer body = null;
|
||||
if (id >= 1 && id <= mails) {
|
||||
body = (ReadBuffer)bodyList.get(idObj);
|
||||
if( body == null ) {
|
||||
body = sendCmdN( "RETR " + id );
|
||||
if (body != null) {
|
||||
bodyList.put(idObj, body);
|
||||
}
|
||||
else {
|
||||
Debug.debug( Debug.DEBUG, "sendCmdN returned null" );
|
||||
}
|
||||
}
|
||||
body = sendCmdN( "RETR " + id );
|
||||
if (body == null)
|
||||
Debug.debug( Debug.DEBUG, "RETR returned null" );
|
||||
}
|
||||
else {
|
||||
lastError = "Message id out of range.";
|
||||
}
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,7 +174,10 @@ public class POP3MailBox {
|
||||
{
|
||||
Debug.debug(Debug.DEBUG, "delete(" + uidl + ")");
|
||||
synchronized( synchronizer ) {
|
||||
return delete( getIDfromUIDL( uidl ) );
|
||||
int id = getIDfromUIDL(uidl);
|
||||
if (id < 0)
|
||||
return false;
|
||||
return delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,34 +205,37 @@ public class POP3MailBox {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached size of a message (via previous LIST command).
|
||||
*
|
||||
* @param uidl
|
||||
* @return Message size in bytes.
|
||||
* @return Message size in bytes or 0 if not found
|
||||
*/
|
||||
public int getSize( String uidl ) {
|
||||
synchronized( synchronizer ) {
|
||||
return getSize( getIDfromUIDL( uidl ) );
|
||||
int id = getIDfromUIDL(uidl);
|
||||
if (id < 0)
|
||||
return 0;
|
||||
return getSize(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get size of a message (via LIST command)
|
||||
* Get cached size of a message (via previous LIST command).
|
||||
* Caller must sync.
|
||||
*
|
||||
* @param id message id
|
||||
* @return Message size in bytes.
|
||||
* @return Message size in bytes or 0 if not found
|
||||
*/
|
||||
private int getSize(int id) {
|
||||
synchronized( synchronizer ) {
|
||||
Debug.debug(Debug.DEBUG, "getSize(" + id + ")");
|
||||
int result = 0;
|
||||
/*
|
||||
* find value in hashtable
|
||||
*/
|
||||
Integer resultObj = (Integer) sizes.get(Integer.valueOf(id));
|
||||
Integer resultObj = sizes.get(Integer.valueOf(id));
|
||||
if (resultObj != null)
|
||||
result = resultObj.intValue();
|
||||
Debug.debug(Debug.DEBUG, "getSize(" + id + ") = " + result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,43 +257,37 @@ public class POP3MailBox {
|
||||
}
|
||||
|
||||
/**
|
||||
* Caller must sync.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void updateUIDLs() throws IOException
|
||||
{
|
||||
synchronized (synchronizer) {
|
||||
|
||||
ReadBuffer readBuffer = null;
|
||||
|
||||
uidlToID.clear();
|
||||
uidlList.clear();
|
||||
|
||||
readBuffer = sendCmdNa( "UIDL", DEFAULT_BUFSIZE );
|
||||
if( readBuffer != null ) {
|
||||
String[] lines = readBuffer.toString().split( "\r\n" );
|
||||
|
||||
for( int i = 0; i < lines.length; i++ ) {
|
||||
int j = lines[i].indexOf( " " );
|
||||
List<String> lines = sendCmdNl( "UIDL");
|
||||
if (lines != null) {
|
||||
for (String line : lines) {
|
||||
int j = line.indexOf( " " );
|
||||
if( j != -1 ) {
|
||||
try {
|
||||
int n = Integer.parseInt( lines[i].substring( 0, j ) );
|
||||
String uidl = lines[i].substring( j+1 );
|
||||
int n = Integer.parseInt( line.substring( 0, j ) );
|
||||
String uidl = line.substring(j + 1).trim();
|
||||
uidlToID.put( uidl, Integer.valueOf( n ) );
|
||||
uidlList.add( n-1, uidl );
|
||||
}
|
||||
catch( NumberFormatException nfe ) {
|
||||
|
||||
} catch (NumberFormatException nfe) {
|
||||
Debug.debug(Debug.DEBUG, "UIDL error " + nfe);
|
||||
} catch (IndexOutOfBoundsException ioobe) {
|
||||
Debug.debug(Debug.DEBUG, "UIDL error " + ioobe);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Debug.debug(Debug.DEBUG, "Error getting UIDL list from server.");
|
||||
}
|
||||
else {
|
||||
System.err.println( "Error getting UIDL list from pop3 server.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Caller must sync.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@@ -312,23 +296,22 @@ public class POP3MailBox {
|
||||
* try LIST
|
||||
*/
|
||||
sizes.clear();
|
||||
ReadBuffer readBuffer = sendCmdNa("LIST", DEFAULT_BUFSIZE );
|
||||
if(readBuffer != null) {
|
||||
String[] lines = new String( readBuffer.content, 0, readBuffer.length ).split( "\r\n" );
|
||||
if (lines != null) {
|
||||
sizes = new Hashtable<Integer, Integer>();
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
int j = lines[i].indexOf(" ");
|
||||
if (j != -1) {
|
||||
int key = Integer.parseInt(lines[i].substring(0, j));
|
||||
int value = Integer.parseInt(lines[i].substring(j + 1));
|
||||
List<String> lines = sendCmdNl("LIST");
|
||||
if (lines != null) {
|
||||
for (String line : lines) {
|
||||
int j = line.indexOf(" ");
|
||||
if (j != -1) {
|
||||
try {
|
||||
int key = Integer.parseInt(line.substring(0, j));
|
||||
int value = Integer.parseInt(line.substring(j + 1).trim());
|
||||
sizes.put(Integer.valueOf(key), Integer.valueOf(value));
|
||||
} catch (NumberFormatException nfe) {
|
||||
Debug.debug(Debug.DEBUG, "LIST error " + nfe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.err.println( "Error getting size LIST from pop3 server.");
|
||||
} else {
|
||||
Debug.debug(Debug.DEBUG, "Error getting LIST from server.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,19 +325,21 @@ public class POP3MailBox {
|
||||
connect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Caller must sync.
|
||||
*/
|
||||
private void clear()
|
||||
{
|
||||
uidlList.clear();
|
||||
uidlToID.clear();
|
||||
sizes.clear();
|
||||
mails = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* connect to pop3 server, login with USER and PASS and try STAT then
|
||||
*
|
||||
* Caller must sync.
|
||||
*/
|
||||
private void connect() {
|
||||
Debug.debug(Debug.DEBUG, "connect()");
|
||||
@@ -367,18 +352,25 @@ public class POP3MailBox {
|
||||
try {
|
||||
socket = new Socket(host, port);
|
||||
} catch (UnknownHostException e) {
|
||||
lastError = e.getMessage();
|
||||
lastError = e.toString();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
lastError = e.getMessage();
|
||||
lastError = e.toString();
|
||||
return;
|
||||
}
|
||||
if (socket != null) {
|
||||
try {
|
||||
if (sendCmd1a("USER " + user)
|
||||
&& sendCmd1a("PASS " + pass)
|
||||
&& sendCmd1a("STAT") ) {
|
||||
|
||||
// pipeline 2 commands
|
||||
lastError = "";
|
||||
List<String> cmds = new ArrayList(2);
|
||||
// TODO APOP (unsupported by postman)
|
||||
cmds.add("USER " + user);
|
||||
cmds.add("PASS " + pass);
|
||||
// We can't pipleline the STAT because we must
|
||||
// enter the transaction state first.
|
||||
//cmds.add("STAT");
|
||||
// wait for 3 +OK lines since the connect generates one
|
||||
if (sendCmds(cmds, 3) && sendCmd1a("STAT")) {
|
||||
int i = lastLine.indexOf(" ", 5);
|
||||
mails =
|
||||
Integer.parseInt(
|
||||
@@ -389,216 +381,236 @@ public class POP3MailBox {
|
||||
connected = true;
|
||||
updateUIDLs();
|
||||
updateSizes();
|
||||
}
|
||||
else {
|
||||
lastError = lastLine;
|
||||
} else {
|
||||
if (lastError.equals(""))
|
||||
lastError = _("Error connecting to server");
|
||||
close();
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException e1) {
|
||||
lastError = "Error getting number of messages: " + e1.getCause();
|
||||
lastError = _("Error opening mailbox") + ": " + e1;
|
||||
}
|
||||
catch (IOException e1) {
|
||||
lastError = "Error while opening mailbox: " + e1.getCause();
|
||||
lastError = _("Error opening mailbox") + ": " + e1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* send command to pop3 server (and expect single line answer)
|
||||
* Response will be in lastLine. Does not read past the first line of the response.
|
||||
* Caller must sync.
|
||||
*
|
||||
* @param cmd command to send
|
||||
* @return true if command was successful (+OK)
|
||||
* @throws IOException
|
||||
*/
|
||||
private boolean sendCmd1a(String cmd) throws IOException {
|
||||
boolean result = false;
|
||||
sendCmd1aNoWait(cmd);
|
||||
socket.getOutputStream().flush();
|
||||
String foo = DataHelper.readLine(socket.getInputStream());
|
||||
// Debug.debug(Debug.DEBUG, "sendCmd1a: read " + read + " bytes");
|
||||
if (foo != null) {
|
||||
lastLine = foo;
|
||||
if (lastLine.startsWith("+OK")) {
|
||||
if (cmd.startsWith("PASS"))
|
||||
cmd = "PASS provided";
|
||||
Debug.debug(Debug.DEBUG, "sendCmd1a: (" + cmd + ") success: \"" + lastLine.trim() + '"');
|
||||
result = true;
|
||||
} else {
|
||||
if (cmd.startsWith("PASS"))
|
||||
cmd = "PASS provided";
|
||||
Debug.debug(Debug.DEBUG, "sendCmd1a: (" + cmd + ") FAIL: \"" + lastLine.trim() + '"');
|
||||
lastError = lastLine;
|
||||
}
|
||||
} else {
|
||||
Debug.debug(Debug.DEBUG, "sendCmd1a: (" + cmd + ") NO RESPONSE");
|
||||
lastError = _("No response from server");
|
||||
throw new IOException(lastError);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send commands to pop3 server all at once (and expect answers).
|
||||
* Sets lastError to the FIRST error.
|
||||
* Caller must sync.
|
||||
*
|
||||
* @param cmd command to send
|
||||
* @param rcvLines lines to receive
|
||||
* @return true if ALL received lines were successful (+OK)
|
||||
* @throws IOException
|
||||
* @since 0.9.13
|
||||
*/
|
||||
private boolean sendCmds(List<String> cmds, int rcvLines) throws IOException {
|
||||
boolean result = true;
|
||||
for (String cmd : cmds) {
|
||||
sendCmd1aNoWait(cmd);
|
||||
}
|
||||
socket.getOutputStream().flush();
|
||||
InputStream in = socket.getInputStream();
|
||||
for (int i = 0; i < rcvLines; i++) {
|
||||
String foo = DataHelper.readLine(in);
|
||||
if (foo == null) {
|
||||
lastError = _("No response from server");
|
||||
throw new IOException(lastError);
|
||||
}
|
||||
//foo = foo.trim(); // readLine() doesn't strip \r
|
||||
if (!foo.startsWith("+OK")) {
|
||||
Debug.debug(Debug.DEBUG, "Fail after " + (i+1) + " of " + rcvLines + " responses: \"" + foo.trim() + '"');
|
||||
if (result)
|
||||
lastError = foo; // actually the first error, for better info to the user
|
||||
result = false;
|
||||
} else {
|
||||
Debug.debug(Debug.DEBUG, "OK after " + (i+1) + " of " + rcvLines + " responses: \"" + foo.trim() + '"');
|
||||
}
|
||||
lastLine = foo;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* send command to pop3 server. Does NOT flush or read or wait.
|
||||
* Caller must sync.
|
||||
*
|
||||
* @param cmd command to send
|
||||
* @throws IOException
|
||||
* @since 0.9.13
|
||||
*/
|
||||
private void sendCmd1aNoWait(String cmd) throws IOException {
|
||||
/*
|
||||
* dont log password
|
||||
*/
|
||||
boolean result = false;
|
||||
String msg = cmd;
|
||||
if (msg.startsWith("PASS"))
|
||||
msg = "PASS provided";
|
||||
Debug.debug(Debug.DEBUG, "sendCmd1a(" + msg + ")");
|
||||
|
||||
cmd += "\r\n";
|
||||
socket.getOutputStream().write(cmd.getBytes());
|
||||
read = socket.getInputStream().read(buffer);
|
||||
// Debug.debug(Debug.DEBUG, "sendCmd1a: read " + read + " bytes");
|
||||
if (read > 0) {
|
||||
lastLine = new String(buffer, 0, read);
|
||||
// Debug.debug( Debug.DEBUG, "sendCmd1a: READBUFFER: '" + lastLine + "'" );
|
||||
if (lastLine.startsWith("+OK")) {
|
||||
result = true;
|
||||
}
|
||||
else {
|
||||
lastError = lastLine;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries twice
|
||||
* Caller must sync.
|
||||
*
|
||||
* @param cmd
|
||||
* @return buffer
|
||||
* @return buffer or null
|
||||
*/
|
||||
private ReadBuffer sendCmdN(String cmd )
|
||||
{
|
||||
return sendCmdN( cmd, DEFAULT_BUFSIZE );
|
||||
}
|
||||
/**
|
||||
* @param cmd
|
||||
* @return buffer
|
||||
*/
|
||||
private ReadBuffer sendCmdN(String cmd, int bufSize )
|
||||
{
|
||||
ReadBuffer result = null;
|
||||
|
||||
synchronized (synchronizer) {
|
||||
|
||||
if (!isConnected())
|
||||
connect();
|
||||
|
||||
try {
|
||||
result = sendCmdNa(cmd, bufSize);
|
||||
return sendCmdNa(cmd);
|
||||
} catch (IOException e) {
|
||||
lastError = e.toString();
|
||||
Debug.debug( Debug.DEBUG, "sendCmdNa throws: " + e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
lastError = e.getMessage();
|
||||
Debug.debug( Debug.DEBUG, "sendCmdNa throws IOException: " + lastError );
|
||||
result = null;
|
||||
}
|
||||
|
||||
if( result == null ) {
|
||||
connect();
|
||||
if (connected) {
|
||||
try {
|
||||
result = sendCmdNa(cmd, bufSize);
|
||||
}
|
||||
catch (IOException e2) {
|
||||
lastError = e2.getMessage();
|
||||
Debug.debug( Debug.DEBUG, "2nd sendCmdNa throws IOException: " + lastError );
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Debug.debug( Debug.DEBUG, "not connected after reconnect" );
|
||||
connect();
|
||||
if (connected) {
|
||||
try {
|
||||
return sendCmdNa(cmd);
|
||||
} catch (IOException e2) {
|
||||
lastError = e2.toString();
|
||||
Debug.debug( Debug.DEBUG, "2nd sendCmdNa throws: " + e2);
|
||||
}
|
||||
} else {
|
||||
Debug.debug( Debug.DEBUG, "not connected after reconnect" );
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param src
|
||||
* @param newSize
|
||||
* @return new array
|
||||
*/
|
||||
private byte[] resizeArray( byte src[], int newSize )
|
||||
{
|
||||
byte dest[] = new byte[newSize];
|
||||
for( int i = 0; i < src.length; i++ )
|
||||
dest[i] = src[i];
|
||||
return dest;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param src
|
||||
* @param srcOffset
|
||||
* @param len
|
||||
* @param dest
|
||||
* @param destOffset
|
||||
*/
|
||||
private void copy( byte[] src, int srcOffset, int len, byte[] dest, int destOffset )
|
||||
{
|
||||
while( len-- > 0 ) {
|
||||
dest[destOffset++] = src[srcOffset++];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param id
|
||||
* @return buffer
|
||||
* No total timeout (result could be large)
|
||||
* Caller must sync.
|
||||
*
|
||||
* @return buffer or null
|
||||
* @throws IOException
|
||||
*/
|
||||
private ReadBuffer sendCmdNa(String cmd, int bufSize ) throws IOException
|
||||
private ReadBuffer sendCmdNa(String cmd) throws IOException
|
||||
{
|
||||
Debug.debug(Debug.DEBUG, "sendCmdNa(" + cmd + ")");
|
||||
|
||||
ReadBuffer readBuffer = null;
|
||||
long timeOut = 60000;
|
||||
byte result[] = new byte[bufSize];
|
||||
int written = 0;
|
||||
|
||||
if (sendCmd1a(cmd)) {
|
||||
int offset = 0;
|
||||
while( offset < read - 1 && buffer[offset] != '\r' && buffer[offset+1] != '\n' )
|
||||
offset++;
|
||||
offset += 2;
|
||||
if( read - offset > result.length )
|
||||
result = resizeArray( result, result.length + result.length );
|
||||
if( read - offset > 0 )
|
||||
copy( buffer, offset, read - offset, result, 0 );
|
||||
written = read - offset;
|
||||
boolean doRead = true;
|
||||
// Debug.debug( Debug.DEBUG, "READBUFFER: '" + new String( result, 0, written ) + "'" );
|
||||
if( written >= 5 && result[ written - 5 ] == '\r' &&
|
||||
result[ written - 4 ] == '\n' &&
|
||||
result[ written - 3 ] == '.' &&
|
||||
result[ written - 2 ] == '\r' &&
|
||||
result[ written - 1 ] == '\n' ) {
|
||||
written -= 3;
|
||||
doRead = false;
|
||||
}
|
||||
InputStream input = socket.getInputStream();
|
||||
long startTime = System.currentTimeMillis();
|
||||
while (doRead) {
|
||||
int len = input.available();
|
||||
if( len == 0 ) {
|
||||
if( System.currentTimeMillis() - startTime > timeOut )
|
||||
throw new IOException( "Timeout while waiting on server response." );
|
||||
try {
|
||||
Thread.sleep( 500 );
|
||||
} catch (InterruptedException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (len > 0) {
|
||||
read = socket.getInputStream().read(buffer, 0, len > buffer.length ? buffer.length : len );
|
||||
// Debug.debug(Debug.DEBUG, "read " + read + " bytes");
|
||||
if( written + read > result.length )
|
||||
result = resizeArray( result, result.length + result.length );
|
||||
copy( buffer, 0, read, result, written );
|
||||
written += read;
|
||||
// Debug.debug( Debug.DEBUG, "READBUFFER: '" + new String( result, 0, written ) + "'" );
|
||||
if( result[ written - 5 ] == '\r' &&
|
||||
result[ written - 4 ] == '\n' &&
|
||||
result[ written - 3 ] == '.' &&
|
||||
result[ written - 2 ] == '\r' &&
|
||||
result[ written - 1 ] == '\n' ) {
|
||||
written -= 3;
|
||||
doRead = false;
|
||||
}
|
||||
len -= read;
|
||||
}
|
||||
}
|
||||
StringBuilder buf = new StringBuilder(512);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
|
||||
while (DataHelper.readLine(input, buf)) {
|
||||
int len = buf.length();
|
||||
if (len == 0)
|
||||
break; // huh? no \r?
|
||||
if (len == 2 && buf.charAt(0) == '.' && buf.charAt(1) == '\r')
|
||||
break;
|
||||
String line;
|
||||
// RFC 1939 sec. 3 de-byte-stuffing
|
||||
if (buf.charAt(0) == '.')
|
||||
line = buf.substring(1);
|
||||
else
|
||||
line = buf.toString();
|
||||
baos.write(DataHelper.getASCII(line));
|
||||
if (buf.charAt(len - 1) != '\r')
|
||||
baos.write((byte) '\n');
|
||||
baos.write((byte) '\n');
|
||||
buf.setLength(0);
|
||||
}
|
||||
readBuffer = new ReadBuffer();
|
||||
readBuffer.content = result;
|
||||
ReadBuffer readBuffer = new ReadBuffer();
|
||||
readBuffer.content = baos.toByteArray();
|
||||
readBuffer.offset = 0;
|
||||
readBuffer.length = written;
|
||||
}
|
||||
else {
|
||||
readBuffer.length = baos.size();
|
||||
return readBuffer;
|
||||
} else {
|
||||
Debug.debug( Debug.DEBUG, "sendCmd1a returned false" );
|
||||
return null;
|
||||
}
|
||||
|
||||
return readBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like sendCmdNa but returns a list of strings, one per line.
|
||||
* Strings will have trailing \r but not \n.
|
||||
* Total timeout 2 minutes.
|
||||
* Caller must sync.
|
||||
*
|
||||
* @return the lines or null on error
|
||||
* @throws IOException on timeout
|
||||
* @since 0.9.13
|
||||
*/
|
||||
private List<String> sendCmdNl(String cmd) throws IOException
|
||||
{
|
||||
if (sendCmd1a(cmd)) {
|
||||
List<String> rv = new ArrayList<String>(16);
|
||||
long timeOut = 120*1000;
|
||||
InputStream input = socket.getInputStream();
|
||||
long startTime = System.currentTimeMillis();
|
||||
StringBuilder buf = new StringBuilder(512);
|
||||
while (DataHelper.readLine(input, buf)) {
|
||||
int len = buf.length();
|
||||
if (len == 0)
|
||||
break; // huh? no \r?
|
||||
if (len == 2 && buf.charAt(0) == '.' && buf.charAt(1) == '\r')
|
||||
break;
|
||||
if( System.currentTimeMillis() - startTime > timeOut )
|
||||
throw new IOException( "Timeout while waiting on server response." );
|
||||
String line;
|
||||
// RFC 1939 sec. 3 de-byte-stuffing
|
||||
if (buf.charAt(0) == '.')
|
||||
line = buf.substring(1);
|
||||
else
|
||||
line = buf.toString();
|
||||
rv.add(line);
|
||||
buf.setLength(0);
|
||||
}
|
||||
return rv;
|
||||
} else {
|
||||
Debug.debug( Debug.DEBUG, "sendCmd1a returned false" );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning - forces a connection.
|
||||
*
|
||||
* @return The amount of e-mails available.
|
||||
* @deprecated unused
|
||||
*/
|
||||
public int getNumMails() {
|
||||
synchronized( synchronizer ) {
|
||||
@@ -615,24 +627,40 @@ public class POP3MailBox {
|
||||
* @return The most recent error message.
|
||||
*/
|
||||
public String lastError() {
|
||||
Debug.debug(Debug.DEBUG, "lastError()");
|
||||
return this.lastError;
|
||||
//Debug.debug(Debug.DEBUG, "lastError()");
|
||||
// Hide the "-ERR" from the user
|
||||
String e = lastError;
|
||||
if (e.startsWith("-ERR ") && e.length() > 5)
|
||||
e = e.substring(5);
|
||||
// translate this common error
|
||||
if (e.trim().equals("Login failed."))
|
||||
e = _("Login failed");
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Close without waiting for response
|
||||
*/
|
||||
public void close() {
|
||||
close(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close and optionally waiting for response
|
||||
* @since 0.9.13
|
||||
*/
|
||||
private void close(boolean shouldWait) {
|
||||
synchronized( synchronizer ) {
|
||||
Debug.debug(Debug.DEBUG, "close()");
|
||||
if (socket != null && socket.isConnected()) {
|
||||
try {
|
||||
sendCmd1a("QUIT");
|
||||
if (shouldWait)
|
||||
sendCmd1a("QUIT");
|
||||
else
|
||||
sendCmd1aNoWait("QUIT");
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
System.err.println(
|
||||
"Error while closing connection: " + e.getCause());
|
||||
Debug.debug( Debug.DEBUG, "Error while closing connection: " + e);
|
||||
}
|
||||
}
|
||||
socket = null;
|
||||
@@ -642,9 +670,10 @@ public class POP3MailBox {
|
||||
|
||||
/**
|
||||
* returns number of message with given UIDL
|
||||
* Caller must sync.
|
||||
*
|
||||
* @param uidl
|
||||
* @return Message number.
|
||||
* @return Message number or -1
|
||||
*/
|
||||
private int getIDfromUIDL( String uidl )
|
||||
{
|
||||
@@ -655,27 +684,41 @@ public class POP3MailBox {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Unused
|
||||
* @param id
|
||||
* @return UIDL.
|
||||
* @return UIDL or null
|
||||
*/
|
||||
/****
|
||||
public String getUIDLfromID( int id )
|
||||
{
|
||||
return uidlList.get( id );
|
||||
synchronized( synchronizer ) {
|
||||
try {
|
||||
return uidlList.get( id );
|
||||
} catch (IndexOutOfBoundsException ioobe) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
*
|
||||
* @return A list of the available UIDLs.
|
||||
* @return A new array of the available UIDLs. No particular order.
|
||||
*/
|
||||
public String[] getUIDLs()
|
||||
{
|
||||
return uidlList.toArray(new String[uidlList.size()]);
|
||||
synchronized( synchronizer ) {
|
||||
return uidlToID.keySet().toArray(new String[uidlToID.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param args
|
||||
*/
|
||||
/****
|
||||
public static void main( String[] args )
|
||||
{
|
||||
Debug.setLevel( Debug.DEBUG );
|
||||
@@ -683,14 +726,17 @@ public class POP3MailBox {
|
||||
ReadBuffer readBuffer = mailbox.sendCmdN( "LIST" );
|
||||
System.out.println( "list='" + readBuffer + "'" );
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
*
|
||||
* Close and reconnect. Takes a while.
|
||||
*/
|
||||
public void performDelete()
|
||||
{
|
||||
close();
|
||||
connect();
|
||||
synchronized( synchronizer ) {
|
||||
close(true);
|
||||
connect();
|
||||
}
|
||||
}
|
||||
|
||||
/** translate */
|
||||
|
||||
@@ -57,10 +57,21 @@ public class SMTPClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for response
|
||||
* @param cmd may be null
|
||||
* @return result code or 0 for failure
|
||||
*/
|
||||
private int sendCmd( String cmd )
|
||||
private int sendCmd( String cmd ) {
|
||||
return sendCmd(cmd, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cmd may be null
|
||||
* @param shouldWait if false, don't wait for response, and return 100
|
||||
* @return result code or 0 for failure
|
||||
* @since 0.9.13
|
||||
*/
|
||||
private int sendCmd(String cmd, boolean shouldWait)
|
||||
{
|
||||
Debug.debug( Debug.DEBUG, "SMTP sendCmd(" + cmd +")" );
|
||||
|
||||
@@ -78,6 +89,9 @@ public class SMTPClient {
|
||||
cmd += "\r\n";
|
||||
out.write( cmd.getBytes() );
|
||||
}
|
||||
if (!shouldWait)
|
||||
return 100;
|
||||
out.flush();
|
||||
String str = "";
|
||||
boolean doContinue = true;
|
||||
while( doContinue ) {
|
||||
@@ -170,7 +184,7 @@ public class SMTPClient {
|
||||
for( int i = 0; i < lines.length; i++ )
|
||||
error += lines[i] + "<br>";
|
||||
}
|
||||
sendCmd( "QUIT" );
|
||||
sendCmd("QUIT", false );
|
||||
if( socket != null ) {
|
||||
try {
|
||||
socket.close();
|
||||
|
||||
@@ -75,9 +75,11 @@ p.mailbody {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*
|
||||
.mailbody {
|
||||
display: block !important;
|
||||
}
|
||||
*/
|
||||
|
||||
a {
|
||||
color:#327BBF;
|
||||
@@ -221,3 +223,7 @@ input.send, input.setpagesize {
|
||||
padding: 2px 3px 2px 24px;
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
input[type=text], input[type=password] {
|
||||
padding: 1px 4px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user