Clover coverage report - Ant Coverage
Coverage timestamp: Tue Apr 8 2003 20:43:55 EST
file stats: LOC: 509   Methods: 34
NCLOC: 246   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
MailMessage.java 0% 0% 0% 0%
 1   
 /*
 2   
  * The Apache Software License, Version 1.1
 3   
  *
 4   
  * Copyright (c) 2000-2002 The Apache Software Foundation.  All rights
 5   
  * reserved.
 6   
  *
 7   
  * Redistribution and use in source and binary forms, with or without
 8   
  * modification, are permitted provided that the following conditions
 9   
  * are met:
 10   
  *
 11   
  * 1. Redistributions of source code must retain the above copyright
 12   
  *    notice, this list of conditions and the following disclaimer.
 13   
  *
 14   
  * 2. Redistributions in binary form must reproduce the above copyright
 15   
  *    notice, this list of conditions and the following disclaimer in
 16   
  *    the documentation and/or other materials provided with the
 17   
  *    distribution.
 18   
  *
 19   
  * 3. The end-user documentation included with the redistribution, if
 20   
  *    any, must include the following acknowlegement:
 21   
  *       "This product includes software developed by the
 22   
  *        Apache Software Foundation (http://www.apache.org/)."
 23   
  *    Alternately, this acknowlegement may appear in the software itself,
 24   
  *    if and wherever such third-party acknowlegements normally appear.
 25   
  *
 26   
  * 4. The names "Ant" and "Apache Software
 27   
  *    Foundation" must not be used to endorse or promote products derived
 28   
  *    from this software without prior written permission. For written
 29   
  *    permission, please contact apache@apache.org.
 30   
  *
 31   
  * 5. Products derived from this software may not be called "Apache"
 32   
  *    nor may "Apache" appear in their names without prior written
 33   
  *    permission of the Apache Group.
 34   
  *
 35   
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 36   
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 37   
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 38   
  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 39   
  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 40   
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 41   
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 42   
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 43   
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 44   
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 45   
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 46   
  * SUCH DAMAGE.
 47   
  * ====================================================================
 48   
  *
 49   
  * This software consists of voluntary contributions made by many
 50   
  * individuals on behalf of the Apache Software Foundation.  For more
 51   
  * information on the Apache Software Foundation, please see
 52   
  * <http://www.apache.org/>.
 53   
  */
 54   
 
 55   
 /*
 56   
  * The original version of this class was donated by Jason Hunter,
 57   
  * who wrote the class as part of the com.oreilly.servlet
 58   
  * package for his book "Java Servlet Programming" (O'Reilly).
 59   
  * See http://www.servlets.com.
 60   
  *
 61   
  */
 62   
 
 63   
 package org.apache.tools.mail;
 64   
 
 65   
 import java.io.IOException;
 66   
 import java.io.PrintStream;
 67   
 import java.io.BufferedOutputStream;
 68   
 import java.io.OutputStream;
 69   
 import java.net.Socket;
 70   
 import java.net.InetAddress;
 71   
 import java.util.Vector;
 72   
 import java.util.Hashtable;
 73   
 import java.util.Enumeration;
 74   
 
 75   
 /**
 76   
  * A class to help send SMTP email.
 77   
  * This class is an improvement on the sun.net.smtp.SmtpClient class
 78   
  * found in the JDK.  This version has extra functionality, and can be used
 79   
  * with JVMs that did not extend from the JDK.  It's not as robust as
 80   
  * the JavaMail Standard Extension classes, but it's easier to use and
 81   
  * easier to install, and has an Open Source license.
 82   
  * <p>
 83   
  * It can be used like this:
 84   
  * <blockquote><pre>
 85   
  * String mailhost = "localhost";  // or another mail host
 86   
  * String from = "Mail Message Servlet &lt;MailMessage@server.com&gt;";
 87   
  * String to = "to@you.com";
 88   
  * String cc1 = "cc1@you.com";
 89   
  * String cc2 = "cc2@you.com";
 90   
  * String bcc = "bcc@you.com";
 91   
  * &nbsp;
 92   
  * MailMessage msg = new MailMessage(mailhost);
 93   
  * msg.setPort(25);
 94   
  * msg.from(from);
 95   
  * msg.to(to);
 96   
  * msg.cc(cc1);
 97   
  * msg.cc(cc2);
 98   
  * msg.bcc(bcc);
 99   
  * msg.setSubject("Test subject");
 100   
  * PrintStream out = msg.getPrintStream();
 101   
  * &nbsp;
 102   
  * Enumeration enum = req.getParameterNames();
 103   
  * while (enum.hasMoreElements()) {
 104   
  *   String name = (String)enum.nextElement();
 105   
  *   String value = req.getParameter(name);
 106   
  *   out.println(name + " = " + value);
 107   
  * }
 108   
  * &nbsp;
 109   
  * msg.sendAndClose();
 110   
  * </pre></blockquote>
 111   
  * <p>
 112   
  * Be sure to set the from address, then set the recepient
 113   
  * addresses, then set the subject and other headers, then get the
 114   
  * PrintStream, then write the message, and finally send and close.
 115   
  * The class does minimal error checking internally; it counts on the mail
 116   
  * host to complain if there's any malformatted input or out of order
 117   
  * execution.
 118   
  * <p>
 119   
  * An attachment mechanism based on RFC 1521 could be implemented on top of
 120   
  * this class.  In the meanwhile, JavaMail is the best solution for sending
 121   
  * email with attachments.
 122   
  * <p>
 123   
  * Still to do:
 124   
  * <ul>
 125   
  * <li>Figure out how to close the connection in case of error
 126   
  * </ul>
 127   
  *
 128   
  * @author Jason Hunter
 129   
  * @version 1.1, 2000/03/19, added angle brackets to address, helps some servers
 130   
  * version 1.0, 1999/12/29
 131   
  */
 132   
 public class MailMessage {
 133   
 
 134   
     /** default port for SMTP: 25 */
 135   
     public static final int DEFAULT_PORT = 25;
 136   
 
 137   
     /** host name for the mail server */
 138   
     private String host;
 139   
 
 140   
     /** host port for the mail server */
 141   
     private int port = DEFAULT_PORT;
 142   
 
 143   
     /** sender email address */
 144   
     private String from;
 145   
 
 146   
     /** list of email addresses to send to */
 147   
     private Vector to;
 148   
 
 149   
     /** list of email addresses to cc to */
 150   
     private Vector cc;
 151   
 
 152   
     /** headers to send in the mail */
 153   
     private Hashtable headers;
 154   
 
 155   
     private MailPrintStream out;
 156   
 
 157   
     private SmtpResponseReader in;
 158   
 
 159   
     private Socket socket;
 160   
 
 161   
   /**
 162   
    * Constructs a new MailMessage to send an email.
 163   
    * Use localhost as the mail server with port 25.
 164   
    *
 165   
    * @exception IOException if there's any problem contacting the mail server
 166   
    */
 167  0
   public MailMessage() throws IOException {
 168  0
     this("localhost",DEFAULT_PORT);
 169   
   }
 170   
 
 171   
   /**
 172   
    * Constructs a new MailMessage to send an email.
 173   
    * Use the given host as the mail server with port 25.
 174   
    *
 175   
    * @param host the mail server to use
 176   
    * @exception IOException if there's any problem contacting the mail server
 177   
    */
 178  0
   public MailMessage(String host) throws IOException {
 179  0
       this(host,DEFAULT_PORT);
 180   
   }
 181   
 
 182   
   /**
 183   
    * Constructs a new MailMessage to send an email.
 184   
    * Use the given host and port as the mail server.
 185   
    *
 186   
    * @param host the mail server to use
 187   
    * @param port the port to connect to
 188   
    * @exception IOException if there's any problem contacting the mail server
 189   
    */
 190  0
   public MailMessage(String host, int port) throws IOException{
 191  0
     this.port = port;
 192  0
     this.host = host;
 193  0
     to = new Vector();
 194  0
     cc = new Vector();
 195  0
     headers = new Hashtable();
 196  0
     setHeader("X-Mailer", "org.apache.tools.mail.MailMessage (jakarta.apache.org)");
 197  0
     connect();
 198  0
     sendHelo();
 199   
   }
 200   
 
 201   
     /**
 202   
      * Set the port to connect to the SMTP host.
 203   
      * @param port the port to use for connection.
 204   
      * @see #DEFAULT_PORT
 205   
      */
 206  0
     public void setPort(int port){
 207  0
         this.port = port;
 208   
     }
 209   
 
 210   
   /**
 211   
    * Sets the from address.  Also sets the "From" header.  This method should
 212   
    * be called only once.
 213   
    *
 214   
    * @exception IOException if there's any problem reported by the mail server
 215   
    */
 216  0
   public void from(String from) throws IOException {
 217  0
     sendFrom(from);
 218  0
     this.from = from;
 219   
   }
 220   
 
 221   
   /**
 222   
    * Sets the to address.  Also sets the "To" header.  This method may be
 223   
    * called multiple times.
 224   
    *
 225   
    * @exception IOException if there's any problem reported by the mail server
 226   
    */
 227  0
   public void to(String to) throws IOException {
 228  0
     sendRcpt(to);
 229  0
     this.to.addElement(to);
 230   
   }
 231   
 
 232   
   /**
 233   
    * Sets the cc address.  Also sets the "Cc" header.  This method may be
 234   
    * called multiple times.
 235   
    *
 236   
    * @exception IOException if there's any problem reported by the mail server
 237   
    */
 238  0
   public void cc(String cc) throws IOException {
 239  0
     sendRcpt(cc);
 240  0
     this.cc.addElement(cc);
 241   
   }
 242   
 
 243   
   /**
 244   
    * Sets the bcc address.  Does NOT set any header since it's a *blind* copy.
 245   
    * This method may be called multiple times.
 246   
    *
 247   
    * @exception IOException if there's any problem reported by the mail server
 248   
    */
 249  0
   public void bcc(String bcc) throws IOException {
 250  0
     sendRcpt(bcc);
 251   
     // No need to keep track of Bcc'd addresses
 252   
   }
 253   
 
 254   
   /**
 255   
    * Sets the subject of the mail message.  Actually sets the "Subject"
 256   
    * header.
 257   
    */
 258  0
   public void setSubject(String subj) {
 259  0
     headers.put("Subject", subj);
 260   
   }
 261   
 
 262   
   /**
 263   
    * Sets the named header to the given value.  RFC 822 provides the rules for
 264   
    * what text may constitute a header name and value.
 265   
    */
 266  0
   public void setHeader(String name, String value) {
 267   
     // Blindly trust the user doesn't set any invalid headers
 268  0
     headers.put(name, value);
 269   
   }
 270   
 
 271   
   /**
 272   
    * Returns a PrintStream that can be used to write the body of the message.
 273   
    * A stream is used since email bodies are byte-oriented.  A writer could
 274   
    * be wrapped on top if necessary for internationalization.
 275   
    *
 276   
    * @exception IOException if there's any problem reported by the mail server
 277   
    */
 278  0
   public PrintStream getPrintStream() throws IOException {
 279  0
     setFromHeader();
 280  0
     setToHeader();
 281  0
     setCcHeader();
 282  0
     sendData();
 283  0
     flushHeaders();
 284  0
     return out;
 285   
   }
 286   
 
 287  0
   void setFromHeader() {
 288  0
     setHeader("From", from);
 289   
   }
 290   
 
 291  0
   void setToHeader() {
 292  0
     setHeader("To", vectorToList(to));
 293   
   }
 294   
 
 295  0
   void setCcHeader() {
 296  0
     setHeader("Cc", vectorToList(cc));
 297   
   }
 298   
 
 299  0
   String vectorToList(Vector v) {
 300  0
     StringBuffer buf = new StringBuffer();
 301  0
     Enumeration e = v.elements();
 302  0
     while (e.hasMoreElements()) {
 303  0
       buf.append(e.nextElement());
 304  0
       if (e.hasMoreElements()) {
 305  0
         buf.append(", ");
 306   
       }
 307   
     }
 308  0
     return buf.toString();
 309   
   }
 310   
 
 311  0
   void flushHeaders() throws IOException {
 312   
     // XXX Should I care about order here?
 313  0
     Enumeration e = headers.keys();
 314  0
     while (e.hasMoreElements()) {
 315  0
       String name = (String) e.nextElement();
 316  0
       String value = (String) headers.get(name);
 317  0
       out.println(name + ": " + value);
 318   
     }
 319  0
     out.println();
 320  0
     out.flush();
 321   
   }
 322   
 
 323   
   /**
 324   
    * Sends the message and closes the connection to the server.
 325   
    * The MailMessage object cannot be reused.
 326   
    *
 327   
    * @exception IOException if there's any problem reported by the mail server
 328   
    */
 329  0
   public void sendAndClose() throws IOException {
 330  0
       try {
 331  0
           sendDot();
 332  0
           sendQuit();
 333   
       } finally {
 334  0
           disconnect();
 335   
       }
 336   
   }
 337   
 
 338   
   // Make a limited attempt to extract a sanitized email address
 339   
   // Prefer text in <brackets>, ignore anything in (parentheses)
 340  0
   static String sanitizeAddress(String s) {
 341  0
     int paramDepth = 0;
 342  0
     int start = 0;
 343  0
     int end = 0;
 344  0
     int len = s.length();
 345   
 
 346  0
     for (int i = 0; i < len; i++) {
 347  0
       char c = s.charAt(i);
 348  0
       if (c == '(') {
 349  0
         paramDepth++;
 350  0
         if (start == 0) {
 351  0
           end = i;  // support "address (name)"
 352   
         }
 353  0
       } else if (c == ')') {
 354  0
         paramDepth--;
 355  0
         if (end == 0) {
 356  0
           start = i + 1;  // support "(name) address"
 357   
         }
 358  0
       } else if (paramDepth == 0 && c == '<') {
 359  0
         start = i + 1;
 360  0
       } else if (paramDepth == 0 && c == '>') {
 361  0
         end = i;
 362   
       }
 363   
     }
 364   
 
 365  0
     if (end == 0) {
 366  0
       end = len;
 367   
     }
 368   
 
 369  0
     return s.substring(start, end);
 370   
   }
 371   
 
 372   
   // * * * * * Raw protocol methods below here * * * * *
 373   
 
 374  0
   void connect() throws IOException {
 375  0
     socket = new Socket(host, port);
 376  0
     out = new MailPrintStream(
 377   
           new BufferedOutputStream(
 378   
           socket.getOutputStream()));
 379  0
     in = new SmtpResponseReader(socket.getInputStream());
 380  0
     getReady();
 381   
   }
 382   
 
 383  0
   void getReady() throws IOException {
 384  0
     String response = in.getResponse();
 385  0
     int[] ok = { 220 };
 386  0
     if (!isResponseOK(response, ok)) {
 387  0
       throw new IOException(
 388   
         "Didn't get introduction from server: " + response);
 389   
     }
 390   
   }
 391   
 
 392  0
   void sendHelo() throws IOException {
 393  0
     String local = InetAddress.getLocalHost().getHostName();
 394  0
     int[] ok = { 250 };
 395  0
     send("HELO " + local, ok);
 396   
   }
 397   
 
 398  0
   void sendFrom(String from) throws IOException {
 399  0
     int[] ok = { 250 };
 400  0
     send("MAIL FROM: " + "<" + sanitizeAddress(from) + ">", ok);
 401   
   }
 402   
 
 403  0
   void sendRcpt(String rcpt) throws IOException {
 404  0
     int[] ok = { 250, 251 };
 405  0
     send("RCPT TO: " + "<" + sanitizeAddress(rcpt) + ">", ok);
 406   
   }
 407   
 
 408  0
   void sendData() throws IOException {
 409  0
     int[] ok = { 354 };
 410  0
     send("DATA", ok);
 411   
   }
 412   
 
 413  0
   void sendDot() throws IOException {
 414  0
     int[] ok = { 250 };
 415  0
     send("\r\n.", ok);  // make sure dot is on new line
 416   
   }
 417   
 
 418  0
     void sendQuit() throws IOException {
 419  0
         int[] ok = { 221 };
 420  0
         try {
 421  0
             send("QUIT", ok);
 422   
         } catch (IOException e) {
 423  0
             throw new ErrorInQuitException(e);
 424   
         }
 425   
     }
 426   
 
 427  0
     void send(String msg, int[] ok) throws IOException {
 428  0
         out.rawPrint(msg + "\r\n");  // raw supports <CRLF>.<CRLF>
 429  0
         String response = in.getResponse();
 430  0
         if (!isResponseOK(response, ok)) {
 431  0
             throw new IOException("Unexpected reply to command: "
 432   
                                   + msg + ": " + response);
 433   
         }
 434   
     }
 435   
 
 436  0
   boolean isResponseOK(String response, int[] ok) {
 437   
     // Check that the response is one of the valid codes
 438  0
     for (int i = 0; i < ok.length; i++) {
 439  0
       if (response.startsWith("" + ok[i])) {
 440  0
         return true;
 441   
       }
 442   
     }
 443  0
     return false;
 444   
   }
 445   
 
 446  0
     void disconnect() throws IOException {
 447  0
         if (out != null) {
 448  0
             out.close();
 449   
         }
 450  0
         if (in != null) {
 451  0
             try {
 452  0
                 in.close();
 453   
             } catch (IOException e) {
 454   
             }
 455   
         }
 456  0
         if (socket != null) {
 457  0
             try {
 458  0
                 socket.close();
 459   
             } catch (IOException e) {
 460   
             }
 461   
         }
 462   
     }
 463   
 }
 464   
 
 465   
 // This PrintStream subclass makes sure that <CRLF>. becomes <CRLF>..
 466   
 // per RFC 821.  It also ensures that new lines are always \r\n.
 467   
 //
 468   
 class MailPrintStream extends PrintStream {
 469   
 
 470   
   int lastChar;
 471   
 
 472  0
   public MailPrintStream(OutputStream out) {
 473  0
     super(out, true);  // deprecated, but email is byte-oriented
 474   
   }
 475   
 
 476   
   // Mac does \n\r, but that's tough to distinguish from Windows \r\n\r\n.
 477   
   // Don't tackle that problem right now.
 478  0
   public void write(int b) {
 479  0
     if (b == '\n' && lastChar != '\r') {
 480  0
       rawWrite('\r');  // ensure always \r\n
 481  0
       rawWrite(b);
 482  0
     } else if (b == '.' && lastChar == '\n') {
 483  0
       rawWrite('.');  // add extra dot
 484  0
       rawWrite(b);
 485   
     } else {
 486  0
       rawWrite(b);
 487   
     }
 488  0
     lastChar = b;
 489   
   }
 490   
 
 491  0
   public void write(byte[] buf, int off, int len) {
 492  0
     for (int i = 0; i < len; i++) {
 493  0
       write(buf[off + i]);
 494   
     }
 495   
   }
 496   
 
 497  0
   void rawWrite(int b) {
 498  0
     super.write(b);
 499   
   }
 500   
 
 501  0
   void rawPrint(String s) {
 502  0
     int len = s.length();
 503  0
     for (int i = 0; i < len; i++) {
 504  0
       rawWrite(s.charAt(i));
 505   
     }
 506   
   }
 507   
 }
 508   
 
 509