comparison src/fschmidt/util/mail/javamail/MailImpl.java @ 68:00520880ad02

add fschmidt source
author Franklin Schmidt <fschmidt@gmail.com>
date Sun, 05 Oct 2025 17:24:15 -0600
parents
children
comparison
equal deleted inserted replaced
67:9d0fefce6985 68:00520880ad02
1 /*
2 Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com>
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
21 */
22
23 package fschmidt.util.mail.javamail;
24
25 import fschmidt.util.java.IoUtils;
26 import fschmidt.util.mail.AlternativeMultipartContent;
27 import fschmidt.util.mail.Content;
28 import fschmidt.util.mail.FileAttachmentContent;
29 import fschmidt.util.mail.HtmlTextContent;
30 import fschmidt.util.mail.Mail;
31 import fschmidt.util.mail.MailAddress;
32 import fschmidt.util.mail.MailAddressException;
33 import fschmidt.util.mail.MailEncodingException;
34 import fschmidt.util.mail.MailException;
35 import fschmidt.util.mail.MailParseException;
36 import fschmidt.util.mail.MixedMultipartContent;
37 import fschmidt.util.mail.MultipartContent;
38 import fschmidt.util.mail.PlainTextContent;
39 import fschmidt.util.mail.TextAttachmentContent;
40 import fschmidt.util.mail.TextContent;
41 import fschmidt.util.mail.UnsupportedContent;
42
43 import javax.activation.CommandMap;
44 import javax.activation.MailcapCommandMap;
45 import javax.mail.Address;
46 import javax.mail.Message;
47 import javax.mail.MessagingException;
48 import javax.mail.Part;
49 import javax.mail.Session;
50 import javax.mail.internet.AddressException;
51 import javax.mail.internet.ContentType;
52 import javax.mail.internet.HeaderTokenizer;
53 import javax.mail.internet.InternetAddress;
54 import javax.mail.internet.MimeBodyPart;
55 import javax.mail.internet.MimeMessage;
56 import javax.mail.internet.MimeMultipart;
57 import javax.mail.internet.MimePart;
58 import javax.mail.internet.MimeUtility;
59 import javax.mail.internet.ParseException;
60 import java.io.ByteArrayInputStream;
61 import java.io.ByteArrayOutputStream;
62 import java.io.IOException;
63 import java.io.InputStream;
64 import java.io.InputStreamReader;
65 import java.io.UnsupportedEncodingException;
66 import java.util.Date;
67 import java.util.Enumeration;
68
69
70 public class MailImpl implements Mail {
71 private static final Session nullSession = null;
72
73 class MyMimeMessage extends MimeMessage {
74 MyMimeMessage() {
75 super(nullSession);
76 }
77
78 MyMimeMessage(InputStream is) throws MessagingException {
79 super(nullSession,is);
80 }
81
82 MyMimeMessage(MimeMessage msg) throws MessagingException {
83 super(msg);
84 }
85
86 void setSession(Session session) {
87 this.session = session;
88 }
89
90 protected void updateHeaders()
91 throws MessagingException
92 {
93 super.updateHeaders();
94 if( messageID != null )
95 setHeader( "Message-ID", messageID );
96 }
97 }
98
99 final MyMimeMessage msg;
100 private String messageID = null;
101
102 public MailImpl() {
103 this.msg = new MyMimeMessage();
104 }
105
106 public MailImpl(String rawInput) {
107 try {
108 this.msg = new MyMimeMessage(new ByteArrayInputStream(rawInput.getBytes("ISO-8859-1")));
109 } catch (UnsupportedEncodingException e) {
110 throw new RuntimeException(e);
111 } catch(MessagingException e) {
112 throw MailImpl.e(e);
113 }
114 }
115
116 MailImpl(MimeMessage msg) throws MessagingException {
117 this.msg = new MyMimeMessage(msg);
118 }
119
120 public String getType() {
121 return "message";
122 }
123
124 public String getSubtype() {
125 return "rfc822";
126 }
127
128 public Content getContent() throws MailException {
129 try {
130 return getPart(msg);
131 } catch(MessagingException e) {
132 throw MailImpl.e(e);
133 } catch(IOException e) {
134 throw MailImpl.e(e);
135 }
136 }
137
138 private static Content getPart(Part part) throws MessagingException, IOException {
139 String ct = part.getContentType().toLowerCase();
140 int end = ct.indexOf(';');
141 if( end != -1 )
142 ct = ct.substring(0,end);
143 String[] a = ct.split("/");
144 if (a.length==0)
145 return new UnsupportedContent(part.getInputStream(), null, null);
146 String type = a[0];
147 String subtype = a.length>1 ? a[1]:"";
148 if( type.equals("text") ) {
149 String text = null;
150 Object obj;
151 try {
152 obj = part.getContent();
153 } catch (UnsupportedEncodingException e) {
154 obj = IoUtils.readAll(new InputStreamReader(part.getInputStream(), "ISO-8859-1"));
155 if (!isAllAscii((String)obj))
156 return new UnsupportedContent(obj, type, subtype);
157 }
158 if( Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition()) )
159 return new FileAttachmentContentImpl((MimePart)part, type, subtype);
160 if( obj instanceof String ) {
161 text = (String)obj;
162 } else if( obj instanceof InputStream ) {
163 try {
164 String charset = null;
165 String filename = null;
166 try {
167 ContentType contentType = new ContentType(part.getContentType());
168 charset = MimeUtility.javaCharset(contentType.getParameter("charset"));
169 filename = contentType.getParameter("name");
170 } catch (ParseException e) {}
171 text = IoUtils.readAll(new InputStreamReader((InputStream)obj,charset!=null?charset:"ISO-8859-1"));
172 } catch (IOException e) {
173 return new UnsupportedContent(part.getInputStream(), type, subtype);
174 } catch (MessagingException e) {
175 return new UnsupportedContent(part.getInputStream(), type, subtype);
176 }
177 } else {
178 return new FileAttachmentContentImpl((MimePart)part, type, subtype);
179 }
180 if( subtype.equals("plain") || subtype.equals(""))
181 return new PlainTextContent(text);
182 if( subtype.equals("html") )
183 return new HtmlTextContent(text);
184 return new TextContent(subtype,text);
185 }
186 if( type.equals("multipart") && part.getContent() instanceof MimeMultipart) {
187 Content[] parts = getParts( (MimeMultipart)part.getContent() );
188 if( subtype.equals("alternative") )
189 return new AlternativeMultipartContent(parts);
190 if( subtype.equals("mixed") )
191 return new MixedMultipartContent(parts);
192 if( subtype.equals("signed") )
193 return new MixedMultipartContent(parts);
194 return new MultipartContent(subtype,parts);
195 }
196 if( type.equals("message") && subtype.equals("rfc822") && part.getContent() instanceof MimeMessage) {
197 MimeMessage mm = (MimeMessage)part.getContent();
198 return new MailImpl(mm);
199 }
200 /*
201 if( type.equals("application") ) {
202 if( subtype.equals("pgp-signature") )
203 return new PlainTextContent((String)obj);
204 }
205 */
206 return new FileAttachmentContentImpl((MimePart)part, type, subtype);
207 }
208
209 private static Content[] getParts(MimeMultipart mp) throws MessagingException, IOException {
210 Content[] parts = new Content[mp.getCount()];
211 for( int i=0; i<parts.length; i++ ) {
212 parts[i] = getPart(mp.getBodyPart(i));
213 }
214 return parts;
215 }
216
217 private static boolean isAllAscii(String s) {
218 for (int i=0;i<s.length();i++)
219 if (nonascii((int)s.charAt(i))) return false;
220 return true;
221 }
222
223 // copied from javamail
224 private static boolean nonascii(int b) {
225 return b >= 0177 || (b < 040 && b != '\r' && b != '\n' && b != '\t');
226 }
227
228 public void setContent(Content content) throws MailException {
229 try {
230 setPart(msg,content);
231 } catch(MessagingException e) {
232 throw MailImpl.e(e);
233 }
234 }
235
236 private final static String defaultCharset = MimeUtility.quote(
237 MimeUtility.mimeCharset(MimeUtility.getDefaultJavaCharset()),
238 HeaderTokenizer.MIME);
239
240 private static void setPart(Part part,Content content) throws MessagingException {
241 if( content instanceof PlainTextContent ) {
242 TextContent textContent = (TextContent)content;
243 // Using part.setContent breaks the encoding of non-ascii email
244 // because com.sun.mail.handlers.text_plain assumes us-ascii charset
245 // if charset is not specified. Part.setText handles this correctly.
246 part.setText( textContent.getText() );
247 return;
248 }
249 if( content instanceof TextAttachmentContent ) {
250 TextAttachmentContent textContent = (TextAttachmentContent)content;
251 // Use same logic as javamail MimeBodyPart.setText() to determine charset
252 String charset = isAllAscii(textContent.getText())?"us-ascii":defaultCharset;
253 part.setContent( textContent.getText(), content.getType()+'/'+content.getSubtype()+"; charset="+charset);
254 part.setFileName( textContent.getFilename() );
255 return;
256 }
257 if( content instanceof FileAttachmentContent ) {
258 FileAttachmentContent fileContent = (FileAttachmentContent)content;
259 byte[] contents = new byte[0];
260 InputStream is = fileContent.getInputStream();
261 try {
262 contents = IoUtils.readAll(is);
263 } catch (IOException e) {
264 throw new RuntimeException(e);
265 } finally {
266 try {
267 is.close();
268 } catch (IOException e) {
269 throw new RuntimeException(e);
270 }
271 }
272 part.setContent( contents, content.getType()+'/'+content.getSubtype());
273 part.setFileName( fileContent.getFileName() );
274 return;
275 }
276 if( content instanceof TextContent ) {
277 TextContent textContent = (TextContent)content;
278 // Use same logic as javamail MimeBodyPart.setText() to determine charset
279 String charset = isAllAscii(textContent.getText())?"us-ascii":defaultCharset;
280 part.setContent( textContent.getText(), content.getType()+'/'+content.getSubtype()+"; charset="+charset);
281 return;
282 }
283 if( content instanceof MultipartContent ) {
284 part.setContent( makeMimeMultipart((MultipartContent)content) );
285 return;
286 }
287 if( content instanceof MailImpl ) {
288 MailImpl mail = (MailImpl)content;
289 part.setContent( mail.msg, "message/rfc822" );
290 part.setDisposition(Part.ATTACHMENT);
291 return;
292 }
293 throw new UnsupportedOperationException("content class "+content.getClass());
294 }
295
296 private static MimeMultipart makeMimeMultipart(MultipartContent mc) throws MessagingException {
297 MimeMultipart mp = new MimeMultipart(mc.getSubtype());
298 Content[] parts = mc.getParts();
299 for( int i=0; i<parts.length; i++ ) {
300 MimeBodyPart mbp = new MimeBodyPart();
301 setPart(mbp,parts[i]);
302 mp.addBodyPart(mbp);
303 }
304 return mp;
305 }
306
307 public String getSubject() throws MailException {
308 try {
309 return msg.getSubject();
310 } catch(MessagingException e) {
311 throw MailImpl.e(e);
312 }
313 }
314
315 public void setSubject(String subject) throws MailException {
316 try {
317 msg.setSubject(subject);
318 } catch(MessagingException e) {
319 throw MailImpl.e(e);
320 }
321 }
322
323 static InternetAddress addr(MailAddress addr)
324 throws MessagingException, UnsupportedEncodingException
325 {
326 return addr.getDisplayName() == null
327 ? new InternetAddress(addr.getAddrSpec())
328 : new InternetAddress(addr.getAddrSpec(),addr.getDisplayName())
329 ;
330 }
331
332 static InternetAddress[] addr(MailAddress[] addrs)
333 throws MessagingException, UnsupportedEncodingException
334 {
335 InternetAddress[] a = new InternetAddress[addrs.length];
336 for( int i=0; i<addrs.length; i++ ) {
337 a[i] = addr(addrs[i]);
338 }
339 return a;
340 }
341
342 static MailAddress addr(Address a) {
343 if( a==null )
344 return null;
345 InternetAddress addr = (InternetAddress)a;
346 return addr.getPersonal() == null
347 ? new MailAddress(addr.getAddress())
348 : new MailAddress(addr.getAddress(),addr.getPersonal())
349 ;
350 }
351
352 private static MailAddress[] addr(Address[] a) {
353 if( a==null )
354 return new MailAddress[0];
355 MailAddress[] ma = new MailAddress[a.length];
356 for( int i=0; i<a.length; i++ ) {
357 ma[i] = addr(a[i]);
358 }
359 return ma;
360 }
361
362 public MailAddress getFrom() throws MailException {
363 try {
364 Address[] addrs = msg.getFrom();
365 if( addrs==null || addrs.length==0 )
366 throw new MailAddressException("number of from addresses is 0");
367 //if( addrs.length > 1 )
368 // log.warn("number of from addresses is "+addrs.length);
369 return addr(addrs[0]);
370 } catch(MessagingException e) {
371 throw MailImpl.e(e);
372 }
373 }
374
375 public void setFrom(MailAddress address) throws MailException {
376 try {
377 msg.setFrom(addr(address));
378 } catch(MessagingException e) {
379 throw MailImpl.e(e);
380 } catch(UnsupportedEncodingException e) {
381 throw new MailException(e);
382 }
383 }
384
385 public MailAddress getSender() throws MailException {
386 try {
387 return addr(msg.getSender());
388 } catch(MessagingException e) {
389 throw MailImpl.e(e);
390 }
391 }
392
393 public void setSender(MailAddress address) throws MailException {
394 try {
395 msg.setSender(addr(address));
396 } catch(MessagingException e) {
397 throw MailImpl.e(e);
398 } catch(UnsupportedEncodingException e) {
399 throw new MailException(e);
400 }
401 }
402
403 public MailAddress[] getReplyTo() throws MailException {
404 try {
405 String replyTo = msg.getHeader("Reply-To", ",");
406 return (replyTo == null) ? null : addr(InternetAddress.parse(replyTo));
407 } catch(MessagingException e) {
408 throw MailImpl.e(e);
409 }
410 }
411
412 public void setReplyTo(MailAddress... addresses) throws MailException {
413 try {
414 msg.setReplyTo(addr(addresses));
415 } catch(MessagingException e) {
416 throw MailImpl.e(e);
417 } catch(UnsupportedEncodingException e) {
418 throw new MailException(e);
419 }
420 }
421
422 public MailAddress[] getTo() throws MailException {
423 try {
424 return addr(msg.getRecipients(Message.RecipientType.TO));
425 } catch(MessagingException e) {
426 throw MailImpl.e(e);
427 }
428 }
429
430 public MailAddress[] getCc() throws MailException {
431 try {
432 return addr(msg.getRecipients(Message.RecipientType.CC));
433 } catch(MessagingException e) {
434 throw MailImpl.e(e);
435 }
436 }
437
438 public MailAddress[] getBcc() throws MailException {
439 try {
440 return addr(msg.getRecipients(Message.RecipientType.BCC));
441 } catch(MessagingException e) {
442 throw MailImpl.e(e);
443 }
444 }
445
446 public void setTo(MailAddress... addresses) throws MailException {
447 try {
448 msg.setRecipients(Message.RecipientType.TO,addr(addresses));
449 } catch(MessagingException e) {
450 throw MailImpl.e(e);
451 } catch(UnsupportedEncodingException e) {
452 throw new MailException(e);
453 }
454 }
455
456 public void setCc(MailAddress... addresses) throws MailException {
457 try {
458 msg.setRecipients(Message.RecipientType.CC,addr(addresses));
459 } catch(MessagingException e) {
460 throw MailImpl.e(e);
461 } catch(UnsupportedEncodingException e) {
462 throw new MailException(e);
463 }
464 }
465
466 public void setBcc(MailAddress... addresses) throws MailException {
467 try {
468 msg.setRecipients(Message.RecipientType.BCC,addr(addresses));
469 } catch(MessagingException e) {
470 throw MailImpl.e(e);
471 } catch(UnsupportedEncodingException e) {
472 throw new MailException(e);
473 }
474 }
475
476 public String[] getHeader(String name) throws MailException {
477 try {
478 return msg.getHeader(name);
479 } catch(MessagingException e) {
480 throw MailImpl.e(e);
481 }
482 }
483
484 public void setHeader(String name,String... values) throws MailException {
485 try {
486 if( values==null ) {
487 msg.removeHeader(name);
488 } else {
489 msg.setHeader(name,values[0]);
490 for( int i=1; i<values.length; i++ ) {
491 msg.addHeader(name,values[i]);
492 }
493 }
494 } catch(MessagingException e) {
495 throw MailImpl.e(e);
496 }
497 }
498
499 public void setHeader(String name, MailAddress address) throws MailException {
500 try {
501 msg.setHeader(name, addr(address).toString());
502 } catch(MessagingException e) {
503 throw MailImpl.e(e);
504 } catch(UnsupportedEncodingException e) {
505 throw new MailException(e);
506 }
507 }
508
509 public String getMessageID() throws MailException {
510 try {
511 if( messageID != null )
512 return messageID;
513 return msg.getMessageID();
514 } catch(MessagingException e) {
515 throw MailImpl.e(e);
516 }
517 }
518
519 public void setMessageID(String messageID) throws MailException {
520 this.messageID = messageID;
521 }
522
523 public Date getSentDate() throws MailException {
524 try {
525 return msg.getSentDate();
526 } catch(MessagingException e) {
527 throw MailImpl.e(e);
528 }
529 }
530
531 public void setSentDate(Date sentDate) throws MailException {
532 try {
533 msg.setSentDate(sentDate);
534 } catch(MessagingException e) {
535 throw MailImpl.e(e);
536 }
537 }
538
539 public String getRawInput() throws MailException {
540 try {
541 ByteArrayOutputStream out = new ByteArrayOutputStream();
542 msg.writeTo(out);
543 return out.toString("ISO-8859-1");
544 } catch(MessagingException e) {
545 throw MailImpl.e(e);
546 } catch(IOException e) {
547 throw new MailException(e);
548 }
549 }
550
551 public String toString() {
552 try {
553 StringBuilder buf = new StringBuilder();
554 for( Enumeration en=msg.getAllHeaderLines(); en.hasMoreElements(); ) {
555 String header = (String)en.nextElement();
556 buf.append(header).append("\n");
557 }
558 buf.append("\n");
559 buf.append(getContent());
560 return buf.toString();
561 } catch(MessagingException e) {
562 throw new RuntimeException(e);
563 }
564 }
565
566
567 // exception handling
568
569 static MailException e(MessagingException e) {
570 return e(e.getMessage(),e);
571 }
572
573 static MailException e(String msg,AddressException e) {
574 return new MailAddressException(msg,e);
575 }
576
577 static MailException e(String msg,MessagingException e) {
578 if( e instanceof AddressException )
579 return e(msg,(AddressException)e);
580 else if(e instanceof ParseException )
581 return e(msg,(ParseException)e);
582 if (msg!=null && msg.startsWith("Missing start boundary"))
583 return new MailParseException(msg,e);
584 Exception e2 = e.getNextException();
585 return e2==null ? new MailException(msg,e) : e(msg,e2);
586 }
587
588 static MailException e(String msg,Exception e) {
589 if( e instanceof MessagingException )
590 return e(msg,(MessagingException)e);
591 return new MailException(msg,e);
592 }
593
594 static MailException e(String msg,ParseException e) {
595 return new MailParseException(msg,e);
596 }
597
598 static MailException e(IOException e) {
599 return e(e.getMessage(),e);
600 }
601
602 static MailException e(String msg,IOException e) {
603 if ( e instanceof UnsupportedEncodingException )
604 return e(msg,(UnsupportedEncodingException)e);
605 if (msg!=null && msg.startsWith("Unknown encoding"))
606 return new MailEncodingException(msg,e);
607 return new MailException(msg,e);
608 }
609
610 static MailException e(String msg,UnsupportedEncodingException e) {
611 return new MailEncodingException(msg,e);
612 }
613
614 static {
615 MailcapCommandMap mcm = (MailcapCommandMap)CommandMap.getDefaultCommandMap();
616 mcm.addMailcap("text/x-aol; ; x-java-content-handler=com.sun.mail.handlers.text_plain");
617 }
618 }