@ -26,12 +26,15 @@ import android.webkit.MimeTypeMap;
import org.jsoup.Jsoup ;
import org.jsoup.Jsoup ;
import org.jsoup.nodes.Element ;
import org.jsoup.nodes.Element ;
import java.io.BufferedOutputStream ;
import java.io.BufferedReader ;
import java.io.BufferedReader ;
import java.io.ByteArrayOutputStream ;
import java.io.ByteArrayOutputStream ;
import java.io.File ;
import java.io.File ;
import java.io.FileOutputStream ;
import java.io.FileReader ;
import java.io.FileReader ;
import java.io.IOException ;
import java.io.IOException ;
import java.io.InputStream ;
import java.io.InputStream ;
import java.io.OutputStream ;
import java.io.UnsupportedEncodingException ;
import java.io.UnsupportedEncodingException ;
import java.nio.charset.Charset ;
import java.nio.charset.Charset ;
import java.util.ArrayList ;
import java.util.ArrayList ;
@ -65,6 +68,8 @@ public class MessageHelper {
private final static int FETCH_SIZE = 1024 * 1024 ; // bytes, default 16K
private final static int FETCH_SIZE = 1024 * 1024 ; // bytes, default 16K
private final static int POOL_TIMEOUT = 3 * 60 * 1000 ; // milliseconds, default 45 sec
private final static int POOL_TIMEOUT = 3 * 60 * 1000 ; // milliseconds, default 45 sec
static final int ATTACHMENT_BUFFER_SIZE = 8192 ; // bytes
static Properties getSessionProperties ( int auth_type , String realm , boolean insecure ) {
static Properties getSessionProperties ( int auth_type , String realm , boolean insecure ) {
Properties props = new Properties ( ) ;
Properties props = new Properties ( ) ;
@ -526,145 +531,172 @@ public class MessageHelper {
return address . getAddress ( ) ;
return address . getAddress ( ) ;
}
}
String getHtml ( ) throws MessagingException , IOException {
class MessageParts {
return getHtml ( imessage ) ;
private Part plain = null ;
}
private Part html = null ;
private List < AttachmentPart > attachments = new ArrayList < > ( ) ;
private static String readStream ( InputStream is , String charset ) throws IOException {
String getHtml ( ) throws MessagingException {
ByteArrayOutputStream os = new ByteArrayOutputStream ( ) ;
if ( plain = = null & & html = = null )
byte [ ] buffer = new byte [ 4096 ] ;
return null ;
for ( int len = is . read ( buffer ) ; len ! = - 1 ; len = is . read ( buffer ) )
os . write ( buffer , 0 , len ) ;
return new String ( os . toByteArray ( ) , charset ) ;
}
private static String getHtml ( Part part ) throws MessagingException , IOException {
String result ;
String disposition ;
boolean text = false ;
try {
Part part = ( html = = null ? plain : html ) ;
disposition = part . getDisposition ( ) ;
} catch ( MessagingException ex ) {
Log . w ( ex ) ;
disposition = null ;
}
if ( ! Part . ATTACHMENT . equalsIgnoreCase ( disposition ) & &
( part . isMimeType ( "text/plain" ) | | part . isMimeType ( "text/html" ) ) ) {
String s ;
try {
try {
Object content = part . getContent ( ) ;
Object content = part . getContent ( ) ;
try {
if ( content instanceof String )
if ( content instanceof String )
s = ( String ) content ;
result = ( String ) content ;
else if ( content instanceof InputStream )
else if ( content instanceof InputStream )
// Typically com.sun.mail.util.QPDecoderStream
// Typically com.sun.mail.util.QPDecoderStream
s = readStream ( ( InputStream ) content , "UTF-8" ) ;
re sult = readStream ( ( InputStream ) content , "UTF-8" ) ;
else
else
s = content . toString ( ) ;
result = content . toString ( ) ;
} catch ( UnsupportedEncodingException ex ) {
} catch ( Throwable ex ) {
// x-binaryenc
// https://javaee.github.io/javamail/FAQ#unsupen
Log . w ( "Unsupported encoding: " + part . getContentType ( ) ) ;
return readStream ( part . getInputStream ( ) , "US-ASCII" ) ;
}
} catch ( IOException ex ) {
// IOException; Unknown encoding: none
Log . w ( ex ) ;
Log . w ( ex ) ;
return "<pre>" + ex + "<br />" + android . util . Log . getStackTraceString ( ex ) + "</pre>" ;
text = true ;
result = ex + "\n" + android . util . Log . getStackTraceString ( ex ) ;
}
}
if ( part . isMimeType ( "text/plain" ) )
if ( part . isMimeType ( "text/plain" ) | | text )
s = "<pre>" + s. replaceAll ( "\\r?\\n" , "<br />" ) + "</pre>" ;
result = "<pre>" + result . replaceAll ( "\\r?\\n" , "<br />" ) + "</pre>" ;
return s ;
if ( part . isMimeType ( "text/plain" ) ) {
Log . i ( "Plain text" ) ;
return result ;
} else {
Log . i ( "HTML text" ) ;
return result ;
}
}
}
if ( part . isMimeType ( "multipart/alternative" ) ) {
List < EntityAttachment > getAttachments ( ) throws MessagingException {
String text = null ;
List < EntityAttachment > result = new ArrayList < > ( ) ;
try {
Multipart mp = ( Multipart ) part . getContent ( ) ;
for ( AttachmentPart apart : attachments ) {
for ( int i = 0 ; i < mp . getCount ( ) ; i + + ) {
ContentType ct = new ContentType ( apart . part . getContentType ( ) ) ;
Part bp = mp . getBodyPart ( i ) ;
String [ ] cid = apart . part . getHeader ( "Content-ID" ) ;
if ( bp . isMimeType ( "text/plain" ) ) {
if ( text = = null )
EntityAttachment attachment = new EntityAttachment ( ) ;
text = getHtml ( bp ) ;
attachment . name = apart . filename ;
} else if ( bp . isMimeType ( "text/html" ) ) {
attachment . type = ct . getBaseType ( ) . toLowerCase ( ) ;
String s = getHtml ( bp ) ;
attachment . disposition = apart . disposition ;
if ( s ! = null )
attachment . size = ( long ) apart . part . getSize ( ) ;
return s ;
attachment . cid = ( cid = = null | | cid . length = = 0 ? null : cid [ 0 ] ) ;
} else
attachment . encryption = ( apart . pgp ? EntityAttachment . PGP_MESSAGE : null ) ;
return getHtml ( bp ) ;
if ( "text/calendar" . equalsIgnoreCase ( attachment . type ) & & TextUtils . isEmpty ( attachment . name ) )
attachment . name = "invite.ics" ;
// Try to guess a better content type
// Sometimes PDF files are sent using the wrong type
if ( "application/octet-stream" . equalsIgnoreCase ( attachment . type ) ) {
String extension = Helper . getExtension ( attachment . name ) ;
if ( extension ! = null ) {
String type = MimeTypeMap . getSingleton ( ) . getMimeTypeFromExtension ( extension . toLowerCase ( ) ) ;
if ( type ! = null ) {
Log . w ( "Guessing file=" + attachment . name + " type=" + type ) ;
attachment . type = type ;
}
}
} catch ( ParseException ex ) {
// ParseException: In parameter list boundary="...">, expected parameter name, got ";"
Log . w ( ex ) ;
text = "<pre>" + ex + "<br />" + android . util . Log . getStackTraceString ( ex ) + "</pre>" ;
}
}
return text ;
}
}
if ( part . isMimeType ( "multipart/*" ) )
if ( attachment . size < 0 )
try {
attachment . size = null ;
Multipart mp = ( Multipart ) part . getContent ( ) ;
for ( int i = 0 ; i < mp . getCount ( ) ; i + + ) {
result . add ( attachment ) ;
String s = getHtml ( mp . getBodyPart ( i ) ) ;
}
if ( s ! = null )
return s ;
// Fix duplicate CIDs
for ( int i = 0 ; i < result . size ( ) ; i + + ) {
String cid = result . get ( i ) . cid ;
if ( cid ! = null )
for ( int j = i + 1 ; j < result . size ( ) ; j + + ) {
EntityAttachment a = result . get ( j ) ;
if ( cid . equals ( a . cid ) )
a . cid = null ;
}
}
} catch ( ParseException ex ) {
Log . w ( ex ) ;
return "<pre>" + ex + "<br />" + android . util . Log . getStackTraceString ( ex ) + "</pre>" ;
}
}
return null ;
return result ;
}
}
public List < EntityAttachment > getAttachments ( ) throws IOException , MessagingException {
void downloadAttachment ( Context context , DB db , long id , int sequence ) throws MessagingException , IOException {
List < EntityAttachment > result = new ArrayList < > ( ) ;
// Attachments of drafts might not have been uploaded yet
if ( sequence > attachments . size ( ) ) {
Log . w ( "Attachment unavailable sequence=" + sequence + " size=" + attachments . size ( ) ) ;
return ;
}
// Get data
AttachmentPart apart = attachments . get ( sequence - 1 ) ;
long total = apart . part . getSize ( ) ;
File file = EntityAttachment . getFile ( context , id ) ;
// Download attachment
OutputStream os = null ;
try {
try {
Object content = imessage . getContent ( ) ;
db . attachment ( ) . setProgress ( id , null ) ;
if ( content instanceof String )
return result ;
if ( content instanceof Multipart ) {
InputStream is = apart . part . getInputStream ( ) ;
boolean pgp = false ;
os = new BufferedOutputStream ( new FileOutputStream ( file ) ) ;
Multipart multipart = ( Multipart ) content ;
for ( int i = 0 ; i < multipart . getCount ( ) ; i + + ) {
long size = 0 ;
BodyPart part = multipart . getBodyPart ( i ) ;
byte [ ] buffer = new byte [ ATTACHMENT_BUFFER_SIZE ] ;
result . addAll ( getAttachments ( part , pgp ) ) ;
for ( int len = is . read ( buffer ) ; len ! = - 1 ; len = is . read ( buffer ) ) {
ContentType ct = new ContentType ( part . getContentType ( ) ) ;
size + = len ;
if ( "application/pgp-encrypted" . equals ( ct . getBaseType ( ) . toLowerCase ( ) ) )
os . write ( buffer , 0 , len ) ;
pgp = true ;
}
// Update progress
if ( total > 0 )
db . attachment ( ) . setProgress ( id , ( int ) ( size * 100 / total ) ) ;
}
}
// Store attachment data
db . attachment ( ) . setDownloaded ( id , size ) ;
Log . i ( "Downloaded attachment size=" + size ) ;
} catch ( IOException ex ) {
} catch ( IOException ex ) {
if ( ex . getCause ( ) instanceof MessagingException )
// Reset progress on failure
Log . w ( ex ) ;
db . attachment ( ) . setProgress ( id , null ) ;
else
throw ex ;
throw ex ;
} catch ( ParseException ex ) {
} finally {
Log . w ( ex ) ;
if ( os ! = null )
os . close ( ) ;
}
}
}
}
return result ;
private class AttachmentPart {
String disposition ;
String filename ;
boolean pgp ;
Part part ;
}
}
private static List < EntityAttachment > getAttachments ( BodyPart part , boolean pgp ) throws
MessageParts getMessageParts ( ) throws IOException , MessagingException {
IOException , MessagingException {
MessageParts parts = new MessageParts ( ) ;
List < EntityAttachment > result = new ArrayList < > ( ) ;
getMessageParts ( imessage , parts , false ) ; // Can throw ParseException
return parts ;
}
Object content ;
private void getMessageParts ( Part part , MessageParts parts , boolean pgp ) throws MessagingException , IOException {
if ( part . isMimeType ( "multipart/*" ) ) {
Multipart multipart = ( Multipart ) part . getContent ( ) ;
for ( int i = 0 ; i < multipart . getCount ( ) ; i + + )
try {
try {
content = part . getContent ( ) ;
Part cpart = multipart . getBodyPart ( i ) ;
} catch ( UnsupportedEncodingException ex ) {
getMessageParts ( cpart , parts , pgp ) ;
Log . w ( "attachment content type=" + part . getContentType ( ) ) ;
ContentType ct = new ContentType ( cpart . getContentType ( ) ) ;
content = part . getInputStream ( ) ;
if ( "application/pgp-encrypted" . equals ( ct . getBaseType ( ) . toLowerCase ( ) ) )
pgp = true ;
} catch ( ParseException ex ) {
} catch ( ParseException ex ) {
// Nested body: try to continue
// ParseException: In parameter list boundary="...">, expected parameter name, got ";"
Log . w ( ex ) ;
Log . w ( ex ) ;
content = null ;
}
}
} else {
if ( content instanceof InputStream | | content instanceof String ) {
// https://www.iana.org/assignments/cont-disp/cont-disp.xhtml
// https://www.iana.org/assignments/cont-disp/cont-disp.xhtml
String disposition ;
String disposition ;
try {
try {
@ -682,54 +714,35 @@ public class MessageHelper {
filename = null ;
filename = null ;
}
}
if ( Part . ATTACHMENT . equalsIgnoreCase ( disposition ) | |
Log . i ( "Part" +
! ( part . isMimeType ( "text/plain" ) | | part . isMimeType ( "text/html" ) ) | |
" disposition=" + disposition +
! TextUtils . isEmpty ( filename ) ) {
" filename=" + filename +
ContentType ct = new ContentType ( part . getContentType ( ) ) ;
" content type=" + part . getContentType ( ) ) ;
String [ ] cid = part . getHeader ( "Content-ID" ) ;
EntityAttachment attachment = new EntityAttachment ( ) ;
if ( ! Part . ATTACHMENT . equalsIgnoreCase ( disposition ) & &
attachment . name = filename ;
( ( parts . plain = = null & & part . isMimeType ( "text/plain" ) ) | |
attachment . type = ct . getBaseType ( ) . toLowerCase ( ) ;
( parts . html = = null & & part . isMimeType ( "text/html" ) ) ) ) {
attachment . disposition = disposition ;
if ( part . isMimeType ( "text/plain" ) )
attachment . size = ( long ) part . getSize ( ) ;
parts . plain = part ;
attachment . cid = ( cid = = null | | cid . length = = 0 ? null : cid [ 0 ] ) ;
else
attachment . encryption = ( pgp ? EntityAttachment . PGP_MESSAGE : null ) ;
parts . html = part ;
attachment . part = part ;
} else {
AttachmentPart apart = new AttachmentPart ( ) ;
if ( TextUtils . isEmpty ( attachment . name ) & & "text/calendar" . equals ( attachment . type ) )
apart . disposition = disposition ;
attachment . name = "invite.ics" ;
apart . filename = filename ;
apart . pgp = pgp ;
// Try to guess a better content type
apart . part = part ;
// Sometimes PDF files are sent using the wrong type
parts . attachments . add ( apart ) ;
if ( "application/octet-stream" . equals ( attachment . type ) ) {
String extension = Helper . getExtension ( attachment . name ) ;
if ( extension ! = null ) {
String type = MimeTypeMap . getSingleton ( ) . getMimeTypeFromExtension ( extension . toLowerCase ( ) ) ;
if ( type ! = null ) {
Log . w ( "Guessing file=" + attachment . name + " type=" + type ) ;
attachment . type = type ;
}
}
}
if ( attachment . size < 0 )
attachment . size = null ;
result . add ( attachment ) ;
}
}
} else if ( content instanceof Multipart ) {
Multipart multipart = ( Multipart ) content ;
for ( int i = 0 ; i < multipart . getCount ( ) ; i + + ) {
BodyPart cpart = multipart . getBodyPart ( i ) ;
result . addAll ( getAttachments ( cpart , pgp ) ) ;
ContentType ct = new ContentType ( cpart . getContentType ( ) ) ;
if ( "application/pgp-encrypted" . equals ( ct . getBaseType ( ) . toLowerCase ( ) ) )
pgp = true ;
}
}
}
}
return result ;
private static String readStream ( InputStream is , String charset ) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream ( ) ;
byte [ ] buffer = new byte [ 4096 ] ;
for ( int len = is . read ( buffer ) ; len ! = - 1 ; len = is . read ( buffer ) )
os . write ( buffer , 0 , len ) ;
return new String ( os . toByteArray ( ) , charset ) ;
}
}
static boolean equal ( Address [ ] a1 , Address [ ] a2 ) {
static boolean equal ( Address [ ] a1 , Address [ ] a2 ) {