/********************************************************************************* * TotalCross Software Development Kit * * Copyright (C) 2000-2012 SuperWaba Ltda. * * All Rights Reserved * * * * This library and virtual machine 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. * * * * This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 * * A copy of this license is located in file license.txt at the root of this * * SDK or can be downloaded here: * * http://www.gnu.org/licenses/lgpl-3.0.txt * * * *********************************************************************************/ package totalcross.net.mail; import totalcross.io.IOException; import totalcross.io.Stream; import totalcross.sys.Convert; import totalcross.util.Vector; /** * This class represents a MIME style e-mail message. * * @since TotalCross 1.13 */ public class Message extends Part { /** The subject of this message */ public String subject; protected Vector vFrom; protected Vector[] vRecipients = new Vector[3]; /** READ-ONLY FIELD! */ Vector recipients = new Vector(); Folder folder; int msgNumber; public String uidl; int size; int headerSize; boolean expunged = false; /** * Creates a new empty message. * * @since TotalCross 1.13 */ public Message() { vRecipients[RecipientType.TO] = new Vector(); vRecipients[RecipientType.CC] = new Vector(); vRecipients[RecipientType.BCC] = new Vector(); } /** * Package constructor used by subclasses of Folder to create a message received from the server. * * @param folder * @param msgNumber * @param uidl * @param size * @param headerSize * @since TotalCross 1.13 */ Message(Folder folder, int msgNumber, String uidl, int size, int headerSize) { this(); this.msgNumber = msgNumber; this.folder = folder; this.size = size; this.headerSize = headerSize; this.uidl = uidl; } public void addHeader(String name, String value) { if (name.length() > 7) // "Subject" is the bigger header we have to check for headers.addHeader(name, value); else if (name.equalsIgnoreCase("From")) throw new IllegalArgumentException(); else if (name.equalsIgnoreCase(RecipientType.recipientPrefix[RecipientType.TO])) throw new IllegalArgumentException(); else if (name.equalsIgnoreCase(RecipientType.recipientPrefix[RecipientType.CC])) throw new IllegalArgumentException(); else if (name.equalsIgnoreCase(RecipientType.recipientPrefix[RecipientType.BCC])) throw new IllegalArgumentException(); else if (name.equalsIgnoreCase("Subject")) throw new IllegalArgumentException(); else headers.addHeader(name, value); } /** * Add this recipient address to the existing ones of the given type. * * @param type * the recipient type * @param address * the address * @throws IllegalArgumentException * If the given recipient type is not valid * @throws NullPointerException * if the given address is null * @since TotalCross 1.13 */ public void addRecipient(int type, Address address) { if (type < RecipientType.TO || type > RecipientType.BCC) throw new IllegalArgumentException(); recipients.addElement(address.address); vRecipients[type].addElement(address); } /** * Add these recipient addresses to the existing ones of the given type. * * @param type * the recipient type * @param addresses * the addresses * @throws IllegalArgumentException * If the given recipient type is not valid * @throws NullPointerException * if addresses is null, or has a null value * @since TotalCross 1.13 */ public void addRecipients(int type, Address[] addresses) { if (type < RecipientType.TO || type > RecipientType.BCC) throw new IllegalArgumentException(); for (int i = 0; i < addresses.length; i++) { recipients.addElement(addresses[i].address); vRecipients[type].addElement(addresses[i]); } } /** * Add these addresses to the existing "From" attribute. * * @param addresses * the senders * @since TotalCross 1.22 */ public void addFrom(Address[] addresses) { if (vFrom == null) vFrom = new Vector(); vFrom.addElements(addresses); } /** * Set the "From" attribute in this Message. * * @param address * the sender * @since TotalCross 1.22 */ public void setFrom(Address address) { if (vFrom == null) vFrom = new Vector(); else vFrom.removeAllElements(); vFrom.addElement(address); } /** * Returns the "From" attribute. The "From" attribute contains the identity of the person(s) who wished this message * to be sent. In certain implementations, this may be different from the entity that actually sent the message. * * This method returns null if this attribute is not present in this message. Returns an empty array if this * attribute is present, but contains no addresses. * * @return array of Address objects * @since TotalCross 1.22 */ public Address[] getFrom() { if (vFrom == null) return null; int fromSize = vFrom.size(); if (fromSize == 0) return new Address[0]; Address[] addresses = new Address[fromSize]; vFrom.copyInto(addresses); return addresses; } public void writeTo(Stream stream) throws MessagingException { try { //flsobral@tc123_49: moved SMTP commands from Message.writeTo to SMTPTransport.sendMessage - Message.writeTo now correctly supports writing to any type of stream. // FROM if (vFrom != null) { stream.writeBytes("From: "); int fromSize = vFrom.size(); if (fromSize > 0) { Address from = (Address) vFrom.items[0]; stream.writeBytes(from.toString()); for (int i = 1; i < fromSize; i++) { from = (Address) vFrom.items[i]; stream.writeBytes(", " + from); } } stream.writeBytes(Convert.CRLF_BYTES); } // TO, CC and BCC if (!vRecipients[RecipientType.TO].isEmpty()) { stream.writeBytes("To: " + vRecipients[RecipientType.TO].toString(", ")); stream.writeBytes(Convert.CRLF_BYTES); } if (!vRecipients[RecipientType.CC].isEmpty()) { stream.writeBytes("Cc: " + vRecipients[RecipientType.CC].toString(", ")); stream.writeBytes(Convert.CRLF_BYTES); } if (!vRecipients[RecipientType.BCC].isEmpty()) { stream.writeBytes("Bcc: " + vRecipients[RecipientType.BCC].toString(", ")); stream.writeBytes(Convert.CRLF_BYTES); } // SUBJECT stream.writeBytes("Subject: " + this.subject); stream.writeBytes(Convert.CRLF_BYTES); // MIME VERSION stream.writeBytes("MIME-Version: 1.0"); stream.writeBytes(Convert.CRLF_BYTES); super.writeTo(stream); } catch (IOException e) { throw new MessagingException(e); } } /** * Mark this message as deleted, removing it from the parent folder. * * @throws MessagingException * @since TotalCross 1.13 */ public void delete() throws MessagingException { if (folder != null) folder.deleteMessage(this); } /** * Get a new Message suitable for a reply to this message. The new Message will have its attributes and headers set * up appropriately. Note that this new message object will be empty, that is, it will not have a "content". These * will have to be suitably filled in by the client. * * If replyToAll is set, the new Message will be addressed to all recipients of this message. Otherwise, the reply * will be addressed to only the sender of this message. * * The "Subject" field is filled in with the original subject prefixed with "Re:" (unless it already starts with * "Re:"). * * The reply message will use the same session as this message. * * NOTE: The replyToAll feature is NOT supported yet, messages returned by this method will contain only the original * sender as recipient. * * @param replyToAll * reply should be sent to all recipients of this message * @return the reply Message * @throws AddressException * @since TotalCross 1.13 */ public Message reply(boolean replyToAll) throws AddressException { Message reply = new Message(); reply.subject = subject; if (!reply.subject.startsWith("Re:")) reply.subject = "Re:" + reply.subject; if (vFrom != null && !vFrom.isEmpty()) reply.addRecipient(Message.RecipientType.TO, (Address) vFrom.items[0]); reply.setFrom(new Address(MailSession.getDefaultInstance().get(MailSession.SMTP_USER).toString(), null)); return reply; } public interface RecipientType { static final int TO = 0; static final int CC = 1; static final int BCC = 2; static final String[] recipientPrefix = { "To", "Cc", "Bcc" }; } private static final String HEADER_SUBJECT = "\r\nSubject: "; private static final String HEADER_FROM = "\r\nFrom: "; private static final String HEADER_TO = "\r\nTo:"; //private static final String HEADER_CC = "\r\nCc: "; //private static final String HEADER_BCC = "\r\nBcc: "; private static final String HEADER_CONTENT_TYPE = "\r\nContent-type: "; void parseHeader(String header) throws IOException, AddressException { subject = getAttribute(HEADER_SUBJECT, header); String szFrom = getAttribute(HEADER_FROM, header); if (szFrom != null) { int start = szFrom.indexOf('<'); if (start != -1) { String address = szFrom.substring(start + 1, szFrom.indexOf('>')); String personal = null; if (start > 0) personal = szFrom.substring(0, start - 1); setFrom(new Address(address, personal)); } else { setFrom(new Address(szFrom, null)); } } String szTo = getAttribute(HEADER_TO, header); if (szTo != null) { String[] tokens = Convert.tokenizeString(szTo, ','); parseRecipient(RecipientType.TO, tokens); } // Cc and Bcc are not supported yet! // String szCc = getAttribute(HEADER_CC, header); // String szBcc = getAttribute(HEADER_BCC, header); mimeType = getAttribute(HEADER_CONTENT_TYPE, header); if (mimeType == null) mimeType = PLAIN; } void parseContent(String content) throws IOException { //if (mimeType == null || mimeType.equals(Part.PLAIN)) this.content = content; } private String getAttribute(String attribute, String source) { int start = source.indexOf(attribute); if (start == -1) return null; start += attribute.length(); int end = source.indexOf(Convert.CRLF, start); if (end == -1) return null; String result = source.substring(start, end); if (result.startsWith("=?iso-8859-1?Q?")) { int endEncoded = result.indexOf("?="); int resultLen = result.length(); String encoded = result.substring(15, endEncoded); StringBuffer sb = new StringBuffer(resultLen); start = 0; while ((end = encoded.indexOf('=', start)) != -1) { if (start != end) sb.append(encoded.substring(start, end)); start = end + 3; sb.append(new String(Convert.hexStringToBytes(encoded.substring(end + 1, start)))); } sb.append(encoded.substring(start)); encoded = sb.toString().replace('_', ' '); if (endEncoded + 2 < resultLen) encoded += result.substring(endEncoded + 2); result = encoded; } return result; } private void parseRecipient(int type, String[] tokens) throws AddressException { int len = tokens.length; for (int i = 0; i < len; i++) { if (tokens[i].length() > 0) { int start = tokens[i].indexOf('<'); if (start != -1) { String address = tokens[i].substring(start + 1, tokens[i].indexOf('>')); String personal = address; if (start > 1) personal = tokens[i].substring(1, start - 1); addRecipient(type, new Address(address, personal)); } else { String address = tokens[i].substring(1); addRecipient(type, new Address(address, null)); } } } } }