2 openisis - an open implementation of the CDS/ISIS database
3 Version 0.8.x (patchlevel see file Version)
4 Copyright (C) 2001-2003 by Erik Grziwotz, erik@openisis.org
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 see README for more information
28 import javax.activation.*;
30 import javax.mail.internet.*;
34 This class contains a collection of javax.mail based E-Mail utilities.
37 $Id: Mail.java,v 1.4 2003/04/08 00:20:53 kripke Exp $
38 @version $Revision: 1.4 $
39 @author $Author: kripke $
41 public abstract class Mail {
43 public static final String EMPTY = "";
45 public static final String MESSAGEID = "message-id";
46 public static final String DATE = "date";
47 public static final String TO = "to";
48 public static final String CC = "cc";
49 public static final String BCC = "bcc";
50 public static final String FROM = "from";
51 public static final String SENDER = "sender";
52 public static final String REPLYTO = "reply-to";
53 public static final String SUBJECT = "subject";
54 public static final String REFERENCES = "references";
55 public static final String INREPLYTO = "in-reply-to";
56 public static final String KEYWORDS = "keywords";
57 public static final String COMMENTS = "comments";
58 public static final String ENCRYPTED = "encrypted";
59 public static final String PRECEDENCE = "precedence"; // non-standard
60 public static final String XPRIORITY = "x-priority"; // non-standard
61 public static final String RETURNPATH = "return-path";
62 public static final String DLVRDTO = "delivered-to"; // qmail
63 public static final String RECEIVED = "received";
64 public static final String XMAILER = "x-mailer";
65 public static final String CTYPE = "content-type";
66 public static final String CTRANSENC = "content-transfer-encoding";
68 public static final String TEXT_PLAIN = "text/plain";
69 public static final String TEXT_ISO = "text/plain; charset=\"iso-8859-1\"";
70 public static final String TEXT_HTML = "text/html";
71 public static final String MESS_MIME = "message/rfc822";
72 public static final String MULT_MIXED = "multipart/mixed";
73 public static final String MULT_ALT = "multipart/alternative";
74 public static final String MULT_APPLE = "multipart/appledouble";
75 public static final String MULT_SIGN = "multipart/signed";
76 public static final String MULT_ANY = "multipart/*";
77 public static final String APPL_WORD = "application/msword";
78 public static final String APPL_PDF = "application/pdf";
79 public static final String APPL_RTF = "application/rtf";
80 public static final String APPL_BYTES = "application/octet-stream";
81 public static final String APPL_APPLE = "application/applefile";
82 public static final String APPL_ANY = "application/*";
83 public static final String IMAG_GIF = "image/gif";
84 public static final String IMAG_JPEG = "image/jpeg";
85 public static final String IMAG_ANY = "image/*";
87 public static final String SMTP_HOST = "mail.smtp.host";
89 public static final Session SES;
91 if ( null == System.getProperty( SMTP_HOST ) )
92 System.setProperty( SMTP_HOST, "localhost" );
93 SES = Session.getDefaultInstance( System.getProperties(), null );
97 public static interface Source {
98 Message[] get () throws Exception;
99 void done ( boolean expunge ) throws Exception;
102 public static interface Sink {
103 void put ( Message m ) throws Exception;
104 void put ( Message[] m ) throws Exception;
108 // //////////////////////////////////////////////////
112 public static void del ( Message m )
115 m.setFlag( Flags.Flag.DELETED, true );
118 public static void del ( Message[] m )
121 for ( int i=0; i<m.length; i++ )
123 m[i].setFlag( Flags.Flag.DELETED, true );
127 public static void put ( Sink s, Message[] m )
130 for ( int i=0; i<m.length; i++ )
135 public static int move ( Sink si, Source so )
140 while ( null != (m = so.get()) && 0 < m.length ) {
149 public static String head ( Part p, String name, String def )
152 String[] hh = p.getHeader( name );
153 return null == hh || 0 == hh.length ? def : hh[0];
156 public static String head ( Part p, String name )
159 return head( p, name, null );
162 // join lines, commonly used for content type
163 public static String oneline ( String ct )
165 return null == ct ? null : ct.replace( '\n', '\t' ).replace( '\r', ' ' );
168 public static Message message ( String[] headers, String body )
171 MimeMessage mm = new MimeMessage( SES );
172 for ( int i=0; i<headers.length; i+=2 )
173 mm.addHeader( headers[i], headers[i+1] );
174 mm.addHeader( CTYPE, TEXT_ISO );
175 mm.setContent( body, TEXT_ISO );
176 if ( null == mm.getSentDate() )
177 mm.setSentDate( new Date() );
181 // //////////////////////////////////////////////////
185 public static class Pop implements Source {
186 static final FetchProfile FPEMPTY = new FetchProfile();
195 public Pop ( String host, int port, String user, String pass, int max )
198 _store = SES.getStore( "pop3" );
206 public Pop ( String host, String user, String pass, int max )
209 this( host, 110, user, pass, max );
212 public Pop ( String host, String user, String pass )
215 this( host, user, pass, 0 );
218 public Message[] get ()
221 _store.connect( _h, _o, _u, _p );
222 _f = _store.getFolder( "INBOX" ); // the magic default folder
223 _f.open( Folder.READ_WRITE );
224 Message[] m = _f.getMessages();
225 System.err.println( "pop "+_u+"@"+_h+":"+_o+" new "
226 + (null==m ? -1 : m.length) );
229 if ( 0 != _max && _max < m.length ) {
230 Message[] mm = new Message[_max];
231 System.arraycopy( m, 0, mm, 0, _max );
234 _f.fetch( m, FPEMPTY );
235 System.err.println( "pop "+_u+"@"+_h+":"+_o+" got "+m.length );
239 public void done ( boolean expunge )
251 public static class Dir implements Source, Sink { // the Maildir
260 class Msg extends MimeMessage {
265 super( SES, new FileInputStream( f ) );
267 System.err.println( "file "+f.getName()
268 +": "+head( this, MESSAGEID, "<?>" ) );
272 public Dir ( String base )
275 _t = base + File.separator + "tmp";
276 _n = base + File.separator + "new";
277 _c = base + File.separator + "cur";
278 _ndir = new File(_n);
279 File t = new File(_t);
280 File c = new File(_c);
281 if ( ! t.isDirectory() && ! t.mkdirs() )
282 throw new FileNotFoundException( _t );
283 if ( ! _ndir.isDirectory() && ! _ndir.mkdirs() )
284 throw new FileNotFoundException( _n );
285 if ( ! c.isDirectory() && ! c.mkdirs() )
286 throw new FileNotFoundException( _c );
289 public String toString () {
290 return "Dir "+_ndir.getParentFile().getAbsolutePath();
294 public Message[] get () throws Exception
296 File[] l = _ndir.listFiles();
297 System.err.println( _ndir+": "+l.length );
299 if ( 0 != _lim && i > _lim )
303 File f = new File( _t+File.separator+l[i].getName() );
305 got[i] = new Msg( f );
310 public void done ( boolean expunge ) throws Exception
312 if ( expunge && null != got )
313 for ( int i=0; i<got.length; i++ )
314 if ( got[i].isSet( Flags.Flag.DELETED ) )
320 public void put ( Message m )
324 String s = File.separator+System.currentTimeMillis();
325 while ( ! (f = new File( _t + s )).createNewFile() )
326 s += (int)(10*Math.random()) % 10;
327 OutputStream o = new FileOutputStream(f);
330 while ( ! f.renameTo(new File( _n + s )) )
331 s += (int)(10*Math.random()) % 10;
334 public void put ( Message[] m )
342 public static class Arc implements Sink { // the archive
343 static final String OK = "ok";
344 static final Map OKHDR = new HashMap();
346 OKHDR.put( MESSAGEID, OK );
347 OKHDR.put( DATE, OK );
348 OKHDR.put( FROM, OK );
349 OKHDR.put( REPLYTO, OK );
350 OKHDR.put( SUBJECT, OK );
351 OKHDR.put( REFERENCES, OK );
352 OKHDR.put( INREPLYTO, OK );
353 OKHDR.put( KEYWORDS, OK );
354 OKHDR.put( COMMENTS, OK );
361 final int _maxlines = 60;
362 final int _minlines = 15;
363 final boolean _zip = true;
366 /** path is /m/d/h/xxx.
367 january is Calendar.JANUARY, which is 0
369 public static String path ( int mon, int day, int hour, int min, int sec )
371 char mo = (char)('a'+mon);
372 char d = (char)(day + (day<10 ? '0' : 'a'-10));
373 char h = (char)('a'+hour);
374 int hs = 60*min + sec;
375 sec = hs - 100*(min = hs / 100);
376 char m = (char)(min + (min<10 ? '0' : 'a'-10));
377 return File.separator+mo + File.separator+d +File.separator+h
378 + File.separator+m+(char)('0'+sec/10)+(char)('0'+sec%10);
381 public static String path ( Calendar c )
383 return path( c.get( Calendar.MONTH ), c.get( Calendar.DATE ),
384 c.get( Calendar.HOUR_OF_DAY ), c.get( Calendar.MINUTE ),
385 c.get( Calendar.SECOND ) );
388 public static String path ( Date d )
390 Calendar c = new GregorianCalendar();
395 public static String path ()
397 return path( new GregorianCalendar() );
401 public Arc ( String base, String url, Sink fwd )
407 File b = new File(_b);
408 if ( ! b.isDirectory() && ! b.mkdirs() )
409 throw new FileNotFoundException( _b );
412 public String toString () {
413 return "Arc "+_b+" "+_u;
416 public void put ( Message m )
419 String mid = head( m, MESSAGEID, "<?>" );
420 System.err.println( _b+" += "+mid );
421 Date d = m.getSentDate();
423 d = m.getReceivedDate();
425 System.err.println( "warning: no date found in "+mid );
428 String base = path( d );
430 File f = new File( _b + path );
431 File dir = f.getParentFile();
432 if ( ! dir.isDirectory() && ! dir.mkdirs() )
433 throw new FileNotFoundException( dir.getAbsolutePath() );
434 for ( int i=0; ! f.createNewFile(); i++ )
435 f = new File( _b + (path = base + Integer.toString( i, 36 )) );
436 String name = f.getName();
437 // got unique path path
438 System.out.println( "new message "+mid+" date "+d+" -> "+path );
439 MimeMessage mm = new MimeMessage( SES );
440 Enumeration e = m.getAllHeaders();
441 while ( e.hasMoreElements() ) {
442 Header h = (Header)e.nextElement();
443 String n = h.getName().toLowerCase();
444 if ( OK == OKHDR.get(n) )
445 mm.addHeader( n, h.getValue() );
447 mm.addHeader( CTYPE, TEXT_ISO );
448 mm.addHeader( CTRANSENC, "8bit" );
449 StringBuffer b = new StringBuffer( 4096 );
450 Stack s = new Stack();
454 List binparts = new ArrayList( 32 );
455 while ( ! s.isEmpty() ) {
456 Part p = (Part)s.pop();
457 String ct = oneline( p.getContentType() );
458 // ct = null == ct ? TEXT_PLAIN : ct.toLowerCase();
460 if ( p.isMimeType( MULT_ANY ) ) {
461 Object content = p.getContent();
462 if ( ! (content instanceof Multipart) ) {
464 System.err.println( "multipart type "+ct
465 +" has class "+content.getClass().getName() );
466 mm.addHeader( COMMENTS, "dropped bad multipart "+ct );
468 Multipart mu = (Multipart)content;
469 int cnt = mu.getCount();
471 System.err.println( "multipart with too many parts: "+cnt );
474 Part[] pu = new Part[cnt];
475 for ( int j=0; j<cnt; j++ )
476 pu[j] = mu.getBodyPart( j );
477 if ( p.isMimeType( MULT_ALT )
478 || p.isMimeType( MULT_SIGN )
479 ) { // alternatives suggested
481 for ( int j=0; j<cnt; j++ ) // see wether we can decide
482 if ( pu[j].isMimeType( TEXT_PLAIN ) ) {
487 for ( int j=0; j<cnt; j++ )
489 mm.addHeader( COMMENTS, "dropped alternative "+
490 oneline( pu[j].getContentType() ) + " for "+i );
495 } else if ( p.isMimeType( MULT_APPLE ) // apple ...
496 && 2 == cnt // ... double
497 ) { // kill ressource fork
499 if ( pu[0].isMimeType( APPL_APPLE )
500 || pu[j=1].isMimeType( APPL_APPLE )
504 mm.addHeader( COMMENTS, "dropped appledouble" );
508 for ( int j=cnt; j-- > 0; ) // push parts backwards
514 i++; // about to create part i
515 String part = path+'-'+i;
517 // if ( ! p.isMimeType( TEXT_PLAIN ) ) {
519 it's so awfully unreliable ... many RTFs go as APPL_WORD ...
520 if ( p.isMimeType( TEXT_HTML ) )
522 else if ( p.isMimeType( APPL_WORD ) )
527 OutputStream o = new FileOutputStream( _b+part );
528 p.getDataHandler().writeTo( o );
530 // can we convert it ?
531 String[] args = new String[] { "any2txt", "-v", name+'-'+i };
532 Process fix = Runtime.getRuntime().exec( args, null, dir );
533 BufferedReader err = new BufferedReader( new InputStreamReader(
534 fix.getErrorStream() ) );
536 while ( null != (line = err.readLine()) )
537 System.err.println( "any2txt:\t"+line );
540 int ret = fix.exitValue();
542 boolean havetext = 0 != (ret & 1) || 0 == ret;
545 case 0: ext = ".txt"; break;
546 case 2: ext = ".html"; break;
547 case 4: ext = ".pdf"; break;
548 case 6: ext = ".rtf"; break;
549 case 8: ext = ".doc"; break;
550 case 16: // mail -- reread and start over
552 s.push( new MimeMessage( SES, new FileInputStream( _b+part ) ) );
555 } catch (Exception ee) {
556 System.err.println( "could not reload mail "+mid+"["+i+"] "+ct
558 ee.printStackTrace();
562 case 32: ext = ".gif"; break;
563 case 34: ext = ".jpg"; break;
564 case 36: ext = ".bmp"; break;
565 case 38: ext = ".png"; break;
566 case 40: ext = ".tiff"; break;
568 System.err.println( mid+"["+i+"] "+ct+" is "+ext+" ("+ret+")" );
570 if ( EMPTY != ext && 1 != ret ) // rename
571 (new File(_b+part)).renameTo( new File(_b+part+ext) );
574 binparts.add( name+'-'+i+ext );
575 // mm.addHeader( COMMENTS, "part "+i+" "+ct+" is "+part );
579 b.append( "\r\n---\r\n\r\n" );
580 b.append( "\r\nget part "+i+" at "+_u+part+ext+"\r\n" );
588 // now _b+part.txt contains plaintext
589 File plain = new File(_b+part+".txt");
590 BufferedReader br = new BufferedReader(
591 new InputStreamReader( new FileInputStream( plain ) ) );
593 int max = _maxlines - totlines;
594 if ( max < _minlines )
598 b.append( "\r\n---\r\n\r\n" );
599 while ( null != (line = br.readLine()) ) {
600 b.append( line ).append( "\r\n" );
601 if ( max == ++lines ) {
602 mm.addHeader( COMMENTS, "plaintext "+i+" continued at "+part );
603 b.append( "... read more: "+_u+part+".txt ("
604 +(1 + plain.length()/1024)+" KB)\r\n" );
608 if ( 0 != type && !_zip )
609 b.append( "\r\nget original part "+i+" at "+_u+part+ext+"\r\n" );
613 int nparts = binparts.size();
614 if ( _zip && 0 != nparts ) {
615 String[] args = new String[3+nparts];
618 args[a++] = "-m"; // move
619 args[a++] = name+".zip";
620 for ( int p = 0; p<nparts; p++ )
621 args[a++] = (String)binparts.get(p);
622 Process zip = Runtime.getRuntime().exec(
624 BufferedReader out = new BufferedReader( new InputStreamReader(
625 zip.getInputStream() ) );
627 while ( null != (line = out.readLine()) )
628 b.append( line+"\r\n" );
629 // System.err.println( "zip:\t"+line );
632 int ret = zip.exitValue();
633 File zipped = new File(_b+path+".zip");
635 System.err.println( "zip exited "+ret );
636 else if ( ! zipped.exists() )
637 System.err.println( "no zipfile "+zipped );
639 b.append( "\r\nget original parts at "+_u+path+".zip ("
640 +(1 + zipped.length()/1024)+" KB)\r\n" );
642 mm.setContent( b.toString(), TEXT_ISO );
643 OutputStream o = new FileOutputStream(f);
646 f.renameTo( new File( f.getPath()+".mail" ) );
651 public void put ( Message[] m )
659 public static class Smtp implements Sink {
665 public Smtp ( Session s, Address[] a ) throws Exception {
666 _t = s.getTransport( s.getProvider("smtp") );
670 public Smtp ( Address[] a ) throws Exception {
674 public Smtp ( String a ) throws Exception {
675 this( SES, new Address[] { new InternetAddress(a) } );
678 /** connect. return true, iff we weren't already connected. */
679 public boolean connect () throws Exception {
686 public void close () throws Exception {
693 public void put ( Message m )
696 boolean nconn = connect();
697 _t.sendMessage( m, null != _a ? _a : m.getAllRecipients() );
698 if ( nconn ) close();
701 public void put ( Message[] m )
704 boolean nconn = connect();
706 if ( nconn ) close();
711 public static class Cmd implements Sink {
715 public Cmd ( String cmd ) {
719 public void put ( Message m )
722 Process run = Runtime.getRuntime().exec( _cmd );
723 OutputStream o = run.getOutputStream();
726 BufferedReader err = new BufferedReader( new InputStreamReader(
727 run.getErrorStream() ) );
729 while ( null != (line = err.readLine()) )
730 System.err.println( _cmd+":\t"+line );
733 int ret = run.exitValue();
735 System.err.println( "Cmd '"+_cmd+"' exited "+ret );
738 public void put ( Message[] m )
746 public static void main ( String[] args )
749 String host = "localhost";
751 String user = "test";
752 String pass = "test";
753 String sodir = "/tmp/test";
754 String sidir = "/tmp/test";
755 String url = "http://openisis.org/archive";
765 while ( a<args.length ) {
766 String arg = args[a++];
767 if ( "-h".equals( arg ) )
769 else if ( "-o".equals( arg ) )
770 port = Integer.parseInt( args[a++] );
771 else if ( "-u".equals( arg ) )
773 else if ( "-p".equals( arg ) )
775 else if ( "-pop".equals( arg ) )
777 else if ( "-url".equals( arg ) ) // arc
779 else if ( "-fwd".equals( arg ) ) // fwd by smtp
780 fwd = new Smtp( args[a++] );
781 else if ( "-fwdcmd".equals( arg ) ) // fwd by command
782 fwd = new Cmd( args[a++] );
783 else if ( "-dir".equals( arg ) ) {
786 } else if ( "-todir".equals( arg ) ) {
789 } else if ( "-toarc".equals( arg ) ) {
792 } else if ( "-move".equals( arg ) )
795 throw new IllegalArgumentException( arg );
800 so = new Pop( host, port, user, pass, 8 );
803 so = new Dir( sodir );
807 si = new Arc( sidir, url, fwd );
810 si = new Dir( sidir );
816 System.err.println( "moved "+n+" messages from "+so+" to "+si );