forked from I2P_Developers/i2p.i2p
388 lines
11 KiB
Java
388 lines
11 KiB
Java
/*
|
|
* Created on Nov 9, 2004
|
|
*
|
|
* This file is part of susimail project, see http://susi.i2p/
|
|
*
|
|
* Copyright (C) 2004-2005 <susi23@mail.i2p>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* $Revision: 1.2 $
|
|
*/
|
|
package i2p.susi.webmail;
|
|
|
|
import i2p.susi.util.Config;
|
|
import i2p.susi.debug.Debug;
|
|
import i2p.susi.util.ReadBuffer;
|
|
import i2p.susi.webmail.encoding.DecodingException;
|
|
import i2p.susi.webmail.encoding.Encoding;
|
|
import i2p.susi.webmail.encoding.EncodingFactory;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.text.DateFormat;
|
|
import java.text.ParseException;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.Date;
|
|
import java.util.Locale;
|
|
import java.util.TimeZone;
|
|
|
|
import net.i2p.I2PAppContext;
|
|
import net.i2p.data.DataHelper;
|
|
|
|
/**
|
|
* data structure to hold a single message, mostly used with folder view and sorting
|
|
*
|
|
* @author susi
|
|
*/
|
|
class Mail {
|
|
|
|
private static final String DATEFORMAT = "date.format";
|
|
|
|
private static final String unknown = "unknown";
|
|
|
|
private int size;
|
|
public String sender, // as received, trimmed only, not HTML escaped
|
|
reply, subject, dateString,
|
|
//formattedSender, // address only, enclosed with <>, not HTML escaped
|
|
formattedSubject,
|
|
formattedDate, // US Locale, UTC
|
|
localFormattedDate, // Current Locale, local time zone
|
|
shortSender, // Either name or address but not both, HTML escaped, double-quotes removed, truncated with hellip
|
|
shortSubject, // HTML escaped, truncated with hellip
|
|
quotedDate; // Current Locale, local time zone, longer format
|
|
public final String uidl;
|
|
public Date date;
|
|
private ReadBuffer header, body;
|
|
private MailPart part;
|
|
String[] to, cc; // addresses only, enclosed by <>
|
|
private boolean isNew, isSpam;
|
|
public String contentType;
|
|
|
|
public String error;
|
|
|
|
public boolean markForDeletion;
|
|
|
|
public Mail(String uidl) {
|
|
this.uidl = uidl;
|
|
//formattedSender = unknown;
|
|
formattedSubject = unknown;
|
|
formattedDate = unknown;
|
|
localFormattedDate = unknown;
|
|
shortSender = unknown;
|
|
shortSubject = unknown;
|
|
quotedDate = unknown;
|
|
error = "";
|
|
}
|
|
|
|
/**
|
|
* This may or may not contain the body also.
|
|
* @return may be null
|
|
*/
|
|
public synchronized ReadBuffer getHeader() {
|
|
return header;
|
|
}
|
|
|
|
public synchronized void setHeader(ReadBuffer rb) {
|
|
if (rb == null)
|
|
return;
|
|
header = rb;
|
|
parseHeaders();
|
|
}
|
|
|
|
public synchronized boolean hasHeader() {
|
|
return header != null;
|
|
}
|
|
|
|
/**
|
|
* This contains the header also.
|
|
* @return may be null
|
|
*/
|
|
public synchronized ReadBuffer getBody() {
|
|
return body;
|
|
}
|
|
|
|
public synchronized void setBody(ReadBuffer rb) {
|
|
if (rb == null)
|
|
return;
|
|
if (header == null)
|
|
setHeader(rb);
|
|
body = rb;
|
|
size = rb.length;
|
|
try {
|
|
part = new MailPart(rb);
|
|
} catch (DecodingException de) {
|
|
Debug.debug(Debug.ERROR, "Decode error: " + de);
|
|
} catch (Exception e) {
|
|
Debug.debug(Debug.ERROR, "Parse error: " + e);
|
|
}
|
|
}
|
|
|
|
public synchronized boolean hasBody() {
|
|
return body != null;
|
|
}
|
|
|
|
public synchronized MailPart getPart() {
|
|
return part;
|
|
}
|
|
|
|
public synchronized boolean hasPart() {
|
|
return part != null;
|
|
}
|
|
|
|
public synchronized int getSize() {
|
|
return size;
|
|
}
|
|
|
|
public synchronized void setSize(int size) {
|
|
if (body != null)
|
|
return;
|
|
this.size = size;
|
|
}
|
|
|
|
public synchronized boolean isSpam() {
|
|
return isSpam;
|
|
}
|
|
|
|
public synchronized boolean isNew() {
|
|
return isNew;
|
|
}
|
|
|
|
public synchronized void setNew(boolean isNew) {
|
|
this.isNew = isNew;
|
|
}
|
|
|
|
public synchronized boolean hasAttachment() {
|
|
// this isn't right but good enough to start
|
|
// if part != null query parts instead?
|
|
return contentType != null &&
|
|
!contentType.contains("text/plain") &&
|
|
!contentType.contains("multipart/alternative") &&
|
|
!contentType.contains("multipart/signed");
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param address E-mail address to be validated
|
|
* @return Is the e-mail address valid?
|
|
*/
|
|
public static boolean validateAddress( String address )
|
|
{
|
|
if( address == null || address.length() == 0 )
|
|
return false;
|
|
|
|
address = address.trim();
|
|
|
|
if( address.indexOf( "\n" ) != -1 ||
|
|
address.indexOf( "\r" ) != -1 )
|
|
return false;
|
|
|
|
String[] tokens = DataHelper.split(address, "[ \t]+");
|
|
|
|
int addresses = 0;
|
|
|
|
for( int i = 0; i < tokens.length; i++ ) {
|
|
if( tokens[i].matches( "^[^@< \t]+@[^> \t]+$" ) ||
|
|
tokens[i].matches( "^<[^@< \t]+@[^> \t]+>$" ) )
|
|
addresses++;
|
|
}
|
|
return addresses == 1;
|
|
}
|
|
|
|
/**
|
|
* Returns the first email address portion, enclosed by <>
|
|
* @param address
|
|
*/
|
|
public static String getAddress(String address )
|
|
{
|
|
String[] tokens = DataHelper.split(address, "[ \t]+");
|
|
|
|
for( int i = 0; i < tokens.length; i++ ) {
|
|
if( tokens[i].matches( "^[^@< \t]+@[^> \t]+$" ) )
|
|
return "<" + tokens[i] + ">";
|
|
if( tokens[i].matches( "^<[^@< \t]+@[^> \t]+>$" ) )
|
|
return tokens[i];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* A little misnamed. Adds all addresses from the comma-separated
|
|
* line in text to the recipients list.
|
|
*
|
|
* @param text comma-separated
|
|
* @param recipients out param
|
|
* @param ok will be returned
|
|
* @return true if ALL e-mail addresses are valid AND the in parameter was true
|
|
*/
|
|
public static boolean getRecipientsFromList( ArrayList<String> recipients, String text, boolean ok )
|
|
{
|
|
if( text != null && text.length() > 0 ) {
|
|
String[] ccs = DataHelper.split(text, ",");
|
|
for( int i = 0; i < ccs.length; i++ ) {
|
|
String recipient = ccs[i].trim();
|
|
if( validateAddress( recipient ) ) {
|
|
String str = getAddress( recipient );
|
|
if( str != null && str.length() > 0 ) {
|
|
recipients.add( str );
|
|
}
|
|
else {
|
|
ok = false;
|
|
}
|
|
}
|
|
else {
|
|
ok = false;
|
|
}
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
/**
|
|
* Adds all items from the list
|
|
* to the builder, separated by tabs.
|
|
*
|
|
* @param buf out param
|
|
* @param prefix prepended to the addresses
|
|
*/
|
|
public static void appendRecipients( StringBuilder buf, ArrayList<String> recipients, String prefix )
|
|
{
|
|
for( String recipient : recipients ) {
|
|
buf.append( prefix );
|
|
prefix ="\t";
|
|
buf.append( recipient );
|
|
buf.append( "\r\n" );
|
|
}
|
|
}
|
|
|
|
private void parseHeaders()
|
|
{
|
|
DateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
|
DateFormat localDateFormatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
|
|
DateFormat longLocalDateFormatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
|
|
// the router sets the JVM time zone to UTC but saves the original here so we can get it
|
|
String systemTimeZone = I2PAppContext.getGlobalContext().getProperty("i2p.systemTimeZone");
|
|
if (systemTimeZone != null) {
|
|
TimeZone tz = TimeZone.getTimeZone(systemTimeZone);
|
|
localDateFormatter.setTimeZone(tz);
|
|
longLocalDateFormatter.setTimeZone(tz);
|
|
}
|
|
DateFormat mailDateFormatter = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH );
|
|
|
|
error = "";
|
|
if( header != null ) {
|
|
|
|
boolean ok = true;
|
|
|
|
Encoding html = EncodingFactory.getEncoding( "HTML" );
|
|
|
|
if( html == null ) {
|
|
error += "HTML encoder not found.\n";
|
|
ok = false;
|
|
}
|
|
|
|
Encoding hl = EncodingFactory.getEncoding( "HEADERLINE" );
|
|
|
|
if( hl == null ) {
|
|
error += "Header line encoder not found.\n";
|
|
ok = false;
|
|
}
|
|
|
|
if( ok ) {
|
|
|
|
try {
|
|
ReadBuffer decoded = hl.decode( header );
|
|
BufferedReader reader = new BufferedReader( new InputStreamReader( new ByteArrayInputStream( decoded.content, decoded.offset, decoded.length ), "UTF-8" ) );
|
|
String line;
|
|
while( ( line = reader.readLine() ) != null ) {
|
|
if( line.length() == 0 )
|
|
break;
|
|
|
|
if( line.startsWith( "From:" ) ) {
|
|
sender = line.substring( 5 ).trim();
|
|
//formattedSender = getAddress( sender );
|
|
shortSender = sender.replace("\"", "").trim();
|
|
int lt = shortSender.indexOf('<');
|
|
if (lt > 0)
|
|
shortSender = shortSender.substring(0, lt).trim();
|
|
else if (lt < 0 && shortSender.contains("@"))
|
|
shortSender = '<' + shortSender + '>'; // add missing <> (but thunderbird doesn't...)
|
|
boolean trim = shortSender.length() > 40;
|
|
if (trim)
|
|
shortSender = shortSender.substring( 0, 37 ).trim();
|
|
shortSender = html.encode( shortSender );
|
|
if (trim)
|
|
shortSender += "…"; // must be after html encode
|
|
}
|
|
else if( line.startsWith( "Date:" ) ) {
|
|
dateString = line.substring( 5 ).trim();
|
|
try {
|
|
date = mailDateFormatter.parse( dateString );
|
|
formattedDate = dateFormatter.format( date );
|
|
localFormattedDate = localDateFormatter.format( date );
|
|
//quotedDate = html.encode( dateString );
|
|
quotedDate = longLocalDateFormatter.format(date);
|
|
}
|
|
catch (ParseException e) {
|
|
date = null;
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
else if( line.startsWith( "Subject:" ) ) {
|
|
subject = line.substring( 8 ).trim();
|
|
formattedSubject = subject;
|
|
shortSubject = formattedSubject;
|
|
boolean trim = formattedSubject.length() > 60;
|
|
if (trim)
|
|
shortSubject = formattedSubject.substring( 0, 57 ).trim();
|
|
shortSubject = html.encode( shortSubject );
|
|
if (trim)
|
|
shortSubject += "…"; // must be after html encode
|
|
}
|
|
else if( line.toLowerCase(Locale.US).startsWith( "reply-to:" ) ) {
|
|
reply = getAddress( line.substring( 9 ).trim() );
|
|
}
|
|
else if( line.startsWith( "To:" ) ) {
|
|
ArrayList<String> list = new ArrayList<String>();
|
|
getRecipientsFromList( list, line.substring( 3 ).trim(), true );
|
|
to = list.toArray(new String[list.size()]);
|
|
}
|
|
else if( line.startsWith( "Cc:" ) ) {
|
|
ArrayList<String> list = new ArrayList<String>();
|
|
getRecipientsFromList( list, line.substring( 3 ).trim(), true );
|
|
cc = list.toArray(new String[list.size()]);
|
|
} else if(line.equals( "X-Spam-Flag: YES" )) {
|
|
// TODO trust.spam.headers config
|
|
isSpam = true;
|
|
} else if(line.toLowerCase(Locale.US).startsWith("content-type:" )) {
|
|
// this is duplicated in MailPart but
|
|
// we want to know if we have attachments, even if
|
|
// we haven't fetched the body
|
|
contentType = line.substring(13).trim();
|
|
}
|
|
}
|
|
}
|
|
catch( Exception e ) {
|
|
error += "Error parsing mail header: " + e.getClass().getName() + '\n';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|