001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/mailarchive/tags/sakai_2-4-1/mailarchive-james/james/src/java/org/sakaiproject/james/SakaiMailet.java $
003: * $Id: SakaiMailet.java 10987 2006-06-21 18:50:17Z ggolden@umich.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.james;
021:
022: import java.io.ByteArrayOutputStream;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.util.Collection;
026: import java.util.Date;
027: import java.util.Enumeration;
028: import java.util.Iterator;
029: import java.util.List;
030: import java.util.Vector;
031:
032: import javax.mail.Address;
033: import javax.mail.BodyPart;
034: import javax.mail.Header;
035: import javax.mail.Message;
036: import javax.mail.MessagingException;
037: import javax.mail.Multipart;
038: import javax.mail.Part;
039: import javax.mail.internet.ContentType;
040: import javax.mail.internet.InternetAddress;
041: import javax.mail.internet.MimeMessage;
042: import javax.mail.internet.MimeUtility;
043:
044: import org.apache.commons.logging.Log;
045: import org.apache.commons.logging.LogFactory;
046: import org.apache.mailet.GenericMailet;
047: import org.apache.mailet.Mail;
048: import org.apache.mailet.MailAddress;
049: import org.sakaiproject.alias.cover.AliasService;
050: import org.sakaiproject.component.cover.ServerConfigurationService;
051: import org.sakaiproject.content.api.ContentResource;
052: import org.sakaiproject.content.cover.ContentHostingService;
053: import org.sakaiproject.entity.api.Reference;
054: import org.sakaiproject.entity.api.ResourceProperties;
055: import org.sakaiproject.entity.api.ResourcePropertiesEdit;
056: import org.sakaiproject.entity.cover.EntityManager;
057: import org.sakaiproject.exception.IdUnusedException;
058: import org.sakaiproject.exception.PermissionException;
059: import org.sakaiproject.mailarchive.api.MailArchiveChannel;
060: import org.sakaiproject.mailarchive.cover.MailArchiveService;
061: import org.sakaiproject.site.cover.SiteService;
062: import org.sakaiproject.thread_local.cover.ThreadLocalManager;
063: import org.sakaiproject.time.cover.TimeService;
064: import org.sakaiproject.tool.api.Session;
065: import org.sakaiproject.tool.cover.SessionManager;
066: import org.sakaiproject.user.api.User;
067: import org.sakaiproject.user.api.UserNotDefinedException;
068: import org.sakaiproject.user.cover.UserDirectoryService;
069: import org.sakaiproject.util.FormattedText;
070: import org.sakaiproject.util.StringUtil;
071: import org.sakaiproject.util.Validator;
072:
073: /**
074: * <p>
075: * SakaiMailet watches incoming mail (via James) and sends mail to the appropriate mail archive channel in Sakai.
076: * </p>
077: */
078: public class SakaiMailet extends GenericMailet {
079: /** Our logger. */
080: private static Log M_log = LogFactory.getLog(SakaiMailet.class);
081:
082: /** The user name of the postmaster user - the one who posts incoming mail. */
083: public static final String POSTMASTER = "postmaster";
084:
085: // Condition: The site doesn't have an email archive turned on
086: public final String errorMsg_I = "Your message cannot be delivered because the site you are emailing"
087: + " does not have the email feature turned on. Please contact the site"
088: + " owner to ask about enabling this feature on the site."
089: + "\n\n"
090: + "If you have further questions about this feature, please email "
091: + ServerConfigurationService.getString("mail.support", "");
092:
093: // Condition: The site is not existing.
094: public final String errorMsg_III = "Your message cannot be delivered because the address is unknown. "
095: + "\n\n"
096: + "If you have further questions about this feature, please email "
097: + ServerConfigurationService.getString("mail.support", "");
098:
099: // Condition: The from email was not matched to a user with permission to send to the system
100: public final String errorMsg_IV = "Your message cannot be delivered because you are not a member of the site, or you are a member "
101: + "but don't have the permission to send email to the site, or because you are registered with a different email address. "
102: + "If you are sending email from the correct email address, and you believe your email should be accepted "
103: + "at the site please contact the site owner and have them check the permission settings for the email "
104: + "archive tool under 'Permissions' for that tool. "
105: + "\n\n"
106: + "If you have further questions about this feature, please contact "
107: + ServerConfigurationService.getString("mail.support", "");
108:
109: /**
110: * Called when created.
111: */
112: public void init() throws MessagingException {
113: M_log.info("init()");
114: }
115:
116: /**
117: * Called when leaving.
118: */
119: public void destroy() {
120: M_log.info("destroy()");
121:
122: super .destroy();
123:
124: } // destroy
125:
126: /**
127: * Process incoming mail.
128: *
129: * @param mail
130: * ...
131: */
132: public void service(Mail mail) throws MessagingException {
133: // get the postmaster user
134: User postmaster = null;
135: try {
136: postmaster = UserDirectoryService.getUser(POSTMASTER);
137: } catch (UserNotDefinedException e) {
138: M_log.warn("service: no postmaster");
139: mail.setState(Mail.GHOST);
140: return;
141: }
142:
143: try {
144: // set the current user to postmaster
145: Session s = SessionManager.getCurrentSession();
146: if (s != null) {
147: s.setUserId(postmaster.getId());
148: } else {
149: M_log
150: .warn("service - no SessionManager.getCurrentSession, cannot set to postmaser user");
151: }
152:
153: MimeMessage msg = mail.getMessage();
154: String id = msg.getMessageID();
155:
156: Address[] fromAddresses = msg.getFrom();
157: String from = null;
158: String fromAddr = null;
159: if ((fromAddresses != null) && (fromAddresses.length == 1)) {
160: from = fromAddresses[0].toString();
161: if (fromAddresses[0] instanceof InternetAddress) {
162: fromAddr = ((InternetAddress) (fromAddresses[0]))
163: .getAddress();
164: }
165: } else {
166: from = mail.getSender().toString();
167: fromAddr = mail.getSender().toInternetAddress()
168: .getAddress();
169: // mail.getSender().getUser() + "@" + mail.getSender().getHost();
170: }
171:
172: Collection to = mail.getRecipients();
173:
174: Date sent = msg.getSentDate();
175:
176: String subject = StringUtil.trimToNull(msg.getSubject());
177: if (subject == null)
178: subject = "<no subject>";
179:
180: Enumeration headers = msg.getAllHeaderLines();
181: List mailHeaders = new Vector();
182: while (headers.hasMoreElements()) {
183: String line = (String) headers.nextElement();
184: if (line.startsWith("Content-Type: "))
185: mailHeaders
186: .add(line
187: .replaceAll(
188: "Content-Type",
189: MailArchiveService.HEADER_OUTER_CONTENT_TYPE));
190: mailHeaders.add(line);
191: }
192:
193: // we will parse the body and attachments later, when we know we need to.
194: String body = null;
195: List attachments = null;
196:
197: M_log.info(id
198: + " : mail: from:"
199: + from
200: + " sent: "
201: + TimeService.newTime(sent.getTime())
202: .toStringLocalFull() + " subject: "
203: + subject);
204:
205: // process for each recipient
206: Iterator it = to.iterator();
207: while (it.hasNext()) {
208: String mailId = null;
209: try {
210: MailAddress recipient = (MailAddress) it.next();
211: M_log.info(id + " : checking to: " + recipient);
212:
213: // is the host ok? %%%
214: // String host = recipient.getHost();
215:
216: // the recipient's mail id
217: mailId = recipient.getUser();
218:
219: // eat the no-reply
220: if ("no-reply".equalsIgnoreCase(mailId)) {
221: mail.setState(Mail.GHOST);
222: continue;
223: }
224:
225: // find the channel (mailbox) that this is adressed to
226: // for now, check only for it being a site or alias to a site.
227: // %%% - add user and other later -ggolden
228: MailArchiveChannel channel = null;
229:
230: // first, assume the mailId is a site id
231: String channelRef = MailArchiveService
232: .channelReference(mailId,
233: SiteService.MAIN_CONTAINER);
234: try {
235: channel = MailArchiveService
236: .getMailArchiveChannel(channelRef);
237: } catch (IdUnusedException goOn) {
238: }
239:
240: // next, if not a site, see if it's an alias to a site or channel
241: if (channel == null) {
242: // if not an alias, it will throw the IdUnusedException caught below
243: Reference ref = EntityManager
244: .newReference(AliasService
245: .getTarget(mailId));
246:
247: // if ref is a site
248: if (ref.getType().equals(
249: SiteService.APPLICATION_ID)) {
250: // now we have a site reference, try for it's channel
251: channelRef = MailArchiveService
252: .channelReference(ref.getId(),
253: SiteService.MAIN_CONTAINER);
254: }
255:
256: // if ref is a channel
257: else if (ref.getType().equals(
258: MailArchiveService.APPLICATION_ID)) {
259: // ref is a channel
260: channelRef = ref.getReference();
261: }
262:
263: // ref is something else
264: else {
265: M_log
266: .info(id
267: + " : mail rejected: unknown address: "
268: + mailId);
269:
270: throw new IdUnusedException(mailId);
271: }
272:
273: // if there's no channel for this site, it will throw the IdUnusedException caught below
274: channel = MailArchiveService
275: .getMailArchiveChannel(channelRef);
276: }
277:
278: // skip disabled channels
279: if (!channel.getEnabled()) {
280: if (from.startsWith(POSTMASTER)) {
281: mail.setState(Mail.GHOST);
282: } else {
283: mail.setErrorMessage(errorMsg_I);
284: }
285:
286: M_log
287: .info(id
288: + " : mail rejected: channel not enabled: "
289: + mailId);
290:
291: continue;
292: }
293:
294: // for non-open channels, make sure the from is a member
295: if (!channel.getOpen()) {
296: // see if our fromAddr is the email address of any of the users who are permitted to add messages to the channel.
297: if (!fromValidUser(fromAddr, channel)) {
298: M_log.info(id + " : mail rejected: from: "
299: + fromAddr
300: + " not authorized for site: "
301: + mailId);
302:
303: mail.setErrorMessage(errorMsg_IV);
304: continue;
305: }
306: }
307:
308: // prepare the message if it has not yet been prepared - we need it.%%%
309: if (body == null) {
310: body = "";
311: attachments = EntityManager.newReferenceList();
312: try {
313: StringBuffer bodyBuf = new StringBuffer();
314: StringBuffer bodyContentType = new StringBuffer();
315: Integer embedCount = parseParts(msg, id,
316: bodyBuf, bodyContentType,
317: attachments, new Integer(-1));
318: body = bodyBuf.toString();
319: // treat the message exactly as-is - as plaintext. Stuff like "<b>" will appear
320: // to the users as "<b>", NOT as bolded text. Since the message service
321: // treats messages as formatted text, plaintext must be converted to formatted text encoding.
322: body = FormattedText
323: .convertPlaintextToFormattedText(body);
324: if (bodyContentType.length() > 0) {
325: // save the content type of the message body - which may be different from the
326: // overall MIME type of the message (multipart, etc)
327: mailHeaders
328: .add(MailArchiveService.HEADER_INNER_CONTENT_TYPE
329: + ": "
330: + bodyContentType);
331: }
332: } catch (MessagingException e) {
333: M_log
334: .warn("service(): msg.getContent() threw: "
335: + e);
336: } catch (IOException e) {
337: M_log
338: .warn("service(): msg.getContent() threw: "
339: + e);
340: }
341: }
342:
343: // post the message to the group's channel
344: channel
345: .addMailArchiveMessage(subject, from
346: .toString(), TimeService
347: .newTime(sent.getTime()),
348: mailHeaders, attachments, body);
349:
350: M_log.info(id + " : delivered to:" + mailId);
351:
352: // all is happy - ghost the message to stop further processing
353: mail.setState(Mail.GHOST);
354: } catch (IdUnusedException goOn) {
355: // if this is to the postmaster, and there's no site, channel or alias for the postmaster, then quietly eat the message
356: if (POSTMASTER.equals(mailId)) {
357: mail.setState(Mail.GHOST);
358: continue;
359: }
360:
361: if (from.startsWith(POSTMASTER + "@")) {
362: mail.setState(Mail.GHOST);
363: continue;
364: }
365:
366: M_log.info(id + " : mail rejected: "
367: + goOn.toString());
368: mail.setErrorMessage(errorMsg_III);
369: } catch (PermissionException e) {
370: M_log.info(id + " : " + e);
371: }
372: }
373: } finally {
374: // clear out any current current bindings
375: ThreadLocalManager.clear();
376: }
377: }
378:
379: /**
380: * Check if the fromAddr email address is recognized as belonging to a user who has permission to add to the channel.
381: *
382: * @param fromAddr
383: * The email address to check.
384: * @param channel
385: * The mail archive channel.
386: * @return True if the email address is from a user who is authorized to add mail, false if not.
387: */
388: protected boolean fromValidUser(String fromAddr,
389: MailArchiveChannel channel) {
390: if ((fromAddr == null) || (fromAddr.length() == 0))
391: return false;
392:
393: // find the users with this email address
394: Collection users = UserDirectoryService
395: .findUsersByEmail(fromAddr);
396:
397: // if none found
398: if ((users == null) || (users.isEmpty()))
399: return false;
400:
401: // see if any of them are allowed to add
402: for (Iterator i = users.iterator(); i.hasNext();) {
403: User u = (User) i.next();
404: if (channel.allowAddMessage(u))
405: return true;
406: }
407:
408: return false;
409: }
410:
411: /**
412: * Create an attachment, adding it to the list of attachments.
413: */
414: protected Reference createAttachment(List attachments, String type,
415: String fileName, byte[] body, String id) {
416: // we just want the file name part - strip off any drive and path stuff
417: String name = Validator.getFileName(fileName);
418: String resourceName = Validator.escapeResourceName(fileName);
419:
420: // make a set of properties to add for the new resource
421: ResourcePropertiesEdit props = ContentHostingService
422: .newResourceProperties();
423: props.addProperty(ResourceProperties.PROP_DISPLAY_NAME, name);
424: props
425: .addProperty(ResourceProperties.PROP_DESCRIPTION,
426: fileName);
427:
428: // make an attachment resource for this URL
429: try {
430: ContentResource attachment = ContentHostingService
431: .addAttachmentResource(resourceName, type, body,
432: props);
433:
434: // add a dereferencer for this to the attachments
435: Reference ref = EntityManager.newReference(attachment
436: .getReference());
437: attachments.add(ref);
438:
439: M_log.info(id + " : attachment: " + ref.getReference()
440: + " size: " + body.length);
441:
442: return ref;
443: } catch (Exception any) {
444: M_log.warn(id + " : exception adding attachment resource: "
445: + name + " : " + any.toString());
446: return null;
447: }
448: }
449:
450: /**
451: * Read in a stream from the mime body into a byte array
452: */
453: protected byte[] readBody(int approxSize, InputStream stream) {
454: // the size is APPROXIMATE, and is sometimes wrong -
455: // so read the body into a ByteArrayOutputStream
456: // that will grow if necessary
457: if (approxSize <= 0)
458: return null;
459:
460: ByteArrayOutputStream baos = new ByteArrayOutputStream(
461: approxSize);
462: byte[] buff = new byte[10000];
463: try {
464: int lenRead = 0;
465: int count = 0;
466: while (count >= 0) {
467: count = stream.read(buff, 0, buff.length);
468: if (count <= 0)
469: break;
470: baos.write(buff, 0, count);
471: lenRead += count;
472: }
473: } catch (IOException e) {
474: M_log.warn("readBody(): " + e);
475: }
476:
477: return baos.toByteArray();
478: }
479:
480: /**
481: * Breaks email messages into parts which can be saved as files (saves as attachments) or viewed as plain text (added to body of message).
482: *
483: * @param p
484: * The message-part embedded in a message..
485: * @param id
486: * The string containing the message's id.
487: * @param bodyBuf
488: * The string-buffer in which the message body is being built.
489: * @param bodyContentType
490: * The value of the Content-Type header for the mesage body.
491: * @param attachments
492: * The ReferenceVector in which references to attachments are collected.
493: * @param embedCount
494: * An Integer that counts embedded messages (outer message is zero).
495: * @return Value of embedCount (updated if this call processed any embedded messages).
496: */
497: protected Integer parseParts(Part p, String id,
498: StringBuffer bodyBuf, StringBuffer bodyContentType,
499: List attachments, Integer embedCount)
500: throws MessagingException, IOException {
501: String closing = "";
502: // process embedded messages
503: if (p instanceof Message) {
504: // increment embedded message counter
505: int this Count = embedCount.intValue() + 1;
506: embedCount = new Integer(this Count);
507:
508: // process inner messages, inserting start and end labels except for outer message.
509: if (this Count > 0) {
510: // make sure previous message parts ended with newline
511: if (bodyBuf.length() > 0
512: && bodyBuf.charAt(bodyBuf.length() - 1) != '\n') {
513: bodyBuf.append("\n");
514: }
515: bodyBuf
516: .append("\n========= Begin embedded email message "
517: + this Count + " =========\n\n");
518: parseEnvelope((Message) p, id, bodyBuf, attachments,
519: embedCount);
520: closing = "\n========== End embedded email message "
521: + this Count + " ==========\n\n";
522: }
523: }
524:
525: String type = p.getContentType();
526:
527: // discard if content-type is unknown
528: if (type == null || type.equals("")) {
529: // do nothing
530: }
531: // add plain text to body
532: else if (p.isMimeType("text/plain") && p.getFileName() == null) {
533: Object o = p.getContent();
534: String txt = null;
535: String innerContentType = p.getContentType();
536:
537: if (o instanceof String) {
538: txt = (String) p.getContent();
539: if (bodyContentType != null
540: && bodyContentType.length() == 0)
541: bodyContentType.append(innerContentType);
542: }
543:
544: else if (o instanceof InputStream) {
545: InputStream in = (InputStream) o;
546: ByteArrayOutputStream out = new ByteArrayOutputStream();
547: byte[] buf = new byte[in.available()];
548: for (int len = in.read(buf); len != -1; len = in
549: .read(buf))
550: out.write(buf, 0, len);
551: String charset = (new ContentType(innerContentType))
552: .getParameter("charset");
553: txt = out.toString(MimeUtility.javaCharset(charset));
554: if (bodyContentType != null
555: && bodyContentType.length() == 0)
556: bodyContentType.append(innerContentType);
557: }
558:
559: // make sure previous message parts ended with newline
560: if (bodyBuf.length() > 0
561: && bodyBuf.charAt(bodyBuf.length() - 1) != '\n') {
562: bodyBuf.append("\n");
563: }
564: bodyBuf.append(txt);
565: }
566: // process subparts of multiparts
567: else if (p.isMimeType("multipart/*")) {
568: Multipart mp = (Multipart) p.getContent();
569: int count = mp.getCount();
570: for (int i = 0; i < count; i++) {
571: embedCount = parseParts(mp.getBodyPart(i), id, bodyBuf,
572: bodyContentType, attachments, embedCount);
573: }
574: }
575: // process embedded messages
576: else if (p.isMimeType("message/rfc822")) {
577: embedCount = parseParts((Part) p.getContent(), id, bodyBuf,
578: bodyContentType, attachments, embedCount);
579: }
580: // Discard parts with mime-type application/applefile. If an e-mail message contains an attachment is sent from
581: // a macintosh, you may get two parts, one for the data fork and one for the resource fork. The part that
582: // corresponds to the resource fork confuses users, this has mime-type application/applefile. The best thing
583: // is to discard it.
584: else if (p.isMimeType("application/applefile")) {
585: // do nothing
586: } else if (p.isMimeType("text/enriched")
587: && p.getFileName() == null) {
588: // ignore this - it is a enriched text version of the message.
589: // Sakai only uses the plain text version of the message.
590: }
591: // everything else gets treated as an attachment
592: else {
593: ContentType cType = new ContentType(type);
594: String name = p.getFileName();
595: String disposition = p.getDisposition();
596: int approxSize = p.getSize();
597:
598: if (name == null) {
599: name = "unknown";
600: // if file's parent is multipart/alternative,
601: // provide a better name for the file
602: if (p instanceof BodyPart) {
603: Multipart parent = ((BodyPart) p).getParent();
604: if (parent != null) {
605: String pType = parent.getContentType();
606: ContentType pcType = new ContentType(pType);
607: if (pcType.getBaseType().equalsIgnoreCase(
608: "multipart/alternative")) {
609: name = "message" + embedCount;
610: }
611: }
612: }
613: if (p.isMimeType("text/html")) {
614: name += ".html";
615: } else if (p.isMimeType("text/richtext")) {
616: name += ".rtx";
617: } else if (p.isMimeType("text/rtf")) {
618: name += ".rtf";
619: } else if (p.isMimeType("text/enriched")) {
620: name += ".etf";
621: } else if (p.isMimeType("text/plain")) {
622: name += ".txt";
623: } else if (p.isMimeType("text/xml")) {
624: name += ".xml";
625: }
626: }
627:
628: // read the attachments bytes, and create it as an attachment in content hosting
629: byte[] bodyBytes = readBody(approxSize, p.getInputStream());
630: if ((bodyBytes != null) && (bodyBytes.length > 0)) {
631: // can we ignore the attachment it it's just whitespace chars??
632: Reference attachment = createAttachment(attachments,
633: cType.getBaseType(), name, bodyBytes, id);
634:
635: // attachment reference URL goes here
636: if (attachment != null) {
637: // make sure previous message parts ended with newline
638: if (bodyBuf.length() > 0
639: && bodyBuf.charAt(bodyBuf.length() - 1) != '\n') {
640: bodyBuf.append("\n");
641: }
642: bodyBuf.append("[see attachment: \"" + name
643: + "\", size: " + bodyBytes.length
644: + " bytes]\n\n");
645: }
646: }
647: }
648: // make sure previous message parts ended with newline
649: if (bodyBuf.length() > 0
650: && bodyBuf.charAt(bodyBuf.length() - 1) != '\n') {
651: bodyBuf.append("\n");
652: }
653: bodyBuf.append(closing);
654:
655: return embedCount;
656: }
657:
658: /**
659: * Saves header info about embedded email messages.
660: *
661: * @param innerMsg
662: * The message-part believed to be an embedded message..
663: * @param id
664: * The string containing the outer message's id.
665: * @param bodyBuf
666: * The string-buffer in which the message body is being built.
667: * @param attachments
668: * The ReferenceVector in which references to attachments are collected.
669: * @param embedCount
670: * An Integer that counts embedded messages (Outer message is zero).
671: */
672: protected void parseEnvelope(Message innerMsg, String id,
673: StringBuffer bodyBuf, List attachments, Integer embedCount)
674: throws MessagingException, IOException {
675: Address[] innerFroms = innerMsg.getFrom();
676: // make sure previous message parts ended with newline
677: if (bodyBuf.length() > 0
678: && bodyBuf.charAt(bodyBuf.length() - 1) != '\n') {
679: bodyBuf.append("\n");
680: }
681: if (innerFroms != null) {
682: String innerFrom = "";
683: for (int n = 0; n < innerFroms.length; n++) {
684: innerFrom += innerFroms[n].toString();
685: }
686: if (innerFrom.length() > 0) {
687: bodyBuf.append("From: " + innerFrom + "\n");
688: }
689: }
690: Address[] innerRecipients = innerMsg
691: .getRecipients(Message.RecipientType.TO);
692: if (innerRecipients != null) {
693: String innerRecipient = "";
694: for (int n = 0; n < innerRecipients.length; n++) {
695: innerRecipient += innerRecipients[n].toString();
696: }
697: if (innerRecipient.length() > 0) {
698: bodyBuf.append("To: " + innerRecipient + "\n");
699: }
700: }
701: String innerSubject = innerMsg.getSubject().trim();
702: if (innerSubject.equals("")) {
703: innerSubject = "<no subject>";
704: }
705: Date innerDate = innerMsg.getSentDate();
706:
707: bodyBuf.append("Subject: " + innerSubject + "\nDate: "
708: + innerDate.toString() + "\n\n");
709: Enumeration innerHdrs = innerMsg.getAllHeaders();
710: String hdrStr = new String();
711: while (innerHdrs.hasMoreElements()) {
712: Header iHdr = (Header) innerHdrs.nextElement();
713: hdrStr += iHdr.getName() + ": " + iHdr.getValue() + "\n";
714: }
715: if (hdrStr.length() > 0) {
716: String name = "headers" + embedCount.toString() + ".txt";
717: Reference attachment = createAttachment(attachments,
718: "text/plain", name, hdrStr.getBytes(), id);
719: bodyBuf.append("[see attachment: \"" + name + "\" size: "
720: + hdrStr.length() + " bytes]\n\n");
721: }
722: }
723: }
|